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

本文涉及的产品
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介:   本次和大家分享的是怎么来消费服务,上篇文章讲了使用Feign来消费,本篇来使用rest+ribbon消费服务,并且通过轮询方式来自定义了个简易消费组件,本文分享的宗旨是:自定义消费服务的思路;思路如果有可取之处还请“赞”一下: Rest+Ribbon实现消费服务 Rest+轮询自定义简易消...

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

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
1月前
|
监控 负载均衡 Java
5 大 SpringCloud 核心组件详解,8 张图彻底弄懂
本文图文详解 Spring Cloud 的五大核心组件,帮助深入理解和掌握微服务架构。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
5 大 SpringCloud 核心组件详解,8 张图彻底弄懂
|
2月前
|
存储 数据可视化 Java
基于MicrometerTracing门面和Zipkin实现集成springcloud2023的服务追踪
Sleuth将会停止维护,Sleuth最新版本也只支持springboot2。作为替代可以使用MicrometerTracing在微服务中作为服务追踪的工具。
147 1
|
4月前
|
缓存 NoSQL Java
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
|
23天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
72 5
|
1月前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
63 3
|
1月前
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
84 5
|
3月前
|
消息中间件 存储 Java
SpringCloud基础9——服务异步通信-高级篇
消息可靠性、死信交换机、惰性队列、MQ集群
SpringCloud基础9——服务异步通信-高级篇
|
3月前
|
Java API 对象存储
微服务魔法启动!Spring Cloud与Netflix OSS联手,零基础也能创造服务奇迹!
这段内容介绍了如何使用Spring Cloud和Netflix OSS构建微服务架构。首先,基于Spring Boot创建项目并添加Spring Cloud依赖项。接着配置Eureka服务器实现服务发现,然后创建REST控制器作为API入口。为提高服务稳定性,利用Hystrix实现断路器模式。最后,在启动类中启用Eureka客户端功能。此外,还可集成其他Netflix OSS组件以增强系统功能。通过这些步骤,开发者可以更高效地构建稳定且可扩展的微服务系统。
64 1
|
2月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
57 0
|
4月前
|
Java Spring
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.