本次和大家分享的是怎么来消费服务,上篇文章讲了使用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定时服务吧最新或者失效的服务加入|移除掉,就达到了咋们的需求了;如果你觉得该篇内容对你有帮助,不防赞一下,谢谢。