一切随缘的技术博客

日拱一卒无有尽、功不唐捐终入海

0%

Spring cloud Alibaba

Spring cloud Alibaba

一. 为什么使用 spring cloud alibaba

很多人可能会问,有了 spring cloud 这个微服务的框架,为什么又要使用 spring cloud alibaba 这个框架了?最重要的原因在于 spring cloud 中的几乎所有的组件都使用 Netflix 公司的产品,然后在其基础上做了一层封装。然而 Netflix 的服务发现组件 Eureka 已经停止更新,我们公司在使用的时候就发现过其一个细小的 Bug;而其他的众多组件预计会在明年(即 2020 年)停止维护。所以急需其他的一些替代产品,也就是 spring cloud alibaba,目前正处于蓬勃发展的态式。

二. 注册中心 Nacos

nacos 是阿里巴巴研发的一个集注册中心与配置中心于一体的管理平台,使用其他非常的简单。下载地址为:
https://github.com/alibaba/nacos/releases,nacos 的主页如下图所示:

其中默认的登录名和密码是:nacos/nacos

2.1 更改用户名和密码

A. 分别执行 conf 目录下的 nacos-mysql.sql 两个脚本文件,生成的数据表如下:

B. 重新配置密码
新建一个 springboot 的项目,引入如下的依赖:

   org.springframework.boot
   spring-boot-starter-security

生成密码的 Java 代码:
// 密码加密处理
public static void main(String[] args) {
   BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
   // 生成的密码为:$2a$10$04MGTL.cJNZPpR3rFt/I2.43F.V75NH.5wdK.jngaO9Mc91mfonAO
   System.out.println(bCryptPasswordEncoder.encode(“admin”));
}
执行如下sql命令:
insert into users values(‘nacos’,’$2a$10$04MGTL.cJNZPpR3rFt/I2.43F.V75NH.5wdK.jngaO9Mc91mfonAO’, 1);
insert into roles values(‘nacos’, ‘ROLE_ADMIN’)
C. 配置数据库的连接
在 conf 目录下的 application.properties 目录下加入如下内容:
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://mysql:3306/cloud-alibaba?useSSL=false&serverTimezone=UTC&serverTimezone=Asia/Shanghai
db.user=root
db.password=

2.2 nacos 集群配置

A. 将 conf 目录下的 cluster.conf.example 拷贝一份重命名为 cluster.conf,在文件中加入所有集群节点的 ip 和端口号,文件内容如下:
127.0.0.1:8848
127.0.0.1:9948
B. 修改 windows 启动文件 startup.cmd 的配置,修改内容如下:
set MODE=”standalone”  #默认的配置
set MODE=”cluster”     #修改后的内容
注:如果是 Linux 环境不用作任何的修改。
C.启动两个 nacos,界面中出现如下的内容,表示集群配置成功

三. 服务提供方

pom.xml 依赖配置

   
       org.springframework.boot
       spring-boot-starter-web
   

   
       org.springframework.cloud
       spring-cloud-starter-alibaba-nacos-discovery
   



   
       
           org.springframework.cloud
           spring-cloud-alibaba-dependencies
           0.9.0.RELEASE
           pom
           import
       

       
           org.springframework.cloud
           spring-cloud-dependencies
           Greenwich.SR2
           pom
           import
       

   


配置
spring:
 application:
   # 服务名
   name: alibaba-provider
 cloud:
   nacos:
     discovery:
       #必须配置 ip 地址
       server-addr: 172.18.96.177:8848,172.18.96.177:9948
启动类
@SpringBootApplication
@EnableDiscoveryClient  //开启 nacos 的服务发现
public class ProviderApplication {
   public static void main(String[] args) {
       SpringApplication.run(ProviderApplication.class, args);
   }
}
服务页面结果

四. 服务消费方

pom.xml 依赖配置

   
       org.springframework.boot
       spring-boot-starter-web
   

   
       org.springframework.cloud
       spring-cloud-starter-alibaba-nacos-discovery
   



   
       
           org.springframework.cloud
           spring-cloud-alibaba-dependencies
           0.9.0.RELEASE
           pom
           import
       

       
           org.springframework.cloud
           spring-cloud-dependencies
           Greenwich.SR2
           pom
           import
       

   


配置
server:
 port: 8080
spring:
 application:
   # 服务名
   name: alibaba-consumer
 cloud:
   nacos:
     discovery:
       server-addr: 172.18.96.177:8848,172.18.96.177:9948
       # 不将自己的服务注册到注册中心
       register-enabled: false
启动类配置
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
   public static void main(String[] args) {
       SpringApplication.run(ConsumerApplication.class, args);
   }
}
调用服务提供方
@RequestMapping
   public Object getUsers() {
       List list = discoveryClient.getInstances(“alibaba-provider”);
       String targetUrl = list.stream().map(si -> si.getUri() + “/user”).findFirst().get();
       List resultList = restTemplate.getForObject(targetUrl, List.class);
       return resultList;
   }

五. Ribbon 负载均衡

Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具。其主要功能是提供客户端的负载均衡算法,并提供了完善的配置项如连接超时,重试等。简单的说,就是配置文件中列出 Load Balancer 后面所有的机器,Ribbon 会自动的基于某种规则(如简单轮询,随机连接等)去连接这些机器,当然我们也可以使用 Ribbon 自定义负载均衡算法。

5.1 实现负载均衡

Ribbon 只是一个客户端的负载均衡器工具,实现起来非常的简单,我们只需要在注入 RestTemplate 的 bean 上加上@LoadBalanced 就可以了。如下:
@Configuration
public class WebConfig {
   @LoadBalanced
   @Bean
   public RestTemplate restTemplate() {
       return new RestTemplate();
   }
}

5.2 服务消费方调用

// 直接写上服务名即可
List resultList = restTemplate.getForObject(“http://alibaba-provider/user", List.class);

5.3 负载均衡策略

Ribbon 提供了一个很重要的接口叫做 IRule,其中定义了很多的负载均衡策略,默认的是轮询的方式,以下是 Ribbon 的负载均衡策略:

类名 描述
RoundRobbinRule 轮询
RandomRule 随机挑选
RetryRule 按照轮询的方式去调用服务,如果其中某个服务不可用,但是还是会尝试几次,如果尝试过几次都没有成功,那么就不在调用该服务,会轮询调用其他的可用服务。
AvailabilityFilteringRule 会先过滤掉因为多次访问不可达和并发超过阈值的服务,然后轮询调用其他的服务
WeightedResponseTimeRule 根据平均响应时间计算权重,响应越快权重越大,越容易被选中。服务刚重启的时候,还未统计出权重会按照轮询的方式;当统计信息足够的时候,就会按照权重信息访问
ZoneAvoidanceRule 判断 server 所在的区域性能和可用性选择服务器
BestAvailableRule 会过滤掉多次访问都不可达的服务,然后选择并发量最小的服务进行调用,默认方式

改变 Ribbon 的负责均衡策略:
@Bean
public IRule getRule() {
   return new RandomRule();
}

5.4 自定义负载均衡策略

我们自定义的负载均衡策略需要继承 AbstractLoadBalancerRule 这个类,然后重写 choose 方法,然后将其注入到容器中,如下所示:
public class Customize_Rule extends AbstractLoadBalancerRule {
   private static Logger logger = LoggerFactory.getLogger(Customize_Rule.class);
   private int currentIndex = 0; //当前调用的索引
   private int num = 1; //次数
   private int limit = 5;
   /**
    _ 初始化工作
    _ @param iClientConfig
    */
   @Override
   public void initWithNiwsConfig(IClientConfig iClientConfig) {
   }
   @Override
   public Server choose(Object key) {
       ILoadBalancer balancer = getLoadBalancer();
       return choose(balancer, key);
   }
   private Server choose(ILoadBalancer balancer, Object key) {
       Server server = null;
       while(null == server) {
           //获取所有可用的服务
           List reachableServers = balancer.getReachableServers();
           if (0 == reachableServers.size()) {
               logger.error(“没有可用的服务”);
               return null;  //退出 while 循环
           }
           int total = reachableServers.size(); //可用服务的数量
           synchronized (this) {
               /**
                _ 有种极端情况,当我们在使用最后一个服务的时候,其他的服务都不可用,可能导致索引越界异常
                _/
               if (currentIndex + 1 > total) {
                   currentIndex = 0;
                   server = reachableServers.get(currentIndex);  //获取第一个服务
                   num = 0;
                   num++;
               } else {
                   if(limit == num) {
                       currentIndex++;
                       num = 0;
                       if(currentIndex == total) {
                           currentIndex=0;
                           server = reachableServers.get(currentIndex);  //获取第一个服务
                           num++;
                       }else{
                           server = reachableServers.get(currentIndex);
                           num++;
                       }
                   }else {
                       server = reachableServers.get(currentIndex);
                       num++;
                   }
               }
           }
       }
       return server;
   }
}
将其注入到容器中,方式如下:
@Bean
public IRule getRule() {
   return new Customize_Rule();
}

六. Feign 负载均衡

feign 是基于 Ribbon 的另外一个负载均衡的客户端框架,只需要在接口上定义要调用的服务名即可,使用起来非常的简单。
pom.xml 依赖

   org.springframework.cloud
   spring-cloud-starter-openfeign

启动类配置
需要在启动类上加上@EnableFeignClients 这个注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
   public static void main(String[] args) {
       SpringApplication.run(ConsumerApplication.class, args);
   }
}
服务接口配置
@FeignClient(name=”alibaba-provider”)
public interface UserService {
   @RequestMapping(“/user”)
   public List getUsers();
}

七. 熔断与服务降级

分布式系统中一个微服务需要依赖于很多的其他的服务,那么服务就会不可避免的失败。例如 A 服务依赖于 B、C、D 等很多的服务,当 B 服务不可用的时候,会一直阻塞或者异常,更不会去调用 C 服务和 D 服务。同时假设有其他的服务也依赖于 B 服务,也会碰到同样的问题,这就及有可能导致雪崩效应。
如下案例:一个用户通过通过 web 容器访问应用,他要先后调用 A、H、I、P 四个模块,一切看着都很美好。

由于某些原因,导致 I 服务不可用,与此同时我们没有快速处理,会导致该用户一直处于阻塞状态。

当其他用户做同样的请求,也会面临着同样的问题,tomcat 支持的最大并发数是有限的,资源都是有限的,将整个服务器拖垮都是有可能的。

Sentinel 是一个用于分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖会不可避免的调用失败,例如超时,异常等,Hystrix 能保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
断路器本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似于保险丝),向调用者返回符合预期的,可处理的备选响应,而不是长时间的等待或者抛出无法处理的异常,这样就保证了服务调用的线程不会被长时间,不必要的占用,从而避免故障在分布式系统中的蔓延,乃至雪崩。
Sentinel 在网络依赖服务出现高延迟或者失败时,为系统提供保护和控制;可以进行快速失败,缩短延迟等待时间;提供失败回退(Fallback)和相对优雅的服务降级机制;提供有效的服务容错监控、报警和运维控制手段。
下载地址:https://github.com/alibaba/Sentinel/releases

7.1 依赖

   org.springframework.cloud    spring-cloud-starter-alibaba-sentinel

7.2 配置

通常情况下我们只需要在服务提供方实现熔断或者服务降级即可,但是如果要相对服务消费方是实现限流,在服务的提供方和消费方都需要加如下配置。
spring:
 cloud:
    sentinel:
     transport:
       # sentinel 控制台的地址
       dashboard: localhost:8080
     # 立即加载
     eager: true

7.3 sentinel 的控制面板

在浏览器输入 localhost:8080,使用 sentinel/sentinel 来访问 sentinel 的控制面板:

接下来对 sentinel 的控制面板一一讲解:
实时监控
用于查看接口调用的 QPS(Query Per Second)以及平均响应时间。
簇点链路
查看当前追踪的所有的访问接口,可以添加流量规则降级规则热点规则授权规则
流量规则

资源名:是需要控制的链路的名字,例如/student/all 等
针对来源: 默认为 default 表示所有,也可以针对特定的服务进行设置。
阈值类型: 是指如何进行限制,可以是 QPS,也可以是线程。
单机阈值: 是控制 QPS 或者线程的数量。
流量模式: 直接表示只是针对指定资源进行限制;关联是指当被关联的资源达到阈值时候,指定资源被限制访问;链路是更加细粒度的控制,控制指定资源对链路的限制。
流控效果: 快速失败是指,当无法访问的时候立即给用户一个错误响应;Warm Up(预热)是指经过指定的时间后才达到指定的阈值(sentinel 内有值为 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值,参考地址:https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81—%E5%86%B7%E5%90%AF%E5%8A%A8);排队等待只是匀速的通过(每秒指定的 QPS),其他的请求进行排队,但是并不会一直排下去,超过指定的时间就会失败,阈值类型必须设置为 QPS,不能为线程。
降级规则

资源名: 要实现降级的资源。
降级策略:

  1. RT(平均响应时间)

如果在一秒钟之内进入的请求的平均响应时间小于 1ms,那么在未来 5s 钟之内所有的请求都会熔断降级。 2) 异常比例
如果在一秒钟之内的请求数异常比例大于指定的数据,那么在未来的时间窗口内会一直熔断降级。统计单位为 s. 3) 异常数
如果在一分钟之内,异常数量大于指定的值,那么在指定的时间窗口内请求一直会熔断降级,注意时间窗口的值一般设置要大于 60,因为设置如果小于 60,可能会一直处于熔断状态。
热点规则
热点规则是针对具体的请求参数进行设置,例如如下的方法:
@RequestMapping(“/edit”)
@SentinelResource(“edit”)   //必须的有
public Object edit(@RequestParam(required = false) String id,
                  @RequestParam(required = false) Integer age) {
   return this.studentService.commons();
}

资源名: 是@SentinelResource 中设置的值
参数索引: 对那个参数进行 QPS 限制,通过索引来指定。
单机阈值:指定在统计时长内的阈值。
统计窗口时长: **统计的 QPS 的时间。
**系统规则

LOAD: 仅对 Linux/Unix-like 机器生效,参考值一般是 CPU cores * 2.5
RT: **当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
**线程数: **当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
**入口 QPS:
当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
授权规则
授权规则是指可以将特定的访问应用加入黑名单或者白名单,但是必须在访问的时候携带应用的名称。代码实现部分如下:
@Component
public class SentinelOriginParser implements RequestOriginParser {
   @Override
   public String parseOrigin(HttpServletRequest httpServletRequest) {
       String origin = httpServletRequest.getParameter(“origin”);
       if(StringUtil.isBlank(origin)) {
           throw new IllegalArgumentException(“origin parameter must be specified.”);
       }
       return origin;
   }
}
加上了来源解析后,在往后的访问中必须要携带 origin 参数,在 sentinel 的 dashboard 中可以作如下配置:

集群流控

是否集群: **是否采用集群
**均摊阈值:
就是每个集群节点每秒的 QPS.
集群阈值模式: **单机均摊是集群中每个节点每秒的 QPS, 总体阈值是整个集群每秒的 QPS.
**集群流控

7.4 @SentinelResource

@SentinelResource 是 sentinel 中非常重要的注解,提供了简单易用的功能。其中 blockHandler 注解是限流的处理方法,fallback 是服务降级的处理方法。
@SentinelResource(value=”edit”, blockHandler=”editBlock”, fallback = “editFallback”)
@RequestMapping(“/edit”)
public Object edit(@RequestParam(required = false) String id,
                  @RequestParam(required = false) Integer age) throws Exception {
   Thread.sleep(20);
   return this.studentService.commons();
}
// 限流的处理
public Object editBlock(String id, Integer age, BlockException ex) {
   Map<String, Object> map = new HashMap<>();
   map.put(“msg”, “限流了.”);
   return map;
}
//服务降级的处理方法
public Object editFallback(String id, Integer age) {
   Map<String, Object> map = new HashMap<>();
   map.put(“msg”, “fallback 服务降级了.”);
   return map;
}

八. Feign 与 Sentinel 的整合

配置
feign:
 sentinel:
   # 默认是没有提示的
   enabled: true

8.1 服务降级后的处理

可以在@FeignClient 中配置 fallback,来指定服务降级后给用户返回的什么样的数据,fallback 的值为 Class 类型的对象,该类必须要实现该对应的接口。
@FeignClient(name=”alibaba-provider”, fallback = UserServiceFallback.class)
public interface UserService {
   @RequestMapping(“/user”)
   public List getUsers();
}
UserServiceFallback 的实现如下:
@Component
public class UserServiceFallback implements UserService {
   @Override
   public List getUsers() {
       return Arrays.asList(“服务降级了, 这是降级后返回的信息.”);
   }
}

8.2 服务降级的异常处理

对于 fallback 来讲,并不能追踪到异常信息,在实际的业务处理过程中,我们往往需要记录异常的信息,那么就要使用 fallbackFactory 属性来实现。
@Component
public class UserServiceFallbackFactory implements FallbackFactory {
   private static Logger logger = LoggerFactory.getLogger(UserServiceFallbackFactory.class);
   @Override
   public UserService create(Throwable cause) {
       return new UserService() {
           @Override
           public List getUsers() {
               logger.info(cause.getMessage());
               return Arrays.asList(“服务降级的方法”);
           }
       };
   }
}

九. Sentinel 的持久化

通过接入 Sentinel Dashboard 后,在页面上操作来更新规则,都无法避免一个问题,那就是服务重新后,规则就丢失了,因为默认情况下规则是保存在内存中的。sentinel 中持久化的方式有两种,pull 模式和 push 模式。
pull 模式是指站在第三方持久化系统(redis, nacos)的角度,他们去到 sentinel 中定时去拉去配置信息,可能会造成数据的不一致性。
push 模式是站在 sentinel 的角度,将其配置信息主动推送给第三方持久化系统,sentinel 官方也推荐在线上使用该模式。

9.1 sentinel-dashboard 改造

A. 将 sentinel 的源码 clone 到本地
B. 进入到 sentinel-dashboard 目录下,修改 pom.xml 文件

   com.alibaba.csp    sentinel-datasource-nacos    test    com.alibaba.csp    sentinel-datasource-nacos C. 修改 src\main\webapp\resources\app\scripts\directives\sidebar\sidebar.html 文件 **未修改之前的内容**
  •              流控规则    
  • **修改之后的内容**
  •              流控规则    
  • D. 将 `src\test\java\com\alibaba\csp\sentinel\dashboard\rule\nacos` 目录下的四个Java文件拷贝到`src\main\java\com\alibaba\csp\sentinel\dashboard\rule` 目录下

    E. 修改 src\main\webapp\resources\app\scripts\controllers\identity.js 文件,修改内容如下:

    F. 重新打包生成 Jar 包,进入到 sentinel 目录下(注:不是 sentinel-dashboard 目录),执行如下命令:
    mvn clean                
    mvn install -DskipTests
    G. 进入到 sentinel-dashboard/target 目录下,执行如下内容:
    java -jar sentinel-dashboard.jar

    9.2 配置

    spring:
     cloud:
       sentinel:
         datasource:
           # 这个名字随意,但是要有意义
           flow:
             nacos:
               server-addr: 192.168.31.173:8848
               groupId: SENTINEL_GROUP
               rule-type: flow

    9.3 测试

    在 sentinel-dashboard 控制面板添加一个流量控制规则