一 为什么使用spring cloud alibaba
很多人可能会问,有了spring cloud这个微服务的框架,为什么又要使用spring cloud alibaba这个框架了?最重要的原因在于spring cloud中的几乎所有的组件都使用Netflix公司的产品,然后在其基础上做了一层封装。然而Netflix的服务发现组件Eureka已经停止更新,我们公司在使用的时候就发现过其一个细小的Bug;而其他的众多组件预计会在明年(即2020年)停止维护。所以急需其他的一些替代产品,也就是spring cloud alibaba,目前正处于蓬勃发展的态式。
二spring cloud alibaba 功能和基本主件
服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
springcloud
spring cloud alibaba
阿里开源组件
Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
RocketMQ:开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
Dubbo:这个就不用多说了,在国内应用非常广泛的一款高性能 Java RPC 框架。
Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
Arthas:开源的Java动态追踪工具,基于字节码增强技术,功能非常强大。
阿里商业化组件
作为一家商业公司,阿里巴巴推出 Spring Cloud Alibaba,很大程度上市希望通过抢占开发者生态,来帮助推广自家的云产品。所以在开源社区,夹带了不少私货,这部分组件我在阿里工作时都曾经使用过,整体易用性和稳定性还是很高的。
Alibaba Cloud ACM:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品。
Alibaba Cloud OSS:阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的云存储服务。
Alibaba Cloud SchedulerX:阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准的定时(基于 Cron 表达式)任务调度服务。
集成 Spring Cloud 组件
Spring Cloud Alibaba 作为整套的微服务解决组件,只依靠目前阿里的开源组件是不够的,更多的是集成当前的社区组件,所以 Spring Cloud Alibaba 可以集成 Zuul,OpenFeign等网关,也支持 Spring Cloud Stream 消息组件。
三 什么是Nacos和能干什么
3.1nacos是阿里巴巴研发的一个集注册中心与配置中心于一体的管理平台,使用其他非常的简单。
https://github.com/alibaba/nacos/releases 下载
2、能干嘛
- 替代Eureka做服务注册中心
- 替代Config做服务配置中
四 简单的进行开发ribbon的负载均衡
4.1介绍
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。其主要功能是提供客户端的负载均衡算法,并提供了完善的配置项如连接超时,重试等。简单的说,就是配置文件中列出Load Balancer后面所有的机器,Ribbon会自动的基于某种规则(如简单轮询,随机连接等)去连接这些机器,当然我们也可以使用Ribbon自定义负载均衡算法。
Ribbon只是一个客户端的负载均衡器工具,实现起来非常的简单,我们只需要在注入RestTemplate的bean上加上@LoadBalanced就可以了。如下:
package com.qf.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class WebConfig { @LoadBalanced//添加注解 @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
5.2 启动多个服务提供方测试
1.修改springcloud-alibaba-microservice-consumer-8080工程中的UserController
package com.qf.controller; import com.qf.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; @RestController @RequestMapping("consumer-user") public class UserController { @Autowired private DiscoveryClient discoveryClient; @Autowired private RestTemplate restTemplate;//用于发送网络请求 // @RequestMapping("getUsers") // public List<User> getUsers(){ // //通过服务提供方的名称拿到服务,由于服务提供方可能是集群,所以使用List封装:每一个服务封装成一个ServiceInstance // List<ServiceInstance> serviceInstances = discoveryClient.getInstances("microservice-provider"); // ServiceInstance serviceInstance = serviceInstances.get(0);//获取服务 // String host = serviceInstance.getHost();//获取主机名 // int port = serviceInstance.getPort();//获取端口号 // String url = "http://"+host+":"+port+"/provider-user/findAll"; // // List<User> users = (List<User>)restTemplate.getForObject(url, List.class); // // return users; // } @RequestMapping("getUsers") public List<User> getUsers(){ //直接写上服务名即可 List<User> users = (List<User>)restTemplate .getForObject("http://microservice-provider/provider-user/findAll", List.class); return users; } }
2.修改springcloud-alibaba-microservice-provider-7070工程中的UserController
package com.qf.controller; import com.qf.pojo.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.List; @RestController @RequestMapping("provider-user") public class UserController { @RequestMapping("findAll") public List<User> findAll(){ //先启动7070,然后修改application.yml配置文件,端口设置为7071再启动 System.out.println("7070"); //System.out.println("7071"); return Arrays.asList(new User(1001,"jack"),new User(1002,"tom")); }
负载均衡策略
类名 | 描述 |
---|---|
RoundRobbinRule | 轮询 |
RandomRule | 随机挑选 |
RetryRule | 按照轮询的方式去调用服务,如果其中某个服务不可用,但是还是会尝试几次,如果尝试过几次都没有成功,那么就不在调用该服务,会轮询调用其他的可用服务。 |
AvailabilityFilteringRule | 会先过滤掉因为多次访问不可达和并发超过阈值的服务,然后轮询调用其他的服务 |
WeightedResponseTimeRule | 根据平均响应时间计算权重,响应越快权重越大,越容易被选中。服务刚重启的时候,还未统计出权重会按照轮询的方式;当统计信息足够的时候,就会按照权重信息访问 |
ZoneAvoidanceRule | 判断server所在的区域性能和可用性选择服务器 |
BestAvailableRule | 会过滤掉多次访问都不可达的服务,然后选择并发量最小的服务进行调用,默认方式 |
在config
//创建对象实现改变Ribbon的负载均衡策略
@Bean
public IRule getRule() {
return new RandomRule();
}
自定义负载均衡策略
我们自定义的负载均衡策略需要继承AbstractLoadBalancerRule这个类,然后重写choose方法,然后将其注入到容器中。
1.创建ServerInfo类
package com.qf.rule; import com.netflix.loadbalancer.Server; public class ServerInfo { private Server server; private int num; public ServerInfo() { } public ServerInfo(Server server, int num) { this.server = server; this.num = num; } public Server getServer() { return server; } public void setServer(Server server) { this.server = server; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } }
2.创建CustomizeRule类
package com.qf.rule; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class CustomizeRule extends AbstractLoadBalancerRule { private int limit = 5; /** * map的key是服务的名字,value是该服务调用的次数 */ private Map<String, ServerInfo> map = new ConcurrentHashMap<>(); @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } /** * 返回值的意思是,当该方法返回什么的时候,那么Ribbon或者Feign就调用谁。 */ @Override public Server choose(Object key) { Server finalServer = null; ILoadBalancer loadBalancer = getLoadBalancer(); // 获取所有的服务 List<Server> servers = loadBalancer.getAllServers(); // 获取所有的可用的服务 List<Server> reachableServers = loadBalancer.getReachableServers(); int allServiceSize = servers.size(); //获取所有的服务的长度 int upCount = reachableServers.size(); //获取所有的可用的服务的长度 if(0 == allServiceSize || 0 == upCount) { return null; } for(int i = 0; i < allServiceSize; i++) { Server server = servers.get(i); // 获取当前遍历的server String instanceId = server.getMetaInfo().getInstanceId(); String providerName = instanceId.split("@@")[1]; //获取服务名 ServerInfo serverInfo = map.get(providerName); //获取对应服务 // 首次调用 if(null == serverInfo) { serverInfo = new ServerInfo(server, 1); map.put(providerName, serverInfo); finalServer = server; break; } else { //不为空,表示之前肯定调用过 // 当前遍历的server与正在调用的server是同一个server if(serverInfo.getServer().getId().equals(server.getId())) { /** * 1. 如果没有满5次,接着走该服务。 * 2. 如果满了5次,接着下个 */ int num = serverInfo.getNum(); //获取已经调用的次数 if(num >= limit) { //超出了5次 // 超出了次数,要走下一个,需要判断是否有下一个,如果没有下一个,就回到第一个 if(i == (allServiceSize - 1)) { //表示当前服务为整个集群的最后一个服务,拿第一个 Server firstServer = servers.get(0); //如果为最后一个就拿第一个 ServerInfo firstServerInfo = new ServerInfo(firstServer, 1); map.put(providerName, firstServerInfo); finalServer = firstServer; }else { //不是最后一个 Server nextServer = servers.get(i + 1); //取下一个 ServerInfo nextServerInfo = new ServerInfo(nextServer, 1); map.put(providerName, nextServerInfo); finalServer = nextServer; } break; }else { serverInfo.setNum(++num); finalServer = server; break; } } } } return finalServer; } }
3.修改WebConfig,添加配置
package com.qf.config; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import com.qf.rule.CustomizeRule; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class WebConfig { @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } // @Bean // public IRule getRule() { // return new RandomRule(); // } //自定义均衡负载服务器 @Bean public IRule getRule() { return new CustomizeRule(); } }
六Feign负载均衡
feign是基于Ribbon的另外一个负载均衡的客户端框架,只需要在接口上定义要调用的服务名即可,使用起来非常的简单。
Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。
Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。
Spring Cloud Feign具备可插拔的注解支持,支持Feign注解、JAX-RS注解和Spring MVC的注解。
简单入门
1.在springcloud-alibaba-microservice-consumer-8080的pom.xml中添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.3.RELEASE</version> </dependency>
2.在启动类上加上@EnableFeignClients这个注解
package com.qf; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class MicroServiceConsumer { public static void main(String[] args) { SpringApplication.run(MicroServiceConsumer.class,args); } }
3.创建UserService
package com.qf.service; import com.qf.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; @Service @FeignClient("microservice-provider") public interface UserService { @RequestMapping("/provider-user/findAll") public List<User> getUsers(); }
4.创建FeignUserController
package com.qf.controller; import com.qf.pojo.User; import com.qf.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("feign") public class FeignUserController { @Autowired private UserService userService; @RequestMapping("getUsers") public List<User> getUsers(){ return userService.getUsers(); } }
5.修改端口号,启动多个provider,然后启动consumer,访问浏览器进行测试
6.2服务之间的参数传递
1.在springcloud-alibaba-microservice-provider-7070工程中的UserController添加CRUD方法
package com.qf.controller; import com.qf.pojo.User; import org.springframework.web.bind.annotation.*; import java.util.Arrays; import java.util.List; @RestController @RequestMapping("provider-user") public class UserController { @RequestMapping("findAll") public List<User> findAll(){ //先启动7070,然后修改application.yml配置文件,端口设置为7071再启动 //System.out.println("7070"); System.out.println("7071"); return Arrays.asList( new User(1001,"jack"),new User(1002,"tom"));//[{user1},{user2}] } //查询单个 @GetMapping("findById") public User findById(@RequestParam("uid") Integer uid){ System.out.println("findById:"+uid); return new User(uid,"张三"); } //添加用户 @PostMapping("add") public String add(@RequestBody User user){ System.out.println("add:"+user); return "success"; } //修改用户 @PutMapping("update") public String update(@RequestBody User user){ System.out.println("update:"+user); return "success"; } //删除用户 @DeleteMapping("delete/{uid}") public String delete(@PathVariable("uid") Integer uid){ System.out.println("delete:"+uid); return "success"; } }
2.在springcloud-alibaba-microservice-consumer-8080工程中的UserService添加对应方法
package com.qf.service; import com.qf.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.*; import java.util.List; @Service @FeignClient("provider-microservice") public interface UserService { @RequestMapping("/provider-user/findAll") public List<User> findAll(); //查询单个 @GetMapping("/provider-user/findById") public User findById(@RequestParam("uid") Integer uid); //添加用户 @PostMapping("/provider-user/add") public String add(@RequestBody User user); //修改用户 @PutMapping("/provider-user/update") public String update(@RequestBody User user); //删除用户 @DeleteMapping("/provider-user/delete/{uid}") public String delete(@PathVariable("uid") Integer uid); }
3.在springcloud-alibaba-microservice-consumer-8080工程中的FeignUserController添加对应方法
package com.qf.controller; import com.qf.pojo.User; import com.qf.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("feign") public class FeignUserController { @Autowired private UserService userService; @RequestMapping("getAll") public List<User> getAll(){ return userService.findAll(); } //查询单个 @GetMapping("findById") public User findById(@RequestParam("uid") Integer uid){ return userService.findById(uid); } //添加 @PostMapping("add") public String add(User user){ return userService.add(user); } //修改 @PutMapping("update") public String update(User user){ return userService.update(user); } //删除 @DeleteMapping("delete/{uid}") public String delete(@PathVariable("uid") Integer uid){ return userService.delete(uid); } }
4.在postman中选择对应的请求方式进行测试
七 熔断和降级sentinel
分布式系统中一个微服务需要依赖于很多的其他的服务,那么服务就会不可避免的失败。例如A服务依赖于B、C、D等很多的服务,当B服务不可用的时候,会一直阻塞或者异常,更不会去调用C服务和D服务。同时假设有其他的服务也依赖于B服务,也会碰到同样的问题,这就及有可能导致雪崩效应。
如下案例:一个用户通过通过web容器访问应用,他要先后调用A、H、I、P四个模块,一切看着都很美好。
由于某些原因,导致I服务不可用,与此同时我们没有快速处理,会导致该用户一直处于阻塞状态。
当其他用户做同样的请求,也会面临着同样的问题,tomcat支持的最大并发数是有限的,资源都是有限的,将整个服务器拖垮都是有可能的。
Sentinel是一个用于分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖会不可避免的调用失败,例如超时,异常等,Sentinel能保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
断路器本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似于保险丝),向调用者返回符合预期的,可处理的备选响应,而不是长时间的等待或者抛出无法处理的异常,这样就保证了服务调用的线程不会被长时间,不必要的占用,从而避免故障在分布式系统中的蔓延,乃至雪崩。
Sentinel在网络依赖服务出现高延迟或者失败时,为系统提供保护和控制;可以进行快速失败,缩短延迟等待时间;提供失败回退(Fallback)和相对优雅的服务降级机制;提供有效的服务容错监控、报警和运维控制手段。