zoukankan      html  css  js  c++  java
  • springcloud之自定义简易消费服务组件

      本次和大家分享的是怎么来消费服务,上篇文章讲了使用Feign来消费,本篇来使用rest+ribbon消费服务,并且通过轮询方式来自定义了个简易消费组件,本文分享的宗旨是:自定义消费服务的思路;思路如果有可取之处还请“赞”一下:

    • Rest+Ribbon实现消费服务
    • Rest+轮询自定义简易消费组件
    • 使用Scheduled刷新服务提供者信息

    Rest+Ribbon实现消费服务

      做为服务消费方准确的来说进行了两种主流程区分1)获取可以服务2)调用服务,那么又是如何获取服务的并且又是通过什么来调用服务的,下面我们来看一副手工图:

      

      手工图上能够看出消费方先获取了服务方的真实接口地址,然后再通过地址去调用接口;然后对于微服务架构来说获取某一个类ip或端口然后去调用接口肯定是不可取的,因此微服务中产生了一种serviceid的概念;简单流程介绍完了,下面通过实例来分析;首先添加依赖如:

    1 <dependency>
    2     <groupId>org.springframework.boot</groupId>
    3     <artifactId>spring-boot-starter-web</artifactId>
    4 </dependency>
    5 <dependency>
    6     <groupId>org.springframework.cloud</groupId>
    7     <artifactId>spring-cloud-starter-eureka</artifactId>
    8 </dependency>

      再来我们通过上篇文章搭建的eureka_server(服务中心),eureka_provider(服务提供者)来做测试用例,这里我重新定义eureka_consumer_ribbon模块做为消费服务;先创建service层类和代码:

     1 @Service
     2 public class UserService implements UserInterface {
     3 
     4     @Autowired
     5     protected RestTemplate restTemplate;
     6 
     7     @Override
     8     public MoRp<List<MoUser>> getUsers(MoRq rq) {
     9         return null;
    10     }
    11 
    12     @Override
    13     public String getMsg() {
    14 
    15         String str = restTemplate.getForObject("http://EUREKA-PROVIDER/msg", String.class);
    16         return str;
    17     }
    18 }

      主要用到了RestTemplate的restTemplate.getForObject函数,然后需要定义个Controller来吧获取到的数据响应到页面上,为了简单这里仅仅只拿getMsg服务接口测试:

     1 @RestController
     2 public class UserController {
     3 
     4     @Autowired
     5     private UserService userService;
     6 
     7     @GetMapping("/msg")
     8     public String getMsg(){
     9 
    10         return userService.getMsg();
    11     }
    12 }

      最后我们在启动类添加入下代码,注意@LoadBalanced标记必须加,因为咋们引入的eureka依赖里面包含了ribbon(Dalston.RELEASE版本),ribbon封装了负载均衡的算法,如果不加这个注解,那后面rest方法的url就必须是可用的url路径了,当然这里加了注解就可以使用上面说的serviceId:

     1 @SpringBootApplication
     2 @EnableDiscoveryClient //消费客户端
     3 public class EurekaConsumerRibbonApplication {
     4 
     5     @Bean 
     6     @LoadBalanced //负载均衡
     7     RestTemplate restTemplate(){
     8         return new RestTemplate();
     9     }
    10     
    11     public static void main(String[] args) {
    12         SpringApplication.run(EurekaConsumerRibbonApplication.class, args);
    13     }
    14 }

      下面来消费方显示的效果:

      

    Rest+轮询自定义简易消费组件

      自定义消费组件原来和面手工图差不多,就是先想法获取服务提供端真实的接口地址,然后通过rest去调用这个url,得到相应的结果输出;这里自定义了一个ShenniuBanlance的组件类:

      1 /**
      2  * Created by shenniu on 2018/6
      3  * <p>
      4  * rest+eureka+自定义client端
      5  */
      6 @Component
      7 public class ShenniuBanlance {
      8 
      9     @Autowired
     10     private RestTemplate restTemplate;
     11 
     12     @Autowired
     13     private DiscoveryClient discoveryClient;
     14 
     15     /**
     16      * 服务真实地址 ConcurrentHashMap<"服务应用名称", ("真实接口ip", 被访问次数)>
     17      */
     18     public static ConcurrentHashMap<String, List<MoService>> sericesMap = new ConcurrentHashMap<>();
     19 
     20     /**
     21      * 设置服务提供者信息到map
     22      */
     23     public void setServicesMap() {
     24         //获取所有服务提供者applicationName
     25         List<String> appNames = discoveryClient.getServices();
     26 
     27         //存储真实地址到map
     28         for (String appName :
     29                 appNames) {
     30             //获取某个服务提供者信息
     31             List<ServiceInstance> instanceInfos = discoveryClient.getInstances(appName);
     32             if (instanceInfos.isEmpty()) {
     33                 continue;
     34             }
     35 
     36             List<MoService> services = new ArrayList<>();
     37             instanceInfos.forEach(b -> {
     38                 MoService service = new MoService();
     39                 //被访问次数
     40                 service.setWatch(0L);
     41                 //真实接口地址
     42                 service.setUrl(b.getUri().toString());
     43                 services.add(service);
     44             });
     45 
     46             //如果存在就更新
     47             sericesMap.put(appName.toLowerCase(), services);
     48         }
     49     }
     50 
     51     /**
     52      * 根据app获取轮询方式选中后的service
     53      *
     54      * @param appName
     55      * @return
     56      */
     57     public MoService choiceServiceByAppName(String appName) throws Exception {
     58         appName = appName.toLowerCase();
     59         //某种app的服务service集合
     60         List<MoService> serviceMap = sericesMap.get(appName);
     61         if (serviceMap == null) {
     62             //初始化所有app服务
     63             setServicesMap();
     64             serviceMap = sericesMap.get(appName);
     65             if (serviceMap == null) {
     66                 throw new Exception("未能找到" + appName + "相关服务");
     67             }
     68         }
     69 
     70         //筛选出被访问量最小的service  轮询的方式
     71         MoService moService = serviceMap.stream().min(
     72                 Comparator.comparing(MoService::getWatch)
     73         ).get();
     74 
     75         //负载记录+1
     76         moService.setWatch(moService.getWatch() + 1);
     77         return moService;
     78     }
     79 
     80     /**
     81      * 自动刷新 服务提供者信息到map
     82      */
     83     @Scheduled(fixedDelay = 1000 * 10)
     84     public void refreshServicesMap() {
     85         setServicesMap();
     86     }
     87 
     88     /**
     89      * get请求服务获取返回数据
     90      *
     91      * @param appName     应用名称 ApplicationName
     92      * @param serviceName 服务名称 ServiceName
     93      * @param map         url上请求参数
     94      * @param tClass      返回类型
     95      * @param <T>
     96      * @return
     97      */
     98     public <T> T getServiceData(
     99             String appName, String serviceName,
    100             Map<String, ?> map,
    101             Class<T> tClass) {
    102         T result = null;
    103         try {
    104             //筛选获取真实Service
    105             MoService service = choiceServiceByAppName(appName);
    106 
    107             //请求该service的url
    108             String apiUrl = service.getUrl() + "/" + serviceName;
    109             System.out.println(apiUrl);
    110             result = map != null ?
    111                     restTemplate.getForObject(apiUrl, tClass, map) :
    112                     restTemplate.getForObject(apiUrl, tClass);
    113         } catch (Exception ex) {
    114             ex.printStackTrace();
    115         }
    116         return result;
    117     }
    118 
    119     /**
    120      * Service信息
    121      */
    122     public class MoService {
    123         /**
    124          * 负载次数记录数
    125          */
    126         private Long watch;
    127         /**
    128          * 真实接口地址: http://xxx.com/api/add
    129          */
    130         private String url;
    131 
    132         public Long getWatch() {
    133             return watch;
    134         }
    135 
    136         public void setWatch(Long watch) {
    137             this.watch = watch;
    138         }
    139 
    140         public String getUrl() {
    141             return url;
    142         }
    143 
    144         public void setUrl(String url) {
    145             this.url = url;
    146         }
    147     }
    148 }

      以上就是主要的实现代码,代码逻辑:设置服务提供者信息到map-》根据app获取轮询方式选中后的service-》请求服务获取返回数据;轮询实现的原理是使用了一个负载记录数,每次被请求后自动+1,当要获取某个服务提供者时,通过记录数筛选出最小值的一个实例,里面存储有真实接口地址url;调用只需要这样(当然可以弄成注解来调用):

     1     @Override
     2     public String getMsg() {
     3 
     4         String str = banlance.getServiceData(
     5                 "EUREKA-PROVIDER", "msg",
     6                 null,
     7                 String.class
     8         );
     9         return str;
    10     }

      这里需要注意由于我们在前面RestTemplate使用加入了注解@LoadBalanced,这样使得rest请求时必须用非ip的访问方式(也就是必须serviceid)才能正常响应,不然会提示错误如:

      

      简单来说就是不用再使用ip了,因为有负载均衡机制;当我们去掉这个注解后,我们自定义的组件就能运行成功,效果图和实例1一样就不贴图了;

    使用Scheduled刷新服务提供者信息

      在微服务架构中,如果某台服务挂了之后,必须要及时更新client端的服务缓存信息,不然就可能请求到down的url去,基于这种考虑我这里采用了EnableSched标记来做定时刷新;首先在启动类增加 @EnableScheduling ,然后定义一个刷行服务信息的服务如:

    1 /**
    2      * 自动刷新 服务提供者信息到map 
    3      */
    4     @Scheduled(fixedDelay = 1000 * 10)
    5     public void refreshServicesMap() {
    6         setServicesMap();
    7     }

      为了方便看测试效果,我们在server,provider(2个),consumer已经启动的情况下,再启动一个端口为2005的provider服务;然后刷新consumer接口看下效果:

      

      这个时候能够看到调用2005端口的接口成功了,通过@Scheduled定时服务吧最新或者失效的服务加入|移除掉,就达到了咋们的需求了;如果你觉得该篇内容对你有帮助,不防赞一下,谢谢。

  • 相关阅读:
    [置顶] 怎么对待重复的代码
    AIX和Linux中wtmp的不同处理方式
    Visio 下载,及密钥
    全局变量和局部变量
    UNIX网络编程--IPV4 IPV6 ICMPV4 ICMPV6
    Android XML文档解析(一)——SAX解析
    rnqoj-30- [stupid]愚蠢的矿工-树形DP
    linux 文件内容的复制
    主流视音频平台参数
    FTP原理
  • 原文地址:https://www.cnblogs.com/wangrudong003/p/9134870.html
Copyright © 2011-2022 走看看