# Nacos 配置管理
# 统一配置管理
当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置
Nacos 一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新
# 再 nacos 中添加配置文件
注意:项目的核心配置,需要热更新的配置才有放到 nacos 管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。
# 从微服务拉取配置
- 微服务要拉取 nacos 中管理的配置,并且与本地的 application.yml 配置合并,才能完成项目启动。
- 因此 spring 引入了一种新的配置文件:bootstrap.yaml 文件,会在 application.yml 之前被读取,流程如下:
# 引入 nacos-config 依赖
<!--nacos 配置管理依赖 --> | |
<dependency> | |
<groupId>com.alibaba.cloud</groupId> | |
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> | |
</dependency> |
# 添加 bootstrap.yaml 文件
spring: | |
application: | |
name: userservice # 服务名称 | |
profiles: | |
active: dev #开发环境,这里是 dev | |
cloud: | |
nacos: | |
server-addr: localhost:8848 # Nacos 地址 | |
config: | |
file-extension: yaml # 文件后缀名 |
- 这里会根据 spring.cloud.nacos.server-addr 获取 nacos 地址
- 在根据
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
作为文件 id,来读取配置。
# 读取 nacos 配置
@Slf4j | |
@RestController | |
@RequestMapping("/user") | |
public class UserController { | |
@Value("${pattern.dateformat}") | |
private String dateformat; | |
@GetMapping("now") | |
public String now(){ | |
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat)); | |
} | |
} |
# 配置热更新
我们最终的目的,是修改 nacos 中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新。
# 方式一
在 @Value
注入的变量所在类上添加注解 @RefreshScope
@Slf4j | |
@RestController | |
@RequestMapping("/user") | |
@RefreshScope | |
public class UserController { | |
@Value("${pattern.dateformat}") | |
private String dateformat; | |
@GetMapping("now") | |
public String now(){ | |
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat)); | |
} | |
} |
# 方式二
使用 @ConfigurationProperties
注解代替 @Value
注解。在 user-service 服务中,添加一个类,读取 patterrn.dateformat 属性
@Component | |
@Data | |
@ConfigurationProperties(prefix = "pattern") | |
public class PatternProperties { | |
private String dateformat; | |
} |
在 UserController 中使用这个类代替 @Value
@Slf4j | |
@RestController | |
@RequestMapping("/user") | |
public class UserController { | |
@Autowired | |
private PatternProperties patternProperties; | |
@GetMapping("/now") | |
public String now(){ | |
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat())); | |
} | |
} |
# 配置共享
# 添加一个环境共享配置
在 nacos 中添加一个 userservice.yaml 文件
# 在 user-service 中读取共享配置
在 user-service 服务中,修改 PatternProperties 类,读取新添加的属性
@Component | |
@Data | |
@ConfigurationProperties(prefix = "pattern") | |
public class PatternProperties { | |
private String dateformat; | |
private String envSharedValue; | |
} |
在 user-service 服务中,修改 UserController,添加一个方法
@Slf4j | |
@RestController | |
@RequestMapping("/user") | |
public class UserController { | |
@Autowired | |
private PatternProperties patternProperties; | |
@GetMapping("/prop") | |
public PatternProperties patternProperties(){ | |
return patternProperties; | |
} | |
} |
# 运行两个不同的 UserApplocation,使用不同的 profile
修改 UserApplication2 这个启动项,改变其 profile 值
这样,UserApplication (8081) 使用的 profile 是 dev,UserApplication2 (8082) 使用的 profile 是 test,启动 UserApplication 和 UserApplication2
# 配置共享优先级
当 nacos、服务本地同时出现相同属性时,优先级有高低之分
# 搭建 Nacos 集群
# 集群结构图
官方给出的 Nacos 集群图:
其中包含 3 个 nacos 节点,然后一个负载均衡器代理 3 个 Nacos。这里负载均衡器可以使用 nginx。
我们计划的集群结构:
三个 nacos 节点的地址:
节点 | ip | port |
---|---|---|
nacos1 | 192.168.150.1 | 8845 |
nacos2 | 192.168.150.1 | 8846 |
nacos3 | 192.168.150.1 | 8847 |
# 搭建集群
搭建集群的基本步骤:
- 搭建数据库,初始化数据库表结构
- 下载 nacos 安装包
- 配置 nacos
- 启动 nacos 集群
- nginx 反向代理
# 初始化数据库
Nacos 默认数据存储在内嵌数据库 Derby 中,不属于生产可用的数据库。
官方推荐的最佳实践是使用带有主从的高可用数据库集群,主从模式的高可用数据库可以参考传智教育的后续高手课程。
这里我们以单点的数据库为例来讲解。
首先新建一个数据库,命名为 nacos,而后导入下面的 SQL:
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'); |
# 下载 nacos
nacos 在 GitHub 上有下载地址:https://github.com/alibaba/nacos/tags,可以选择任意版本下载。
本例中才用 1.4.1 版本
# 配置 Nacos
将这个包解压到任意非中文目录下,如图:
目录说明:
- bin:启动脚本
- conf:配置文件
进入 nacos 的 conf 目录,修改配置文件 cluster.conf.example,重命名为 cluster.conf:
然后添加内容:
127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847
然后修改 application.properties 文件,添加数据库配置
spring.datasource.platform=mysql | |
db.num=1 | |
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC | |
db.user.0=root | |
db.password.0=123 |
# 启动
将 nacos 文件夹复制三份,分别命名为:nacos1、nacos2、nacos3
然后分别修改三个文件夹中的 application.properties,
nacos1:
server.port=8845 |
nacos2:
server.port=8846 |
nacos3:
server.port=8847 |
然后分别启动三个 nacos 节点:
startup.cmd
# nginx 反向代理
找到课前资料提供的 nginx 安装包:
解压到任意非中文目录下:
修改 conf/nginx.conf 文件,配置如下:
upstream nacos-cluster { | |
server 127.0.0.1:8845; | |
server 127.0.0.1:8846; | |
server 127.0.0.1:8847; | |
} | |
server { | |
listen 80; | |
server_name localhost; | |
location /nacos { | |
proxy_pass http://nacos-cluster; | |
} | |
} |
而后在浏览器访问:http://localhost/nacos 即可。
代码中 application.yml 文件配置如下:
spring: | |
cloud: | |
nacos: | |
server-addr: localhost:80 # Nacos 地址 |
# 优化
-
实际部署时,需要给做反向代理的 nginx 服务器设置一个域名,这样后续如果有服务器迁移 nacos 的客户端也无需更改配置.
-
Nacos 的各个节点应该部署到多个不同服务器,做好容灾和隔离
# Fegin 远程调用
# Fegin 替代 RestTemplate
# 引入依赖
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-openfeign</artifactId> | |
</dependency> |
# 添加注解
@EnableFeignClients | |
@MappingScan("cn.baozi.order.mapper") | |
@SpringBootApplication | |
public class OrderApplication { | |
public static void main(String[] args) { | |
SpringApplication.run(OrderApplication.class, args); | |
} | |
} |
# 编写 Fegin 客户端
@FeignClient("userservice") | |
public interface UserClient { | |
@GetMapping("/user/{id}") | |
User findById(@PathVariable("id") Long id); | |
} |
这个客户端主要是基于 SpringMVC 的注解来声明远程调用信息
- 服务名称:
user-service
- 请求方式:GET
- 请求路径:
/user/{id}
- ・请求参数:
Long id
- 返回值类型:
User
这样,Fegin 就可以帮助我们发送 http 请求,无需自己使用 RestTemplate 发送了
# 测试
修改 order-service 中的 OrderService 类中的 queryOrderById 方法,使用 Feign 客户端代替 RestTemplate
@Autowired | |
private UserClient userClient; | |
public Order queryOrderById(Long orderId) { | |
Order order = orderMapper.findById(orderId); | |
User user = userClient.findById(order.getUserId()); | |
order.setUser(user); | |
return order; | |
} |
# 自定义配置
Feign 可以支持很多的自定义配置,如下表所示:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http 远程调用的结果做解析,例如解析 json 字符串为 java 对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过 http 请求发送 |
feign. Contract | 支持的注解格式 | 默认是 SpringMVC 的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用 Ribbon 的重试 |
一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的 @Bean 覆盖默认 Bean 即可
# 配置文件方式
# 针对单个服务
feign: | |
client: | |
config: | |
userservice: # 针对某个微服务的配置 | |
loggerLevel: FULL # 日志级别 |
# 针对所有服务
feign: | |
client: | |
config: | |
default: # 这里用 default 就是全局配置,如果是写服务名称,则是针对某个微服务的配置 | |
loggerLevel: FULL # 日志级别 |
# 日志级别
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL 以及响应状态码和执行时间
- HEADERS:在 BASIC 的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
# Java 代码方式
也可以基于 Java 代码来修改日志级别,先声明一个类,然后声明一个 Logger.Level 的对象
public class DefaultFiginConfiguration { | |
@Bean | |
public Logger.Level feginLogLevel() { | |
return Logger.Level.BASIC; // 日志界别为 BASIC | |
} | |
} |
# 全局生效
在启动类上使用 @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
这个注解
# 局部生效
在启动类上使用 @FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
这个注解
# Fegin 的优化
提高 Feign 的性能主要手段就是使用连接池代替默认的 URLConnection
# 引入依赖
<!--httpClient 的依赖 --> | |
<dependency> | |
<groupId>io.github.openfeign</groupId> | |
<artifactId>feign-httpclient</artifactId> | |
</dependency> |
# 配置连接池
feign: | |
client: | |
config: | |
default: # default 全局的配置 | |
loggerLevel: BASIC # 日志级别,BASIC 就是基本的请求和响应信息 | |
httpclient: | |
enabled: true # 开启 feign 对 HttpClient 的支持 | |
max-connections: 200 # 最大的连接数 | |
max-connections-per-route: 50 # 每个路径的最大连接数 |
# 最佳实践
# 继承方式
- 定义一个 API 接口,利用定义方法,并基于 SpringMVC 注解做声明
- Fegin 客户端和 Controller 都集成改接口
- 优点:
- 简单
- 实现了代码共享
- 缺点:
- 服务提供方、服务消费方紧耦合
- 参数列表中的注解映射并不会继承,因此 Controller 中必须再次声明方法,参数列表,注解
# 抽取方式
将 Feign 的 Client 抽取为独立模块,并且把接口有关的 POJO、默认的 Feign 配置都放到这个模块中,提供给所有消费者使用
# Gatway 服务网关
# 网关定义
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式
# 为什么需要网关
Gateway 网关是我们服务的守门神,所有微服务的统一入口
# 网关的核心功能特性
- 请求路由
- 限制控制
- 限流
# 架构图
# 权限控制
网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截
# 路由和负载均衡
一切请求都必须先经过 gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡
# 限流
当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大
# SpringCloud 中网关的两种实现方式
- gatway
- zuul
Zuul 是基于 Servlet 的实现,属于阻塞式编程。而 SpringCloudGateway 则是基于 Spring5 中提供的 WebFlux,属于响应式编程的实现,具备更好的性能
# gatway 快速入门
- 创建 SpringBoot 工程 gateway,引入网关依赖
- 编写启动类
- 编写基础配置和路由规则
- 启动网关服务进行测试
# 创建 gatway 服务引入依赖
<!-- 网关 --> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-gateway</artifactId> | |
</dependency> | |
<!--nacos 服务发现依赖 --> | |
<dependency> | |
<groupId>com.alibaba.cloud</groupId> | |
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> | |
</dependency> |
# 编写启动类
@SpringBootApplication | |
public class GatewayApplication { | |
public static void main(String[] args) { | |
SpringApplication.run(GatewayApplication.class, args); | |
} | |
} |
# 编写基础配置和路由规则
server: | |
port: 10010 # 网关端口 | |
spring: | |
application: | |
name: gateway # 服务名称 | |
cloud: | |
nacos: | |
server-addr: localhost:8848 # nacos 地址 | |
gateway: | |
routes: # 网关路由配置 | |
- id: user-service # 路由 id,自定义,只要唯一即可 | |
# uri: http://127.0.0.1:8081 # 路由的目标地址 http 就是固定地址 | |
uri: lb://userservice # 路由的目标地址 lb 就是负载均衡,后面跟服务名称 | |
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件 | |
- Path=/user/** # 这个是按照路径匹配,只要以 /user/ 开头就符合要求 |
# 重启测试
重启网关,访问 http://localhost:10010/user/1 时,符合 `/user/**` 规则,请求转发到 uri:http://userservice/user/1,得到了结果
# 网关路由流程图
# 断言工厂
我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory 读取并处理,转变为路由判断的条件,例如 Path=/user/** 是按照路径匹配,这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的,像这样的断言工厂在 SpringCloudGateway 还有十几个
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些 cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些 header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个 host(域名) | - Host=.somehost.org,.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack 或者 - Query=name |
RemoteAddr | 请求者的 ip 必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
# 过滤器工厂
GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理
# 路由过滤器种类
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
# 请求头过滤器
只需要修改 gateway 服务的 application.yml 文件,添加路由过滤即可
spring: | |
cloud: | |
gateway: | |
routes: | |
- id: user-service | |
uri: lb://userservice | |
predicates: | |
- Path=/user/** | |
filters: # 过滤器 | |
- AddRequestHeader=Truth, bao is cool! # 添加请求头 |
当前过滤器写在 userservice 路由下,因此仅仅对访问 userservice 的请求有效
# 默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到 default 下。格式如下
spring: | |
cloud: | |
gateway: | |
routes: | |
- id: user-service | |
uri: lb://userservice | |
predicates: | |
- Path=/user/** | |
default-filters: # 默认过滤项 | |
- AddRequestHeader=Truth, bao is cool! |
# 全局过滤器
# 全局过滤器作用
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与 GatewayFilter 的作用一样。区别在于 GatewayFilter 通过配置定义,处理逻辑是固定的;而 GlobalFilter 的逻辑需要自己写代码实现
定义方式是实现 GlobalFilter 接口
public interface GlobalFilter { | |
/** | |
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理 | |
* | |
* @param exchange 请求上下文,里面可以获取Request、Response等信息 | |
* @param chain 用来把请求委托给下一个过滤器 | |
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束 | |
*/ | |
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain); | |
} |
在 filter 中编写自定义逻辑,可以实现下列功能:
- 登录状态判断
- 权限校验
- 请求限流等
# 自定义全局过滤器
# 在 gateway 中定义一个过滤器
@Order(-1) | |
@Component | |
public class AuthorizeFilter implements GlobalFilter { | |
@Override | |
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { | |
// 1. 获取请求参数 | |
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams(); | |
// 2. 获取 authorization 参数 | |
String auth = params.getFirst("authorization"); | |
// 3. 校验 | |
if ("admin".equals(auth)) { | |
// 放行 | |
return chain.filter(exchange); | |
} | |
// 4. 拦截 | |
// 4.1. 禁止访问,设置状态码 | |
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); | |
// 4.2. 结束处理 | |
return exchange.getResponse().setComplete(); | |
} | |
} |
# 过滤器执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和 DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤
- 每一个过滤器都必须指定一个 int 类型的 order 值,order 值越小,优先级越高,执行顺序越靠前。
- GlobalFilter 通过实现 Ordered 接口,或者添加 @Order 注解来指定 order 值,由我们自己指定
- 路由过滤器和 defaultFilter 的 order 由 Spring 指定,默认是按照声明顺序从 1 递增。
- 当过滤器的 order 值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行。
# 跨域问题
# 什么是跨域
跨域:域名不一致就是跨域,主要包括:
-
域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
-
域名相同,端口不同:localhost:8080 和 localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域 ajax 请求,请求被浏览器拦截的问题
# 模拟跨域问题
# 解决跨域问题
在 gateway 服务的 application.yml 文件中,添加下面的配置
spring: | |
cloud: | |
gateway: | |
# 。。。 | |
globalcors: # 全局的跨域处理 | |
add-to-simple-url-handler-mapping: true # 解决 options 请求被拦截问题 | |
corsConfigurations: | |
'[/**]': | |
allowedOrigins: # 允许哪些网站的跨域请求 | |
- "http://localhost:8090" | |
allowedMethods: # 允许的跨域 ajax 的请求方式 | |
- "GET" | |
- "POST" | |
- "DELETE" | |
- "PUT" | |
- "OPTIONS" | |
allowedHeaders: "*" # 允许在请求中携带的头信息 | |
allowCredentials: true # 是否允许携带 cookie | |
maxAge: 360000 # 这次跨域检测的有效期 |