Spring Cloud Alibaba笔记修订版-第三章Nacos Discovery--服务治理
一、什么是服务治理服务治理是微服务架构中最核心最基本的模块，用于实现各个微服务的自动化注册与发现。服务注册：在服务治理框架中，都会构建一个注册中心，每个服务单元向注册中心登记自己提供的服务的详细信息。并在注册中心形成一张服务清单，服务注册中心需要以心跳的方式去监测清单中的服务是否可用，若不可用，需要再服务清单中剔除不可用的服务。服务发现：服务调用方向服务注册中心咨询服务，保宁获取所有服务的实例清单，实现对具体服务实例的访问。通过上面的图会发现，除了微服务，还有一个组件是服务注册中心，它是微服务架构中非常重要的一个组件，在微服务架构里起到了一个协调者的作用。注册中心一般包含以下几个功能：服务发现服务注册：保存服务提供者和服务调用者信息服务订阅：服务调用者订阅服务提供者的信息，注册中心向订阅者推送提供者信息服务配置配置订阅：服务提供者和服务调用者订阅微服务相关配置配置下发：主动将配置推送给服务提供者和服务调用者服务健康检测检测服务提供者的健康状况，如果发现异常，执行服务剔除常见的服务注册中心包括：Zookeeper、Eureka、Consul、Nacos。Nacos是Spring Cloud Alibaba组件之一，负责服务注册发现和服务配置，因为我们使用Spring Cloud Alibaba，所以这里只介绍Nacos的使用。二、Nacos简介Nacos致力于帮助您发现、配置和管理微服务。Nacos提供了一组简单易用的特性及，帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。三、搭建Nacos环境注意使用Nacos之前，需要先配置好Java环境变量。我这里使用的服务器环境是Ubuntu 20.04，以下Nacos安装使用均以此为准，目前Nacos最新版本是2.1.2Nacos下载地址：Releases · alibaba/nacos (github.com)这里只介绍Nacos的基本使用，具体集群等高级用法，可以自行查找相关资料。3.1、下载Nacos下载nacos-server-2.1.2.tar.gz后，加压到任意位置。3.2、Nacos数据库文件运行Nacos之前，需要将Nacos数据库配置文件导入，我这里使用的是MySql，我直接导入上面Demo里面的数据库了。MySql的语句如下/* * 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, `encrypted_data_key` text NOT NULL COMMENT '秘钥', 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 '租户字段', `encrypted_data_key` text NOT NULL 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(20) 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 '租户字段', `encrypted_data_key` text NOT NULL 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');3.3、配置Nacos在conf文件夹下有一个application.properties，我们需要配置里面的数据库连接信息把大概34行往下的位置，取消注释并根据自己情况进行配置### If use MySQL as datasource: spring.datasource.platform=mysql ### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://127.0.0.1:3306/shop?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0=root db.password.0=root3.4、运行Nacos进入bin文件夹执行./startup.sh -m standaloneNacos启动后，浏览器输入localhost:8848/nacos默认用户名及密码都是nacos四、将商品微服务注册到Nacos我们改造商品微服务，以便支持Nacos4.1、修改配置文件增加Nacos依赖<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.4.0</version> </dependency>4.2、主类上注解@EnableDiscoveryClient@SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) @EnableDiscoveryClient public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); } }4.3、配置文件添加nacos服务的地址spring: cloud: nacos: discovery: server-addr: 192.168.236.2:8848修改后配置文件如下server: port: 8081 spring: cloud: nacos: discovery: server-addr: 192.168.236.2:8848 application: name: service-user datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.236.2/shop?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=true username: root password: root jpa: hibernate: #指定为update，每次启动项目检测表结构有变化的时候会新增字段，表不存在时会新建，如果指定create，则每次启动项目都会清空数据并删除表，再新建 ddl-auto: update naming: #指定jpa的自动表生成策略，驼峰自动映射为下划线格式 implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl # 默认false，在日志里显示执行的sql语句 show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect database: mysql database-platform: org.hibernate.dialect.MySQL5Dialect4.4、查看服务是否注册成功重新启动product微服务服务名就是我们配置文件配置的应用名称。五、将订单微服务注册到Nacos我们改造订单微服务，以便支持Nacos5.1、修改配置文件增加Nacos依赖<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.4.0</version> </dependency>5.2、主类上注解@EnableDiscoveryClient@SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) @EnableDiscoveryClient public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } } 5.3、配置文件添加nacos服务的地址spring: cloud: nacos: discovery: server-addr: 192.168.236.2:8848修改后配置文件如下server: port: 8091 spring: cloud: nacos: discovery: server-addr: 192.168.236.2:8848 application: name: service-user datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.236.2/shop?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=true username: root password: root jpa: hibernate: #指定为update，每次启动项目检测表结构有变化的时候会新增字段，表不存在时会新建，如果指定create，则每次启动项目都会清空数据并删除表，再新建 ddl-auto: update naming: #指定jpa的自动表生成策略，驼峰自动映射为下划线格式 implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl # 默认false，在日志里显示执行的sql语句 show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect database: mysql database-platform: org.hibernate.dialect.MySQL5Dialect5.4、查看服务是否注册成功重新启动product微服务服务名就是我们配置文件配置的应用名称。5.5、改造订单接口，实现微服务调用package cc.lisen.shop.order.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; @RestController @RequestMapping("order") @Slf4j public class OrderController { @Resource private RestTemplate restTemplate; @Resource private DiscoveryClient discoveryClient; @GetMapping("product/{id}") public Product order(@PathVariable("id") Integer productID) { List<ServiceInstance> serviceInstanceLList = discoveryClient.getInstances("service-product"); log.info("获取到服务：" + serviceInstanceLList.size()); //忽略下面可能导致的错误 ServiceInstance serviceInstance = serviceInstanceLList.get(0); return restTemplate.getForObject(serviceInstance.getUri() + "/product/1", Product.class); } }浏览器访问订单接口六、负载均衡通俗的讲，负载均衡就是将负载（工作任务、访问请求）进行分摊到多个操作单元（服务器、组件）上进行执行。根据负载均衡发生的位置不同，一般分为服务端负载均衡和客户端负载均衡。服务端负载均衡指的是发生在服务提供者一方，比如nginx负载均衡客户端负载均衡指的是发生在服务请求一方，也就是在发送请求之前已经选好了由那个实例处理请求。微服务调用关系中一般会选择客户端负载均衡。6.1、准备负载均衡环境为了实现负载均衡，我们需要准备至少两个服务，这里以商品服务为例。在上面的代码中，我们pom.xml文件都没有加入打包的插件，为了启动两个服务，我们需要现将程序打包成jar包。在shop-user、shop-product、shop-order三个模块都加入打包插件。 <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>6.2、准备商品微服务为了区分调用的服务，我们在商品服务输出一下端口号。package cc.lisen.shop.product.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import cc.lisen.shop.product.service.ProductService; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j public class ProductController { @Resource private ProductService productService; @Resource private Environment environment; @GetMapping("/product/{id}") public Product product(@PathVariable("id") Integer ID) { log.error("当前端口号：" + environment.getProperty("local.server.port")); return productService.findByID(ID); } } 然后将shop-product打包成jar包，通过一下命令，启动两个服务java -jar shop-product-1.0-SNAPSHOT.jar --server.port=8081 java -jar shop-product-1.0-SNAPSHOT.jar --server.port=8082此时打开Nacos，可以看到shop-product有两个服务此时，我们运行订单工程，访问接口http://127.0.0.1:8091/order/product/1多次刷新之后，可以用看到服务全部都打到了8081端口上。8082一条都没有，可见并没有实现负载均衡。6.3、基于Ribbon实现负载均衡Ribbon是Spring Cloud的一个组件，它可以让我们使用一个注解就能轻松搞定负载均衡。nacos 2021版本已经没有自带Ribbon的整合，所以需要引入另一个支持的jar包 loadbalancer早shop-order中引入loadbalancer实现Ribbon支持<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>在RestTemplate上注解上@LoadBalanced即可。package cc.lisen.shop.order.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class OrderConfiguration { @LoadBalanced @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } } 改造接口，通过服务名访问package cc.lisen.shop.order.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; @RestController @RequestMapping("order") @Slf4j public class OrderController { @Resource RestTemplate restTemplate; @Resource DiscoveryClient discoveryClient; @GetMapping("product/{id}") public Product order(@PathVariable("id") Integer productID) { String serviceName = "service-product"; return restTemplate.getForObject("http://"+serviceName + "/product/1", Product.class); } }此时，我们运行订单工程，访问接口http://127.0.0.1:8091/order/product/1多次刷新之后，可以用看到服务平均打到了8081和8082端口。Ribbon默认的均衡策略是轮训。Ribbon自带的负载均衡策略我们可以通过修改配置文件改变默认的负载均衡策略。Ribbon 已经在最新的Spring Cloud 版本中被废弃，Spring Cloud Loadbalancer 是官方正式推出的一款新负载均衡利器，在未来，LoadBalancer 很有可能取代Ribbon的地位成为新一代的负载均衡器6.4、基于Feign实现负载均衡Feign是Spring Cloud提供的一个声明式的伪HTTP客户端，它使得调用远程服务就像调用本地服务一样简单，只需要创建一个接口并添加注解即可。Nacos很好的兼容了Feign，Feign默认集成了Ribbon，所以在Nacos下使用Feign默认就实现了负载均衡的效果。在进行一下代码之前，记得先移除6.3添加的Ribbon相关的代码我们改造shop-order工程，实现Feign的使用6.4.1、引入依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-loadbalancer</artifactId> </dependency>6.4.2、在主类上添加Feign注解package cc.lisen.shop.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) @EnableDiscoveryClient @EnableFeignClients public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } } 6.4.3、添加一个servicepackage cc.lisen.shop.order.Service; import cc.lisen.shop.common.entity.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient("service-product") public interface ProductService { @GetMapping("/product/{id}") Product findByID(@PathVariable("id") Integer id); }Feign调用服务的地址就是@FeignClient+@GetMapping(获取其他映射)的地址6.4.4、改造Controllerpackage cc.lisen.shop.order.controller; import lombok.extern.slf4j.Slf4j; import cc.lisen.shop.common.entity.Product; import cc.lisen.shop.order.Service.ProductService; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; @RestController @RequestMapping("order") @Slf4j public class OrderController { @Resource ProductService productService; @GetMapping("product/{id}") public Product order(@PathVariable("id") Integer productID) { return productService.findByID(productID); } } 此时，我们运行订单工程，访问接口http://127.0.0.1:8091/order/product/1多次刷新之后，可以用看到服务平均打到了8081和8082端口。6.4.5、修改轮训策略以前的Ribbon有多种负载均衡策略但LoadBalancer貌似只提供了两种负载均衡器，不指定的时候默认用的是轮询。RandomLoadBalancer 随机RoundRobinLoadBalancer 轮询添加LoadBalance配置类package cc.lisen.shop.order.config; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer; import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer; import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import javax.annotation.Resource; public class MyLoadBalancerConfig { @Resource private NacosDiscoveryProperties nacosDiscoveryProperties; //自定义loadBlancer负载均衡策略 @Bean public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); //返回随机轮询负载均衡方式 return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); //返回加权随机轮询负载均衡方式 //return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); //nacos服务注册中心权重的负载均衡策略 // return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name, nacosDiscoveryProperties); } } 启动类上配置服务使用的负载均衡策略package cc.lisen.shop.order; import cc.lisen.shop.order.config.MyLoadBalancerConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EntityScan({"cc.lisen.shop.common.entity"}) @EnableDiscoveryClient @EnableFeignClients @LoadBalancerClients(value = @LoadBalancerClient(name = "service-product",configuration = MyLoadBalancerConfig.class)) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }此时，我们运行订单工程，访问接口http://127.0.0.1:8091/order/product/1多次刷新之后，可以用看到服务随机打到了8081和8082端口。
一、SpringCloud Gateway 简介SpringCloud Gateway 是 Spring Cloud 的一个全新项目，该项目是基于 Spring 5.0，Spring Boot 2.0 和 Project Reactor 等技术开发的网关，它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关，目标是替代 Zuul，在Spring Cloud 2.0以上版本中，没有对新版本的Zuul 2.0以上最新高性能版本进行集成，仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能，SpringCloud Gateway是基于WebFlux框架实现的，而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。Spring Cloud Gateway 的目标，不仅提供统一的路由方式，并且基于 Filter 链的方式提供了网关基本的功能，例如：安全，监控/指标，和限流。二、SpringCloud Gateway 特征SpringCloud官方，对SpringCloud Gateway 特征介绍如下：基于 Spring Framework 5，Project Reactor 和 Spring Boot 2.0集成 Hystrix 断路器集成 Spring Cloud DiscoveryClientPredicates 和 Filters 作用于特定路由，易于编写的 Predicates 和 Filters具备一些网关的高级功能：动态路由、限流、路径重写从以上的特征来说，和Zuul的特征差别不大。SpringCloud Gateway和Zuul主要的区别，还是在底层的通信框架上。简单说明一下上文中的三个术语：Filter（过滤器）：和Zuul的过滤器在概念上类似，可以使用它拦截和修改请求，并且对上游的响应，进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。Route（路由）：网关配置的基本组成模块，和Zuul的路由配置模块类似。一个Route模块由一个ID，一个目标URI，一组断言和一组过滤器定义。如果断言为真，则路由匹配，目标URI会被访问。Predicate（断言）：这是一个 Java 8 的Predicate，可以使用它来匹配来自HTTP请求的任何内容，例如headers或参数。断言的输入类型是一个 ServerWebExchange。三、Spring Cloud Gateway的处理流程客户端向 Spring Cloud Gateway 发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由，将其发送到Gateway Web Handler。Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑，然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前（pre）或之后（post）执行业务逻辑。四、路由匹配规则对于这种配置类的，我们一般情况都会放到配置文件中去配置。Spring Cloud Gateway 的功能很强大，我们仅仅通过 Predicates 的设计就可以看出来，前面我们只是使用了 predicates 进行了简单的条件匹配，其实 Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。Spring Cloud Gateway 是通过 Spring WebFlux 的 HandlerMapping 做为底层支持来匹配到转发路由，Spring Cloud Gateway 内置了很多 Predicates 工厂，这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配，多个 Predicates 工厂可以组合使用。代码Demo父工程依赖<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.12.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR12</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <!-- <version>2.1.0.RELEASE</version>--> <version>2.2.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> </dependencies> </dependencyManagement>当前模块依赖 <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>配置文件server: port: 9527 spring: application: name: cloud-gateway-service cloud: nacos: discovery: server-addr: 192.168.120.180:8848 gateway: discovery: locator: enabled: true routes: - id: payment-routh uri: lb://nacos-provider-payment # 服务名称 predicates: - Path=/app/** - Header=username,laughing可以看到gateway的路由匹配规格主要是包含id、uri、predicates。id随意指定，唯一即可。uri代表配置的路径，可以匹配URL或者服务名称，如果是服务名称，以lb:打头。predicates代表断言，条件为true时才能进入对应的路径。说白了 Predicate 就是为了实现一组匹配规则，方便让请求过来找到对应的 Route 进行处理，接下来我们接下 Spring Cloud GateWay 内置几种 Predicate 的使用。假定转发uri都设定为lb://nacos-provider-payment规则实例说明Path- Path=/gate/,/rule/当请求的路径为gate、rule开头的时，转发到nacos-provider-payment服务Before- Before=2017-01-20T17:42:47.789-07:00[America/Denver]在某个时间之前的请求才会被转发到nacos-provider-payment服务After- After=2017-01-20T17:42:47.789-07:00[America/Denver]在某个时间之后的请求才会被转发到nacos-provider-payment服务Between- Between=2017-01-20T17:42:47.789-07:00[America/Denver],2017-01-21T17:42:47.789-07:00[America/Denver]在某个时间段之间的才会被转发Cookie- Cookie=chocolate, ch.p名为chocolate的表单或者满足正则ch.p的表单才会被匹配到进行请求转发Header- Header=X-Request-Id, \d+携带参数X-Request-Id或者满足\d+的请求头才会匹配Host- Host=lisen.cc当主机名为lisen.cc的时候直接转发到nacos-provider-payment服务Method- Method=GET只有GET方法才会匹配转发请求，还可以限定POST、PUT等请求方式测试我们上面代码的匹配规则如果我们访问的路径是/echo开头，并且Header中username=laughing就转发到nacos-provider-payment服务。第一次正常访问第二次，我们配置Header可以看到报404第三次，我们配置Header但是参数值设置为test,可以看到也是报404其他参数配置我们不再测试。五、过滤器规则（Filter）过滤规则实例说明PrefixPath- PrefixPath=/app在请求路径前加上appRewritePath- RewritePath=/test, /app/test访问localhost:9022/test,请求会转发到localhost:8001/app/testSetPathSetPath=/app/{path}通过模板设置路径，转发的规则时会在路径前增加app，{path}表示原请求路径RedirectTo 重定向RemoveRequestHeader 去掉某个请求头信息测试Demo我们只测试一个PrefixPath服务提供者 @GetMapping(value = "app/echo/{string}") public String echo1(@PathVariable("string") String string) { return "app/echo/{string} " + string+"\t当前服务端口:"+serverPort; }配置文件server: port: 9527 spring: application: name: cloud-gateway-service cloud: nacos: discovery: server-addr: 192.168.120.180:8848 gateway: discovery: locator: enabled: true routes: - id: payment-routh uri: lb://nacos-provider-payment # 服务名称 predicates: - Path=/** filters: PrefixPath=/app
在Spring Cloud使用OpenFeign调用Nacos服务提供者中，我们介绍了OpenFeign调用Nacos的方式。在Spring Cloud集成Sentinel之@SentinelResource注解使用一文中，我们也介绍了@SentinelResource注解，但是注解的方式。本文我们更进一步，介绍以下OpenFeign如何与Setinel集成。添加依赖<?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>Cloud2020</artifactId> <groupId>cc.lisen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-consumer-order9003</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>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.5</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> </dependencies> </project>修改配置文件server: port: 8888 spring: application: name: nacos-consumer-order cloud: nacos: discovery: server-addr: 192.168.120.180:1111 sentinel: transport: dashboard: 192.168.120.180:9000 port: 8719 management: endpoints: web: exposure: include: "*" logging: level: cc.lisen.springcloud.alibaba.service.NacosPaymentService: debug #设置feign客户端超时时间(OpenFeign默认支持ribbon) nacos-provider-payment: ribbon: #指的是建立连接所用的时间，适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 5000 NFLoadBalancerRuleClassName: cc.lisen.springcloud.alibaba.config.CustomerBalancerRule sentinel: enabled: true修改启动类@SpringBootApplication //@EnableDiscoveryClient @EnableFeignClients public class NacosConsumerOrderMain8888 { public static void main(String[] args) { SpringApplication.run(NacosConsumerOrderMain8888.class, args); } }定义Feign接口@FeignClient(value = "nacos-provider-payment", fallback = NacosPaymentServiceFallback.class) public interface NacosPaymentService { @GetMapping(value = "/echo/{string}") String echo(@PathVariable("string") String string); } class NacosPaymentServiceFallback implements NacosPaymentService { @Override public String echo(String string) { return "进入feignClient fallback了"; } }定义测试接口 @GetMapping("/echo/{id}") public String echo(@PathVariable("id") String id) { log.info("******************输出：" + id); return nacosPaymentService.echo(id); }
Spring Cloud集成Sentinel之@SentinelResource注解使用
通过Spring Cloud集成Sentinel之流量控制及Spring Cloud集成Sentinel之服务熔断降级两篇文章，我们可以发现一个问题，当系统触发限流或者熔断时，系统排除的异常很不友好。现在前后端分离项目，我们后端一般会封装一个公共的实体返回给前端，比如下面代码中的CommonResult，了解到这，我们分别介绍以下@SentinelResource注解中常用的几个参数：value、blockHandler、fallback。测试代码@SentinelResource注解代码@RestController public class RateLimitController { @GetMapping("/byResource") @SentinelResource(value = "byResource", blockHandler = "handleException",fallback = "handleFallback") 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 服务不可用"); } public CommonResult handleFallback(Throwable exception) { return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用"); } @GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler",blockHandlerClass = MyHandler.class,blockHandler = "handlerException2") public CommonResult customerBlockHandler() { return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); } }异常代码public class MyHandler { public static CommonResult handlerException1(BlockException exception) { return new CommonResult(4444, "按客户自定义的global handlerException---1"); } public static CommonResult handlerException2(BlockException exception) { return new CommonResult(4444, "按客户自定义的global handlerException---2"); } }valuevalue用于指定资源名称。在Sentinel配置中，我们前面使用的api地址作为的资源名称，其实也可以通过这个value指定资源名称。在Sentinel中，如果以/开头，那么便意味着通过api地址作为资源名，否则系统认为value作为资源名。比如这个方法@GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler",blockHandlerClass = MyHandler.class,blockHandler = "handlerException2") public CommonResult customerBlockHandler() { return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); }在sentinel配置中，指定资源名称/rateLimit/customerBlockHandler或customerBlockHandler是等价的。{message type="warning" content="value必须是唯一的"/}只指定fallbackfallback负责业务异常和限流时处理。测试代码 @GetMapping("/fallbackOnly") @SentinelResource(value = "fallbackOnly", fallback = "handlefallbackOnlyException") public CommonResult fallbackOnly() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); }我们触发限流规则，可以看到如下输出，已经是我们自定义的异常类，而不是系统自带的。还有一个fallbackClass注解，如此类似，不再赘诉。只指定blockHandlerblockHandler只负责sentinel控制台配置违规测试代码 @GetMapping("/blockHandlerOnly") @SentinelResource(value = "blockHandlerOnly", blockHandler = "handleblockHandlerException") public CommonResult blockHandlerOnly() { int age = 10/0; return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); } public CommonResult handleblockHandlerException(BlockException exception) { return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用"); }我们触发一个熔断规则，可以看到如下输出fallback和blockHandler同时存在fallback负责处理异常，blockHandler负责sentinel控制台配置违规。@GetMapping("/byResource") @SentinelResource(value = "byResource", blockHandler = "handleException", fallback = "handleFallback") public CommonResult byResource() { return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); } public CommonResult handleException(BlockException exception) { return new CommonResult(444, "handleException\t 服务不可用"); } public CommonResult handleFallback(Throwable exception) { return new CommonResult(444, "handleFallback\t 服务不可用"); }
热点参数何为热点？热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据，并对其访问进行限制。比如：商品 ID 为参数，统计一段时间内最常购买的商品 ID 并进行限制用户 ID 为参数，针对一段时间内频繁访问的用户 ID 进行限制热点参数限流会统计传入参数中的热点参数，并根据配置的限流阈值与模式，对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制，仅对包含热点参数的资源调用生效。Sentinel 利用LRU策略统计最近最常访问的热点参数，结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。 @GetMapping("/testHotkey") // @SentinelResource(value = "testHotkey", blockHandler = "deal_testHotkey") @SentinelResource(value = "testHotkey") public String testHotkey(@RequestParam(value = "productId", required = false) String productId, @RequestParam(value = "userId", required = false) String userId) { return "--------------testHotkey"; }如果参数0，1s请求次数超过依次，触发熔断，但是如果参数0的值是p2，那么可以请求2次。自适应限流Sentinel 系统自适应限流从整体维度对应用入口流量进行控制，结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标，通过自适应的流控策略，让系统的入口流量和系统的负载达到一个平衡，让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统规则系统保护规则是从应用级别的入口流量进行控制，从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标，让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统保护规则是应用整体维度的，而不是资源维度的，并且仅对入口流量生效。入口流量指的是进入应用的流量（EntryType.IN），比如 Web 服务或 Dubbo 服务端接收的请求，都属于入口流量。系统规则支持以下的模式：Load 自适应（仅对 Linux/Unix-like 机器生效）：系统的 load1 作为启发指标，进行自适应系统保护。当系统 load1 超过设定的启发值，且系统当前的并发线程数超过估算的系统容量时才会触发系统保护（BBR 阶段）。系统容量由系统的 maxQps minRt 估算得出。设定参考值一般是 CPU cores 2.5。CPU usage（1.5.0+ 版本）：当系统 CPU 使用率超过阈值即触发系统保护（取值范围 0.0-1.0），比较灵敏。平均 RT：当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护，单位是毫秒。并发线程数：当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。入口 QPS：当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
Sentinel除了流量控制以外，对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性，如果调用链路中的某个资源不稳定，最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时（例如调用超时或异常比例升高），对这个资源的调用进行限制，让请求快速失败，避免影响到其它的资源而导致级联错误。当资源被降级后，在接下来的降级时间窗口之内，对该资源的调用都自动熔断（默认行为是抛出DegradeException）。降级策略通常用以下几种方式来衡量资源是否处于稳定的状态：慢调用比例 (DEGRADE_GRADE_RT)：当统计时常内持续进入N个请求同时请求数到达最小请求数，对应时刻的平均响应时间（秒级）均超过比例阈值，那么在接下的时间窗口（DegradeRule中的timeWindow，以s为单位）之内，对这个方法的调用都会自动地熔断（抛出DegradeException）。注意 Sentinel 默认统计的RT上限是4900ms，超出此阈值的都会算作4900ms。若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx来配置。异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO)：当资源的在统计时常内请求量 >= N（可配置），并且每秒异常总数占通过量的比值超过阈值（DegradeRule中的count）之后，资源进入降级状态，即在接下的时间窗口（DegradeRule中的timeWindow，以s为单位）之内，对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0]，代表0% - 100%。异常数 (DEGRADE_GRADE_EXCEPTION_COUNT)：当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的，若timeWindow小于60s，则结束熔断状态后仍可能再进入熔断状态。代码测试慢调用比例测试代码@GetMapping("/testRT") public String testRT() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return "-----------testRT"; }在1s内，请求数如果超过5个，平均请求时常超过800ms的比例达到20%，那么在接下来的1min内进行服务熔断。一开始正常访问，但是访问第6次时，达到条件，出发熔断降级。异常比例测试代码@GetMapping("/testExceptionPer") public String testExceptionPer() { int age = 1 / 0; return "-----------testExceptionPer"; }在1s内，请求数如果超过5个，异常的比例达到10%，那么在接下来的1min内进行服务熔断。一开始访问，报以下异常，多次访问后，触发熔断降级异常数可以参考异常比例，不再赘述。
在Spring Cloud Alibaba sentinel的安装与配置中，我们介绍了Sentinel的功能以及安装部署方式。在Spring Cloud集成Sentinel之基础功能介绍中，我们介绍了如何在Spring Cloud中集成Sentinel。本文我们在上面的基础上继续深入，介绍一下Sentinel的流量控制功能。Sentinel流量控制介绍流量控制（flow control），其原理是监控应用流量的 QPS 或并发线程数等指标，当达到指定的阈值时对流量进行控制，以避免被瞬时的流量高峰冲垮，从而保障应用的高可用性。FlowSlot会根据预设的规则，结合前面NodeSelectorSlot、ClusterNodeBuilderSlot、StatisticSlot 统计出来的实时信息进行流量控制。限流的直接表现是在执行 Entry nodeA = SphU.entry(resourceName) 的时候抛出 FlowException 异常。FlowException 是 BlockException 的子类，您可以捕捉BlockException来自定义被限流之后的处理逻辑。同一个资源可以创建多条限流规则。FlowSlot会对该资源的所有限流规则依次遍历，直到有规则触发限流或者所有规则遍历完毕。一条限流规则主要由下面几个因素组成，我们可以组合这些元素来实现不同的限流效果：resource：资源名，即限流规则的作用对象count: 限流阈值grade: 限流阈值类型（QPS 或并发线程数）limitApp: 流控针对的调用来源，若为 default 则不区分调用来源strategy: 调用关系限流策略controlBehavior: 流量控制效果（直接拒绝、Warm Up、匀速排队）名词解释资源名：唯一名称、默认请求路径，也可以通过@SentinelResource的value属性指定，不可重复。针对来源：Sentinel可以针对调用者进行限流，填写微服务名，默认default（不区分来源）阀值类型/单机阀值：QPS（每秒钟的请求数量）：当调用该api的QPS达到阀值的时候，进行限流线程数：当调用该api的线程数达到阀值的时候，进行限流是否集群：不需要集群流控模式：直接：api达到限流条件时，直接限流关联：当关联的资源达到阀值时，就限流自己链路：只记录指定链路上的流量（指定资源从入口资源进来的流量，如果达到阀值，就限流）【api级别的针对来源】流控效果：快速失败：直接失败，抛异常Warm Up：根据codeFactor（冷加载因子，默认3）的值，从阀值/codeFactor，经过预热时长，才达到设置的QPS阀值排队等待：匀速排队，让请求以均匀的速度通过，阀值类型必须设置为QPS，否则无效代码展示基于QPS的流量控制当QPS超过某个阈值的时候，则采取措施进行流量控制。流量控制的效果包括以下几种：直接拒绝、Warm Up、匀速排队。对应FlowRule中的 controlBehavior字段。在代码中增加以下服务接口 @GetMapping("/testQPSA") public String testA() { return "-----------testQPSA"; }如何此时我们不配置Sentinel，无论如何访问，都不会报错，流量会直接打到服务器，直到服务器崩溃。此时，我们打开Sentinel，设置流控如下配置的信息代表每秒最多允许一个请求，超过依次的请求直接报错。此时我们快速刷新接口，可以看到以下信息基于并发数的流量控制基于并发数的控制，也就是控制同一时刻能够访问接口的数量。 @GetMapping("/testThreadA") public String testThreadA() { try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } return "-----------testThreadA"; }为了测试方便，我让程序暂停10S。上图代表我们统一时刻只能有一个线程访问。然后启动用浏览器及curl命令同时访问。可以看到第一个浏览器能正常打开，但是curl命令的无法访问。流控模式：直接以上两个例子都是直接控制，也就是关联自己的资源。流控模式：关联当两个资源之间具有资源争抢或者依赖关系的时候，这两个资源便具有了关联。像对数据库同一个字段的读操作和写操作存在争抢，读的速度过高会影响写得速度，写的速度过高会影响读的速度。如果放任读写操作争抢资源，则争抢本身带来的开销会降低整体的吞吐量。这次我们新增以下两个服务。 @GetMapping("/testRelationA") public String testRelationA() { log.info(Thread.currentThread().getName() + "\t testRelationA"); return "-----------testRelationA"; } @GetMapping("/testRelationB") public String testRelationB() { log.info(Thread.currentThread().getName() + "\t testRelationB"); return "-----------testRelationB"; }我们模拟：当testRelationB达到阈值时，停止testRelationA服务。这次我们用JMeter进行测试，使用JMeter循环访问testRelationB可以看到testRelationB访问一直正常，但是testRelationA此时已经不能访问了。流控模式：链路只记录指定链路上的流量（指定资源从入口资源进来的流量，如果达到阈值，就可以限流）这次我们删除所有的流控规则，查看sentinel我们可以看到/testRelationB的资源入口是sentinel_web_servlet_context这次我们设置流控规则如下多次点击http://localhost:8401/testRelationB,可以看到浏览器显示流控效果：快速失败以上代码都是直接快速失败的例子，当QPS超过任意规则的阈值后，新的请求就会被立即拒绝，拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下，比如通过压测确定了系统的准确水位时。流控效果：Warm Up（预热）Warm Up（RuleConstant.CONTROL_BEHAVIOR_WARM_UP）方式，即预热/冷启动方式。当系统长期处于低水位的情况下，当流量突然增加时，直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动"，让通过的流量缓慢增加，在一定时间内逐渐增加到阈值上限，给冷系统一个预热的时间，避免冷系统被压垮。我们增加以下代码。测试预测@GetMapping("/testWarmup") public String testWarmup() { return "-----------testWarmup"; }以上表示，一开始系统QPS为1（3/3），10秒之后，系统QPS为3。启用JMeter测试，设置请求100个，系统处理时间为50s，也就说1s发送两个请求。可以看到，一开始，间隔成功一个、失败一个（因为我们JMeter设置的是1s处理两个请求，而Sentinel设置的是1s只能处理一个请求），10秒之后，就一直成功了（因为10s后，阈值为10，也就是说我们1s能处理10个请求，小于我们JMeter测试的一秒两个请求）流控效果：排队等待排队等待（RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER）方式会严格控制请求通过的间隔时间，也即是让请求以均匀的速度通过，对应的是漏桶算法。这种方式主要用于处理间隔性突发的流量，例如消息队列。想象一下这样的场景，在某一秒有大量的请求到来，而接下来的几秒则处于空闲状态，我们希望系统能够在接下来的空闲期间逐渐处理这些请求，而不是在第一秒直接拒绝多余的请求。 @GetMapping("/testQueue") public String testQueue() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return "-----------testQueue"; }一次处理一个请求，超时时间为20S使用JMeter默认10个请求可以看到每个线程大概耗时都是1s。
Sentinel涉及的知识点比较多，我们这篇文章先看一下基础的，如何在Spring Cloud中引入Sentiel。父工程与前面章节介绍的父工程一样，这里之简单罗列一下依赖<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.12.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR12</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <!-- <version>2.1.0.RELEASE</version>--> <version>2.2.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> </dependencies> </dependencyManagement>添加服务提供者添加依赖<?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>Cloud2020</artifactId> <groupId>cc.lisen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-sentiel-service8401</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>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.5</version> </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> <dependency> <groupId>cc.lisen</groupId> <artifactId>cloud-api-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>修改配置文件主要是配置Nacos服务器及Sentinel Dashboard的地址。server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: 192.168.120.180:1111 sentinel: transport: dashboard: 192.168.120.180:9000 # 默认8719端口，假如端口被占用，依次+1，直到找到未被占用端口 port: 8719 management: endpoints: web: exposure: include: "*"增加接口@RestController @Slf4j public class FlowLimitController { @GetMapping("/testA") public String testA() { return "-----------testA"; } @GetMapping("/testB") public String testB() { log.info(Thread.currentThread().getName() + "\ttestB"); return "-----------testB"; } @GetMapping("/testD") public String testD() { int age = 10 / 0; log.info(Thread.currentThread().getName() + "\ttestB"); return "-----------testD"; } @GetMapping("/testHotkey") @SentinelResource(value = "testHotkey", blockHandler = "deal_testHotkey") public String testHotkey(@RequestParam(value = "p1", required = false) String p1, @RequestParam(value = "p2", required = false) String p2) { return "--------------testHotkey"; } public String deal_testHotkey(String p1, String p2, BlockException blockException) { return "--------------deal_testHotkey"; } }测试访问http://localhost:8401/testA,然后打开Sentinel Dashboard，可以看到我们当前定义的服务接口。一定要访问依次我们的服务接口，否则在sentinel dashboard中无法看到
什么是OpenFeignOpenFeign为微服务架构下服务之间的调用提供了解决方案，OpenFeign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用OpenFeign，可以做到使用HTTP请求访问远程服务，就像调用本地方法一样的，开发者完全感知不到这是在调用远程方法，更感知不到在访问HTTP请求。Spring Cloud 2020版本已经彻底移除了Netflix相关组件，OpenFeign便是Feign的替换者。本文我们是在Spring Cloud使用Nacos作为服务中心编写服务提供者的基础上进行的，通过OpenFeign调用前面编写的nacos-provider-payment服务。编写服务消费者创建cloudalibaba-consumer-order8888工程，调用我们编写的nacos-provider-payment服务。添加依赖<?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>Cloud2020</artifactId> <groupId>cc.lisen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-consumer-order9003</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>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.5</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> </dependencies> </project>修改配置文件修改application.yml增加Nacos相关配置。server: port: 8888 spring: application: name: nacos-consumer-order cloud: nacos: discovery: server-addr: 192.168.120.180:1111 management: endpoints: web: exposure: include: "*" 修改启动类添加@EnableFeignClients注解，支持使用OpenFeign@SpringBootApplication //@EnableDiscoveryClient @EnableFeignClients public class NacosConsumerOrderMain8888 { public static void main(String[] args) { SpringApplication.run(NacosConsumerOrderMain8888.class, args); } }编写Feign客户端添加@FeignClient注解，value便是我们服务提供者的名称。@FeignClient(value = "nacos-provider-payment") public interface NacosPaymentService { @GetMapping(value = "/echo/{string}") String echo(@PathVariable("string") String string); }测试编写controller进行测试@RestController @Slf4j @RequestMapping("/nacos/consumer") public class NacosConsumerController { @Resource private NacosPaymentService nacosPaymentService; @GetMapping("/echo/{id}") public String echo(@PathVariable("id") String id) throws IllegalAccessException { log.info("******************输出：" + id); return nacosPaymentService.echo(id); } }OpenFeign超时控制OpenFeign调用接口，默认超时时间为1S。我们修改一下nacos-provider-payment服务，让放服务暂停2s，然后查看一下接口调用。 @GetMapping(value = "/echo/{string}") public String echo(@PathVariable("string") String string) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return "Hello Nacos Discovery " + string+"\t当前服务端口:"+serverPort; }但是有时候我们业务接口可能需要处理的时间较长，那么我们可以配置接口调用的超时时间。我们可以在配置文件中增加以下内容#设置feign客户端超时时间(OpenFeign默认支持ribbon) ribbon: #指的是建立连接所用的时间，适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 5000我们再次测试接口，可以发现接口可以正常调用。OpenFeign日志打印功能增加日志bean@Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }修改配置文件YML文件里配置需要开启日志的Feign客户端logging: level: cc.lisen.springcloud.alibaba.service.NacosPaymentService: debug测试再次调用接口，可以看到输出的日志负载均衡如果我们同时启动了9001、9002端口，也就是说我们针对服务提供者做了集群，那么我们多次调用服务提供者时，会发现系统会依次输出9001、9002端口。也就是说，OpenFeign自带了负债均衡功能，默认采用的时轮询算法。我们修改一下默认的负债均衡算法，设置的简单一点，如果我们的请求包含name=zhangsan参数，就调用到9001端口，否则就调用9002端口。实现IRule接口public class CustomerBalancerRule implements IRule { private ILoadBalancer balancer = new BaseLoadBalancer(); @Override public Server choose(Object object) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String name = request.getParameter("name"); List<Server> servers = balancer.getAllServers(); if ("zhangsan".equals(name)) { if (servers.stream().anyMatch(e -> e.getPort() == 9001)) { return servers.stream().filter(e -> e.getPort() == 9001).collect(Collectors.toList()).get(0); } return servers.get(0); } return servers.get(1); } @Override public void setLoadBalancer(ILoadBalancer iLoadBalancer) { this.balancer = iLoadBalancer; } @Override public ILoadBalancer getLoadBalancer() { return this.balancer; } }配置服务提供者，调用负载均衡算法#设置feign客户端超时时间(OpenFeign默认支持ribbon) nacos-provider-payment: ribbon: #指的是建立连接所用的时间，适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 5000 NFLoadBalancerRuleClassName: cc.lisen.springcloud.alibaba.config.CustomerBalancerRule
在Nacos集群配置中，我们介绍了Nacos的安装以及集群配置，本文我们介绍一下在Spring Cloud中如何使用Nacos作为服务注册中心。编写父工程父工程只提供基本的依赖关系，一定要注意版本，否则会出现很多乱七八糟的错误。父工程依赖如下<?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> <groupId>cc.lisen</groupId> <artifactId>Cloud2020</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>cloud-provider-payment8001</module> <module>cloud-consumer-order80</module> <module>cloud-api-common</module> <module>cloud-provider-payment8004</module> <module>cloud-consumerzk-order80</module> <module>cloud-consumer-feign-order80</module> <module>cloud-provider-hystrix-payment8001</module> <module>cloud-consumer-feign-hystrix-order80</module> <module>cloud-consumer-hystrix-dashboard9001</module> <module>cloud-gateway-gateway9527</module> <module>cloud-config-center-3344</module> <module>cloud-config-client-3355</module> <module>cloud-config-client-3366</module> <module>cloud-stream-rabbitmq-provider8801</module> <module>cloud-stream-rabbitmq-consumer8802</module> <module>cloud-stream-rabbitmq-consumer8803</module> <module>cloud-stream-rabbitmq-consumer8802</module> <module>cloud-stream-rabbitmq-consumer8803</module> <module>cloud-stream-rabbitmq-consumer8803</module> <module>cloudalibaba-provider-payment9001</module> <module>cloudalibaba-provider-payment9002</module> <module>cloudalibaba-consumer-order8888</module> <module>cloudalibaba-config-nacos-client3377</module> <module>cloudalibaba-sentinel-service8401</module> <module>seata-order-service2001</module> <module>seata-storage-service2002</module> <module>seata-account-service2003</module> </modules> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.12.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR12</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <!-- <version>2.1.0.RELEASE</version>--> <version>2.2.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> </dependencies> </dependencyManagement> <build> <pluginManagement> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> </plugin> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <configuration> <locales>en,fr</locales> </configuration> </plugin> </plugins> </build> <reporting> <plugins> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> </plugin> </plugins> </reporting> </project> 请忽略其他的项目模块。编写服务提供者编写一个简单的服务提供者，输出一个字符串。添加依赖<?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>Cloud2020</artifactId> <groupId>cc.lisen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-provider-payment9002</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>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.5</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies> </project>修改配置文件server: port: 9001 spring: application: name: nacos-provider-payment cloud: nacos: discovery: server-addr: 192.168.120.180:8848 management: endpoints: web: exposure: include: "*"修改启动类@SpringBootApplication public class PaymentProviderMain9001 { public static void main(String[] args){ SpringApplication.run(PaymentProviderMain9001.class,args); } }编写服务接口@RestController @Slf4j public class NacosController { @Value("${server.port}") private String serverPort; @GetMapping(value = "/echo/{string}") public String echo(@PathVariable String string) { return "Hello Nacos Discovery " + string+"\t当前服务端口:"+serverPort; } }启动服务，我们可以查看一下Nacos是否有对应的服务。集群一般情况下，为了系统的健壮性，我们都会进行集群。我这里为了方便调试，复制了一个cloudalibaba-provider-payment9001工程，只是将端口号改成9002,然后，我们启动9002端口项目，再次查看Nacos可以看到当前有两个实例了。集群的关键时服务名称需要一致，也就是我们配置文件中的spring.application.name必须保持一致。