基于SpringCloudAlibaba的学习和搭建的微服务项目案例

一,说明

  1. 为了学习,并且搭建一个可在实际工作中使用的微服务项目,并且在单体服务中整合开发中会使用到的其他技术,如redisshiro等技术。
  2. 注:此处参考了很多Jeecg-boot开源项目。
  3. 参考了很多网上的资料。

二,技术选型及其版本

一,技术选型

  1. 开发环境
    | 开发环境(Java) | 备注 | | —- | —- | | JDK | JDK8以上。(本机使用的JDK1.8.0_291) | | 依赖管理 | Maven | | 数据库 | MySQL5.7+ & Oracle 11g & Sqlserver2017 我们目前服务器用的sqlserver2012/mysql5.5+ | | 缓存数据库 | Redis | | 开发工具 | IDEA |

  2. 整体项目所用技术
    | 整体项目所需框架(后端) | 描述 | 版本 | | —- | —- | —- | | 基础框架 | SpringBoot | 2.3.5.RELEASE | | 微服务框架 | SpringCloud Alibaba | 2.2.3.RELEASE | | 原生微服务支持 | SpringCloud | Hoxton.SR8 | | 持久层框架 | Mybatis-plus | 3.4.1 | | MVC框架 | SpringMvc | 跟随springboot即可 | | 安全框架 | Apache Shiro ;Jwt | Apache Shiro 1.7.0,Jwt 3.11.0 | | 数据库连接池 | Driud | | | 日志打印 | logback | | | 其他 | fastjson,poi,Swagger-ui,quartz, lombok | |

  3. 微服务相关技术组件
    | 功能组件 | 使用框架 | 备注 | | —- | —- | —- | | 注册中心 | nacos | 动态服务发现、配置管理和服务管理平台。 | | 服务熔断 | sentinel | | | 负载均衡 | ribbon | 使用openFeign后默认集成 | | 服务调用 | OpenFeign | springcloudalibaba兼容了openFeign(feign),而feign默认集成了Ribbon(负载均衡)。 | | 消息队列 | RocketMQ | | | 配置中心 | nacos config | 集成后和注册中心应该是同一个界面。 | | 分布式事务 | seata | | | 网关 | GateWay | | | 链路追踪 | Skywalking | | | 分布式任务 | xxl-job | |

二,微服务相关组件简单说明

  • 注册中心 nocas
    使用nacos作为注册中心,用来管理注册上来的各个微服务。
    在springclod中,我们的注册中心和配置中心是使用eureka + config + bus,而nacos直接将这三个给取代了,而且不需要像之前一样需要重启项目才能进行访问。
  • 服务熔断 sentinel
  • 负载均衡 ribbon
  • 服务调用 feign
  • 消息队列 RocketMQ
  • 配置中心 nocas config
  • 分布式事务 seata
  • 网关 GateWay
  • 消息总线 SpringCloud Bus
  • 链路追踪 SkyWalking
  • 分布式任务 xxl-job

三,搭建学习案例

案例准备

一,案例说明

使用电商项目中的商品、订单、用户为案例进行搭建一个案例。

二,模块设计

  • 父工程 yh-cloud-parent
  • 公共模块(实体类) yh-cloud-common
  • 用户微服务 yh-cloud-user
  • 商品微服务 yh-cloud-product
  • 订单微服务 yh-cloud-order

三,业务模块创建

  1. 创建父工程

创建案例的父工程,命名为ali-cloud-study-parent。pom.xml为:

父工程的pom中引入了数据库驱动,mybatis-plus等依赖,其他子模块挂在父工程下,这些依赖就不用重新引入了。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.yuanhai</groupId>
    <artifactId>ali-cloud-study-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ali-cloud-study-parent</name>
    <description>spring cloud alibaba study case</description>



    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
    </properties>



    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- springboot web依赖 ,缺少这个依赖会出现项目刚启动就结束的问题-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
            <!--            <scope>runtime</scope>-->
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- json -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>

        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!-- spring-cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- spring-cloud-alibaba -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  1. 创建子模块

    • 公共模块 ali-cloud-shop-common
      该案例设计中,此为公共模块(基础模块),用来处理各种实体类相关的东西。
      pom.xml如下:
      application.properties:
      创建各个实体类:
      用户实体类ShopUser,对应数据库表shop_user:
      商品实体类ShopProduct,对应数据库表 shop_product:
      订单实体类ShopOrder,对应数据库表shop_order:
      ```xml <?xml version=”1.0” encoding=”UTF-8”?> 4.0.0 ali-cloud-study-parent org.yuanhai 0.0.1-SNAPSHOT

    org.yuanhai ali-cloud-shop-common 0.0.1-SNAPSHOT ali-cloud-shop-common SpringCloudAlibaba demo,module shop-common

    <java.version>1.8</java.version>
    

    org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin

```properties
server.port=10000
#当前服务名称
spring.application.name=ali-cloud-shop-common

#DB Configuration:
# 现在要这么写driver,原本的写法失效
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver   
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ali-cloud-study?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=wt19980929wt

#mybatis-plus设置
mybatis-plus.mapper-locations=classpath*:org/yuanhai/**/xml/*Mapper.xml
mybatis-plus.global-config.banner=false
mybatis-plus.global-config.db-config.id-type=assign_uuid
mybatis-plus.global-config.db-config.table-underline=true
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 返回类型为Map,显示null对应的字段
mybatis-plus.configuration.call-setters-on-nulls=true
@Data
@TableName("shop_user")
public class ShopUser {

    @TableId(type = IdType.ASSIGN_UUID)
    private String id;

    private String username;

    private String password;

    private String telephone;

}
@Data
@TableName("shop_product")
public class ShopProduct {

    @TableId(type = IdType.ASSIGN_UUID)
    private String id;

    private String productName;

    private String productPrice;

    private Integer productStock;  // 商品库存

}
@Data
@TableName("shop_order")
public class ShopOrder {

    @TableId(type = IdType.ASSIGN_UUID)
    private String id;

    private String userId;  // 用户id

    private String username;  // 订单id

}
  • 用户微服务 ali-cloud-shop-user
    步骤:1.创建模块,导入所需依赖
    2.创建pringBoot主类(使用idea直接构建springboot项目,会自动创建)
    3.编写配置文件
    4.创建必要的接口和实现类
    pom.xml如下:
    创建(编写)配置文件application.properties:
    ```xml <?xml version=”1.0” encoding=”UTF-8”?> 4.0.0

    ali-cloud-study-parent org.yuanhai 0.0.1-SNAPSHOT

    org.yuanhai ali-cloud-shop-user 0.0.1-SNAPSHOT ali-cloud-shop-user shop user module 1.8

    org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin

```properties
server.port=10001
#当前服务名称
spring.application.name=ali-cloud-shop-user

#DB Configuration:
#现在要这么写driver,原本的写法失效
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver  
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ali-cloud-study?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=wt19980929wt

#mybatis-plus设置
mybatis-plus.mapper-locations=classpath*:org/yuanhai/**/xml/*Mapper.xml
mybatis-plus.global-config.banner=false
mybatis-plus.global-config.db-config.id-type=assign_uuid
mybatis-plus.global-config.db-config.table-underline=true
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 返回类型为Map,显示null对应的字段
mybatis-plus.configuration.call-setters-on-nulls=true
  • 商品微服务
    pom.xml:
    application.properties:
    ```xml <?xml version=”1.0” encoding=”UTF-8”?> 4.0.0

    ali-cloud-study-parent org.yuanhai 0.0.1-SNAPSHOT

    org.yuanhai ali-cloud-shop-product 0.0.1-SNAPSHOT ali-cloud-shop-product

    shop product moudle 1.8 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin

```properties
server.port=10002
#当前服务名称
spring.application.name=ali-cloud-shop-product

#DB Configuration:
#现在要这么写driver,原本的写法失效
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver  
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ali-cloud-study?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=wt19980929wt

#mybatis-plus设置
mybatis-plus.mapper-locations=classpath*:org/yuanhai/**/xml/*Mapper.xml
mybatis-plus.global-config.banner=false
mybatis-plus.global-config.db-config.id-type=assign_uuid
mybatis-plus.global-config.db-config.table-underline=true
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 返回类型为Map,显示null对应的字段
mybatis-plus.configuration.call-setters-on-nulls=true
  • 订单微服务
    pom.xml:
    application.properties:
    ```xml <?xml version=”1.0” encoding=”UTF-8”?>

    4.0.0 ali-cloud-study-parent org.yuanhai 0.0.1-SNAPSHOT

    org.yuanhai ali-cloud-shop-order 0.0.1-SNAPSHOT ali-cloud-shop-order

    shop order module 1.8 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin

```properties
server.port=10003
#当前服务名称
spring.application.name=ali-cloud-shop-order

#DB Configuration:
# 现在要这么写driver,原本的写法失效
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver   
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ali-cloud-study?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=wt19980929wt

#mybatis-plus设置
mybatis-plus.mapper-locations=classpath*:org/yuanhai/**/xml/*Mapper.xml
mybatis-plus.global-config.banner=false
mybatis-plus.global-config.db-config.id-type=assign_uuid
mybatis-plus.global-config.db-config.table-underline=true
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 返回类型为Map,显示null对应的字段
mybatis-plus.configuration.call-setters-on-nulls=true

四,案例项目结构

五,各子模块之间互相调用

略。(后面直接学习OpenFeign来调用。)

四,组件学习

本节内容基于第三节搭建的学习案例。

1 Nacos注册中心

nacos的作用就是一个注册中心,用来管理注册上来的各个微服务。

是SpringCloud Alibaba 组件之一,负责服务注册发现和服务配置,可以这样认为 nacos=eureka+config。

1.1 搭建nacos环境

**第一步:安装nocas(服务端)**

下载地址: [https://github.com/alibaba/nacos/releases](https://github.com/alibaba/nacos/releases)

下载zip格式的安装包,然后进行解压缩操作。

此处我选择的是2.0.3版本。

**第2步: 启动naco**s

方法一:运行nacos/bin/startup.cmd

方法二:#切换目录 cd nacos/bin #命令启动 startup.cmd -m standalone

**_注意:_**刚下载下来时,启动报错[Unable to start web server; nested exception is org.springframework.boot.web.server。是因为
nacos默认是集群模式,所以我们在startup.cmd里面第28行改成单机模式就好了
即:set MODE="standalone"

**第3步: 访问nacos**

打开浏览器输入http://localhost:8848/nacos,即可访问服务, 默认密码是nacos/nacos

登录之后界面如下:

1.2 将各个服务注册到nacos

将各服务注册到nacos,需要进行以下步骤

第一步:在各自的pom文件中,引入nacos客户端依赖

    这里我们把nacos客户端依赖引入到父工程中,各个子模块服务就不用依次引入了。
<!-- Nacos注册中心 -->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
第二步:在各自的启动类上,添加@EnableDiscoveryClient注解,如:
@EnableDiscoveryClient
@SpringBootApplication
public class AliCloudShopCommonApplication {

    public static void main(String[] args) {
        SpringApplication.run(AliCloudShopCommonApplication.class, args);
    }

}
第三步:在各自的application.yml中添加nacos服务的地址
#nacos相关配置
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

按照上面的步骤,将公共模块、用户模块、商品模块、订单模块都注册到nacos。刷新nacos服务端页面,即可看到四个服务都被注册到nacos中了。如下图:

注:实际操作中发现,需先启动nacos,再启动各注册到nacos上的微服务。如果先启动微服务,没有找到nacos,那么再启动nacos,也不会自动去找已启动的nacos。

2 Ribbon负载均衡

一般我们使用Ribbon组件来实现负载均衡,但是下一个组件,用来处理服务之间的调用的组件-OpenFeign,会默认集成Ribbon,因此在Nacos下使用OpenFeign(Feign)就默认实现了Ribbon。

因此Ribbon组件无需引入,且,处于功利性考虑,此处只写相应的介绍,不去动手实现了。

Ribbon内置了多种负载均衡策略(**默认为轮询策略**),内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule , 具体的 负载策略如下图所示:
策略名 策略描述 实现说明
BestAvailableRule 选择一个最小的并发请求的 server 逐个考察Server,如果Server被 tripped了,则忽略,在选择其中 ActiveRequestsCount最小的 server
AvailabilityFilteringRule 过滤掉那些因为一直连接失 败的被标记为circuit tripped的后端server,并过 滤掉那些高并发的的后端 server(activeconnections 超过配置的阈值) 使用一个AvailabilityPredicate来包 含过滤server的逻辑,其实就就是 检查status里记录的各个server的 运行状态
WeightedResponseTimeRule 根据相应时间分配一个 weight,相应时间越长, weight越小,被选中的可能 性越低。 一个后台线程定期的从status里面 读取评价响应时间,为每个server 计算一个weight。Weight的计算也 比较简单responsetime 减去每个 server自己平均的responsetime是 server的权重。当刚开始运行,没 有形成statas时,使用roubine策略 选择server。
RetryRule 对选定的负载均衡策略机上 重试机制。 在一个配置时间段内当选择server 不成功,则一直尝试使用subRule 的方式选择一个可用的server
RoundRobinRule 轮询方式轮询选择server 轮询index,选择index对应位置的 server
RandomRule 随机选择一个server 在index上随机,选择index对应位 置的server
ZoneAvoidanceRule 复合判断server所在区域的 性能和server的可用性选择 serve 使用ZoneAvoidancePredicate和 AvailabilityPredicate来判断是否选 择某个server,前一个判断判定一 个zone的运行性能是否可用,剔除 不可用的zone(的所有server), AvailabilityPredicate用于过滤掉连 接数过多的Server。
我们可以通过修改配置文件的方式来修改负载均衡策略:

serrvice-product: # 调用的提供者的名称

     ribbon:

        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

3 OpenFeign(Feign)服务调用

其实Feign和OpenFeign是两个不同的组件,我们使用OpenFeign,二者具体区别可以百度一下。

Feign是Springcloud组件中的一个轻量级Restful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。它使得调用远程服务就像调用本地服务一样 简单, 只需要创建一个接口并添加一个注解即可。

OpenFeign是springcloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。<br />    摘自网络:springcloud F 及F版本以上 springboot 2.0 以上基本上使用openfeign,openfeign 如果从框架结构上看就是2019年feign停更后出现版本,也可以说大多数新项目都用openfeign ,2018年以前的项目在使用feign。

3.1 各微服务案例要实现的功能

  • 公共微服务:用户、商品、订单的增加、修改、删除;这些功能都要从各自的微服务,发送到公共微服务来进行。
  • 用户微服务:新增、删除、修改用户(发送到公共微服务处理);查询用户(本服务处理);查询商品(发送到商品微服务);查询订单(发送到订单微服务);
  • 商品微服务:查询商品(本服务进行);新增、删除、修改商品信息(发送到公共微服务进行);
  • 订单微服务:查询订单(本服务进行);新增、删除、修改订单信息(发送到公共微服务进行);

3.2 以新增用户为例,使用openFeign

准备:在ali-cloud-shop-common中,编写ShopController,加入新增用户的方法:

@RestController
@RequestMapping("/common/shopUser")
@Slf4j
public class ShopUserController {

    @Autowired
    private ShopUserService shopUserService;

    @PostMapping("/addUser")
    public Boolean addUser(@RequestBody ShopUser shopUser){
        log.info("---shopUser:"+shopUser);
        boolean saveFlag = shopUserService.save(shopUser);
        return saveFlag;
    }

}

第一步:加入OpenFeign的依赖,我们加入父工程中:

<!-- OpenFeign -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

第二步:在需要调用其他微服务的微服务的启动类上,添加使用Feign的注解@EnableFeignClients:

比如,我们要在ali-cloud-shop-user中,调用ali-cloud-shop-common中的接口,就在ali-cloud-shop-user的启动类上添加开启Feign的注解,如下:

@EnableDiscoveryClient
@SpringBootApplication
@Slf4j
@EnableFeignClients   // 开启Feign
public class AliCloudShopUserApplication {

    public static void main(String[] args) {
        SpringApplication.run(AliCloudShopUserApplication.class, args);
        log.info("-----------------------AliCloudShopUser is started.");
    }

}

客户端,即调用者需要这个注解,被调用者如果不去调用别人,不需要这个注解。

第三步:在ali-cloud-shop-user创建一个用来实现Feign调用微服务接口的service。

要在接口上使用@FeignClient(“xxx”)注解,其中”xxx”是name属性,用来指明调用的提供者的name。

接口下的方法,使用@RequstMapping注解,来指名调取具体哪个接口

@FeignClient+@GetMapping的值拼接起来,就是一个完整的请求路径

这里我们创建一个名为AliCloudShopCommonService的接口,专门用来向ali-cloud-shop-common服务调取接口:

@FeignClient("ali-cloud-shop-common")   // 此处的值为调用的服务的提供者的name
public interface AliCloudShopCommonService {

    @PostMapping("/common/shopUser/addUser")
    Boolean addUser(ShopUser shopUser);

}

第四步:在ali-cloud-shop-user中,新建controller,调用接口验证是否通过OpenFeign的方式,调用到ali-cloud-shop-common的接口

@RestController
@RequestMapping("/user/shopUser")
@Slf4j
public class ShopUserController {

    @Autowired
    private AliCloudShopCommonService aliCloudShopCommonService;

     @RequestMapping("/addUser")
//    public String addUser(@RequestBody ShopUser shopUser)
    public String addUser(){
        String resultStr = "添加用户失败";

        ShopUser shopUser = new ShopUser();
        shopUser.setUsername("测试用户1");
        shopUser.setPassword("123456");
        shopUser.setTelephone("15799999999");

        Boolean addFlag = aliCloudShopCommonService.addUser(shopUser);
        if(addFlag){
            resultStr = "添加用户成功";
        }
        return resultStr;
    }


}

第五步:测试。我们重启ali-cloud-shop-common和ali-cloud-shop-user服务,通过浏览器调用ali-cloud-shop-user的添加用户接口,看看能否从ali-cloud-shop-user中调用到ali-cloud-shop-common的接口。

浏览器中输入ali-cloud-shop-user的新增用户接口的地址:http://localhost:10001/user/shopUser/addUser,访问后如下:

注:可能调用接口时,会出现Mysql相关错误,You must configure either the server or JDBC driver (via the serverTimezone conf),因为安装mysql的时候时区设置的不正确 mysql默认的是美国的时区,而我们中国大陆要比他们迟8小时,采用+8:00格式。在配置文件,链接数据库的url后面拼接?serverTimezone=UTC可以解决(还有其他方式,详细百度)。

第六步:我们还可以修改服务提供者的负载均衡策略。

默认策略为轮询,我们来将ali-cloud-shop-common的负载均衡策略修改为随机

.properties文件的写法:

#修改负载均衡策略为随机  ali-cloud-shop-common为服务提供者的名称
ali-cloud-shop-common.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

.yaml文件的写法:

ali-cloud-shop-common: #此为服务提供者的名称

ribbon:

    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

疑问:

关于负载均衡,我们应该是在服务消费者的配置文件中,配置生产者的负载均衡,每次往生产者调用时,进行负载均衡;还是在生产者的配置文件中配置自己的负载均衡,被调用时,进行负载均衡?  **我想,应该是在消费者的配置文件中配置生产者的负载均衡。**

4 Sentinel服务容错(熔断,降级等)

4.1 微服务的雪崩效应

我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,但是由于网络原因 或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现 网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。

雪崩效应: 在分布式系统中,由于网络原因或自身的原因,服务一般无法保证 100% 可用。如果一个服务出现了问 题,调用这个服务就会出现线程阻塞的情况,此时若有大量的请求涌入,就会出现多条线程阻塞等待, 进而导致服务瘫痪。 由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务 故障的 “雪崩效应” 。

雪崩发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机 器的资源耗尽。我们无法完全杜绝雪崩源头的发生,只有做好足够的容错,保证在一个服务发生问题, 不会影响到其它服务的正常运行。

4.2 常见容错方案

常见的容错思路有隔离、超时、限流、熔断、降级等。

4.2.1 隔离
是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发 生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服 务。常见的隔离方式有:线程池隔离和信号量隔离。

4.2.2 超时
在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,就断 开请求,释放掉线程。

4.2.3 限流
限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到的需要 限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。

4.2.4 熔断
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可 用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。

熔断一般有三种状态:

1.熔断关闭状态(Closed)

    服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制

2.熔断开启状态(Open)

    后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法

3.半熔断状态(Half-Open)

    尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服 务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。

4.2.5 降级
降级其实就是为服务提供一个托底方案(备用方案),一旦服务无法正常调用,就使用托底方案。

4.3 Sentinel的起步学习和使用

Sentinel) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量 为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。

4.3.1 Sentinel的组成
Sentinel包括两个部分:

1.核心库(Java客户端):不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo /Spring Cloud 等框架也有较好的支持。作为maven依赖引入项目中使用。

2.控制台制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等 应用容器;它提供机器发现、单机资源实时监控以及规则管理等功能。

4.3.2 在微服务中集成Sentinel

第一步:只需加入Sentinel依赖即可。我们在父工程中加入它。

<!-- 服务降级 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>

第二步:在ali-clou-shop-user中编写一个测试类

@RestController
@Slf4j
@RequestMapping("/user/sentinelTest")
public class SentinelTestController {

    @RequestMapping("/message1")
    public String message1() {
        return "message1";
    }

    @RequestMapping("/message2")
    public String message2() {
        return "message2";
    }

}

4.3.3 安装sentinel控制台

1.下载jar包,解压到文件夹。下载地址: https://github.com/alibaba/Sentinel/releases

我们下载一个1.8.2的版本来使用。

注意,我们直接**下载jar包**来使用和运行,github地址的zip包是源码。

2.启动控制台

Sentinel的控制台其实就是一个SpringBoot编写的程序。我们需要将我们的微服务程序注册到控制台上, 即在微服务中指定控制台的地址, 并且还要开启一个跟控制台传递数据的端口, 控制台也可以通过此端口 调用微服务中的监控程序获取微服务的各种信息。

直接使用jar命令启动项目(控制台本身是一个SpringBoot项目):

到下载的jar文件夹下cmd在使用如下命令启动控制台:
java -Dserver.port=8088 -Dcsp.sentinel.dashboard.server=localhost:8088 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.2.jar

-Dserver.port=8088 用于指定 Sentinel 控制台端口为 8088
-Dcsp.sentinel.dashboard.server 用于控制台对外暴露的服务地址
启动后,如下图所示:

3.在微服务模块中加入有关控制台的配置

我们先在ali-cloud-shop-user中加入相关配置
#sentinel相关配置
#跟控制台交流的端口,随意指定一个未使用的端口即可
spring.cloud.sentinel.transport.port=9999
# 指定控制台服务的地址
spring.cloud.sentinel.transport.dashboard=localhost:8088
  1. 通过浏览器访问localhost:8088 进入控制台
    默认用户名密码是 sentinel/sentinel
    进入该地址后,如下图所示:
    登录之后,如下图所示:
    5.这个时候,我们重启配置了sentinel属性的微服务,并且刷新控制台,会发现我们的服务并没有出现在控制台上。
    这是因为sentinel是懒加载的,需要我们调用配置的这个服务,才会把他配置到控制台上,我们浏览器访问一下http://localhost:10001/user/shopUser/addUser,然后刷新一下sentinel,会发现ali-clou-shop-user服务被配置到了控制台上。如下图:

4.4 通过sentinel实现一个接口的限流

第一步:我们通过sentinel控制台,为我们之前添加的SentinelTestController中的message1()方法添加一个流控规则:

在对应的服务下的簇点链路中,找到需要添加流控规则的方法(资源)(实际操作中发现:服务里面的方法,也是被调用后,才会在簇点链路中展现出来)。:

第二步:点击流控按钮后,我们进行如下操作,将单机阀值设为1:

第三步:在浏览器快速频繁访问http://localhost:10001/user/sentinelTest/message1资源,会出现如下效果:

第四步:我们在左侧列表的流控规则中,可以看到我们自己添加的流控规则。

疑问(关于容错信息的持久化):

sentinel服务停止后,原本设置的流控规则会失效,这些信息如何保存?nacos也是一个独立的应用,里面的信息如何保存?是不是会独立带一个数据库呢?目前入门的学习还没有学到这个,后面继续深入。

4.5 Sentinel的概念和功能

4.5.1 Sentinel相关概念
  • 资源
    资源是Sentinel的重要观念。资源可以是JJava程序中的任何内容,可以是一个服务,一个方法,甚至是一段代码。
    我们4.4节实现的一个接口的限流,/user/sentinelTest/message1就是一个资源。
  • 规则
    规则是定义如何进行保护资源。规则作用于资源上,主要包括:流量控制规则、熔断降级规则以及系统保护规则。
    4.4节实现的一个接口的限流,就是为message1资源设置了一种流控规则, 限制了进入message1的流量。

4.5.2 Sentinel主要功能
Sentinel主要功能就是容错。主要有一下三个方面:
  • 流量控制
    流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意时间到来的请求往往是随机 不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为 一个调配器,可以根据需要把随机的请求调整成合适的形状。
  • 熔断降级
    当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这 个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
    Sentinel对这个问题采取了两种手段:
    • 通过并发线程数进行控制
      Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。当某个资源出现不稳定 的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源 上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
    • 通过响应时间对资源进行降级
      当依赖的资 源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
  • 系统负载保护
    Sentinel 同时提供系统维度的自适应保护能力。当系统负载较高的时候,如果还持续让请求进入 可能会导致系统崩溃,无法响应。在集群环境下,会把本应这台机器承载的流量转发到其它的机器 上去。如果这个时候其它的机器也处在一个边缘状态的时候,Sentinel 提供了对应的保护机制, 让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

针对Sentinel的这些功能,我们需要在Sentinel资源上配置规则,来实现需要的容错功能。

4.6 Sentinel规则

先知道有这些规则,具体的各种规则如何设置,后续再学习。

4.6.1 流控规则
即流量控制规则。其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

4.6.1.1 如何设置流控规则,以及流控规则相关属性说明:

点击对应微服务下的簇点链路页面,可以看到访问过的接口地址,然后点击对应的流控按钮,进入流控规则配置页面。新增流控规则界面如下:

这里我们将阀值设为了1,默认是没有的,可以根据实际情况设定一个值。

属性说明:

**资源名:**唯一名称,默认是请求路径,可自定义

**针对来源:**指定对哪个微服务进行限流,默认指default,意思是不区分来源,全部限制

**阈值类型单机阈值:**

    QPS(每秒请求数量): 当调用该接口的QPS达到阈值的时候,进行限流

    线程数:当调用该接口的线程数达到阈值的时候,进行限流

4.6.1.2 流控规则设置案例

第一步:

我们对ali-cloud-shop-user微服务下的/user/sentinelTest/message1设置流控规则。设置阈值类型为QPS,单机阈值为3。即每秒请求量大于3的时候开始限流。

第二步:

设置完毕之后,我们就可以在sentinel控制台的ali-cloud-shop-user功能列表的流控规则界面,看到设置过的规则:

第三步:

配置流控模式。 点击第二步中,流控规则的编辑按钮,然后在编辑页面点击高级选项,会看到有流控模式一栏:

我们来分别解释和设置一下这些流控模式:

  • 直接模式(默认的模式):接口达到限流条件时,开启限流
  • 关联模式:当关联的资源达到限流条件时,开启限流 [适合做应用让步]
  • 链路模式:当从某个接口过来的资源达到限流条件时,开启限流

直接模式设置

4.4节就是一个直接模式的案例。

关联模式设置

1.将流控模式选择为关联模式,这时会出现关联资源的输入框,在输入框中将/user/sentinelTest/message2设为关联资源

2.通过访问http://localhost:10001/user/sentinelTest/message2 (一秒内三次以上),然后再去访问http://localhost:10001/user/sentinelTest/message1,会发现message1被限流

这个没有做到,需要配合jmeter和portman工具来做。

链路模式设置:

链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度 更细。

后面详细说,先知道有这么回事。

第四步:

配置流控效果。流控效果有三种选择:
  • 快速失败(默认): 直接失败,抛出异常,不做任何额外的处理,是最简单的效果 。
  • Warm Up:它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的 1/3,然后慢慢增长,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景。
  • 排队等待:让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设 置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。

4.6.2 降级规则(熔断规则)
降级规则(熔断规则)就是设置当满足什么条件的时候,对服务进行降级。

Sentinel提供了三个衡量条件:
  • 平均响应时间 :当资源的平均响应时间超过阈值(以 ms 为单位)之后,资源进入准降级状态。 如果接下来 1s 内持续进入 5 个请求,它们的 RT都持续超过这个阈值,那么在接下的时间窗口 (以 s 为单位)之内,就会对这个方法进行服务降级。
    注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此 上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
  • 异常比例:当资源的每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的 时间窗口(以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0,1.0]。
  • 异常数 :当资源近 1 分钟的异常数目超过阈值之后会进行服务降级。注意由于统计时间窗口是分 钟级别的,若时间窗口小于 60s,则结束熔断状态后仍可能再进入熔断状态。

4.6.3 热点规则
参数流控规则是一种更细粒度的流控规则, 它允许将规则具体到参数上。

4.6.4 授权规则
有时候我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问 控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:

 若配置白名单,则只有请求来源位于白名单内时才可通过;

 若配置黑名单,则请求来源位于黑名单时不通过,其余的请求通过。

4.6.5 系统规则
系统=统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、CPU使用 率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。

Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过 系统容量时才会触发系统保护。系统容量由系统的 maxQps _ minRt 计算得出。设定参考值一般 是 CPU cores _ 2.5。

RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。

 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

 CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护

4.7 @SentinelResource的使用(出现异常时的处理策略)

4.7.1 异常处理和fallback配置项

我们可以通过@SentinelResource来指定出现异常时的处理策略。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。其主要参数如下:

属性 作用
value 资源名称
entryType entry类型,标记流量的方向,取值IN/OUT,默认是OUT
blockHandler 处理BlockException的函数名称,函数要求:1. 必须是 public2. 返回类型 参数与原方法一致3. 默认需和原方法在同一个类中。若希望使用其他类 的函数,可配置blockHandlerClass ,并指定blockHandlerClass里面的 方法。
blockHandlerClass 存放blockHandler的类,对应的处理函数必须static修饰。
fallback 用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所 有类型的异常(除了exceptionsToIgnore 里面排除掉的异常类型)进行 处理。函数要求:1. 返回类型与原方法一致2. 参数类型需要和原方法相 匹配3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配 置fallbackClass ,并指定fallbackClass里面的方法。
fallbackClass fallbackClass
defaultFallback 用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进 行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函 数要求:1. 返回类型与原方法一致2. 方法参数列表为空,或者有一个 Throwable 类型的参数。3. 默认需要和原方法在同一个类中。若希望使 用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方 法。
exceptionsToIgnore 指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入 fallback逻辑,而是原样抛出
exceptionsToTrace 需要trace的异常

4.7.2 定义限流和降级后的处理方法案例
具体如何定义和使用,后面讲,这里只先了解有这么个东西。

方式一:直接将限流和降级方法定义在方法中

方式二:将限流和降级方法外置到单独的类中

4.8 Sentinel规则持久化

之前我们提到一个疑问:sentinel服务停止后,原本设置的流控规则会失效,这些信息如何保存?是不是会独立带一个数据库?

Sentinel规则默认是存放在内存中,极不稳定,一旦我们重启应用,Sentinel规则将消失,所以需要将其持久化。这节我们来实现Sentinel规则的持久化。

目前 Sentinel 中默认实现了5种规则持久化的方式,分别是:file、redis、nacos、zk和apollo。

与我们相关或者会使用的有file,nacos和redis。先简单入门一下这三种持久化的方式。

可以参考:https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel

4.8.1 file方式(本地文件持久化)

本地文件是一种“拉模式(pull)”

本地文件数据源会定时轮询文件的变更,读取规则。这样我们既可以在应用本地直接修改文件来更新规 则,也可以通过 Sentinel 控制台推送规则。首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则 保存到本地的文件中。

文件持久化有一个问题就是文件不像其他的配置中心,其他配置中心数据发生变更后会发出通知。使用文件来持久化的话就需要我们自己定时去扫描文件,来确定文件是否发现了变更。

文件数据源是通过 FileRefreshableDataSource 类来实现的,他是通过文件的最后更新时间来判断规则是否发生变更的。

需要引入依赖:com.alibaba.csp sentinel-datasource-extension

需要注意的是,我们需要在系统启动的时候调用该数据源注册的方法,否则不会生效的。具体的方式有很多,可以借助 Spring 来初始化该方法,也可以自定义一个类来实现 Sentinel 中的 InitFunc 接口来完成初始化。

Sentinel 会在系统启动的时候通过 spi 来扫描 InitFunc 的实现类,并执行 InitFunc 的 init 方法,所以这也是一种可行的方法,如果我们的系统没有使用 Spring 的话,可以尝试这种方式。

案例就不写了,我们重点考虑使用nacos方式或者redis方式。

4.8.2 nacos方式

生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非常清晰了:

nacos方式的规则持久化,是“推模式(push)”,生产环境下一般采用 push 模式的数据源。

Nacos 数据源的实现类是 NacosDataSource。

需要引入依赖:com.alibaba.csp sentinel-datasource-nacos

案例:

第一步:

推送规则原理简介: 1.将规则推送到Nacos或其他远程配置中心

                                2.Sentinel客户端链接Nacos,获取规则配置;并监听Nacos配置变化;

                                3.如发生变化,就更新本地缓存(从而让本地缓存总是和Nacos一致)

第二步:

微服务中加上nacos方式持久化的依赖,我们添加到父工程中,省去每个微服务都要添加一遍的步骤:

<!-- sentinel规则推送到nacos控制台 -->
<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

第三步:

在需要用到规则推送的微服务中添加配置,我们以ali-cloud-shop-user为例:

#sentinel相关配置
# 指定控制台服务的地址
spring.cloud.sentinel.transport.dashboard=192.168.0.215:8088
#跟控制台交流的端口,随意指定一个未使用的端口即可
spring.cloud.sentinel.transport.port=9999


# sentinel规则推送到nacos相关配置
# 此处ds1是自己定义的名字,名字随意,实际工作中依据实际情况来即可
spring.cloud.sentinel.datasource.shop-user-ds1.nacos.server-addr=127.0.0.1:8848
spring.cloud.sentinel.datasource.shop-user-ds1.nacos.data-id=ali-cloud-shop-user-sentinel-ds1
spring.cloud.sentinel.datasource.shop-user-ds1.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.shop-user-ds1.nacos.rule-type=flow
spring.cloud.sentinel.datasource.shop-user-ds1.nacos.data-type=json

在这些配置汇总,spring.cloud.sentinel.datasource.后面的字段是我们定义的名称。

  • server-addr属性为nacos服务端ip和端口号
  • data-id属性为 nacos中存储规则的dataId
  • group-id属性为 nacos中存储规则的groupId
  • rule-type属性为 spring cloud alibaba升级到0.2.2之后增加的配置,用来定义存储的规则类型。所有的规则类型可查看枚举类:org.springframework.cloud.alibaba.sentinel.datasource.RuleType,各规则的定义格式可通过各枚举值中定义的规则对象来查看。
  • data-type 应该是nacos中规则配置文件的类型,还不确定。

第三步:

在Nacos控制台,对应的namespace ,新建一个json配置文件:DataID为ali-cloud-shop-user-sentinel-ds1,Group为DEFAULT_GROUP;(这两个属性与配置文件中写成一样,对应起来) ,配置格式改为json,如下:

我们这里暂时没有对应的namespace,后面再考虑这个。

在以下页面,点击+号添加配置:

弹出以下页面,按如下配置:

配置内容是json格式,里面的属性现在是简略版的,实际上还有其他很多的属性,后续再学习。先解释目前有的属性:

resource:资源名,即限流规则的作用对象

limitApp:流控针对的调用来源,若为 default 则不区分调用来源

grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制

count:限流阈值

strategy:调用关系限流策略

controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)

clusterMode:是否为集群模式

配置完毕,我们点击发布,发布此规则。

第四步:

启动ali-clou-shop-user服务(该服务要注册到nacos上),打开sentinel控制台的流控规则菜单,可以看到我们配置的规则。

但是实际操作时,并没有我刚刚推送到流控规则,这是怎么回事?

注意,这个问题还不知道如何解决,先往下走,把需要用的组件先都入门一下。

后来这个问题解决了,好像是配置的时候不够仔细,配置文件的data-id和nacos控制台的data-id不一样导致的。改成一样的就ok了。

如下图:

第五步:

第四步是从nacos向sentinel控制台推送规则,这时,对于接口流控规则的修改就存在两个地方:Sentinel控制台、Nacos控制台。

这个时候,通过Nacos修改该条规则是可以同步到Sentinel的但是通过Sentinel控制台修改或新增却不可以同步到Nacos。因为当前版本的Sentinel控制台不具备同步修改Nacos配置的能力。

我们还需要让sentinel控制台的规则可以将规则同步到nacos。通过对Sentinel Dashboard的改造,使得Nacos与Sentinel可以互相同步限流规则。

如何实现?后续学习

4.8.3 redis方式

略。

4.9 OpenFeign整合Sentinel

这里的步骤暂时没有自己去写,使用了一个网上的案例作为了解:

第一步:

引入sentinel依赖:

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
我们案例之前已经引入了,这里不再引入。

第二步:

配置文件开启Feign对Sentinel的支持:

#开启feign对sentinel的支持
feign.sentinel.enabled=true

第三步:

创建容错类。容错类要求必须实现被容错的接口,并且为每个方法都实现容错方案。

//容错类要求必须实现被容错的接口,并为每个方法实现容错方案
@Component
@Slf4j
public class ProductServiceFallBack implements ProductService {
    @Override
    public Product findByPid(Integer pid) {
        Product product = new Product();
        product.setPid(-1);
        return product;
    }
}

第四步:

为被容错的接口指定容错类。

//value用于指定调用nacos下哪个微服务
//fallback用于指定容错类
@FeignClient(value = "service-product", fallback = ProductServiceFallBack.class)
public interface ProductService {
@RequestMapping("/product/{pid}")//指定请求的URI部分
    Product findByPid(@PathVariable Integer pid);
}

第5步:

修改controller。

@RestController
@Slf4j
public class OrderController {
    @Autowired
    private OrderService orderService;
    @Autowired
    private ProductService productService;

    //下单--fegin@RequestMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid) {
        log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息", pid);
        //调用商品微服务,查询商品信息
        Product product = productService.findByPid(pid);
        if (product.getPid() == -1) {
        Order order = new Order();
        order.setPname("下单失败");
        return order;
        }
        log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product));
        //下单(创建订单)
        Order order = new Order();
        order.setUid(1);
        order.setUsername("测试用户");
        order.setPid(pid);
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);
        orderService.createOrder(order);
        log.info("创建订单成功,订单信息为{}", JSON.toJSONString(order));
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
            return order;
    }
}

扩展: 如果想在容错类中拿到具体的错误,可以使用下面的方式

@FeignClient(
value = "service-product",
//fallback = ProductServiceFallBack.class,
fallbackFactory = ProductServiceFallBackFactory.class)
public interface ProductService {
//@FeignClient的value + @RequestMapping的value值 其实就是完成的请求地址
"http://service-product/product/" + pid
@RequestMapping("/product/{pid}")//指定请求的URI部分
Product findByPid(@PathVariable Integer pid);
}
@Component
public class ProductServiceFallBackFactory implements
FallbackFactory<ProductService> {
@Override
public ProductService create(Throwable throwable) {
return new ProductService() {
@Override
public Product findByPid(Integer pid) {
throwable.printStackTrace();
Product product = new Product();
product.setPid(-1);
return product
}
}:
}
}

注意:fallback和fallbackFactory只能使用其中一种方式

5 GateWay服务网关

API网关,是指系统的统一入口。它封装了应用程序的内部结构,为客户端提供统一服务,一 些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。

简单来讲,就是原本请求直接发送到各个微服务,添加了网关之后,都是发送到网关,通过网关发送到各个微服务。在经过网关时,可以进行一些公共逻辑的处理。

GateWay不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行;需要Spring Boot 2.0及以上的版本才支持。

5.1 GateWay快速入门案例

基于之前的案例做一个入门案例,目的是,实现通过浏览器访问api网关,然后通过网关将请求发送到用户微服务。

5.1.1 基础版

第一步:

创建一个ali-cloud-api-gateway模块,导入相关依赖(主要是gateway网关的依赖),只在网关模块导入该依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
<!--        <groupId>org.springframework.boot</groupId>-->
<!--        <artifactId>spring-boot-starter-parent</artifactId>-->
<!--        <version>2.5.3</version>-->
<!--        <relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
        <artifactId>ali-cloud-study-parent</artifactId>
        <groupId>org.yuanhai</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <groupId>org.yuanhai</groupId>
    <artifactId>ali-cloud-api-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ali-cloud-api-gateway</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!--gateway网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

第二步:

添加配置文件:

server:
  port: 9000

spring:
  application:
    name: ali-cloud-api-gateway
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/ali-cloud-study?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: wt19980929wt

  cloud:
    gateway:
      routes: #  路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
        - id: shop_user_route # 当前路由的标识, 要求唯一
          uri: http://localhost:10001  # 请求要转发到的地址
          order: 1    # 路由的优先级,数字越小,优先级越高
          predicates: # 断言(就是路由转发要满足的条件)
            - Path=/** # 当请求路径满足Path指定的规则时,才进行路由转发
#          filter:   # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
#            - StripPrefix=1 # 转发之前去掉1层路径

第三步:

启动网关服务

注意:我们启动时,出现这个错误:

Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.

这是因为,Spring MVC在类路径中找到,目前与Spring Cloud Gateway不兼容。请删除spring-boot-starter-web依赖项。

拓展:

我们的springmvc依赖实在父工程中引用,需要设置排除引入父工程中的springmvc依赖。这个时候我们用到maven重点标签。

标签的作用是排除关联依赖的引入。因为maven的pom依赖其中 有一点是将关联的依赖全都引入进来 ,如果关联的依赖和引入的其他依赖可能存在冲突, 就必须将关联的依赖排除掉,所以就用这个标签。

例:

<!--        common公用依赖-->
        <dependency>
            <groupId>com.loonmall</groupId>
            <artifactId>loonmall-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
<!--            gateway和spingMVC依赖冲突,使用exclusions去除它-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

但是我们的项目中,是继承自父工程中的依赖,无法用这种方式(可能有别的用法,但是我不太了解,maven基础不太好),gateway不需要引入过多其他的依赖,我们可以通过maven子类重写,定义不需要的依赖的scope为test,那么此依赖打包则不会出现

如,我们当前的案例中,不需要spring-boot-starter-web,它与gateway冲突,要在gateway中去掉,则可以这么写:

<!-- spring-boot-starter-web与gateway冲突,要在gateway中去掉 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <scope>test</scope> <!-- 特殊处理,不引入父类lib -->
</dependency>

整体pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
<!--        <groupId>org.springframework.boot</groupId>-->
<!--        <artifactId>spring-boot-starter-parent</artifactId>-->
<!--        <version>2.5.3</version>-->
<!--        <relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
        <artifactId>ali-cloud-study-parent</artifactId>
        <groupId>org.yuanhai</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <groupId>org.yuanhai</groupId>
    <artifactId>ali-cloud-api-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ali-cloud-api-gateway</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!--gateway网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!-- spring-boot-starter-web与gateway冲突,要在gateway中去掉 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>test</scope> <!-- 特殊处理,不引入父类lib -->
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

同理,如果子工程中有别的继承自父工程的依赖用不到,可以通过这个方式来去除。

经过上面的操作,ali-cloud-api-gateway已经在设定的9000端口上启动了。

第四步:

通过浏览器,去通过网关访问用户微服务的添加用户的方法。

我们浏览器输入:http://localhost:9000/user/shopUser/addUser ,会成功执行ali-cloud-shop-user的添加方法。

相关说明:

  • 配置文件中,predicates 下面可以设置断言(就是路由转发要满足的条件),比如设置路径条件,我们当前设置Path为 /**,是任何情况都可以请求到这里,后面深入学习,会加入其他判断,
  • 配置文件中,filter是过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改。比如去掉一层路径等。
    假设我们断言中,访问user服务的接口,都必须第一层路径以/user-route开头,但是真正user微服务没有这层路径,就可以使用filter设置一定的规则,实际请求的发送中,把第一层/user-route路径去掉,让断言只发挥判断往哪里转发的作用。

5.1.2 nacos版

第一步:

在ali-clou-api-gateway微服务中加入nacos依赖,我们已经在其继承的父工程中加入了,就不用重新加了。

<!-- Nacos注册中心客户端 -->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

第二步:

在ali-clou-api-gateway的启动类上加上@EnableDiscoveryClient注解

@SpringBootApplication
@EnableDiscoveryClient
public class AliCloudApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(AliCloudApiGatewayApplication.class, args);
    }

}

注:我一开始没有在启动类上加上@EnableDiscoveryClient注解,也没有在配置文件中配置nacos相关的属性,但是在nacos服务端也看到了ali-cloud-api-gateway的服务,有些奇怪。

第三步:

修改配置文件。

server:
  port: 9000

spring:
  application:
    name: ali-cloud-api-gateway
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/ali-cloud-study?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: wt19980929wt
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848    # nacos注册中心地址
    gateway:
      discovery:
        locator:
          enabled: true   # 让gateway可以发现nacos中的微服务
      routes: #  路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
        - id: shop_user_route # 当前路由的标识, 要求唯一
          # uri: http://localhost:10001  # 请求要转发到的地址
          uri: lb://ali-cloud-shop-user # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略,lb:// 后面跟的是需要用到的微服务的名称
          order: 1    # 路由的优先级,数字越小,优先级越高
          predicates: # 断言(就是路由转发要满足的条件)
            - Path=/** # 当请求路径满足Path指定的规则时,才进行路由转发
#          filter:   # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
#            - StripPrefix=1 # 转发之前去掉1层路径

在现在的配置文件中,相较于基础版,多配置了nacos相关的属性,如下:

nacos服务端地址:spring.cloud.nacos.discovery.server-addr

gatewate可以发现nacos中的微服务:spring.cloud.gateway.discovery.locator.enabled

spring.cloud.gateway.routes配置的数组属性中,uri发生了变化,基础版本是配置 协议://ip:端口号,现在配置为

    uri: lb://ali-clou-shop-user   其中lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略,lb:// 后面跟的是需要用到的微服务        的名称,注意名称不要写错了。

第四步:

重启服务验证。 我们浏览器输入http://localhost:9000/user/shopUser/addUser去访问,可以访问成功。

5.1.3 简写版

第一步:

我们去掉ali-cloud-api-gateway中关于路由的配置,即将spring.cloud.gateway.routes相关配置注释掉:

server:
  port: 9000

spring:
  application:
    name: ali-cloud-api-gateway
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/ali-cloud-study?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: wt19980929wt
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848    # nacos注册中心地址
    gateway:
      discovery:
        locator:
          enabled: true   # 让gateway可以发现nacos中的微服务
#      routes: #  路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
#        - id: shop_user_route # 当前路由的标识, 要求唯一
#          # uri: http://localhost:10001  # 请求要转发到的地址
#          uri: lb://ali-cloud-shop-user # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略,lb:// 后面跟的是需要用到的微服务的名称
#          order: 1    # 路由的优先级,数字越小,优先级越高
#          predicates: # 断言(就是路由转发要满足的条件)
#            - Path=/** # 当请求路径满足Path指定的规则时,才进行路由转发
##          filter:   # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
##            - StripPrefix=1 # 转发之前去掉1层路径

第二步:

然后重启服务,通过微服务网关去访问:http://localhost:9000/ali-cloud-shop-user/user/shopUser/addUser,注意,端口号和原本的接口地址之间,加上了微服务的名称,是可以访问到的。

说明:

由上面的步骤可以看出,在我们引入开了nacos相关配置,并且配置了让gateway可以发现nacos中的微服务后,可以不配置路有相关配置,要按照**网关地址微服务接口**的格式去访问,就可以得到成功响应。

5.2 GateWay核心架构

5.2.1 GateWay基本概念

路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要有以下几个信息属性:

  • id:路由标识符,区别于其他 Route,配置时得是唯一的。
  • uri: 路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
  • order: 用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。
  • predicate: 断言。断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
  • filter:过滤器用于修改请求和响应信息。

5.2.2 GateWay执行流程

执行流程大体如下:

1.Gateway Client向Gateway Server发送请求

2.请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文

3.然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给 RoutePredicateHandlerMapping

4.RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用

  1. 如果过断言成功,由FilteringWebHandler创建过滤器链并调用 6. 请求会一次经过PreFilter—微服务-PostFilter的方法,最终返回响应

5.3 断言

断言,predicate。在gateway中用于进行条件判断,只有当断言都返回为true时,才会真正的执行路由。

断言就是说: 在 什么条件下 才能进行路由转发

断言具体的断言工厂(即各种条件),后续进行学习和填充。

5.4 过滤器

过滤器,filter。起作用是在请求的传递过程中,对请求和响应做一些手脚。

过滤器生命周期:Pre和Post

过滤器分类:局部过滤器(作用在某一个路由上) 全局过滤器(作用全部路由上)

5.4.1 过滤器生命周期

Gateway中, Filter的生命周期只有两个:“pre” 和 “post”。

  • PRE: 这种过滤器在请求被路由之前调用。
    我们可利用这种过滤器实现身份验证、在集群中选择 请求的微服务、记录调试信息等。
  • POST:这种过滤器在路由到微服务以后执行。
    这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。

  • GatewayFilter:应用到单个路由或者一个分组的路由上。
  • GlobalFilter:应用到所有的路由上。

5.4.2 局部过滤器
待补充

5.4.3 全局过滤器
待补充

5.5 网关限流

后续补充

6 RocketMQ消息驱动

6.1 认识MQ

6.1.1 什么是MQ

MQ(消息队列,Message Queue)是一种跨进程的通信机制,用于传递消息。通俗点说,就是一个先进先出的数 据结构。一般用来解决应用解耦,异步消息,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构。

6.1.2 MQ的应用场景(作用)

MQ主要是用来进行异步解耦和流量削峰。

异步解耦:

异步解耦是消息队列 MQ 的主要特点,主要目的是减少请求响应时间和解耦。主要的使用场景就是将比 较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时,由于使用了消息队列 MQ,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即 解耦合。

流量削峰:

流量削峰也是消息队列 MQ 的常用场景,一般在秒杀或团队抢购(高并发)活动中使用广泛。在秒杀或团 队抢购活动中,由于用户请求量较大,导致流量暴增,秒杀的应用在处理如此大量的访问流量后,下游 的通知系统无法承载海量的调用量,甚至会导致系统崩溃等问题而发生漏通知的情况。为解决这些问 题,可在应用和下游通知系统之间加入消息队列 MQ。

具体解释参考度娘。

我们选择RocketMQ这一产品。

6.2 RocketMQ入门

RocketMQ是阿里巴巴开源的分布式消息中间件,现在是Apache的一个顶级项目。在阿里内部使用非常 广泛,已经经过了”双11”这种万亿级的消息流转。

6.2.1 RocketMQ环境搭建

我们还是以本机的windows系统来搭建rocketMQ环境。

第一步:

下载rocketmq。

下载地址:http://rocketmq.apache.org/release_notes/release-notes-4.2.0/

注意:我们使用windows系统搭建rocketmq环境,需要下载Binary(二进制)包。

第二步:

解压下载下来的RocketMQ压缩包到指定地址。

第三步:

配置RocketMQ的环境变量。

变量名:ROCKETMQ_HOME

变量值:MQ解压路径\MQ文件夹名

第四步:

启动rocketmq之前的注意事项:

启动之前要注意:

1,可启动的东西有两个,一个是broker,另一个是namsever,他们分别是什么,在后面的学习中,我们来逐步了解。在启动时,要先启动nameserver,再启动broker。

2.RocketMQ NAMESERVER默认分配的jvm参数需要占用较大内存,一般pc没有这么大内存需要修改小些才行。

例如:修改broker启动脚本runbroker.sh里面的jvm参数,修改后的参数可以根据实际情况来

JAVA_OPT=”${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g”

改为

JAVA_OPT=”${JAVA_OPT} -server -Xms128m -Xmx256m -Xmn256m”

然后重新启动broker。

另外,nameserver的启动脚本是runserver.sh,也可修改其中的jvm参数。

但是这里也有说,是修改runserver.cmd/runbroker.cmd文件里面的对应参数,后续自己试验(应该还是改.sh文件)。

第五步:

启动namesever。 在解压的rocketmq目录下的bin目录下,mqnamesrv.cmd是nameserver的启动脚本。

在命令行窗口cmd进入到RocketMQ安装目录下的bin目录,执行start mqnamesrv.cmd

这里面报错了:

这个错误修改rocketmq启动的jvm内存分配没用,查找网上的资料如下:

可能是因为 RocketMQ的启动文件都是按照JDK8配置的,而我们用的jdk9,有很多命令参数不支持导致的。

方法一:本机jdk环境更换为jdk1.8 (此路可行,但是我他妈想用jdk9啊cnm)

这里我不想修改自己机器的jdk版本,看下有没有别的解决方案。

方法二:

RocketMQ的nameserver启动文件runserver.sh 中,很多地方的写法是jdk8的写法,我们把他换成jdk9的写法:

摘自rocketmq的github的问题修复解答: (屌用没有,也可能我的修改方法有问题)

  1. JDK1.8 不支持 PermSize,用 Metaspace 代替。
  2. JDK9 不支持 -Djava.ext.dirs,可使用 -classpath。
  3. JDK9 不支持 PrintGCDetails,将其替换为 -Xlog:gc 和 -Xlog:gc*
  4. JDK9 不支持 UseCMSCompactAtFullCollection。
  5. UseParNewGC 在 Java 9 中被弃用,在 Java 10 中被移除。
  6. UseConcMarkSweepGC 在 Java 9 中已被弃用。
  7. JDK9 使 G1 成为默认垃圾收集器

我们把runserver.sh中,jdk9与jdk8不同的写法替换掉,算了,也没成功。

然后查找网上资料:

更改runserver.sh的以下内容:

更改 CLASSPATH的值为:基于SpringCloudAlibaba搭建的微服务项目案例 - 图1{BASE_DIR}/lib/*:基于SpringCloudAlibaba搭建的微服务项目案例 - 图2{CLASSPATH}rocketmq-broker-4.5.1.jar需要查看lib目录下的版本

删除 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection和-XX:-UseParNewGC和-XX:+PrintGCDetails。

-Xloggc改成-Xlog:gc

删除 JAVA_OPT=”基于SpringCloudAlibaba搭建的微服务项目案例 - 图3{JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib”

放到我这里的情况就是:

1.原本的 export CLASSPATH=.:基于SpringCloudAlibaba搭建的微服务项目案例 - 图4{CLASSPATH}

改为: export CLASSPATH=.:基于SpringCloudAlibaba搭建的微服务项目案例 - 图5{CLASSPATH}:${BASE_DIR}/lib/*

2.原本的 JAVA_OPT=”${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC”

改为 JAVA_OPT=”${JAVA_OPT} -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8”

3.原本的 JAVA_OPT=”${JAVA_OPT} -verbose:gc -Xloggc:/dev/shm/rmq_srv_gc.log -XX:+PrintGCDetails”

改为 JAVA_OPT=”${JAVA_OPT} -verbose:gc -Xlog:gc:/dev/shm/rmq_srv_gc.log”

4.删除 JAVA_OPT=”基于SpringCloudAlibaba搭建的微服务项目案例 - 图6{JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib”

改你妈个头,全是行不通的。,我放弃,我把本机jdk版本换成1.8。

然后就成功了:

运行成功会弹出这个黑窗口,看到黑窗口的最后一行就是运行成功。

此框不要关闭

第六步:

启动broker。 在解压的rocketmq目录下的bin目录下,mqbroker.cmd是broker的启动脚本。

在命令行窗口cmd进入到RocketMQ安装目录下的bin目录, Cmd命令框执行进入至‘MQ文件夹\bin’下,然后执行start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true,启动BROKER。成功后会弹出提示框,此框勿关闭。

broker启动成功后黑窗口没有任何提示,这是正常的。

第七步:

下载安装可视化工具。

这个可视化工具的下载好像必须通过git,网上这么说,我也不知道为什么,暂且这么用。

下载地址:https://github.com/apache/rocketmq-externals.git

下载之后的目录如下:

下载来之后,进入‘rocketmq-externals\rocketmq-console\src\main\resources’文件夹,打开‘application.properties’进行配置。

主要配置启动的端口号server.port,以及rocketmq.config.namesrvAddr

server.port配置的是此可视化界面插件的启动端口

rocketmq.config.namesrvAddr配置的是MQ的启动端口,rocketmq的默认启动端口(borker的)是9876。

我们不改变rocketmq的默认启动端口,将可视化插件的启动端口改为9010。

server.address=0.0.0.0
server.port=9010

### SSL setting
#server.ssl.key-store=classpath:rmqcngkeystore.jks
#server.ssl.key-store-password=rocketmq
#server.ssl.keyStoreType=PKCS12
#server.ssl.keyAlias=rmqcngkey

#spring.application.index=true
spring.application.name=rocketmq-console
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
logging.level.root=INFO
logging.config=classpath:logback.xml
#if this value is empty,use env value rocketmq.config.namesrvAddr  NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
rocketmq.config.namesrvAddr=127.0.0.1:9876
#if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
rocketmq.config.isVIPChannel=
#rocketmq-console's data path:dashboard/monitor
rocketmq.config.dataPath=/tmp/rocketmq-console/data
#set it false if you don't want use dashboard.default true
rocketmq.config.enableDashBoardCollect=true
#set the message track trace topic if you don't want use the default one
rocketmq.config.msgTrackTopicName=
rocketmq.config.ticketKey=ticket

#Must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required
rocketmq.config.loginRequired=false

#set the accessKey and secretKey if you used acl
#rocketmq.config.accessKey=
#rocketmq.config.secretKey=
rocketmq.config.useTLS=false

修改完配置文件,我们编译启动该可视化插件

进入‘\rocketmq-externals\rocketmq-console’文件夹,执行mvn clean package -Dmaven.test.skip=true,编译生成jar文件。

编译成功之后,Cmd进入‘target’文件夹,执行java -jar rocketmq-console-ng-2.0.0.jar,启动编译出来的rocketmq-console-ng-2.0.0。

执行成功后,浏览器输入127.0.0.1:配置端口,即可查看,我们配置的端口是9010,所以是127.0.0.1:9010。如下:

6.2.2 RocketMQ的架构和相关概念

RocketMQ整体分为四个角色:NameServer,Broker,Producer,Consumer。除去这四个角色,还有Topic,Message,Producer Group和Consumer Group四个部分。

  • Broker(邮递员)
    Broker是RocketMQ的核心,负责消息的接收,存储,投递以及服务高可用保证等功能。
    Broker启动时,会主动创建一些系统的Topic,然后向NameServer注册自己的信息,消息持久化采用文件的形式来实现,Broker也可以集群部署,角色分为Master和Slave,Slave只能进行消息的读操作,不能进行写操作,Master既可以读也可以写。还维持消息的消费的offset(记录消费的进度)。
  • NameServer(邮局)
    NameServer底层是Netty实现的,提供了服务注册路由管理服务发现的功能。
    消息队列的协调者,Broker向它注册路由信息,同时Producer和Consumer 向其获取路由信息。Producer(寄件人,消息的生产者),需要从NameServer获取Broker信息,然后与 Broker建立连接,向Broker发送消息。
    作为服务的发现者,集群中的各个角色都需要定时向NameServer上报自己的状态和同步数据信息,NameServer把数据存储在内存中,不会对数据进行持久化。
    NameServer部署多个的时候,其他角色需要分别向所有的NameServer上报自己的状态和同步数据信息,来确保高可用。NameServer之间互不通讯,所有NameServer都是平等的,没有主从的概念
  • Producer(发件人)
    消息发布的角色。
  • Consumer(收件人)
    消息的消费者,需要从NameServer获取Broker信息,然后与Broker建立连 接,从Broker获取消息。
  • Topic(地区)
    用来区分不同类型的消息,发送和接收消息前都需要先创建Topic,针对Topic来发送和接收消息。
    一个Topic可以设置一个或多个Message Queue,这样消息就可以并行往各个Message Queue发送消息,消费者也可以并行的从多个Message Queue读取消息。
  • Message
    Message 是消息的载体。
  • Producer Group
    生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组。
  • Consumer Group
    消费者组,消费同一类消息的多个 consumer 实例组成一个消费者组。

整体架构如下图:

6.3 RocketMQ消息发送和接收演示

首先,我们在父工程中引入rockectmq的相关依赖。

<!-- rocketmq 相关依赖 -->
<dependency>
   <groupId>org.apache.rocketmq</groupId>
   <artifactId>rocketmq-spring-boot-starter</artifactId>
   <version>2.0.4</version>
</dependency>

这里我们引入这个依赖,我看网上有引入rockectmq-client这个依赖的 ,不知道什么原因和区别,是因为springboot整合了rocketmq的起步依赖,就是现在这个rocketmq-spring-boot-starter吗?

后续学习中发现,这两个都要引入。

6.3.1 发送消息

消息发送步骤:

1.创建消息生产者,并指定生产者所属组名

2.指定nameserver地址

3.启动生产者

4.创建消息对象,指定主题、标签和消息体

5.发送消息

6.关闭生产者

我们在ali-cloud-shop-user中创建一个测试类,编写rocketmq发送消息的案例代码(注意,这是个以main函数启动的测试类,后续关于项目中如何使用,下面章节会讲到,此处只是简单测试消息的发送):

@Slf4j
public class RocketMQSendTest {

    public static void main(String[] args) throws Exception {

        // 1.创建消息生产者,并指定生产者所属组名
        DefaultMQProducer producer = new DefaultMQProducer("test-producer-group");
        // 2.指定nameserver地址
        producer.setNamesrvAddr("127.0.0.1:9876");
        // 3.启动生产者
        producer.start();
        // 4.创建消息对象,指定主题、标签和消息体
        Message msg = new Message();
        msg.setTopic("myTopic");
        msg.setTags("myTag");
        msg.setBody(("My RocketMQ Message Test.").getBytes());
        // 5.发送消息
        SendResult sendResult = producer.send(msg,10000);
        log.info("----sendResult:"+sendResult);
        // 6.关闭生产者
        producer.shutdown();


    }

}

运行测试类,运行成功,打印出SendResult对象信息。

6.3.2 接收消息

消息接收步骤:

1.创建消息消费者,指定消费者所属的组名

2.指定Nameserver地址

3.指定消费者订阅的主题和标签

4.设置回调函数,编写处理回调消息的方法

5.启动消息消费者

6.消息消费者是否需要shutdown,根据实际情况?

我们在ali-cloud-shop-user中创建一个测试类,编写rocketmq接收消息的案例代码(注意,这是个以main函数启动的测试类,后续关于项目中如何使用,下面章节会讲到,此处只是简单测试消息的发送):

@Slf4j
public class RocketMQReceiveTest {

    public static void main(String[] args) throws Exception{
        // 1.创建消息消费者, 指定消费者所属的组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test-consumer-group");
        // 2.指定nameserver地址
        consumer.setNamesrvAddr("127.0.0.1:9876");
        // 3.指定消费者订阅的主题和标签
        consumer.subscribe("myTopic","myTag");
        // 4.设置回调函数,编写处理消息的方法
        consumer.registerMessageListener(new MessageListenerConcurrently() {
             @Override
             public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgList,
                                                             ConsumeConcurrentlyContext context) {
                 log.info("---接收到的消息:"+msgList);
                 log.info("---消息体:" +  new String(msgList.get(0).getBody()));
                 // 返回消费状态,案例中默认返回成功
                 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
             }
         });
        // 5.启动消息消费者
        consumer.start();
        log.info("---consumer start.");

    }

}

注: 我们这里没有关闭消费者, 启动消费者案例后,重启生产者案例,就相当于生产者生产并发送了一次消息,指定的主题与标签相同的情况下,消费者会接收到生产者发送的消息(因为我们这里用的是push模式)。

我们可以在第四步回调函数编写的处理消息的方法中,msgList是获取到的所有message的list,取得其中的每个MessageExt对象信息,调用其对象的getBody()方法得到值,将这个值用new String()转换为string格式,就可以得到,是我们通过生产者发送过来的消息体。

引申:

消费者获取消息的两种模式: 一般用push模式

push:客户端与服务器建立连接后,当服务器有消息时,服务器将消息推送到客户端

pull:客户端不断轮询请求服务端,来获取新的消息

6.4 项目中使用RocketMQ

我们来模拟一个场景:新增用户时,是需要从ali-cloud-shop-user服务,调用ali-cloud-shop-common服务中的新增用户的方法来新增一个用户。 那么我们来完成新增一个用户时,新增成功后,ali-cloud-shop-common通过mq向mq投递新增的用户信息,ali-cloud-shop-user订阅mq中common服务投递的信息。

首先,我们需要在原本添加了rocketmq-spring-boot-starter依赖的基础上,再添加一个rocketmq客户端依赖:

<!-- rocketmq client 相关依赖 -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>

也就是说,我们在项目中使用rockeMQ需要添加以下两个依赖:

<!-- rocketmq 相关依赖 -->
<dependency>
   <groupId>org.apache.rocketmq</groupId>
   <artifactId>rocketmq-spring-boot-starter</artifactId>
   <version>2.0.4</version>
</dependency>

<!-- rocketmq client 相关依赖 -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.6.0</version>
</dependency>

6.4.1 ali-coud-shop-common服务的新增用户接口中加入发送消息的逻辑

第一步:

ali-cloud-shop-common的配置文件加入rocketmq相关配置:

#rocketMQ相关配置
# 配置rocketMQ服务的地址
rocketmq.name-server=127.0.0.1:9876
# 配置消息生产者组
rocketmq.producer.group=produce-user-group

第二步:

改造原本案例中,ali-cloud-shop-common服务的新增用户的接口,加上发送消息的逻辑。

原本的ali-cloud-shop-common服务的新增用户方法:

@RestController
@RequestMapping("/common/shopUser")
@Slf4j
public class ShopUserController {

    @Autowired
    private ShopUserService shopUserService;

    @RequestMapping("/addUser")
    public Boolean addUser(@RequestBody ShopUser shopUser){
        log.info("---shopUser:"+shopUser);
        boolean saveFlag = shopUserService.save(shopUser);
        return saveFlag;
    }

}

加入消息生产者后的ali-cloud-shop-common服务的新增用户方法如下:

@RestController
@RequestMapping("/common/shopUser")
@Slf4j
public class ShopUserController {

    @Autowired
    private ShopUserService shopUserService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

//    // 原本的addUser()方法
//    @RequestMapping("/addUser")
//    public Boolean addUser(@RequestBody ShopUser shopUser){
//        log.info("---shopUser:"+shopUser);
//        boolean saveFlag = shopUserService.save(shopUser);
//        return saveFlag;
//    }


    // 添加了消息生产者的的addUser()方法
    @RequestMapping("/addUser")
    public Boolean addUser(@RequestBody ShopUser shopUser){
        log.info("---shopUser:"+shopUser);
        boolean saveFlag = shopUserService.save(shopUser);

        // 新增用户成功后,将新增的用户信息存入mq
        log.info("--after insert shopUser:"+shopUser);
        // 此方法第一个参数是配置的topic,第二个参数是要传递的信息
        rocketMQTemplate.convertAndSend("common-topic",shopUser);

        return saveFlag;
    }


}

注意,需要注入RocketMQTemplate对象使用。

6.4.2 ali-cloud-shop-user服务新增用户后加入接收消息的逻辑

第一步:

修改配置文件。如下:

#RocketMQ相关配置
rocketmq.name-server=127.0.0.1:9876

因为用户微服务案例中只用来接收消息,没有进行生产者操作,所以未配置生产者组。

第二步:

改造原本案例中,ali-cloud-shop-user服务的新增用户的接口,加上接收消息的逻辑。

原本的addUser()方法的逻辑:

@RestController
@RequestMapping("/user/shopUser")
@Slf4j
public class ShopUserController {

    @Autowired
    private AliCloudShopCommonService aliCloudShopCommonService;

    @RequestMapping("/addUser")
//    public String addUser(@RequestBody ShopUser shopUser)
    public String addUser(){
        log.info("---shop-user addUser start:");
        String resultStr = "添加用户失败";

        ShopUser shopUser = new ShopUser();
        shopUser.setUsername("测试用户3");
        shopUser.setPassword("123456");
        shopUser.setTelephone("15799999999");

        Boolean addFlag = aliCloudShopCommonService.addUser(shopUser);
        if(addFlag){
            resultStr = "添加用户成功";
        }

        log.info("---shop-user addUser end.");

        return resultStr;
    }


}

加入消息消费者后的ali-cloud-shop-user服务的新增用户方法如下:

controlelr:


/**
 * @author yuanhai
 * @date 2021/8/3
 */
@RestController
@RequestMapping("/user/shopUser")
@Slf4j
public class ShopUserController {

    @Autowired
    private AliCloudShopCommonService aliCloudShopCommonService;

    @Autowired
    private ShopUserService shopUserService;

    // 原本的新增用户方法
//    @RequestMapping("/addUser")
////    public String addUser(@RequestBody ShopUser shopUser)
//    public String addUser(){
//        log.info("---shop-user addUser start:");
//        String resultStr = "添加用户失败";
//
//        ShopUser shopUser = new ShopUser();
//        shopUser.setUsername("测试用户3");
//        shopUser.setPassword("123456");
//        shopUser.setTelephone("15799999999");
//
//        Boolean addFlag = aliCloudShopCommonService.addUser(shopUser);
//        if(addFlag){
//            resultStr = "添加用户成功";
//        }
//
//        log.info("---shop-user addUser end.");
//
//        return resultStr;
//    }


    @RequestMapping("/addUser")
//    public String addUser(@RequestBody ShopUser shopUser)
    public String addUser(){
        String s = shopUserService.addUser();
        return s;
    }

}

ShopUserServiceImpl:

@Service
@Slf4j
@RocketMQMessageListener(consumerGroup = "consumer-user-group", topic = "common-topic")
public class ShopUserServiceImpl extends ServiceImpl<ShopUserMapper, ShopUser> implements ShopUserService,
        RocketMQListener<ShopUser> {

    @Autowired
    private AliCloudShopCommonService aliCloudShopCommonService;

    @Override
    public String addUser() {
        log.info("---shop-user addUser start:");
        String resultStr = "添加用户失败";

        ShopUser shopUser = new ShopUser();
        shopUser.setUsername("测试用户3");
        shopUser.setPassword("123456");
        shopUser.setTelephone("15799999999");

        Boolean addFlag = aliCloudShopCommonService.addUser(shopUser);
        if(addFlag){
            resultStr = "添加用户成功";
        }

        log.info("---shop-user addUser end.");

        return resultStr;
    }

    @Override
    public void onMessage(ShopUser shopUser) {
        log.info("---user微服务接收到消息:"+ JSON.toJSONString(shopUser));
    }


}

可以看到,需要作为消费者接收消息时,

首先类要实现RocketMQListener接口,并实现其中的void onMessage()方法,在onMessage()方法中处理接收到的消息;

还要在类上添加@RocketMQMessageListener注解,该注解的consumerGroup属性是指定消费者组,topic属性用来指定接收哪个topic的消息,要设置为想要接收的消息生产者的topic。

注意:这里出现过一个错误,service实现类上面加上了@RocketMQMessageListener注解后,项目启动失败,报错找不到org/apache/rocketmq/client/AccessChannel 这个类,后面我更换了rocketmq-client依赖的版本,原本是4.4.0,我改为了4.6.0,就成功了。

重启common和user微服务,调用http://localhost:10001/user/shopUser/addUser添加新用户,可以验证消息的生产,发送,接收成功了。

ali-cloud-shop-common控制台:

ali-cloud-shop-user控制台:

6.5 发送不同类型的消息

RocketMQ发送的消息分为三种:

  • 普通消息
  • 顺序消息
  • 事务消息

具体三种消息的各种操作,后续学习

6.6 消息消费要注意的细节

消息消费者使用@RocketMQMessageListener注解,该注解需注意以下几个属性:

@RocketMQMessageListener(
consumerGroup = "shop",//消费者分组
topic = "order-topic",//要消费的主题
consumeMode = ConsumeMode.CONCURRENTLY, //消费模式:无序和有序
messageModel = MessageModel.CLUSTERING, //消息模式:广播和集群,默认是集群
)
public class SmsService implements RocketMQListener<Order>{
}

RocketMQ支持两种消息模式:

广播消费: 每个消费者实例都会收到消息,也就是一条消息可以被每个消费者实例处理;

集群消费: 一条消息只能被一个消费者实例消费

7 Nacos Config服务配置中心

7.1 服务配置中心介绍

首先我们来看一下,微服务架构下关于配置文件的一些问题:

  1. 配置文件相对分散。在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散 在各个微服务中,不好统一配置和管理。
  2. 配置文件无法区分环境。微服务项目可能会有多个环境,例如:测试环境、预发布环境、生产环 境。每一个环境所使用的配置理论上都是不同的,一旦需要修改,就需要我们去各个微服务下手动 维护,这比较困难。
  3. 置文件无法实时更新。我们修改了配置文件之后,必须重新启动微服务才能使配置生效,这对一 个正在运行的项目来说是非常不友好的。 基于上面这些问题,我们就需要配置中心的加入来解决这些问题

配置中心的思路是:

首先把项目中各种配置全部都放到一个集中的地方进行统一管理,并提供一套标准的接口。

当各个服务需要获取配置的时候,就来配置中心的接口拉取自己的配置。

当配置中心中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。

当加入了服务配置中心之后,我们的系统架构图会变成下面这样

我们选择nacos config。其他配置中心想要了解可以自行度娘。

7.2 Nocas Config入门

使用nacos作为配置中心,其实就是将nacos当做一个服务端,将各个微服务看成是客户端,

我们将各个微服务的配置文件统一存放在nacos上,然后各个微服务从nacos上拉取配置即可。

我们以用户微服务ali-cloud-shop-user为例,学习nacos config的使用。

7.2.1 入门准备
首先我们要有一个使用nacos作为注册中心的项目环境,就使用这里原本的案例即可。

7.2.2 引入nacos config相关依赖
我们把nacos config的依赖加到父工程中:
<!-- nacos config相关依赖 -->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

7.2.3 在微服务中添加nacos config的配置
**注意:不能使用原来的application.yml作为配置文件,而是新建一个bootstrap.yml作为配置文件 配置文件优先级(由高到低):**
bootstrap.properties -> bootstrap.yml -> application.properties -> application.yml

在bootstrap.yml文件中,添加如下配置:

spring:
  application:
    name: ali-cloud-shop-user
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848      # nacos注册中心地址
        file-extension: yaml # 配置文件扩展格式,这个yaml和bootstrap配置文件的格式不是一致的,用的yaml,暂时还不知道为什么
  profiles:
    active: dev   # 环境标识

7.2.4 在nacos控制台添加配置

点击配置列表,点击右边+号,新建配置。

在新建配置过程中,要注意下面的细节:
1)Data ID不能随便写,要跟配置文件中的对应,对应关系如图所示
2)配置文件格式要跟配置文件的格式对应,且目前仅仅支持YAML和Properties
3)配置内容按照上面选定的格式书写

下图是网上案例的Data ID书写格式:

下面我们来添加自己的ali-cloud-shop-user的配置:

注意,上面新建配置的图中,我们需要.yaml格式的配置文件,图片中赋值项目中的.properties格式,要改为.yaml格式,如下:

server:
  port: 10001

#当前服务名称
spring:
  application:
    name: ali-cloud-shop-user
  #DB Configuration:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/ali-cloud-study?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: ****
    password: ****
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848    #nacos控制台地址
    sentinel:
      transport:
        dashboard: localhost:8088     # 指定控制台服务的地址
        port: 9999        #跟控制台交流的端口,随意指定一个未使用的端口即可
      datasource:
        shop-user-ds1:
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ali-cloud-shop-user-sentinel-ds1
            group-id: DEFAULT_GROUP
            rule_type: flow
            data-type: json




#mybatis-plus设置
mybatis-plus:
  mapper-locations: classpath*:org/yuanhai/**/xml/*Mapper.xml
  global-config:
    banner: false
    db-config:
      id-type: assign_uuid
      table-underline: true
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    call-setters-on-nulls: true   # 返回类型为Map,显示null对应的字段
#开启feign对sentinel的支持
feign:
  sentinel:
    enabled: true
#RocketMQ相关配置
rocketmq:
  name-server: 127.0.0.1:9876

配置完成后的截图:

7.2.5 测试是否配置成功

将原本的配置文件中的内容注释掉,只留下bootstrap.yml文件的配置,启动项目进行测试。

启动后,发现成功启动,调用接口,发现配置的rocketmq消息队列相关,sentinel流控规则相关也是正常生效使用的。

7.3 Nacos Config配置动态刷新

在入门案例中,我们实现了配置的远程存放,但是此时如果修改了配置,我们的程序是无法读取到的, 因此,我们需要开启配置的动态刷新功能。

第一步:

nacos中的ali-cloud-shop-user-dev.yaml配置项中添加下面配置,用来测试动态刷新是否生效:

testRefresh: 
    val: val1

第二步:

在用到配置文件值的地方,在其类上添加@RefreshScope注解,即可获取最新的值:

@RestController
@Slf4j
@RequestMapping("/user/nacosConfig")
@RefreshScope  // 只需要在需要动态读取配置的类上添加此注解就可以
public class NacosConfigTestController {

    @Value("${testRefresh.val}")
    private String val;

    public String getConfigVal(){
        log.info("----val:"+val);
        return val;
    }

}

我们在nacos配置中心中,更改testRefresh.val的值,运行后发现获取到的testRefresh.val的值改变了,实现了动态刷新。

@RefreshScope也有不生效的情况,后续学习与实践中学习。

问题:这里@RefreshScope注解放到某个controller上,使其他对某个controller生效,如果我想让动态刷新对整个项目都生效,如何做到呢?

目前没有找到动态刷新全局配置的这种方法。难道只能具体的类获取这种自定义值的情况可以动态更新,如果我我想全局更换掉连接的数据库地址,就只能重启项目了吗?

7.4 Nacos Config配置共享

当配置越来越多的时候,会有很多配置是重复的,这时候可以将公共配置文件提取出来,实现共享。

配置共享有两种情况:

  • 同一个微服务的不同环境之间共享配置
  • 不同微服务之间共享配置

7.4.1 同一微服务不同环境的配置共享

同一个微服务的不同环境之间实现配置共享,只需要提取一个以 spring.application.name 命名的配置文件,然后将其所有环境的公共配置放在里面即可。

注意: spring.application.name 命名的配置文件,不是指配置文件名就叫spring.application.name,而是这个项目的spring.application.name对应的属性值。 比如用户微服务,我们配置的spring.application.name的值为ali-cloud-shop-user,那么这个公共配置文件的名字就叫ali-cloud-shop-user.yaml(.peroperties格式就叫ali-cloud-shop-user.properties)。

具体案例这里不去实现了。

bootstrap.yml文件的spring.profiles.active属性,就是用来指定使用哪个环境配置文件。

7.4.2 不同微服务之间的配置共享

不同为服务之间实现配置共享的原理类似于文件引入,就是定义一个公共配置,然后在当前配置中引入

第一步:在nacos中定义一个配置,设置Data-Id,作为公共配置,用于给其他微服务共享。

第二步:各自微服务的配置文件中,如果有和公共配置文件重合的部分,可以删去。

第三步:修改各个微服务项目中的bootstrap.yml文件,该文件配置上要引入的配置。

bootstrap.yml文件**配置上要引入的配置,通过spring.cloud.nacos.shared-dataids属性实现,其值为公共配置的Data-Id**

7.5 Nocas的几个概念解释

命名空间(Namespace)
命名空间可用于进行不同环境的配置隔离。一般一个环境划分到一个命名空间

配置分组(Group)
配置分组用于将不同的服务可以归类到同一分组。一般将一个项目的配置分到一组

配置集(Data ID)
在系统中,一个配置文件通常就是一个配置集。一般微服务的配置就是一个配置集

8 Skywalking链路追踪

在大型系统的微服务化构建中,一个系统被拆分成了许多模块。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种架构中,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、 有可能布在了几千台服务器,横跨多个不同的数据中心,也就意味着这种架构形式也会存在一些问题:

如何快速发现问题?

如何判断故障影响范围?

如何梳理服务依赖以及依赖的合理性?

如何分析链路性能问题以及实时容量规划?

对以上情况, 我们就需要一些可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题,这时候 APM(应用性能管理)工具就该闪亮登场了。

目前主要的一些 APM 工具有: Cat、Zipkin、Pinpoint、SkyWalking,这里主要介绍SkyWalking ,它是一款优秀的国产 APM 工具,包括了分布式追踪、性能指标分析、应用和服务依赖分析等。

SkyWalking是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。 特点是支持多种插件,UI功能较强,接入端无代码侵入。目前已加入Apache孵化器。

内容涉及的有些复杂,后续再学习。