微服务技术栈 微服务治理 认识微服务 单体架构: 将业务的所有功能集中在一个项目中开发,打成一个包部署
优点 :
缺点:
分布式架构 分布式架构: 根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,就称为一个服务。
优点:
服务治理 分布式架构要考虑的问题:
服务拆分粒度如何?
服务集群地址如何维护?
服务之间如何实现远程调用?
服务健康状态如何感知?
微服务 微服务是一种经过良好架构设计的分布式 架构方案,微服务架构特征:
单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
面向服务:微服务对外暴露业务接口
自治:团队独立、技术独立、数据独立、部署独立
隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
微服务结构 微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是 SpringCloud 和 阿里巴巴的 Dubbo。
微服务技术对比
Dubbo
SpringCloud
SpringCloudAlibaba
注册中心
zookeeper、Redis
Eureka、Consul
Nacos、Eureka
服务远程调用
Dubbo 协议
Feign(http 协议)
Dubbo、Fegin
配置中心
无
SpringCloudConfig
SpringCloudConfig、Nacos
服务网关
无
SpringCloudGateway、Zuul
SpringCloudGateway、Zuul
服务监控和保护
dubbo-admin、功能弱
Hystrix
Sentinel
企业需求
SpringCloud + Fegin
使用 SpringCloud 技术栈
服务接口使用 Restful 风格
服务调用采用 Fegin 方式
SpringCloudAlibaba + Dubbo
使用 SpringCloudAlibaba 技术栈
服务接口采用 Dubbo 协议标准
服务调用采用 Dubbo 方式
SpringCloudAlibaba + Fegin
使用 SpringCloudAlibaba 技术栈
服务接口采用 Restful 风格
服务调用采用 Fegin 方式
Dubbo 原始模式
基于 Dubbo 老旧技术体系
服务接口采用 Dubbo 协议标准
服务调用采用 Dubbo 方式
SpringCloud
SpringCloud 与 SpringBoot 的版本兼容关系如下:
Release Train
Boot Version
2020.0.x aka Ilford
2.4.x
Hoxton
2.2.x,2.3.x(Starting with SR5)
Greenwich
2.1.x
Finchley
2.0.x
Edgware
1.5.x
Dalston
1.5.x
本次使用的版本是 Hoxton.SR10,因此对应的 SpringBoot 版本是 2.3.x 版本。
服务拆分与远程调用 服务拆分注意事项
1、不同微服务,不要重复开发相同业务
2、微服务数据独立,不要访问其他微服务的数据库
3、微服务可以将自己的业务暴露为接口,供其他微服务调用
导入服务拆分 Demo
项目结构
cloud-demo
order-service(根据 id 查询订单)
user-service(根据 id 查询用户)
案例:根据订单 id 查询订单功能 需求:根据订单 id 查询订单的同时,把订单所属的用户信息一起返回
远程调用方式分析 1、注册 RestTemplate
在 order-service 的 OrderAppplication 中注册 RestTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 @MapperScan("cn.itcast.order.mapper") @SpringBootApplication public class OrderApplication { public static void main (String[] args) { SpringApplication.run(OrderApplication.class, args); } @Bean public RestTemplate restTemplate () { return new RestTemplate(); } }
2、服务远程调用 RestTemplate
修改 order-service 中的 OrderService 中的 queryOrderById 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Service public class OrderService { @Autowired private RestTemplate restTemplate; public Order queryOrderById (Long orderId) { Order order = orderMapper.findById(orderId); String url = "http://localhost:8081/user/" + order.getUserId(); User user = restTemplate.getForObject(url, User.class); order.setUser(user); return order; } }
Eureka 提供者与消费者
服务提供者:一次约业务中,被其他微服务调用的服务。(提供接口给其他微服务)
服务消费者:一次业务中,调用其他微服务的服务。(调用其他微服务提供的接口)
思考: 服务 A 调用 B,服务 B 调用服务 C,那么服务 B 是什么角色?(答:相对而言,业务不同身份不同)
Eureka 注册中心 服务调用出现的问题: “http://localhost:8081/user/" + order.getUserId();
服务消费者该如何获取服务者提供的地址信息?
如果有多个服务者,消费者该如何选择?
消费者如何得知服务提供者的健康状态?
Eureka 的作用
消费者如何获取服务提供者的具体信息?
服务提供者启动时向 Eureka 注册自己的信息
Eureka 保存这些信息
消费者根据服务名称向 Eureka 拉取提供者信息
如果有多个服务者,消费者该如何选择?
消费者如何得知服务提供者的健康状态?
服务提供者会每隔 30 秒向 EurekaServer 发送心跳请求,报告健康状态
Eureka 会更新记录到服务列表信息,心跳不正常会被删除
消费者就可以拉取到最新的信息
动手实践 搭建 EurekaServer 搭建 EurekaServer 服务步骤如下:
1、创建项目,引入 spring-cloud-starter-netfix-eureka-server 的依赖
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netfix-eureka-server</artifactId > </dependency >
2、编写启动类,添加 @EnableEurekaServer 注解
3、添加 application.yml 文件,编写下面配置:
1 2 3 4 5 6 7 8 9 server: port: 10086 spring: application: name: eurekaserver eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka
注册 user-service 将 user-service 项目引入到 EurekaServer 步骤如下:
1、在 user-service 项目中引入 spring-cloud-starter-netfix-eureka-client 的依赖
注意: 记得导入 spring-boot-starter-web 包
1 2 3 4 5 <!-- eureka客户端依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2、在 application.yml 文件,编写下面的配置:
1 2 3 4 5 6 7 spring: application: name: userservice eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka
另外,我们可以将 user-service 多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口配置
在 order-service 完成服务拉取 服务器拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡
1、修改 OrderService 的代码,修改访问的 url 路径,用服务名代替 ip、端口:
1 String url = "http://userserice/user" + order.getUserId();
2、在 order-service 项目的启动类 OrderApplication 中的 RestTemplate 添加负载均衡 注解:
1 2 3 4 5 @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate(); }
Ribbon 负载均衡 负载均衡流程
负载均衡策略 Ribbon 的负载均衡规则是一个叫做 IRule 的接口来定义的,每一个子接口都是一种规则:
内置负载均衡规则类
规则描述
RoundRobinRule
简单轮询服务列表来选择服务器。它是 Ribbon 默认的负载均衡规则
AvailabilityFilteringRule
对以下两种服务器进行忽略:(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“”短路“状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级的增加。(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了 AvailabilityFilteringRule 规则的客户端也会将其忽略。并发连接数的上线,可以由客户端的clientName.clientConfigNamespace.ActiveConnextionsLimit 属性进行配置
WeightedResponseTimeRule
为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule
以区域可用的服务器为基础进行服务器的选择。使用 Zone 对服务器进行分类,这个 Zone 可以理解为一个机房、一个机架等。然后再对 Zone 内的多个服务做轮询。
BestAvailableRule
忽略那些短路的服务器,选择并发数较低的服务器。
RandomRule
随机选择一个可用的服务器
RetryRule
重试机制的选择逻辑
通过定义 IRule 实现可以修改负载均衡规则,有两种方式:
1、代码方式(全局):在 order-service 中的 OrderApplication 类中,定义一个新的 Rule:
1 2 3 4 5 @Bean public IRule randomRule () { return new RandomRule(); }
2、配置文件方式(局部):在 order-service 的 application.yml 文件中,添加新的配置也可以修改规则:
1 2 3 userservice: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
饥饿加载 Ribbon 默认是采用懒加载,即第一次访问时才会去创建 LoadBalancerClient,请求的时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
1 2 3 4 ribbon: eager-load: enable: true clients: userservice
Nacos 注册中心 Nacos 是阿里巴巴的产品,现在是 SpringCloud 中的一个组件。相比 Eureka 功能更加丰富,在国内受欢迎的程度较高。
Nacos 安装指南 Windows 安装 开发阶段采用单机安装即可。
1.1、下载安装包
在 Nacos 的 github 页面,提供有下载连接,可以下载编译好的 Nacos 服务端或者源码
github 主页:https://github.com/alibaba/nacos
github 的 release 下载页:https://github.com/alibaba/nacos/releases
1.2、解压
1.3、端口配置
打开 conf 文件夹下的 application.properties 文件找到 server.port 即可更改端口,默认端口为 8848
1.4、启动
启动非常简单,进入 bin 目录,执行命令:startup.cmd -m standalone 即可
注意: nacos 默认账户和密码都为 nacos
服务注册到 Nacos 1、在父工程中添加 spring-cloud-alibaba 的管理依赖:
1 2 3 4 5 6 7 8 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-alibaba-dependencies</artifactId > <version > 2.2.5.RELEASE</version > <type > pom</type > <scope > import</scope > </dependency >
2、注释掉所有 eureka 依赖
3、添加 nacos 的客户端依赖
1 2 3 4 5 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency >
4、修改项目配置文件(application.yml),注释掉 eureka 地址,添加 nacos 地址:\
1 2 3 4 spring: cloud: nacos: server-addr: localhost:8848
5、启动并测试
Nacos 服务分级存储模型
服务跨集群调用问题 服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
本地集群不可访问时,再去访问其他集群
服务集群属性 1、修改 application.yml,添加如下内容:
1 2 3 4 5 6 spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: HZ
2、在 Nacos 控制台可以看到集群变化:
根据集群负载均衡 1、修改 order-service 中的 application.yml,设置集群为 HZ:
1 2 3 4 5 6 spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: HZ
2、然后在 order-service 中设置负载均衡的 IRule 为 NacosRule,这个规则会优先寻找与自己同集群的服务:
1 2 3 userservice: ribbion: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.NacosRule
3、注意将 user-service 的权重都设置为 1
根据权重负载均衡 实际部署中会出现这样的场景:
服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求
Nacos 提供了权重配置来控制访问频率,权重越大则访问频率越高、
1、在 Nacos 控制台可以设置实例的权重值,首先选中实例后面的编辑按钮
2、将权重设置为 0.1,测试可以发现 8081 被访问到的频率大大降低(权重为 0 时不会被访问)
环境隔离-namespace Nacos 中服务存储和数据存储的最外层都是一个名为 namespace 的东西,用来做最外层隔离
1、在 Nacos 控制台可以创建 namespace,用来隔离不同环境
2、然后填写一个新的命名空间信息
3、保存后会在控制台看到这个命名空间的 id
4、修改 order-service 的 application.yml,添加 namespace
1 2 3 4 5 6 7 spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: SH namespace: 33741fd3-501d-496d-a1cd-02b086f9c789
5、重启 order-service 后,再来查看控制台
6、此时访问 order-service,因为 namespace 不同,会导致找不到 userservice,控制台会报错
Nacos 注册中心细节分析
临时实例和非临时实例 服务注册到 Nacos 时,可以选择注册为临时或非临时实例,通过下面的配置来设置:
1 2 3 4 5 spring: cloud: nacos: discovery: ephemeral: false
总结:
1、Nacos 与 Euerka 的共同点
都支持服务注册和服务拉取
都支持服务提供者心跳方式做健康检测
2、Nacos 与 Eureka 的区别
Nacos 支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
临时实例心跳不正常会被剔除,非临时实例则不会被剔除
Nacos 支持服务列表变更的消息推送模式,服务列表更新及时
Nacos 集群默认采用 AP 方式,当集群中存在非临时实例时,采用 CP 模式;Eureka 采用 AP 方式
Nacos 配置管理 统一配置管理
在 Nacos 中添加配置信息:
在弹出表单中填写配置信息:
注意: 配置内容不要无脑填,要填那些将来会发生变化的内容
微服务配置拉取
1、引入 Nacos 的配置管理端客户依赖:
1 2 3 4 5 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency >
2、在 userservice 中的 resource 目录添加一个 bootstrap.yml 文件,这个文件是引导文件,优先级高于 application.yml:
1 2 3 4 5 6 7 8 9 10 spring: application: name: userservice profiles: active: dev cloud: nacos: server-addr: localhost:8848 config: file-extension: yaml
3、测试
我们在 user-service 中将 pattern.dateformat 这个属性注入到 UserController 中做测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController @RequestMapping("/user") public class UserController { @Value("${pattern.dateformat}") private String dateformat; @GetMapping("/now") public String now () { return LocalDate.now().format(DateTimeFormatter.ofPattern(dateformat, Locale.CHINA)); } }
配置热更新 Nacos 中的配置文件变更后,微服务无需添加就可以感知。不过需要通过下面两种配置实现:
方式一:在 @Value 注入的变量所在类上添加注解 @RefreshScope
1 2 3 4 5 6 7 8 @RestController @RequestMapping("/user") @RefreshScope public class UserController { @Value("${pattern.dateformat}") private String dateformat; }
方式二:使用 @ConfigurationProperties 注解
1 2 3 4 5 6 @Component @Data @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; }
多环境配置共享 微服务启动时会从 nacos 读取多个配置文件:
[spring.application.name]-[spring-profiles.active].yaml,例如:userservice-dev.yaml
[spring-application.name].yaml,例如:userservice.yaml
无论 profile 如何变化,[spring.application.name].yaml 这个文件一定会加载,因此多环境共享配置可以写入这个文件
多种配置的优先级:
服务名-profile.yaml > 服务名称.yaml > 本地配置
Nacos 集群搭建 Nacos 生产环境下一定要部署为集群状态
集群结构图
搭建集群 搭建集群的基本步骤:
搭建数据库,初始化数据库表结构
下载 nacos 安装包
配置 nacos
启动 nacos 集群
nginx 反向代理
初始化数据库
Nacos默认数据存储在内嵌数据库 Derby 中,不属于生产可用的数据库。
官方推荐的最佳实践时使用带有主从的高可用数据库集群。
这里我们以单点的数据库为例来讲解。
首先新建一个数据库,命名为 nacos,然后导入下面的 sql:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 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' ; 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= '增加租户字段' ; 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' ; 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' ; 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' ; 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容量信息表' ; 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= '多租户改造' ; 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 的 conf 目录,修改配置文件 cluster.conf.example,命名为 cluster.conf:
然后添加内容:
1 2 3 127.0.0.1:8845 127.0.0.1:8846 127.0.0.1:8847
然后修改 application.properties
1 2 3 4 5 6 7 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 =root
将 nacos 文件夹复制 3 份,分别命名为:nacos1、nacos2、nacos3
然后分别修改三个文件夹中的 application.properties
nacos1:
nacos2:
nacos3:
然后分别启动 nacos(注意!!!文件目录有中文可能会报错)
Nginx 反向代理
解压 Nginx 安装包到任意非中文目录下:
修改 conf/nginx.conf 文件,配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 客户端 Feign RestTemplate 方式调用存在的问题
先看看我们以前利用 RestTemplate 发起远程调用的代码:
1 2 String url = "http://userservice/user/" + order.getUserId(); User user = restTemplate.getForObject(url, User.class);
存在下面的问题:
代码可读性差,编程体验不统一
参数复杂 URL 难以维护
Feign 的介绍 Feign 是一个声明式的 http 客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现 http 请求发送,解决上面提到的问题
定义和使用 Feign 客户端 使用 Feign 的步骤如下:
1、引入依赖:
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
2、在 order-service 的启动类添加注解开启 Feign 的功能:
1 2 3 4 5 6 7 8 @EnableFeignClients @MapperScan("cn.itcast.order.mapper") @SpringBootApplication public class OrderApplication { public static void main (String[] args) { SpringApplication.run(OrderApplication.class, args); } }
3、编写 Feign 客户端:
1 2 3 4 5 @FeignClient("userservice") public interface UserClient { @GetMapping("/user/{id}") User findById (@PathVariable("id") Long id) ; }
主要是基于 SpringMVC 的注解来声明远程调用的信息,比如:
服务名称:userservice
请求方式:GET
请求路径:/user/{id}
请求参数: Long id
返回值类型:User
4、用 Feign 客户端代替 RestTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 @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 运行自定义配置来覆盖默认配置,可以修改的配置如下:
类型
作用
说明
feign.Logger.Level
修改日志级别
包含四种不同的级别:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder
响应结果的解析器
http远程调用的结果做解析,例如解析json 字符串为 java 对象
feign.codec.Encoder
请求参数编码
将请求参数编码,便于通过 http 请求发送
feign.Contract
支持的注解格式
默认是 SpringMVC 的注解
feign.Retryer
失败重试机制
请求失败的重试机制,默认是没有,不过会使用 Ribbon 的重试
一般我们需要配置的就是日志级别
配置 Feign 日志有两种方式:
方式一:配置文件方式
1、全局生效:
1 2 3 4 5 feign: client: config: default: loggerLevel: FULL
2、局部生效
1 2 3 4 5 feign: client: config: userservice: loggerLevel: FULL
方式二:java 代码方式,需要先声明一个 Bean:
1 2 3 4 5 6 public class FeignClientConfiguration { @Bean public Logger.Level feignLogLevle () { return Logger.Level.BASIC; } }
1、如果是全局配置,则把它放到 @EnableFeignClients 这个注解中:
1 @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
2、如果是局部配置,则把它放到 @FeignClient 这个注解中:
1 @FeignClient(value = "userservice", configuration = FeignClientConfiguration.class)
Feign 的性能优化 Feign 底层的客户端实现:
URLConnection:默认实现,不支持连接池
Apache HttpClient:支持连接池
OKHttp:支持连接池
因此优化 Feign 的性能主要包括:
1、使用连接池代替默认的 URLConnection
2、日志级别,最好用 basic 或 none
Feign 的性能优化-连接池配置 引入依赖:
1 2 3 4 <dependency > <groupId > io.github.openfeign</groupId > <artifactedId > feign-httpclient</artifactedId > </dependency >
配置连接池:
1 2 3 4 5 6 7 8 9 feign: client: config: default: loggerLevel: BASIC httpclient: enabled: true max-connections: 200 max-connections-per-route: 50
Feign 的实践 方法一(继承):给消费者的 FeignClient 和 提供者的 Controller 定义统一的 父接口作为标准。
方式二(抽取):将 FeignClient抽取为独立模块,并且把接口有关的 POJO、默认的 Feign 配置都放到这个模块中,提供给所有消费者使用
抽取 FeignClient 实现最佳实践方式二的步骤如下:
1、首先创建一个 module,命名为 feign-api,然后引入 feign 的 starter 依赖
2、将 order-service 中编写的 UserClient、User、DefaultFeignConfiguration 都复制到 feign-api 项目中
3、在 order-service 中引入 feign-api 的依赖
4、修改 order-service 中的所有与上述三个组件有关的 import 部分,改成导入 feign-api 中的包
5、重启测试
当定义的 FeignClient 不在 SpringBootApplication 的扫描包范围内时,这些 FeignClient 无法使用。有两种方法解决。
方式一:指定 FeignClient 所在包
1 @EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:指定 FeignClient 字节码
1 @EnableFeignClients(clients = {userClients.class})
统一网关 Gateway 为什么需要网关 网关功能:
网关的技术实现 在 SpringCloud 中网关的实现包括两种:
zuul 是基于 Servlet 的实现,属于阻塞式编程。而 SpringCloudGateway 则是基于 Spring5 中提供的 WebFlux,属于响应式编程的实现,具备更好的性能。
搭建网关服务 搭建网关服务的步骤: 1、创建新的 module,引入 SpringCloudGateway 的依赖和 nacos 的服务发现依赖:
1 2 3 4 5 6 7 8 9 10 11 <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 >
2、编写路由配置及 nacos 地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 server: port: 10010 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** - id: order-service uri: lb://orderservice predicates: - Path=/order/**
路由断言工厂 Route Predicate Factory 网关路由可以配置的内容包括:
路由 id:路由唯一标识
uri:路由目的地,支持 lb(loadbalanced) 和 http 两种
predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
filters:路由过滤器,处理请求或响应
我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory 读取并处理,转变为路由判断的条件
例如 Path=/user/** 是按照路径匹配,这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRouterPredicateFactory 类来处理的
像这样的断言工厂在 SpringCloudGateway 还有十几个
名称
说明
示例
After
是某个时间点后的请求
- After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before
是某个时间点之前的请求
- Before=2037-01-20T17:42:47.789-07:00[America/Denver]
Between
是某两个时间点之间的请求
- Between=2037-01-20T17:42:47.789-07:00[America/Denver],2037-02-20T17: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 GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
Spring 提供了 31 种不同的路由过滤器工厂。例如:
名称
说明
AddRequestHeader
给当前请求添加一个请求头
RemoveRequestHeader
移除请求中的一个请求头
AddResponseHeader
从响应结果中添加一个响应头
RemoveResponseHeader
从响应结果中移除一个响应头
RequestRateLimiter
限制请求的流量
…
全局过滤器 GlobalFilter 全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与 GatewayFilter 的作用一样。
区别在于 GatewayFilter 通过配置定义,处理逻辑是固定的。而 GlobalFilter 的逻辑需要自己写代码实现。
定义方式是实现 GlobalFilter 接口。
1 2 3 4 5 6 7 8 9 10 public interface GlobalFilter { Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) ; }
案例-定义全局过滤器,拦截并判断用户身份 需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
参数中是否有 authorization
authorization 参数值是否为 admin
如果同时满足则放行,否则拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Order(-1) @Component public class AuthorizeFilter implements GlobalFilter { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); MultiValueMap<String, String> params = request.getQueryParams(); String auth = params.getFirst("authorization" ); if ("admin" .equals(auth)) { return chain.filter(exchange); } exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } }
过滤器执行顺序 请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和 DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
每一个过滤器都必须指定一个 int 类型的 order 值,order 值越小,优先级越高,执行顺序越靠前
GlobalFilter 通过实现 Ordered 接口,或者添加 @Order 注解来指定 order 值,由我们自己指定
路由过滤器和 defaultFilter 的 order 由 Spring 指定,默认是按照声明顺序从 1 递增
当过滤器的 order 值一样是,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行
可以参考下面几个类的源码来查看:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters() 方法是先加载 defaultFilters,然后再加载某个 route 的 filters,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle() 方法会加载全局过滤器,与前面的过滤器合并后根据 order 排序,组织过滤器链
跨域问题处理 跨域:域名不一致就是跨域,主要包括:
跨域问题: 浏览器禁止请求的发起者与服务端发生跨域 ajax 请求,请求被浏览器拦截的问题
解决方案:CORS
网关处理跨域采用的同样是 CORS 方案,并且只需要简单配置即可实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 spring: cloud: gateway: globalcors: add-to-simple-url-handler-mapping: true corsConfigurations: '[/**]' : allwoedOrigins: - "http://localhost:8090" - "http://www.leyou.com" allowedMethods: - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowedHeaders: "*" allowCredentials: true maxAge: 360000
Docker 异步通信 分布式搜索 微服务保护 分布式事务 分布式缓存 多级缓存 可靠消息服务 Nacos 源码 Sentinel 源码