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定时服务吧最新或者失效的服务加入|移除掉,就达到了咋们的需求了;如果你觉得该篇内容对你有帮助,不防赞一下,谢谢。

posted @ 2018-06-04 18:48  神牛003  阅读(910)  评论(0编辑  收藏  举报