springCloud --- 高级篇(1)

本系列笔记涉及到的代码在GitHub上,地址:https://github.com/zsllsz/cloud

本文涉及知识点:

  • springCloud Alibaba介绍;

  • nacos做注册中心;

  • nacos做配置中心;

  • sentinel流控;


欢迎大家关注我的公众号 javawebkf,目前正在慢慢地将简书文章搬到公众号,以后简书和公众号文章将同步更新,且简书上的付费文章在公众号上将免费。


一、springCloud Alibaba简介

1、为什么会诞生springCloud Alibaba?
通过springCloud初级篇和中级篇的学习,我们知道springCloud很多技术现在都停更了,Netflix也说明了不再维护了。一开始阿里搞出了一个Dubbo,但是后面停更了几年,spring就联合Netflix搞出了一个springCloud生态,Netflix主要技术有5个,eureka、ribbon、feign、zuul和config。但是呢Netflix内部神仙打架,意见不一,然后导致这些技术大部分停更了。这时,阿里又杀出来了,搞出了一套springCloud Alibaba,spring官方看它还不错,阿里也想推广自己,所以在2018年四月份spring又把springCloud Alibaba给收编了。所以对于我们使用者而言,无非就是从 springCloud Netflix 换成 springCloud Alibaba而已。

2、springCloud Alibaba能干嘛?

  • 服务的注册与发现
  • 服务的降级与限流
  • 分布式配置管理
  • 消息驱动能力
  • 分布式任务调度
  • 阿里云对象存储
  • 阿里云短信服务

可以发现,springCloud能干的它几乎都能干,还多了一些springCloud不能干的。springCloud Alibaba相关网址如下,可以查阅相关文档:
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
一般我们要学怎么使用查第二个文档就好了。

二、springCloud Alibaba Nacos 代替eureka做服务注册中心

之前springCloud的服务注册可以用eureka、consul和zookeeper,配置中心用config和bus,现在springCloud Alibaba用Nacos搞定这些,简言之就是注册中心加配置中心。
1、下载安装:

bash startup.sh -m standalone
  • 测试:访问 ip:8848/nacos,看到如下画面就启动成功了,默认账号密码都是nacos。


    nacos

2、基于nacos的服务提供者:

  • 新建名为cloudalibaba-provider-payment9001的module
  • 父pom.xml中添加springCloud Alibaba依赖(一开始就添加过了)
  • pom.xml:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator </artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<!-- nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • application.yml:
server:
  port: 9001
spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
# 端点启动和暴露(这时actuator的功能,学springcloud config时也配置过)
management:
  endpoints:
    web:
      exposure:
        include:
        - "*"
  • 主启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class NacosMainPayment9001 {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(NacosMainPayment9001.class, args);
    }
}
  • 业务类:
@RestController
@RequestMapping("/provider")
public class PaymentController {
    
    @GetMapping("/payment/{id}")
    public String payment(@PathVariable("id") Integer id) {
        return "success" + id;
    }
}
  • 最后启动该项目,并访问测试一下,然后看看nacos控制台。


    服务成功注册进nacos
  • 为了等下演示nacos的负载均衡,新建9002,内容几乎和9001一样。

3、基于nacos的服务消费者:

  • 新建名为cloudalibaba-consumer-nacos-order80的module
  • pom.xml:和9001的一样
  • application.yml:
server:
  port: 80
spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
# 消费者将要去访问的微服务名称
service-url:
  nacos-user-service: http://nacos-payment-provider
  • 主启动类:和9001的一样,也是那两个注解
  • 配置RestTemplate:
@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
  • controller:
@RestController
@RequestMapping("/consumer")
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;
    
    @Value("${service-url.nacos-user-service}")
    private String serverUrl;
    
    @GetMapping("/order/{id}")
    public String getPayment(@PathVariable("id") Integer id) {
        return restTemplate.getForObject(serverUrl + "/provider/payment/" + id, String.class);
    }
}

访问这个controller,就会发现一次是调用9001,一次是调用9002。因为nacos也集成了ribbon,所以自带负载均衡。

4、nacos的CAP模型:
nacos支持CP和AP,可以自由切换。这样一来,nacos可以代替所有的注册中心。如果要代替eureka,用AP,如果要代替consul或者zookeeper,用CP。

三、springCloud Alibaba Nacos 代替config做服务配置中心

1、新建名为cloudalibaba-config-nacos-client3377的module:

  • pom.xml:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator </artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<!-- nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos config -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  • yml:和config一样,要先搞一个bootstrap.yml从配置中心拉取配置,其他自己专有的再用application.yml。先有共性再有个性。
    bootstrap.yml:
server:
  port: 3377
spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
      config:
        server-addr: 192.168.0.106:8848
        file-extension: yaml #指定yaml格式的配置,就是对应以前用config时GitHub上的配置文件

application.yml:

spring:
  profiles:
    active:
    - dev #激活开发环境
  • 主启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConfigMain3377 {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(NacosConfigMain3377.class, args);
    }
}
  • controller:用@Value注解尝试读取nacos中的配置文件
@RestController
@RequestMapping("/config")
@RefreshScope // 动态刷新
public class NacosConfigController {
    @Value("${config.info}")
    private String info;
    
    @GetMapping("/info")
    public String info() {
        return info;
    }
}

至此工程新建完毕,接下来就要去nacos中新建配置文件。

2、在nacos上新建配置文件:

  • 先看一下nacos配置管理的界面:
nacos配置管理

可以看到有一个Data Id,一个Group。
Data Id的格式:

${prefix}-${spring.profile.active}.${file-extension}
  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
  • spring.profile.active 即为当前环境对应的 profile,注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}。在使用的时候,还是不要让它为空,免得出现奇奇怪怪的问题。
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 类型。
  • 通过 Spring Cloud 原生注解 @RefreshScope 实现配置自动更新,这就是刚才controller中加这个注解的原因。
    按照上面的格式,那3377这个项目的data Id就是:nacos-config-client-dev.yaml
    然后在nacos上新建如下配置:
新建配置

这个配置一定要注意每个冒号后面要加一个空格!

  • 然后访问3377的controller,就可以成功获取到配置了:
成功读取到配置
  • 动态刷新:现在修改nacos上的配置文件,然后再次访问3377的controller,会发现可以获取到最新的内容。(如果没有获取到最新的,那就是浏览器缓存的原因)

3、nacos配置管理的命名空间、data id和group的关系:

nacos
  • 命名空间:有一个默认的public,相当于对group进行分组管理
  • 分组group:有一个默认的DEFAULT_GROUP,对data Id进行分组管理
  • data Id:理解为配置文件的id

所以三者关系就是:namespace + group + data Id可以确认一个唯一的配置文件。

这样设计的好处就是可以方便地区分不同的环境。比如现在有开发、测试和生产三个环境,那我们就新建三个不同的namespace。

使用默认命名空间默认分组读取不同data id的配置文件:

  • 在nacos上新建nacos-config-client-test.yaml,当作测试环境的配置文件,目前都是public命名空间默认分组下;
  • 将3377的application.yml中的dev改成test,就可以读测试环境配置文件了;

使用默认命名空间不同分组读取配置配置文件:

  • 在nacos上新建两个配置文件,名字都叫nacos-config-client-info.yaml,分组分别为DEV_GROUP和TEST_GROUP;
  • 然后bootstrap.yml和application.yml分别改成这样:
server:
  port: 3377
spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
      config:
        server-addr: 192.168.0.106:8848
        file-extension: yaml #指定yaml格式的配置,就是对应以前用config时GitHub上的配置文件
        group: DEV_GROUP
spring:
  profiles:
    active:
    #- dev #激活开发环境
    - info

这样就搞定了,这样读取的就是dev_group分组下的info配置文件。

使用不同的命名空间读取不同的配置文件:

  • 新建DEV和TEST命名空间;
命名空间

bootstrap.yml再加一行配置,如下:

server:
  port: 3377
spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
      config:
        server-addr: 192.168.0.106:8848
        file-extension: yaml #指定yaml格式的配置,就是对应以前用config时GitHub上的配置文件
        namespace: 4d4b4c98-2746-4ff7-8ce9-eb2837db456c
        group: DEV_GROUP

这样就可以指定命名空间指定分组读取指定后缀文件了。

四、nacos集群和持久化

官网的集群架构图:VIP的意思是虚拟IP,我们可以认为vip就是nginx的集群。

nacos集群架构图

nacos默认自带了一个数据库(derby)用来做持久化,当nacos集群的时候,每一台服务器上的nacos都以自己自带的数据库来搞持久化,会存在数据一致性的问题。所以干脆都别用自带的数据库了,都用MySQL。

1、用MySQL做持久化:

  • 在nacos/conf目录下有一个nacos-mysql.sql的脚本,在MySQL中新建一个名为nacos_config的数据库,在此数据库中执行这个脚本即可;
  • 在nacos/conf目录下有一个application.properties文件,打开修改它将官网的这段配置粘贴到其中,并将MySQL连接地址改成自己的就可以了:
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://192.168.0.106:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=你的MySQL密码
  • 修改完重启nacos,重启之后立即访问可能会访问不了,毕竟人家启动也要时间嘛。启动完登陆进去,发现之前的配置都没了。然后自己新增配置,再去数据库查看,发现nacos_config数据库的config_info表中就有数据了,说明切换到MySQL成功了。

2、nacos集群:
需要的环境:

  • 2个nginx(此处为了简单就只启动了一个nginx,想了解nginx集群可以参考我nginx相关文章)
  • 3个nacos
  • 1个mysql

nginx集群和mysql已经安装好了,nacos集群通过如下步骤搞定:

  • nacos的application.properties中加上MySQL配置(之前已经加过);
  • 拷贝cluster.conf.example改名为cluster.conf,然后修改其内容:
192.168.2.43:8848
192.168.2.43:8849
192.168.2.43:8850

由于这里是在一台虚拟机上启动3份nacos,所以后面要加上端口。如果有3台不同的机器,那么这里直接写那3台机器的ip就好了。并且要注意,这个ip不能填写127.0.0.1,执行hostname -i,填写ens33后面跟着的那个IP。

  • 修改nacos的startup.sh,使其能够指定端口启动:
    修改有两处,第一处,增加端口启动,如下图,左边是修改前,右边是修改后:
增加端口启动

第二次启动,启动日志增加端口打印,如下图,上面是修改前,下面是修改后:

增加端口打印

这样就改完了。

  • 修改nginx的conf配置文件:
 upstream cluster {
      server  192.168.2.43:8848;
      server  192.168.2.43:8849;
      server  192.168.2.43:8850;
  }

  server {
      listen       80;
      server_name  192.168.2.43;
      location / {
      proxy_pass  http://cluster;
  }

这样所有配置都整完了,接下来依次启动nginx和nacos.

  • 启动nginx;
  • 启动nacos集群:
./startup.sh -p 8848
./startup.sh -p 8849
./startup.sh -p 8850

启动成功后,访问:192.168.2.43/nacos,登录后可以看到如下效果:

集群节点

可能遇到的坑:

  • 异常1:
unable to find local peer: 192.168.2.43:0, all peers: [192.168.2.43:8850, 192.168.2.43:8849, 192.168.2.43:8848]

解决办法:在nacos/config/application.properties中加上如下配置:

nacos.inetutils.ip-address=192.168.2.43
  • 异常2:一直启动中,nacos is starting……,可能是虚拟机内存不够,将启动命令中为jvm分配的内存设置小一点即可,如下:
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"


五、springCloud Alibaba Sentinel介绍和基本使用

1、sentinel介绍:
之前我们用hystrix豪猪哥来实现服务降级熔断,sentinel也是干这事的,并且更加强大和好用。

  • 官网:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

sentinel能够解决的问题:

  • 服务雪崩
  • 服务降级
  • 服务熔断
  • 服务限流

2、优点:

  • 丰富的应用场景:秒杀、消息削峰填谷,集群流量控制,实时熔断等
  • 完备的实时监控:精确到秒的实时监控
  • 广泛的开原生态:可以快速的与springCloud、dubbo等框架整合
  • 完善的SPI扩展点:提供了完善的SPI扩展接口,可以通过实现扩展接口来定制逻辑

3、下载安装:

  • 下载地址:https://github.com/alibaba/Sentinel/releases,选择对应版本点击进入,下载dashboard即可,比如我下载的是sentinel-dashboard-1.7.0.jar

  • 使用文档:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel

  • sentinel运行:控制台由两部分构成,前台和后台。前台(即web界面)的端口是8080,所以8080端口不能被占用,并且要jdk8及以上。下载完成后直接java -jar运行jar包即可。然后访问8080,出现如下界面即启动成功,默认用户名和密码都是sentinel。

sentinel

相比豪猪哥,简单了很多,豪猪哥需要我们引入依赖后自己启动一个微服务当成dashboard,而sentinel不需要自己写,直接运行jar包就可以了。

4、初用sentinel:

  • 启动8848的nacos(为了简单,就启动了单机版而不是集群版)
  • 新建名为cloudalibaba-sentinel-service8401的module:
    pom.xml:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator </artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<!-- nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 做持久化的时候会用到的 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

application.yml:

server:
  port: 8401
spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
    sentinel:
      transport:
        dashboard: 192.168.0.106:8080
        port: 8719 # 默认8719,如果被占用会依次加1,直至找到没有被占用的端口
# actuator图形化配置
management:
  endpoints:
    web:
      exposure:
        include:
        - "*"

主启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class SentinalMain8401 {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(SentinalMain8401.class, args);
    }
}

controller:

@RestController
@RequestMapping("/sentinel")
public class FlowLimitController {

    @GetMapping("/testA")
    public String testA() {
        return "=========== testA ==========";
    }
    
    @GetMapping("/testB")
    public String testB() {
        return "=========== testB ==========";
    }
}
  • 启动8080的sentinel
  • 启动8401的微服务
  • 访问8401微服务,然后看sentinel控制台是否有相关信息

8080控制台并没有任何8401的相关信息,不要方,这是因为sentinel使用懒加载机制,没调用的不会出现在dashboard中。访问一下8401,然后再看dashboard中就有了。

sentinel正在监控8401

然后访问几次8401,实时监控中就会有相关信息了,如下图:

testA的调用信息

可能遇到的坑:不管怎么访问8401,实时监控中都没有调用信息,可能的原因有:

  • 如果你的sentinel运行在虚拟机上,8401运行在自己电脑上,那么可能原因是虚拟机时间和本机时间不一致导致。解决办法就是更新虚拟机时间,命令如下:
yum -y install ntp ntpdate
ntpdate cn.pool.ntp.org
  • 如果sentinel运行在虚拟机上,8401在本机,访问8401后,sentinel没有监控到,并且sentinel运行日志报了如下的错误:
java.util.concurrent.ExecutionException: java.net.NoRouteToHostException: 没有到主机的路由

解决办法就是在8401的application.yml中加上如下配置:

sentinel:
      transport:
        dashboard: 192.168.0.106:8080
        client-ip: 192.168.0.104 # 这一行是新加的
        port: 8719 # 默认8719,如果被占用会依次加1,直至找到没有被占用的端口

就是加上client-ip,值就是8401项目,即你要sentinel监控的项目所运行的机器的IP。

六、sentinel流控规则

1、基本介绍:

sentinel流控规则

在sentinel的dashboard中有一个流控规则,如上图,下面对这些名词进行解释。

  • 资源名:唯一名称,默认请求路径
  • 针对来源:sentinel可以针对调用者进行限流,填写微服务名称,默认default,表示不区分来源
  • 阈值类型:QPS(每秒钟的请求数):当调用该API的QPS达到阈值时,进行限流;线程数:当调用该API的线程数达到阈值时进行限流
  • 是否集群:不需要集群
  • 流控模式:直接:达到限流条件时直接限流;关联:关联的资源达到阈值时限流自己(当与A关联的资源B达到阈值时,就限流A自己。应用场景:支付服务达到阈值的时候,就限流下订单的服务);链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流,API级别的针对来源)
  • 流控效果:快速失败:直接失败,抛异常;warm up:根据codeFactor(冷加载因子,默认是3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
  • 排队等待:匀速排队,让请求以匀速通过,阈值类型必须设置为QPS,否则无效

2、流控模式:

流控模式之直接:

QPS直接快速失败:

  • 添加流控规则:在簇点链路那里添加流控即可,比如我添加的如下:
sentinel QPS直接快速失败

这里设置的意思就是,访问testB,一秒钟超过一次,就是直接快速报错。我现在对testB连点两下,就会返回如下信息:

sentinel对testB进行流控
  • 存在的问题:超过QPS阈值的时候,返回的是默认信息Blocked by Sentinel (flow limiting),如何自定义呢?应该有hystrix一样的兜底方法(后续再说其用法)。

线程数直接失败:

sentinel线程数直接失败

我们再访问testB,发现不管点多快,都没有被流控给拦住。因为我们在一个浏览器中访问,始终是一个线程。这样配置的意思就是并发线程数超过阈值1时,就会返回失败信息。可以用jmeter模拟并发访问的情况。

流控模式之关联:

配置如下:

sentinel流控之关联

这里的意思就是testA的QPS数超过1,就会导致testB不能用。测试方法:用jmeter对testA进行并发访问,然后我们在浏览器访问testB。就会发现也会返回Blocked by Sentinel (flow limiting)

流控模式之链路:
多个请求调用了同一个微服务超过了QPS阈值就会快速失败,配置如下:

sentinel流控之链路

注意入口资源的名字就是sentinel簇点链路中显示的名字。然后快速访问A,只要QPS超过1,就会返回Blocked by Sentinel (flow limiting)

3、流控效果:
上面用的流控效果都是快速失败,现在来认识一下这些流控效果。

  • 快速失败:上面的案例都是直接失败,就是超过阈值就返回Blocked by Sentinel (flow limiting)
  • 预热( Warm Up):请求的QPS从 阈值 / 冷加载因子(默认是3)开始,经过 预热时长,最后达到阈值。比如下图的配置意思是:初始阈值为 10 / 3 = 3,经过10秒钟的时间,阈值慢慢升到10。现在快速访问testA,因为一开始QPS阈值为3,所以你点快一点可能就失败了,但是慢慢地,你点很快都不会失败了,因为最后阈值升为10,正常情况下没有人手速能达到1秒钟点11次吧。
sentinel流控效果之预热
  • 排队等待:就是不管同时有多少个请求过来,我每秒钟只处理阈值数的请求,其他老老实实排队等待去。如下图配置意思就是每秒钟只处理一个,其他的排队等着,每隔1000毫秒才放下一个请求进来。
sentinel流控效果之排队等待
禁止转载,如需转载请通过简信或评论联系作者。