SpringCloud--Alibaba入门(Nacos+Sentinel)

上次说到Netflix已经过时了,目前除了一些大厂使用自己内部的微服务架构外,市面上使用最广泛的就是SpringCloudAlibaba,再说说后台架构的演进

  • 单体架构:整体打包为一个服务进行部署,简单,代码集中,不易于后期扩展和维护,不支持水平扩展
  • 垂体架构:将一个项目拆分成多个进行部署,项目与项目之间独立,但有冗余业务代码,如:都需要用户信息业务
  • 分布式架构: 使用RPC进行服务与服务之间的通信,服务治理需要自己手动管理,后期难以维护
  • SOA架构:由专门的服务治理中心管理服务与服务之间的调用关系,服务之间有依赖关系(分布式--使用Dubbo搭建分布式项目)导致服务关系也很复杂,共用一套数据库可能出现同步问题
  • 微服务架构:每个服务都是独立的个体,需要单独部署,强调每个服务都是独立的数据库,做到真正的解耦

SpringCloudAlibaba使用的组件如下:

组件 描述
Nacos 注册中心,除了提供服务治理、服务发现外,还提供服务配置
Sentinel 除了熔断策略,还包含流控规则、热点规则等
Ribbon 实现服务与服务之间调用的负载均衡
OpenFeign 服务与服务之间调用,集成了Ribbon
GateWay 应用内网关,提供对外统一入口
Seata 分布式事务组件

一、Nacos

微服务中最核心组件就是注册中心,Nacos作为服务注册中心,比Eureka多个配置中心的功能

Nacos官方文档:https://nacos.io/zh-cn/docs/quick-start.html

1. 安装Nacos

下载2.0.3稳定版本:https://github.com/alibaba/nacos/releases/tag/2.0.3,对应JDK版本需要1.8以上

下载解压后就可以启动Nacos了,来到bin目录下:

image.png

执行命令,standalone表示单机模式:

startup.cmd -m standalone

浏览器访问:http://localhost:8848/nacos

默认账号密码均为:nacos,登录后

2. 服务注册

SpringBoot项目中如果使用SpringCloudAlibaba,需要对应SpringBoot的版本:

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2021.0.1.0 Spring Cloud 2021.0.1 2.6.3
2.2.7.RELEASE Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2021.1 Spring Cloud 2020.0.1 2.4.2
2.2.6.RELEASE Spring Cloud Hoxton.SR9 2.3.2.RELEASE
2.1.4.RELEASE Spring Cloud Greenwich.SR6 2.1.13.RELEASE
2.2.1.RELEASE Spring Cloud Hoxton.SR3 2.2.5.RELEASE
2.2.0.RELEASE Spring Cloud Hoxton.RELEASE 2.2.X.RELEASE
2.1.2.RELEASE Spring Cloud Greenwich 2.1.X.RELEASE
2.0.4.RELEASE(停止维护,建议升级) Spring Cloud Finchley 2.0.X.RELEASE
1.5.1.RELEASE(停止维护,建议升级) Spring Cloud Edgware 1.5.X.RELEASE
2.1 建立项目

新建Maven聚合项目,并添加一个provider模块:

主工程pom文件:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <modules>
        <module>provider-8001</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring-cloud-alibaba-version>2.2.7.RELEASE</spring-cloud-alibaba-version>
    </properties>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.12.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.aruba</groupId>
    <artifactId>SpringCloudAlibabaStudy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencyManagement>
        <dependencies>
            <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>

</project>

provider模块pom文件:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloudAlibabaStudy</artifactId>
        <groupId>com.aruba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>provider-8001</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

</project>
2.2 配置文件

配置文件中配置nacos服务的地址:

spring:
  application:
    name: provider
  cloud:
    discovery:
      server-addr: 127.0.0.1:8848

server:
  port: 8001

management:
  endpoint:
    web:
      exposure:
        include:'*'

2.3 @EnableDiscoveryClient

使用@EnableDiscoveryClient注解启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class Provider8001Application {

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

}

启动后,nacos的控制台会出现我们启动的服务,服务名就是spring.application.name

2.4 测试controller

创建一个测试的处理单元:

@RestController
public class DemoController {

    @Value("${server.port}")
    private String port;

    @RequestMapping("/demo")
    public String demo() {
        return "demo " + port;
    }

}

2.5 集群

Nacos会自动按照spring.application.name来辨别是否是同一个服务,并加入到一个集群中,按照上面步骤再次创建一个provider模块,来模拟集群环境:

配置文件中只是修改了端口:

spring:
  application:
    name: provider
  cloud:
    discovery:
      server-addr: 127.0.0.1:8848

server:
  port: 8002

management:
  endpoint:
    web:
      exposure:
        include:'*'

启动后,控制台该服务的集群节点数目会变为2:

3. 服务消费

和Netflix一样,服务之间的调用是通过http的,并集成了Ribbon,自带负载均衡效果

3.1 创建消费者项目

依赖是相同的:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

配置文件中自定义一个调用服务的请求主地址:

spring:
  application:
    name: consumer
  cloud:
    discovery:
      server-addr: 127.0.0.1:8848

server:
  port: 9001

# 消费者将要去访问的微服务名称(注册成功的Nacos的微服务提供者)
service-url:
  nacos-user-service: http://provider

3.2 RestTemplate

RestTemplate就是一个http请求的工具,集成了Ribbon,把他注入到spring容器中,并使用@LoadBalanced注解:

@Configuration
public class RestConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}
3.3 controller层

通过RestTemplate调用服务,方法为:getForObject(请求地址,返回类型, 请求参数...)

@RestController
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    @Value("${service-url.nacos-user-service}")
    private String serverURL;

    @RequestMapping("/consume")
    public String consume() {
        return restTemplate.getForObject(serverURL + "/demo", String.class);
    }

}
3.4 启动

启动类依然使用@EnableDiscoveryClient注解:

@SpringBootApplication
@EnableDiscoveryClient
public class Consumer9001Application {

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

}

访问接口:

4. 配置中心

Nacos的特有功能就是配置中心,它能够做到动态的改变配置,而不需要重启服务

4.1 创建项目

创建一个config模块:

依赖:

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

application.yml中配置spring.profiles.active

spring:
  profiles:
    active: dev # 表示开发环境
4.2 bootstrap.yml

创建bootstrap.yml配置文件,该配置文件优先加载于application.yml

使用file-extension指定支持的Nacos配置文件类型:

server:
  port: 8100

spring:
  application:
    name: config
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml # 支持的配置文件类型,目前只有yaml和properties

4.3 Nacos中新建配置

使用Nacos控制台,创建一个配置:

填写DataID,规则为:spring.application.name-spring.profiles.active.file-extension
所以我们这边为:config-dev.yaml

选择配置格式为yaml,并填写配置的内容:

4.4 @RefreshScope

写一个测试controller,使用@RefreshScope注解Class并使用@value注解与上面Nacos配置文件内容对应:

@RestController
@RefreshScope
public class ConfigController {

    @Value("${config.info}")
    private String info;

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

}

启动服务后,进行访问:

修改配置,重新请求,经测试后也生效

4.5 Namespace和Group

上面我们使用active标识不同环境的文件,DataID对应一个配置文件,Nacos还支持Namespace和Group,分组级别为:Namespace>Group>DataID。

控制台上可以创建Namespace:

项目中使用namespace进行配置,对应创建的命名空间id:

spring:
  application:
    name: config
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml # 支持的配置文件类型,目前只有yaml和properties
        namespace: 921785db-0999-4f7b-b1c1-7511f88ef320

Group可以在新建配置时指定一个新的:

项目中使用group进行配置:

spring:
  application:
    name: config
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml # 支持的配置文件类型,目前只有yaml和properties
        namespace: my
        group: DEV_GROUP

5. 集群搭建

nacos本质上就是一个Jar,所以windows和linux使用的都一样,只是linux中启动脚本使用的是shell脚本

之前我们在nacos中进行的配置都会存到它自带的嵌入式数据库derby中,针对nacos集群,内置数据库显然已满足不了要求了,接下来搭建的nacos集群,我们将使用MySQL数据库,使得多台nacos节点可以访问同一个数据源,要知道MySQL也支持集群,可以有效提升实际项目中的可用性

注:nacos目前只支持MySQL

5.1 安装

将nacos压缩包传到linux系统中

执行解压命令,并移动到/usr/local/目录:

unzip nacos-server-2.0.3.zip
mv ./nacos /usr/local/
5.2 创建数据库

在解压后的conf目录下,有一个nacos-mysql.sql文件

将内容复制后,创建一个数据库,我这边数据库名就为nacos,执行sql语句:

复制的sql内容为:

/*
 * 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');

5.3 配置数据库

有了数据库,接着在nacos配置文件application.properties中修改数据源:

cd /usr/local/nacos/conf
vi application.properties

修改内容为:

方便复制:

### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://192.168.42.170:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
5.4 配置集群

这边一会通过复制二份nacos,模拟一个集群环境

在当前目录下,将集群的配置文件cluster.conf.example进行复制,并编辑:

cp cluster.conf.example cluster.conf
vi cluster.conf

文件末尾添加集群节点,只是端口不同:

192.168.42.4:8848
192.168.42.4:8858
192.168.42.4:8868

复制二份nacos:

cp -r nacos/ nacos2/
cp -r nacos/ nacos3/

进入复制后的配置目录,修改配置文件application.properties的对应端口号:

改为88588868后,分别启动nacos,此时不用加上指定单机版了:

cd /usr/local/nacos/bin
./startup.sh

执行后访问一个节点的管理界面,如下,就说明启动成功了:

5.5 nginx代理集群

使用nginx作为nacos统一的集群出入口,nginx入门可以看:分布式--Nginx入门

修改nginx配置,添加一个stream块指令:

stream {
        upstream cls {
                server 192.168.42.4:8848;
                server 192.168.42.4:8858;
                server 192.168.42.4:8868;
        }

        server {
                listen 81;
                proxy_pass cls;
        }
}

启动后,通过81端口进行访问:

想要在项目中使用,SpringBoot配置文件中使用nginx的地址和端口即可:

spring:
  application:
    name: consumer
  cloud:
    discovery:
      server-addr: 192.168.42.4:81

新建一个配置,数据库中也成功存入了:

二、Sentinel

Sentinel的核心思想是对资源的细粒度控制,对比hystrix,除了熔断策略外,还能针对处理单元的流量进行控制,Sentinel拥有自己的控制台,在上面可以进行各种资源配置

Sentinel控制台下载:https://github.com/alibaba/Sentinel/releases,下载后直接运行Jar即可

默认使用8080端口,账号密码均为:sentinel

后面我们会在该控制台上进行一系列配置

1. 服务提供者使用Sentinel

创建一个项目作为服务提供者

1.1 依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
1.2 yml中配置Sentinel

除了nacos的配置外,还需要进行sentinel的配置:

spring:
  application:
    name: sentinel-provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        # 配置Sentinel dashboard地址
        dashboard: localhost:8080
        # 默认8719端口,键入被占用会自动从8719+1,直到找到未被占用的端口
        port: 8719

server:
  port: 8003

management:
  endpoint:
    web:
      exposure:
        include:'*'
1.3 编写controller并启动

controller层:

@RestController
public class DemoController {

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

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

}

启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class SentinelProvider8003Application {

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

}

Sentinel采用的懒加载,对接口进行访问后,才会在控制台出现服务名:

2. 流控规则

配置流控规则,可以对资源的访问QPS(每秒请求量)并发量进行控制

可配置规则如下:

  • 资源名:唯一名称,默认请求路径
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型/单机阈值
    1.QPS(每秒钟的请求数量):当调用该API的QPS达到阈值的时候,进行限流
    2.并发线程数:当调用该API的线程数量达到阈值的时候,进行限流
  • 是否集群:当前不需要集群
  • 流控模式
    1.直接:API达到限流条件时,直接限流
    2.关联:当关联的资源达到阈值时,就限流自己
    3.链路:与关联相反,当自己达到阈值时,限流链路的资源
  • 流控效果:阈值类型为并发线程数,只有快速失败
    1.快速失败:直接失败,抛异常
    2.Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFacotor,经过预热时长,才达到设置的QPS阈值
    3.排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效

下面使用QPS来进行配置,并发线程数只是少了流控效果

2.1 流控模式:直接

效果:当每秒请求量达到阈值时,进行限流

配置如下:

尝试快速访问demo1接口:

2.2 流控模式:关联

效果:当关联的资源达到阈值时,就限流自己

配置如下:

postman中模拟请求demo2:

此时访问demo1接口,还是被限流了:

2.3 流控模式:链路与@SentinelResource

效果:链路与关联相反,在达到阈值时,资源本身不会限流,而是限流链路的资源

除了接口外,Sentinel还可以使用@SentinelResource注解标记一个资源:

@Component
public class ShareServiceImpl {

    @SentinelResource(value = "shared")
    public String shared() {
        return "shared";
    }

}

再定义两个接口,去访问上面的shared资源:

    @RequestMapping("/demo3")
    public String demo3() {
        return shareService.shared();
    }

    @RequestMapping("/demo4")
    public String demo4() {
        return shareService.shared();
    }

我们针对shared资源进行流控配置,模式为链路,并指定链路到/demo3

yml中配置web-context-unifyfalse

spring:
  application:
    name: sentinel-provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        # 配置Sentinel dashboard地址
        dashboard: localhost:8080
        # 默认8719端口,键入被占用会自动从8719+1,直到找到未被占用的端口
        port: 8719
      # 链路模式需要配置为false
      web-context-unify: false

重启服务后,快速访问demo4接口可以正常访问,快速访问demo3会出现异常:

2.4 流控效果:预热(Warm Up)

阈值类型为QPS的流控效果除了快速失败外,还有预热(Warm Up)排队等待

预热指的是QPS的最大阈值一开始并不是我们设定的值,而是从设定阈值/coldFactor(冷加载因子,默认3)开始,经过预热时长后达到设定阈值,目的是防止服务器突然接收到大量请求,导致崩溃
其效果为:刚开始大量的访问资源会被限流,后面到达设定阈值后,在阈值范围内访问不会被限流

快速访问效果:

2.5 流控效果:排队等待

排队等待是指每秒只处理设置阈值个请求,其他的进行排队等待,后续请求到达超时时间后进行限流

1秒1次请求,后续请求超时时间为3s,访问效果:

3. 熔断

Sentinel的熔断策略有三种:

  • 慢调用比例:请求到达最大RT(超时时间)没有返回结果,记为一次慢调用。在统计时长内,请求数达到最小请求数,并且慢调用次数达到最小请求数*比例阈值,则触发熔断
  • 异常比例:在统计时长内,请求数达到最小请求数,并且请求返回异常次数达到最小请求数*比例阈值,则触发熔断
  • 异常数:在统计时长内,请求数达到最小请求数,并且请求返回异常次数达到设定异常数,则触发熔断
3.1 慢调用比例

编写一个接口,睡眠1.5s后返回:

@RestController
public class Demo2Controller {

    @RequestMapping("/demo5")
    public String demo5() throws InterruptedException {
        Thread.sleep(1500);
        return "demo5";
    }

}

配置熔断策略,1s内请求数达到5,并慢调用达到比例阈值,触发熔断:

快速请求后,达到限流:

3.2 异常比例

编写一个接口,手动抛出异常:

    @RequestMapping("/demo6")
    public String demo6(Integer id) {
        if (id == null) {
            throw new RuntimeException("异常比例");
        }
        return "demo6";
    }

配置熔断策略:

快速访问,被限流:

剩下的异常数策略和异常比例使用上差不多

4. 热点规则

热点规则是更加细粒度的流控,我们在流控模式:链路中使用了@SentinelResource注解定义了一个资源,热点规则也需要和@SentinelResource注解配合使用

流控仅仅是对资源的阈值进行限制,而热点规则可以指定访问资源的参数阈值

定义一个接口,使用@SentinelResource注解:

    @RequestMapping("/demo7")
    @SentinelResource(value = "test_hotkey")
    public String demo7(@RequestParam(value = "hotkey", required = false) Integer hotkey) {
        return "demo7";
    }

为资源test_hotkey配置热点规则:

不带参数和带参数的访问效果:

4.1 参数例外项

更加细粒度的操作是指定参数具体的值,以及对应的阈值:

目前支持的数据类型如下:

5. @SentinelResource自定义异常处理

通过上面使用,我们知道了可以通过@SentinelResource自定义资源,但使用该注解时,一旦达到限流,接口返回的却是500,实际上,使用@SentinelResource注解还需要指定异常处理的属性

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
    String value() default "";

    EntryType entryType() default EntryType.OUT;

    int resourceType() default 0;

    // 限流处理的方法
    String blockHandler() default "";

    //限流处理的类
    Class<?>[] blockHandlerClass() default {};

    //异常处理的方法
    String fallback() default "";

    String defaultFallback() default "";

    Class<?>[] fallbackClass() default {};

    Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};

    Class<? extends Throwable>[] exceptionsToIgnore() default {};
}
5.1 blockHandler

blockHandler可以指定限流异常的处理方法,定义该方法后,达到限流就不会出现500错误了

限流异常的处理方法拥有和源资源方法相同的参数,以及多了一个BlockException的参数:

    @RequestMapping("/demo8")
    @SentinelResource(value = "demo8", blockHandler = "blockHandler")
    public String demo8(Integer id) {
        return "demo8";
    }

    public String blockHandler(Integer id, BlockException e) {
        return "您被限制访问" + id;
    }

demo8配置流控规则:

快速访问效果:

5.2 blockHandlerClass

除了在当前类中定义限流异常处理方法外,还可以指定一个类

public class MyBlockHandler {

    public static String myBlockHandler(Integer id, BlockException e) {
        return "您被限制访问" + id;
    }

}

请求接口:

    @RequestMapping("/demo9")
    @SentinelResource(value = "demo9",
            blockHandler = "myBlockHandler",
            blockHandlerClass = MyBlockHandler.class)
    public String demo9(Integer id) {
        return "demo9";
    }
5.3 fallback

block只是针对限流异常,其他异常除了使用SpringMVC的异常处理,也可以使用注解的fallback属性处理

异常处理方法除了和原资源方法参数相同外,还额外需要一个Throwable参数:

    @RequestMapping("/demo10")
    @SentinelResource(value = "demo10",
            fallback = "fallback")
    public String demo10(Integer id) {
        if (id == null) throw new NullPointerException("id 不能为空");
        return "demo10";
    }

    public String fallback(Integer id, Throwable e) {
        return "服务器异常 " + e.getMessage();
    }

访问效果:

6. 配置持久化

实操过的同学都会发现,每次服务重启后,之前的配置都消失了,你想的没错,Sentinel本身不带持久化功能,需要实现持久化,要和Nacos进行配合,交由Nacos进行持久化处理

添加依赖:

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.8.5</version>
        </dependency>

yml进行持久化配置,dataId还是使用之前的格式:

spring:
  application:
    name: sentinel-provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        # 配置Sentinel dashboard地址
        dashboard: localhost:8080
        # 默认8719端口,键入被占用会自动从8719+1,直到找到未被占用的端口
        port: 8719
      # 配置为false
      web-context-unify: false
      datasource: # sentinel持久化
        nacos:
          nacos:
            serverAddr: localhost:8848
            groupId: DEFAULT_GROUP
            dataId: sentinel-provider-dev.json #dataid格式还是nacos的格式
            ruleType: flow
  profiles:
    active: dev

使用Nacos配置中心的功能,在控制台创建配置:

内容为:

[   
    {
        "resource": "test1",
        "limitApp": "default",
        "grade": 1,
        "count": 2,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

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

访问接口后,查看Sentinel控制台,会出现对应的流控配置:

项目地址:

https://gitee.com/aruba/spring-cloud-alibaba-study.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容