springcloud(3/3)

19.SpringCloud Alibaba入门简介

19.1.为什么会出现SpringCloud alibaba

Spring Cloud Netflix项目进入维护模式

https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now

Spring Cloud Netflix Projects Entering Maintenance Mode

  1. 什么是维护模式

    将模块置于维护模式,意味着 Spring Cloud 团队将不会再向模块添加新功能。
    我们将修复 block 级别的 bug 以及安全问题,我们也会考虑并审查社区的小型 pull request。

  2. 进入维护模式意味着什么呢?

    • Spring Cloud Netflix 将不再开发新的组件

      我们都知道Spring Cloud 版本迭代算是比较快的,因而出现了很多重大ISSUE都还来不及Fix就又推另一个Release了。进入维护模式意思就是目前一直以后一段时间Spring Cloud Netflix提供的服务和功能就这么多了,不在开发新的组件和功能了。以后将以维护和Merge分支Full Request为主

    • 新组件功能将以其他替代平代替的方式实现

19.2.SpringCloud alibaba带来了什么

19.2.1.是什么

官网:
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

诞生:
2018.10.31,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 Maven 中央库发布了第一个版本。

19.2.2.主要功能

  • 服务限流降级:默认支持 WebServlet、WebFlux、OpenFeign、RestTemplate、Spring Cloud Gateway、Zuul、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  • 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  • 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
  • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
  • 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

19.2.3.下载

https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

19.2.4.组件

19.3.SpringCloud alibaba参考文档

19.3.1.官网

https://spring.io/projects/spring-cloud-alibaba#overview

19.3.2.英文文档

https://github.com/alibaba/spring-cloud-alibaba

英文文档

https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html

19.3.3.中文文档

https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

20.SpringCloud Alibaba Nacos服务注册和配置中心

20.1.Nacos简介

20.1.1.为什么叫Nacos

前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。

20.1.2.是什么

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos: Dynamic Naming and Configuration Service

Nacos就是注册中心 + 配置中心的组合等价于Nacos = Eureka+Config +Bus

20.1.3.主要功能

  • 替代Eureka做服务注册中心
  • 替代Config做服务配置中心

20.1.4.发布以及文档地址

下载:https://github.com/alibaba/Nacos

官方文档:

  • 中文地址:https://nacos.io/zh-cn/index.html

  • 英文地址:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_discovery

20.1.5.各种注册中心比较

04163839267

据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验

20.2.安装并运行Nacos

本地Java8+Maven环境

20.2.1.官网下载Nacos

https://github.com/alibaba/nacos/releases

20.2.2.安装

  1. 下载后解压

  2. 解压安装包,直接运行bin目录下的startup.cmd

    startup.cmd -m standalone
    
  3. 命令运行成功后直接访问http://localhost:8848/nacos

    默认账号密码都是nacos

20.3.Nacos作为服务注册中心

20.3.1.官网文档

https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_config

20.3.2.基于Nacos的服务提供者

20.3.2.1.新建Module
20.3.2.2.pom.xml
  • 父项目pom.xml

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.1.0.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    
  • 本项目pom.xml

    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <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>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
20.3.2.4.application.yaml
server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*'
20.3.2.5.主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9001.class, args);
    }
}
20.3.2.6.业务类
@Controller
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    @RequestMapping(value = "/payment/nacos/{id}")
    @ResponseBody
    public String getPayment(@PathVariable("id") Integer id) {
        return "nacos registry, serverPort: " + serverPort + "\t id" + id;
    }
}
20.3.2.7.测试

http://localhost:9001/payment/nacos/1

nacos控制台

20.3.2.8.参照9001新建9002

为了下一章节演示nacos的负载均衡,参照9001新建9002

20.3.3.基于Nacos的服务消费者

20.3.3.1.新建Module

cloudalibaba-consumer-nacos-order83

20.3.3.2.pom.xml

com.alibaba.cloud

<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.stonebridge.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <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>
    <!--日常通用jar包配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

为什么nacos支持负载均衡

04181645348
20.3.3.3.apllication.yaml
server:
  port: 83


spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider 
20.3.3.4.主启动类
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83 {
    public static void main(String[] args) {
        SpringApplication.run(OrderNacosMain83.class, args);
    }
}
20.3.3.5.业务类
  • config

    负载均衡一定要加@LoadBalanced注解

    @Configuration
    public class ApplicationContextBean {
        @Bean
        @LoadBalanced
        public RestTemplate getRestTemplate() {
            return new RestTemplate();
        }
    }
    
  • controller

    @Controller
    public class OrderNacosController {
        @Resource
        private RestTemplate restTemplate;
    
        @Value("${service-url.nacos-user-service}")
        private String serverURL;
    
        @RequestMapping("/consumer/payment/nacos/{id}")
        @ResponseBody
        public String paymentInfo(@PathVariable("id") Long id) {
            return restTemplate.getForObject(serverURL + "/payment/nacos/" + id, String.class);
        }
    }
    
20.3.3.6.测试

启动

访问:http://localhost:83/consumer/payment/nacos/13

83访问9001/9002,轮询负载OK

20.3.4.服务注册中心对比

20.3.4.1.Nacos全景图所示
20.3.4.2.Nacos和CAP
20.3.4.3.切换

Nacos 支持AP和CP模式的切换

C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。

何时选择使用何种模式?

一般来说,
如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。

如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。

curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

20.4.Nacos作为服务配置中心

20.4.1.Nacos作为配置中心-基础配置

20.4.1.1.新建Module

cloudalibaba-config-nacos-client3377

20.4.1.2.pom.xml
<dependencies>
    <!--nacos-config-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!--nacos-discovery-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--web + actuator-->
    <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>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
20.4.1.3.application.yaml

Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,
拉取配置之后,才能保证项目的正常启动。

springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application

  • bootstrap.yaml

    # nacos配置
    server:
      port: 3377
    
    spring:
      application:
        name: nacos-config-client
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 #Nacos服务注册中心地址
          config:
            server-addr: localhost:8848 #Nacos作为配置中心地址
            file-extension: yaml #指定yaml格式的配置
    # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
    
  • application.yaml

    spring:
      profiles:
        active: dev # 表示开发环境
    
20.4.1.4.业务类

通过Spring Cloud 原生注解@RefreshScope实现配置自动更新:

@Controller
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class ConfigClientController {
    @Value("${config.info}")
    private String configInfo;

    @RequestMapping("/config/info")
    @ResponseBody
    public String getConfigInfo() {
        return configInfo;
    }
}
20.4.1.5.在Nacos中添加配置信息

Nacos中的匹配规则

  • 理论

    Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则

    官网

    04224319007

    最后公式:

    ${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

    根据公式拼接的dataId为nacos-config-client-dev.yaml。因此在Nacos注册中心配置的配置新的dataId为nacos-config-client-dev.yaml,便可以被该应用获取。

  • 实操

    1. 配置新增

      nacos-config-client-dev

    2. Nacos界面配置对应

      设置DataId

      1. 公式:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

      2. prefix 默认为 spring.application.name 的值

      3. spring.profile.active 即为当前环境对应的 profile,可以通过配置项 spring.profile.active 来配置。

      4. file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置

        04225353807
    3. 历史配置

      Nacos会记录配置文件的历史版本默认保留30天,此外还有一键回滚功能,回滚操作将会触发配置更新

20.4.1.6.测试

注意应用的配置的是spring.cloud.nacos.config.file-extension是yaml还是yml

启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件

运行cloud-config-nacos-client3377的主启动类

调用接口查看配置信息:http://localhost:3377/config/info

20.4.1.7.自带动态刷新

修改下Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新

20.4.2.Nacos作为配置中心-分类配置

20.4.2.1.问题

多环境多项目管理

  1. 问题1:
    实际开发中,通常一个系统会准备
    dev开发环境
    test测试环境
    prod生产环境。
    如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?

  2. 问题2:
    一个大型分布式微服务系统会有很多微服务子项目,
    每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…
    那怎么对这些微服务配置进行管理呢?

20.4.2.2.Nacos的图形化管理界面
  1. 配置管理

  2. 命名空间

20.4.2.3.Namespace+Group+Data ID三者关系、设计思想
  1. 是什么

    类似Java里面的package名和类名

    最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。

  2. 三者情况

    默认情况:

    Namespace=public,Group=DEFAULT_GROUP, 默认Cluster是DEFAULT

Nacos默认的命名空间是public,Namespace主要用来实现隔离。

比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。

Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去

Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,
这时就可以给杭州机房的Service微服务起一个集群名称(HZ),
给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。

最后是Instance,就是微服务的实例。

20.4.2.4.DataID方案
  1. 指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置

  2. 默认空间+默认分组+新建dev和test两个DataID

    1. 新建dev配置DataID

    2. 新建test配置DataID

  3. 通过spring.profile.active属性就能进行多环境下配置文件的读取

  4. 测试

    重启配置spring.profile.active的3377应用

    访问:http://localhost:3377/config/info

20.4.2.5.Group方案
  1. 通过Group实现环境区分

    新建Group

  2. 在nacos图形界面控制台上面新建配置文件DataID

  3. bootstrap+application

    在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP

    20.4.2.6.Namespace方案
    1. 新建dev/test的Namespace

    2. 回到服务管理-服务列表查看

    3. 在命名空间下增加配置

      访问:http://localhost:3377/config/info

20.5.Nacos集群和持久化配置

20.5.1.官网说明

https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html

20.5.1.1.官网架构图

VIP的意思是virtual IP,就是Nginx

20.5.1.2.上图官网翻译,真实情况
20.5.1.3.说明

默认Nacos使用嵌入式数据库实现数据的存储(比如现在的单机模式中,保存的配置信息就保存在内嵌数据库Derby中)。

所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。数据源只有一份就可以保持数据的一致性

https://nacos.io/zh-cn/docs/deployment.html

20.5.2.Nacos持久化配置解释

20.5.2.1.Nacos默认自带的是嵌入式数据库derby

https://github.com/alibaba/nacos/blob/develop/config/pom.xml

20.5.2.2.derby到mysql切换配置步骤
  1. nacos\conf目录下找到sql脚本

    nacos-mysql.sql

    先建库结构nacos_config

    /*
     * Copyright 1999-2018 Alibaba Group Holding Ltd.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    /******************************************/
    /*   数据库全名 = nacos_config   */
    /*   表名称 = config_info   */
    /******************************************/
    CREATE TABLE `config_info` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
      `data_id` varchar(255) NOT NULL COMMENT 'data_id',
      `group_id` varchar(255) DEFAULT NULL,
      `content` longtext NOT NULL COMMENT 'content',
      `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
      `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
      `src_user` text COMMENT 'source user',
      `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
      `app_name` varchar(128) DEFAULT NULL,
      `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
      `c_desc` varchar(256) DEFAULT NULL,
      `c_use` varchar(64) DEFAULT NULL,
      `effect` varchar(64) DEFAULT NULL,
      `type` varchar(64) DEFAULT NULL,
      `c_schema` text,
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
    
    /******************************************/
    /*   数据库全名 = nacos_config   */
    /*   表名称 = config_info_aggr   */
    /******************************************/
    CREATE TABLE `config_info_aggr` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
      `data_id` varchar(255) NOT NULL COMMENT 'data_id',
      `group_id` varchar(255) NOT NULL COMMENT 'group_id',
      `datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
      `content` longtext NOT NULL COMMENT '内容',
      `gmt_modified` datetime NOT NULL COMMENT '修改时间',
      `app_name` varchar(128) DEFAULT NULL,
      `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
    
    
    /******************************************/
    /*   数据库全名 = nacos_config   */
    /*   表名称 = config_info_beta   */
    /******************************************/
    CREATE TABLE `config_info_beta` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
      `data_id` varchar(255) NOT NULL COMMENT 'data_id',
      `group_id` varchar(128) NOT NULL COMMENT 'group_id',
      `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
      `content` longtext NOT NULL COMMENT 'content',
      `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
      `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
      `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
      `src_user` text COMMENT 'source user',
      `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
      `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
    
    /******************************************/
    /*   数据库全名 = nacos_config   */
    /*   表名称 = config_info_tag   */
    /******************************************/
    CREATE TABLE `config_info_tag` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
      `data_id` varchar(255) NOT NULL COMMENT 'data_id',
      `group_id` varchar(128) NOT NULL COMMENT 'group_id',
      `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
      `tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
      `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
      `content` longtext NOT NULL COMMENT 'content',
      `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
      `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
      `src_user` text COMMENT 'source user',
      `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
    
    /******************************************/
    /*   数据库全名 = nacos_config   */
    /*   表名称 = config_tags_relation   */
    /******************************************/
    CREATE TABLE `config_tags_relation` (
      `id` bigint(20) NOT NULL COMMENT 'id',
      `tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
      `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
      `data_id` varchar(255) NOT NULL COMMENT 'data_id',
      `group_id` varchar(128) NOT NULL COMMENT 'group_id',
      `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
      `nid` bigint(20) NOT NULL AUTO_INCREMENT,
      PRIMARY KEY (`nid`),
      UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
      KEY `idx_tenant_id` (`tenant_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
    
    /******************************************/
    /*   数据库全名 = nacos_config   */
    /*   表名称 = group_capacity   */
    /******************************************/
    CREATE TABLE `group_capacity` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
      `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
      `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
      `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
      `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
      `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
      `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
      `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
      `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_group_id` (`group_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
    
    /******************************************/
    /*   数据库全名 = nacos_config   */
    /*   表名称 = his_config_info   */
    /******************************************/
    CREATE TABLE `his_config_info` (
      `id` bigint(64) unsigned NOT NULL,
      `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      `data_id` varchar(255) NOT NULL,
      `group_id` varchar(128) NOT NULL,
      `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
      `content` longtext NOT NULL,
      `md5` varchar(32) DEFAULT NULL,
      `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `src_user` text,
      `src_ip` varchar(50) DEFAULT NULL,
      `op_type` char(10) DEFAULT NULL,
      `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
      PRIMARY KEY (`nid`),
      KEY `idx_gmt_create` (`gmt_create`),
      KEY `idx_gmt_modified` (`gmt_modified`),
      KEY `idx_did` (`data_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
    
    
    /******************************************/
    /*   数据库全名 = nacos_config   */
    /*   表名称 = tenant_capacity   */
    /******************************************/
    CREATE TABLE `tenant_capacity` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
      `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
      `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
      `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
      `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
      `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
      `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
      `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
      `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_tenant_id` (`tenant_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
    
    
    CREATE TABLE `tenant_info` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
      `kp` varchar(128) NOT NULL COMMENT 'kp',
      `tenant_id` varchar(128) default '' COMMENT 'tenant_id',
      `tenant_name` varchar(128) default '' COMMENT 'tenant_name',
      `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
      `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
      `gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
      `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
      KEY `idx_tenant_id` (`tenant_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
    
    CREATE TABLE `users` (
    	`username` varchar(50) NOT NULL PRIMARY KEY,
    	`password` varchar(500) NOT NULL,
    	`enabled` boolean NOT NULL
    );
    
    CREATE TABLE `roles` (
    	`username` varchar(50) NOT NULL,
    	`role` varchar(50) NOT NULL,
    	UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
    );
    
    CREATE TABLE `permissions` (
        `role` varchar(50) NOT NULL,
        `resource` varchar(255) NOT NULL,
        `action` varchar(8) NOT NULL,
        UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
    );
    
    INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
    
    INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
    
  2. nacos\conf目录下找到application.properties

    添加数据库配置

    nacos.istio.mcp.server.enabled=false
    ############################################
    spring.datasource.platform=mysql
    db.num=1
    db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=UTC
    db.user=root
    db.password=123456
    
20.5.2.3.启动Nacos
startup.cmd -m standalone
20.5.2.4.访问http://localhost:8848/nacos

新建配置

查看数据库

20.5.3.Linux配置Nacos+MySQL生产环境配置

20.5.3.1.CentOS7安装MySQL
  1. 查看Liunx版本

    [root@192 bin]# cat /etc/redhat-release
    
    06190614949
  2. 下载MySQL

    https://dev.mysql.com/downloads/mysql/5.7.html#downloads

  3. 解压

    安装目录:/usr/local/mysqldb/mysql

    数据目录:/usr/local/mysqldb/data

    [root@192 Downloads]# tar -zxvf mysql-5.7.29-linux-glibc2.12-x86_64.tar.gz
    
  4. 重命名到usr/local/mysqldb目录下,名字为mysql

    [root@192 Downloads]# mkdir -p /usr/local/mysqldb && mv mysql-5.7.29-linux-glibc2.12-x86_64 /usr/local/mysqldb/mysql
    
  5. 增加一个mysql用户并赋权限

    [root@192 Downloads]# useradd mysql
    
    [root@192 Downloads]# chown -R mysql /usr/local/mysqldb/
    
    [root@192 Downloads]# chgrp -R mysql /usr/local/mysqldb/
    
  6. 切换到mysql安装目录

    [root@192 Downloads]# cd /usr/local/mysqldb/mysql/bin
    
  7. 生成初始化密码并记住这个初始化密码

    [root@192 bin]# mkdir -p /usr/local/mysqldb/data && ./mysqld --initialize --user=mysql --basedir=/usr/local/mysqldb/mysql --datadir=/usr/local/mysqldb/data
    
  8. 在/etc/目录新增my.cnf文件并配置如下

    mysql5.7.29从之前的某个版本开始,在/usr/local/mysqldb/mysql/support-files目录下面呢,就没有名为

    my_default.cnf这个文件了,该文件作为配置my.cnf的配置模板

    [root@192 bin]# rm -rf /etc/my.cnf
    [root@192 bin]# vim /etc/my.cnf
    
    [mysql]
    # 设置mysql客户端默认字符集
    default-character-set=utf8 
    socket=/var/lib/mysql/mysql.sock
    
    [mysqld]
    sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
    skip-name-resolve
    #设置3306端口
    port = 3306 
    socket=/var/lib/mysql/mysql.sock
    # 设置mysql的安装目录
    basedir=/usr/local/mysqldb/mysql
    # 设置mysql数据库的数据的存放目录
    datadir=/usr/local/mysqldb/data
    # 允许最大连接数
    max_connections=200
    # 服务端使用的字符集默认为8比特编码的latin1字符集
    character-set-server=utf8
    # 创建新表时将使用的默认存储引擎
    default-storage-engine=INNODB
    lower_case_table_names=1
    max_allowed_packet=16M
    
    #mysql 5.6占用内存400多兆,太大了,配置优化
    performance_schema_max_table_instances=600
    table_definition_cache=400
    table_open_cache=256
    

    创建上面用到的文件夹

    [root@192 bin]# mkdir -p /var/lib/mysql&&chmod 777  /var/lib/mysql
    
  9. mysqld

    [root@192 bin]# mv /usr/local/mysqldb/mysql/support-files/mysql.server  /etc/init.d/mysqld
    

    增加可执行权限

    [root@192 bin]# chmod +x /etc/init.d/mysqld
    
  10. 配置/etc/init.d/mysqld文件,修改内容如下:

    vim /etc/init.d/mysqld
    
    basedir=/usr/local/mysqldb/mysql
    datadir=/usr/local/mysqldb/data
    
  11. 配置mysql开机自动启动(可选:不想开机启动可以不执行)

    chkconfig --add mysqld
    chkconfig --list mysqld
    chkconfig --level 35 mysqld on
    
  12. 启动mysql

    [root@192 bin]# systemctl start mysqld
    
  13. 登录

    [root@192 bin]# ln -s /usr/local/mysqldb/mysql/bin/mysql /usr/bin
    
    [root@192 bin]# mysql -uroot -p
    

    输入临时密码

    06181055822

  14. 重置默认密码

    mysql> set password = password('itcast');
    
  15. 后续修改密码

    -- 改root的默认生成密码
    alter user 'root'@'localhost' identified  by '123456';
    flush privileges;
    -- 修改用户密码的方式
    -- 方式一
    SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword');
    flush privileges;
    -- 方式二:5.7版本没有password这个字段了,改成了authentication_string
    update user set authentication_string=password('123') where user='root';
    flush privileges;
    -- 方式三
    alter user 'root'@'%' IDENTIFIED BY '123456';
    flush privileges;
    -- 创建新用户格式 host可以是localhost、%、具体的ip
    CREATE USER 'username'@'host' IDENTIFIED BY 'password';
    -- 创建新用户举例:
    create user 'aaa'@'%' IDENTIFIED BY '123';
    -- 对用户的操作命令其实是操作的mysql.user表
    flush privileges;
    
  16. 授权操作

    06183301776

    查看用户信息,HOST为localhost,root用户只能本机登录

    -- 授权语法 privileges可以是select、INSERT,UPDATE等,如果要授予所的权限则使用ALL
    -- 所有数据库的所有表: *.* 
    -- []代表此用户可以再给其他用户授权
    GRANT privileges ON databasename.tablename TO 'username'@'host' [WITH GRANT OPTION];
    flush privileges; -- 授权后需要刷新
    -- 举例: 会新增一条记录
    grant all on *.* to 'root'@'%';
    flush privileges; -- 授权后需要刷新
    

    或者

  17. 开放3306端口

    sudo firewall-cmd --add-port=3306/tcp --permanent
    

    防火墙重启

    firewall-cmd --reload
    

    查看开放的端口号是否存在3306

    firewall-cmd --list-all
    
  18. 连接

  19. 重启

    [root@localhost conf]# cd /usr/local/mysqldb/mysql/bin
    [root@localhost bin]# systemctl start mysqld
    
20.5.3.2.Linux配置Nacos集群
  1. 下载

    https://github.com/alibaba/nacos/releases/tag/1.1.4

    05223445276

  2. 解压

    tar -zxvf nacos-server-1.1.4.tar.gz
    
  3. 切换root,将解压后的文件转义到opt目录

    [root@localhost Downloads]# cp -r nacos /mynacos/
    
  4. 进入bin

  5. 修改startup.sh

  6. startup.sh修改内容

  7. 复制出cluster.conf

    修改内容

    这个IP不能写127.0.0.1,必须是Linux命令hostname -i能够识别的IP

  8. startup.sh修改完成后集群启动

    ./startup.sh -p 3333
    
    ps -ef|grep nacos |grep -v grep|wc -l
    
  9. 关闭nacos集群

20.5.3.3.Linux配置Nginx

安装Nginx必须具备的组件

  1. 安装 pcre 依赖

    1. 联网下载 pcre 压缩文件依赖

      wget http://downloads.sourceforge.net/project/pcre/pcre/8.37/pcre-8.37.tar.gz
      
    2. 解压该文件

      tar -zxvf pcre-8.37.tar.gz
      
    3. 将解压文件复制到/usr/local/pcre目录下

      mkdir -p /usr/local/pcre && mv pcre-8.37 /usr/local/pcre
      
    4. 进入/usr/local/pcre/pcre-8.37/

      cd /usr/local/pcre/pcre-8.37/
      
    5. 执行 ./configure编译

       ./configure
      

      执行./configure后如果出现

      需要执行

      yum install -y gcc gcc-c++
      

      再次执行

       ./configure
      

      ./configure执行正常

    6. 安装

      make && make install
      
    7. 校验是否安装成功,查看版本

      pcre-config --version
      
  2. 安装 openssl 、zlib 、 gcc 依赖

    yum -y install make zlib zlib-devel gcc-c++ libtool openssl openssl-devel
    
  3. 安装Nginx

    1. 下载Nginxhttp://nginx.org/

    2. 下载成功

    3. 解压Nginx

      tar -zxvf nginx-1.12.2.tar.gz
      
    4. 将nginx移动到指定文件夹

      此处最好创建/usr/local/mynginx文件夹,因此安装成功会创建/usr/local/nginx文件夹

      mkdir -p /usr/local/nginx && mv nginx-1.12.2 /usr/local/nginx
      
    5. 切换到nginx目录

      cd /usr/local/nginx/nginx-1.12.2
      
    6. 检查

      ./configure 
      
    7. 编译安装

      make && make install
      
    8. 安装成功后在/usr/local多出一个文件nginx文件夹

      在里面sbin有启动脚本

    9. 启动

    10. 查看配置文件的监听端口

    11. 访问:

    12. 修改nginx的配置文件访问nacos集群

      修改内容

      07142618441
      upstream cluster{
          server 127.0.0.1:3333;
          server 127.0.0.1:4444;
          server 127.0.0.1:5555;
      }
      
      server {
          listen       1111;
          server_name  localhost;
          #charset koi8-r;
          #access_log  logs/host.access.log  main;
      location / {
          #root   html;
          #index  index.html index.htm;
          proxy_pass http://cluster;
      }    
      
    13. 根据配置启动nginx

      07143859578

    14. 开放1111端口

    15. Linux关于防火墙端口管理

      • 查看防火墙状态

        systemctl status firewalld
        
      • 开启防火墙

        systemctl start firewalld
        
      • 关闭防火墙

        systemctl stop firewalld
        
      • 重启防火墙

        firewall-cmd --reload
        
      • 查看开放的端口号

        firewall-cmd --list-all
        
      • 设置开放端口

        sudo firewall-cmd --add-port=80/tcp --permanent
        

        增加开放端口后要重启防火墙

    16. 关闭nginx

      ./nginx -s stop
      

20.5.4.Linux集群配置步骤(重点)

20.5.4.1.Linux服务器上mysql数据库配置

执行sql脚本

20.5.4.2.application.properties 配置
nacos.istio.mcp.server.enabled=false
############################################
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=UTC
db.user=root
db.password=123456
20.5.4.3.Linux服务器上nacos的集群配置cluster.conf

梳理出3台nacos集器的不同服务端口号

  1. 复制出cluster.conf

  2. 修改内容

    这个IP不能写127.0.0.1,必须是Linux命令hostname -i能够识别的IP

20.5.4.4.编辑Nacos的启动脚本startup.sh

使它能够接受不同的启动端口

思考

/mynacos/nacos/bin 目录下有startup.sh

平时单机版的启动,都是./startup.sh即可。

但是集群启动,我们希望可以类似其它软件的shell命令,传递不同的端口号启动不同的nacos实例。
命令:./startup.sh -p 3333 表示启动端口号为3333的nacos服务器实例,和上一步的cluster.conf配置的一致。

修改内容

20.5.4.5.Nginx的配置,由它作为负载均衡器
  1. 修改nginx的配置文件

  2. nginx.conf

    修改位置

    upstream cluster{
        server 127.0.0.1:3333;
        server 127.0.0.1:4444;
        server 127.0.0.1:5555;
    }
    
    server {
        listen       1111;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
    location / {
        #root   html;
        #index  index.html index.htm;
        proxy_pass http://cluster;
    }    
    
20.5.4.6.启动Nginx以及nacos集群

截止到此处,1个Nginx+3个nacos注册中心+1个mysql已经准备就绪

  1. 集群启动nacos

  2. 启动nginx

  3. 开启1111端口

20.5.4.7.访问nacos集群注册中心

发布新配置

20.5.4.8.测试注册到nacos集群注册中心

微服务cloudalibaba-provider-payment9002启动注册进nacos集群

启动

20.5.4.9.关闭集群
  1. 关闭nginx

    ./nginx -s stop
    
  2. 关闭nacos集群

20.5.4.10.高可用小总结

21.SpringCloud Alibaba Sentinel实现熔断与限流

21.1.Sentinel

Sentinel作为一个组件,可以独立出来。可以直接界面化的细粒度统一配置管理。

21.1.1官网

  1. GitHub

    https://github.com/alibaba/Sentinel

  2. 中文文档

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

21.1.2.是什么

A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)

一句话解释,之前我们讲解过的Hystrix

21.1.3.去哪下

https://github.com/alibaba/Sentinel/releases

21.1.4.能干嘛

Sentinel 具有以下特征:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
  • 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

21.1.5.怎么玩

https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel

服务使用中的各种问题

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

21.2.安装Sentinel控制台

21.2.1.sentinel组件由2部分构成

Sentinel 分为两个部分:

  • 核心库(Java客户端)不依赖任何框架/库,能够运行于所有Java运行时环境,同时对Dubbo /Spring Cloud等框架也有较好的支持。
  • 控制台(Dashboard)基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。

21.2.2.安装步骤

21.2.2.1.下载

https://github.com/alibaba/Sentinel/releases

下载到本地sentinel-dashboard-1.7.0.jar

21.2.2.2.运行命令

前提: java8环境、8080端口不能被占用

java -jar sentinel-dashboard-1.7.0.jar
21.2.2.3.访问sentinel管理界面

http://localhost:8080

登录账号密码均为sentinel

21.3.初始化演示工程

21.3.1.启动Nacos8848成功

http://localhost:8848/nacos/#/login

21.3.2.新建Module

新建的cloudalibaba-sentinel-service8401项目注册到Nacos8848,并且被Sentinel8080保护。

21.3.2.1.新建cloudalibaba-sentinel-service8401
21.3.2.2.pom.xml

主要是:spring-cloud-starter-alibaba-sentinel

<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    <!--SpringCloud ailibaba 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>
    <!-- SpringBoot整合Web组件+actuator -->
    <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>
    <!--日常通用jar包配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>4.6.3</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
21.3.2.3.application.yaml
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址,即8080会监控8401
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
management:
  endpoints:
    web:
      exposure:
        include: '*'
21.3.2.4.主启动类
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class, args);
    }
}
21.3.2.5.业务类FlowLimitController
@Controller
public class FlowLimitController {
    @RequestMapping("/testA")
    @ResponseBody
    public String testA() {
        return "------testA";
    }

    @RequestMapping("/testB")
    @ResponseBody
    public String testB() {
        return "------testB";
    }
}
21.3.3.启动Sentinel8080
java -jar sentinel-dashboard-1.7.0.jar

21.3.4.启动微服务8401

21.3.4.1.nacos注册中心
21.3.4.2.sentienl控制台
  1. 直接访问sentinel时,没有任何显示

    Sentinel采用的懒加载说明,执行一次访问8401即可http://localhost:8401/testA

  2. 访问8401后继续访问

21.4.流控规则

21.4.1.基本介绍

簇点链路会显示所有的访问的路径

流控规则

Sentinel名词解释

  1. 资源名:唯一名称,默认请求路径

  2. 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制)

  3. 阈值类型/单机阈值:

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

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

  4. 是否集群:不需要集群

  5. 流控模式:

    1. 直接:api达到限流条件时,直接限流
    2. 关联:当关联的资源达到阈值时,就限流自己
    3. 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就可以限流)[api级别的针对来源]
  6. 流控来源

    1. 快速失败:直接失败,抛异常
    2. Warm Up:默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
    3. 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。

21.4.2.流控模式

流控模式是只设置流控时的具体模式

资源名的解释:目前的流控规则首先拿请求地址(@RequestMapping映射)进行控制,即为唯一名称,资源名,默认请求路径。

21.4.2.1.直接模式1(默认)-OPS

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

  1. 现象:直接->快速失败

  2. 配置及说明

    表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误

  3. 测试

    快速点击访问http://localhost:8401/testA

    结果:Blocked by Sentinel (flow limiting)

  4. 思考

    直接调用默认报错信息,技术方面OK。but,是否应该有我们自己的后续处理?类似有个fallback的兜底方法?

21.4.2.2.直接模式2-线程数

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

当线程数设置为2时,表示现在只有两个线程对该路径的请求进行处理,如果发起的请求时,两个线程正在处理之前的请求,则会返回Blocked by Sentinel (flow limiting)

21.4.2.3.关联模式
  1. 基本概念:当关联的资源达到阈值时,就限流自己。即当与A关联的资源B达到阀值后,就限流A自己。比如支付接口达到阈值,就限流下订单的接口。

  2. 设置效果

    当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名

  3. postman模拟并发密集访问testB

    大批量线程高并发访问B,导致A失效了

21.4.2.4.链路模式

21.4.3.流控效果

21.4.3.1.直接->快速失败(默认的流控处理)

直接失败,抛出异常:Blocked by Sentinel (flow limiting)

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

21.4.3.2.预热
  1. 说明

    默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。

    案例,阀值为10+预热时长设置5秒。
    系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10

  2. 官网

    https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

    默认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

  3. 源码

    com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

  4. WarmUp配置

    默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。案例,阀值为10+预热时长设置5秒。

    系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10

  5. 测试

    多次点击http://localhost:8401/testB,刚开始不行,后续慢慢OK

  6. 应用场景

    如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。

21.4.3.3.排队等待

匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。

设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。

  1. 官网

    https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

  2. com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

  3. 配置

  4. 测试

  5. 结果

21.5.降级规则

21.5.1.官网

https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7

21.5.2.基本介绍

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,
让请求快速失败,避免影响到其它的资源而导致级联错误。

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

注意:Sentinel的断路器是没有半开状态的

半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix

RT(平均响应时间,秒级)

一秒钟的请求数大于或者等于5个时,如果所有请求的平均响应时间大于阈值。在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用

RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

异常比列(秒级)

QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

异常数(分钟级)

异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

21.5.3.RT-降级策略实战

21.5.3.1.基本概念

一秒钟的请求数大于或者等于5个时,如果所有请求的平均响应时间大于阈值。在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用

RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

21.5.3.2.配置测试
  1. 代码

    @Controller
    @Slf4j
    public class FlowLimitController {
        @RequestMapping("/testD")
        @ResponseBody
        public String testD() {
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("testD 测试RT");
            return "------testD";
        }
    }
    
  2. 配置

  3. jmeter压测

    开启Jmeter循环请求后,断路器开启

    10215647082

    关闭Jmeter循环请求后,断路器关闭

  4. 结论

    按照上述配置,

    永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,
    如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了

    后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复。

21.5.4.异常比例-降级策略实战

21.5.4.1.基本概念
21.5.4.2.配置测试
  1. 代码

    @Controller
    @Slf4j
    public class FlowLimitController {
    
        @RequestMapping("/testE")
        @ResponseBody
        public String testE() {
            System.out.println("testE 测试RT");
            int age = 10 / 0;
            return "------testE";
        }
    }
    
  2. 配置

  3. jmeter配置

    开启Jmeter循环请求后,断路器开启

    关闭Jmeter循环请求后,断路器关闭

  4. 结论

    按照上述配置,
    单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;

    开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。

21.5.5.异常数-降级策略实战

21.5.5.1.基本概念

时间窗口一定要大于等于60秒。

异常数是按照分钟统计的

21.5.5.2.配置测试
  1. 代码

    @Controller
    @Slf4j
    public class FlowLimitController {
        @RequestMapping("/testF")
        @ResponseBody
        public String testF() {
            System.out.println("testF 测试RT");
            int age = 10 / 0;
            return "------testF";
        }
    }
    
  2. 配置

  3. 浏览器访问http://localhost:8401/testF五次

    访问五次后一分钟的异常数目达到5次,再次访问时,Sentinel的断路器打开。

21.6.热点key限流

21.6.1.基本介绍

热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作。

21.6.2.官网

https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。

比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

21.6.3.对比Hystrix

兜底方法

分为系统默认方法用户自定义义方法两种

之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?

结论
从Hystrix的@Command 到@SentinelResource

21.6.4.配置限流处理方法

默认:com.alibaba.csp.sentinel.slots.block.BlockException

sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

@Controller
@Slf4j
public class FlowLimitController {
   
    @RequestMapping("/testHotKey")
    @ResponseBody
    @SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
    public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                             @RequestParam(value = "p2", required = false) String p2) {
        return "------testHotKey";
    }

    public String dealHandler_testHotKey(String p1, String p2, BlockException exception) {
        return "-----dealHandler_testHotKey";
    }

}

@SentinelResource注解标签类似于Hystrix的@Command。value是资源名称;blockHandler是如果触发Sentinel的限流规则就会触发的取代testHotKey的兜底函数。

21.6.5.配置热点限流规则

21.6.5.1.配置说明1

配置内容

@RequestMapping("/testHotKey")
@ResponseBody
@SentinelResource(value = "testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                         @RequestParam(value = "p2", required = false) String p2) {
    return "------testHotKey";
}

异常打到了前台用户界面看到,不友好。

21.6.5.2.配置说明2
@RequestMapping("/testHotKey")
@ResponseBody
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                         @RequestParam(value = "p2", required = false) String p2) {
    return "------testHotKey";
}

方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理。调用自定义的降级处理方法dealHandler_testHotKey

21.6.6.测试

正常访问,低于配置的降级规则:(携带了对应函数中的第一个参数,且在一秒访问次数超过1次就会触发)

触发降级规则调用降级处理函数:

21.6.7.参数例外项

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流

21.6.7.1.新增要求
11212934283
21.6.7.2.配置及其说明

热点参数的注意点,参数必须是基本类型

资源名为testHotKey请求,索引序号为0的参数,参数值为5时,限流阈值为10,当不为5时,限流阈值为1。

添加按钮不能忘记

21.6.7.3.测试
11213131582

21.6.8.@SentinelResource不处理程序异常

@SentinelResource处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

RuntimeException,int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管

总结:@SentinelResource主管配置出错,运行出错该走异常走异常

21.7.系统规则

https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81

21.7.1.基础概念

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

21.7.2.各项配置参数说明

21.8.@SentinelResource

21.8.1.按资源名称限流+后续处理

21.8.1.1.启动Nacos、Sentinel

http://localhost:8848/nacos/#/login

java -jar sentinel-dashboard-1.7.0.jar
21.8.1.2.修改Module cloudalibaba-sentinel-service8401
  1. pom.xml

    新增引入cloud-api-commons

    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.stonebridge.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
  2. application.yaml不变

  3. 业务类

    @Controller
    public class RateLimitController {
        @RequestMapping("/byResource")
        @ResponseBody
        @SentinelResource(value = "byResource", blockHandler = "handleException")
        public CommonResult byResource() {
            return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, "serial001"));
        }
    
        public CommonResult handleException(BlockException exception) {
            return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用");
        }
    }
    
21.8.1.3.配置流控规则
  1. 配置

  2. 图形配置和代码关系

    表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流

21.8.1.4.测试
  1. 1秒钟点击1下

  2. 超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生

21.8.1.5.额外问题

此时关闭问服务8401,Sentinel控制台,流控规则消失。

21.8.2.按照Url地址限流+后续处理

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息

21.8.2.1.业务类RateLimitController
@Controller
public class RateLimitController {
    @RequestMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")
    public CommonResult byUrl() {
        return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
    }
}
21.8.2.2.Sentinel控制台配置
21.8.2.3.测试

频繁点击http://localhost:8401/rateLimit/byUrl

会返回Sentinel自带的限流处理结果

21.8.3.上面兜底方案面临的问题

  1. 系统默认的,没有体现我们自己的业务要求。
  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  3. 每个业务方法都添加一个兜底的,那代码膨胀加剧。
  4. 全局统一的处理方法没有体现。

21.8.4.用户自定义限流处理逻辑

21.8.4.1.创建CustomerBlockHandler类自定义限流处理类

定义的方法必须在是public static修饰

public class CustomerBlockHandler {
    public static CommonResult handleException1(BlockException exception) {
        return new CommonResult(1234, "自定义的限流处理信息......CustomerBlockHandler");
    }

    public static CommonResult handleException2(BlockException exception) {
        return new CommonResult(4567, "自定义的限流处理信息......CustomerBlockHandler");
    }
}
21.8.4.2.RateLimitController
@Controller
public class RateLimitController {
    @RequestMapping("/rateLimit/TestBlockHandler")
    @ResponseBody
    @SentinelResource(value = "TestBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
    public CommonResult TestBlockHandler() {
        return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
    }
}
21.8.4.3.启动微服务后先调用一次

http://localhost:8401/rateLimit/customerBlockHandler

21.8.4.4.Sentinel控制台配置
21.8.4.5.测试
  1. 正常访问

  2. 触发限流

21.8.4.6.说明

21.8.5.更多注解属性说明

21.9.服务熔断功能

sentinel整合ribbon+openFeign+fallback

21.9.1.测试环境:创建服务提供者

创建项目cloudalibaba-provider-payment9003/9004

21.9.1.1.新建项目cloudalibaba-provider-payment9003
21.9.1.2.pom.xml
<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <groupId>com.atguigu.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <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>
    <!--日常通用jar包配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
21.9.1.3.application.yaml
server:
  port: 9002

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        # 换成nginx的1111端口,做集群
        server-addr: 192.168.174.148:1111

management:
  endpoints:
    web:
      exposure:
        include: '*'
21.9.1.4.主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9003.class, args);
    }
}
21.9.1.5.业务类
@Controller
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    public static HashMap<Long, Payment> hashMap = new HashMap<>();

    static {
        hashMap.put(1L, new Payment(1L, "28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2L, new Payment(2L, "bba8c1e3bc2742d8848569891ac32182"));
        hashMap.put(3L, new Payment(3L, "6ua8c1e3bc2742d8848569891xt92183"));
    }

    @RequestMapping(value = "/paymentSQL/{id}")
    @ResponseBody
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult(200, "from mysql,serverPort:  " + serverPort, payment);
        return result;
    }
}
21.9.1.6.启动访问测试

http://localhost:9003/paymentSQL/1

21.9.1.7.创建项目cloudalibaba-provider-payment9004

参照cloudalibaba-provider-payment9003创建项目cloudalibaba-provider-payment9004

21.9.2.测试环境:创建服务消费者

21.9.2.1.新建cloudalibaba-consumer-nacos-order84
21.9.2.2.pom.xml文件
<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.stonebridge.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <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>
    <!--日常通用jar包配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
21.9.2.3.application.yaml
server:
  port: 84
spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider
21.9.2.4.主启动类
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84 {
    public static void main(String[] args) {
        SpringApplication.run(OrderNacosMain84.class, args);
    }
}
21.9.2.5.配置类
@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
21.9.2.6.Controller
@Controller
@Slf4j
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://nacos-payment-provider";
    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @ResponseBody
    @SentinelResource(value = "fallback")
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }
        return result;
    }
}
21.9.2.7.启动测试

访问:http://localhost:84/consumer/fallback/1

21.9.3.Ribbon系列

sentinel整合ribbon+openFeign+fallback

21.9.3.1.没有任何配置

访问:http://localhost:84/consumer/fallback/4

没有任何配置,给客户error页面,不友好

21.9.3.2.只配置fallback

fallback负责业务异常

  1. 代码实现

    @RequestMapping("/consumer/fallback/{id}")
    @ResponseBody
    @SentinelResource(value = "fallback", fallback = "handlerFallback") //fallback负责业务异常
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }
        return result;
    }
    
    public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(444, "兜底异常handlerFallback,exception内容  " + e.getMessage(), payment);
    }
    
  2. 图解

  3. 测试

    1. 正常访问:http://localhost:84/consumer/fallback/2

    2. 非法参数异常http://localhost:84/consumer/fallback/4

    3. 空指针异常:http://localhost:84/consumer/fallback/7

21.9.3.3.只配置blockHandler
  1. 代码实现

    @RequestMapping("/consumer/fallback/{id}")
    @ResponseBody
    @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("非法参数异常....");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录");
        }
        return result;
    }
    
    public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException  " + blockException.getMessage(), payment);
    }
    
  2. 图解配置

  3. 配置限流

  4. 测试

    1. 访问:http://localhost:84/consumer/fallback/7两次,出现异常两次进入限流

    2. 再次访问:http://localhost:84/consumer/fallback/7

21.9.3.4.fallback和blockHandler都配置
  1. 代码实现

    @Controller
    @Slf4j
    public class CircleBreakerController {
        public static final String SERVICE_URL = "http://nacos-payment-provider";
    
        @Resource
        private RestTemplate restTemplate;
    
        @RequestMapping("/consumer/fallback/{id}")
        @ResponseBody
        @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
        public CommonResult<Payment> fallback(@PathVariable Long id) {
            CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
            if (id == 4) {
                throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
            } else if (result.getData() == null) {
                throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
            }
            return result;
        }
    
        //本例是fallback
        public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
            Payment payment = new Payment(id, "null");
            return new CommonResult<>(444, "兜底异常handlerFallback,exception内容  " + e.getMessage(), payment);
        }
    
        //本例是blockHandler
        public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
            Payment payment = new Payment(id, "null");
            return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException  " + blockException.getMessage(), payment);
        }
    }
    
  2. 配置图解

  3. 配置流控规则

  4. 访问测试

    1. 正常访问:http://localhost:84/consumer/fallback/1

    2. 频繁快速点击访问触发流控规则

      触发流控规则调用blockHandler配置的函数

    3. 访问会抛出异常的地址触发fallback

      抛出异常的地址触发fallback配置的函数

  5. 说明

    若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。

21.9.3.5.忽略属性exceptionsToIgnore
  1. 代码实现

    @RequestMapping("/consumer/fallback/{id}")
    @ResponseBody
    @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }
        return result;
    }
    
  2. 配置图解

  3. 测试

    访问会报IllegalArgumentException的地址:http://localhost:84/consumer/fallback/4

21.9.4.Feign系列

21.9.4.1.创建Feign的服务消费服务
  1. 创建项目cloudalibaba-consumer-nacos-order85

  2. pom.xml

    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--SpringCloud openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.stonebridge.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <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>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
  3. application.yaml

    激活Sentinel对Feign的支持

    server:
      port: 85
    spring:
      application:
        name: nacos-order-consumer
      cloud:
        nacos:
          discovery:
            #Nacos服务注册中心地址
            server-addr: localhost:8848
        sentinel:
          transport:
            #配置Sentinel dashboard地址
            dashboard: localhost:8080
            #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
            port: 8719
    
    management:
      endpoints:
        web:
          exposure:
            include: '*'
    # 激活Sentinel对Feign的支持
    feign:
      sentinel:
        enabled: true  
    
  4. 主启动类

    添加@EnableFeignClients启动Feign的功能

    @EnableDiscoveryClient
    @SpringBootApplication
    @EnableFeignClients
    public class OrderNacosMain85 {
        public static void main(String[] args) {
            SpringApplication.run(OrderNacosMain85.class, args);
        }
    }
    
  5. 业务类

    1. controller

      @Controller
      @Slf4j
      public class CircleBreakerController {
          @Resource
          private PaymentService paymentService;
      
          @RequestMapping(value = "/consumer/openfeign/{id}")
          @ResponseBody
          public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
              if (id == 4) {
                  throw new RuntimeException("没有该id");
              }
              return paymentService.paymentSQL(id);
          }
      }
      
    2. service

      • PaymentService

        @FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
        public interface PaymentService {
            @RequestMapping(value = "/paymentSQL/{id}")
            CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
        }
        
      • PaymentFallbackService

        PaymentService对应的异常处理处理方法

        @Component
        public class PaymentFallbackService implements PaymentService {
            @Override
            public CommonResult<Payment> paymentSQL(Long id) {
                return new CommonResult<>(444, "服务降级返回,没有该流水信息", new Payment(id, "errorSerial......"));
            }
        }
        
  6. 测试

    启动的项目消费侧微服务(端口85),服务提供微服务(端口9004)

    访问:http://localhost:85/consumer/openfeign/1

21.9.4.2.测试消费侧自动降级

因为启动的项目只有消费侧端口为85,服务侧服务提供者9004。此时关闭服务侧服务提供者,触发消费侧降级。

21.9.5.熔断框架比较

21.10.规则持久化

21.10.1.是什么

一旦我们重启应用,在sentinel为其接口创建的流控规则,生产环境需要将配置规则进行持久化。

21.10.2.怎么用

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。

21.10.3.具体实现步骤

修改cloudalibaba-sentinel-service8401

21.10.3.1.pom.xml新增引入
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
21.10.3.2.application.yaml

添加Nacos数据源配置

spring:
  cloud:
    sentinel:
      transport:
        datasource:
          ds1:
            nacos:
              server-addr: localhost:8848
              dataId: cloudalibaba-sentinel-service
              groupId: DEFAULT_GROUP
              data-type: json
              rule-type: flow
21.10.3.3.添加Nacos业务规则配置
[
    {
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。
21.10.3.4.启动8401后刷新sentinel发现业务规则有了
21.10.3.5.快速访问测试接口

http://localhost:8401/rateLimit/byUrl

出现限流

21.10.3.6.停止8401再看sentinel
21.10.3.7.重新启动8401再看sentinel

多次调用:http://localhost:8401/rateLimit/byUrl

重新配置出现了,持久化验证通过

22.SpringCloud Alibaba Seata处理分布式事务

22.1.分布式事务问题

分布式前:单机单库没这个问题

我们的系统从1:1 (单机单库) 到 1:N(多台数据库) 到现在的 N:N多系统多数据库的

分布式之后

单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。

22.2.Seata简介

22.2.1.是什么

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

官网地址:http://seata.io/zh-cn/

22.2.2.功能

一个典型的分布式事务过程

2.2.2.1.分布式事务处理的三组件模型

分布式事务处理过程的一ID+三组件模型

  • Transaction ID XID

    全局唯一的事务ID。

    比如一次事务的开始,比如一次操作涉及三个数据库,三个数据源。每一次事务的所有操作都有全局唯一的相同的事务id

  • 3组件概念

    1. Transaction Coordinator (TC)

      事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;

    2. Transaction Manager ™

      控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;

    3. Resource Manager (RM)

      控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚

2.2.2.2.处理过程
  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
  2. XID 在微服务调用链路的上下文中传播;
  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  4. TM 向 TC 发起针对 XID 的全局提交或回滚决议;
  5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

直白解释:

一个班60个同学,将Microservices想象成分为20人一组,一共三组。TC是授课老师,TM是班主任。

首先TM(班主任)向TC(授课老师)申请开启一个全局事务(新的课程),XID就是新的课程的微信群号,TM(班主任)将XID(课程的微信群号)上下传播。

所有的同学要向TC(授课老师)注册分支事务,将其纳入全局事务管理。

TM(班主任)向TC(授课老师)发起全局提交或者回滚事务。

上课完毕TC(授课老师)在XID(课程的微信群号)下管辖的全部分支事务完成提交或回滚请求。

22.2.3.下载

发布说明: https://github.com/seata/seata/releases

22.2.4.怎么用

本地@Transactional

全局@GlobalTransactional

SEATA 的分布式交易解决方案

22.3.Seata-Server安装

22.3.1.官网地址

http://seata.io/zh-cn/

22.3.2.下载版本

https://github.com/seata/seata/releases

22.3.3.配置文件

seata-server-1.4.0.zip解压到指定目录并修改conf目录下的file.conf配置文件

  1. 先备份原始file.conf文件

  2. 修改file.conf

    主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息

22.4.订单/库存/账户业务数据库准备

22.5.订单/库存/账户业务微服务准备

22.5.1.业务需求

下订单->减库存->扣余额->改(订单)状态

22.5.2.新建订单Order-Module

18210535185

18212732161

18214124763

<strong></strong>
<font color=red><strong></strong></font>
<font color=red> </font>

rMode": false
}
]

resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。


#### 21.10.3.4.启动8401后刷新sentinel发现业务规则有了

<img src="https://i-blog.csdnimg.cn/blog_migrate/e3db142778aae91d06568c94eedcd8bd.png"   style="zoom:80%;" />

#### 21.10.3.5.快速访问测试接口

http://localhost:8401/rateLimit/byUrl

出现限流

<img src="https://i-blog.csdnimg.cn/blog_migrate/cdef0aa223c1bec5c615d0c5d0211b91.png"  />



#### 21.10.3.6.停止8401再看sentinel

<img src="https://i-blog.csdnimg.cn/blog_migrate/9e6156c206f463f0b160d5b37abc076e.png"  />

#### 21.10.3.7.重新启动8401再看sentinel

多次调用:http://localhost:8401/rateLimit/byUrl

重新配置出现了,持久化验证通过

# 22.SpringCloud Alibaba Seata处理分布式事务

## 22.1.分布式事务问题

分布式前:单机单库没这个问题

我们的系统从1:1 (单机单库) 到 1:N(多台数据库) 到现在的  N:N多系统多数据库的

分布式之后

单体应用被拆分成微服务应用,原来的三个模块被拆分成**三个独立的应用**,分别使用**三个独立的数据源**,业务操作需要调用三个服务来完成。此时<font color=red><strong>每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。</strong></font>

<img src="https://i-blog.csdnimg.cn/blog_migrate/0908a7305c3027af47889ccb64ba36f6.png" />

<font color=red><strong>一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。</strong></font>

## 22.2.Seata简介

### 22.2.1.是什么

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

官网地址:http://seata.io/zh-cn/

<img src="https://i-blog.csdnimg.cn/blog_migrate/cf2cbcdd4c4d076b6d4b2c67a5a3d4fa.png"  style="zoom:80%;" />

### 22.2.2.功能

一个典型的分布式事务过程

#### 2.2.2.1.分布式事务处理的三组件模型

分布式事务处理过程的一ID+三组件模型

-   Transaction ID XID

    全局唯一的事务ID。

    <font color=red><strong>比如一次事务的开始,比如一次操作涉及三个数据库,三个数据源。每一次事务的所有操作都有全局唯一的相同的事务id</strong></font>

-   3组件概念

    1.   Transaction Coordinator (TC)

         事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;

    2.   Transaction Manager (TM)

         控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;

    3.   Resource Manager (RM)

         控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚

#### 2.2.2.2.处理过程

1.   TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
2.   XID 在微服务调用链路的上下文中传播;
3.   RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
4.   TM 向 TC 发起针对 XID 的全局提交或回滚决议;
5.   TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

<img src="https://i-blog.csdnimg.cn/blog_migrate/220c916171208659646da3a003a55652.png"  style="zoom:80%;" />

直白解释:

一个班60个同学,将Microservices想象成分为20人一组,一共三组。TC是授课老师,TM是班主任。

首先TM(班主任)向TC(授课老师)申请开启一个全局事务(新的课程),XID就是新的课程的微信群号,TM(班主任)将XID(课程的微信群号)上下传播。

所有的同学要向TC(授课老师)注册分支事务,将其纳入全局事务管理。

TM(班主任)向TC(授课老师)发起全局提交或者回滚事务。

上课完毕TC(授课老师)在XID(课程的微信群号)下管辖的全部分支事务完成提交或回滚请求。

### 22.2.3.下载

发布说明: https://github.com/seata/seata/releases

### 22.2.4.怎么用

本地@Transactional

全局@GlobalTransactional

SEATA 的分布式交易解决方案

<img src="https://i-blog.csdnimg.cn/blog_migrate/ff5b50c15038fef174df2c5bb24f54ca.png"   />

## 22.3.Seata-Server安装

### 22.3.1.官网地址

http://seata.io/zh-cn/

### 22.3.2.下载版本

https://github.com/seata/seata/releases

### 22.3.3.配置文件

seata-server-1.4.0.zip解压到指定目录并修改conf目录下的file.conf配置文件

1.   先备份原始file.conf文件

2.   修改file.conf

     主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息

## 22.4.订单/库存/账户业务数据库准备

## 22.5.订单/库存/账户业务微服务准备

### 22.5.1.业务需求

下订单->减库存->扣余额->改(订单)状态

### 22.5.2.新建订单Order-Module

<img src="https://i-blog.csdnimg.cn/blog_migrate/e400e24c13ac07ac96f1a971a3d94d02.png" alt="18210535185" style="zoom:80%;" />



[外链图片转存中...(img-jQhXNFVr-1635212140008)]

[外链图片转存中...(img-C2jW3One-1635212140009)]







```html
<strong></strong>
<font color=red><strong></strong></font>
<font color=red> </font>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值