您现在的位置是:首页 > 文章详情

springcloud之自定义简易消费服务组件

日期:2018-06-03点击:322

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

git地址: https://github.com/shenniubuxing3    nuget发布包: https://www.nuget.org/profiles/shenniubuxing3
img_b0f7f3094651b5685efcc535a055b326.gif
原文链接:https://yq.aliyun.com/articles/618883
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章