文章目录
官网介绍:https://spring.io/projects/spring-cloud-alibaba#overview
- AlibabaCloud全家桶介绍
- https://github.com/alibaba/spring-cloud-alibaba
- 服务注册发现:Nacos
- 服务限流降级:Sentinel
- 分布配置中心:Nacos
- 服务网关:SpringCloud Gateway
- 服务之间调用:Feign、Ribbon
- 链路追踪:Sleuth+Zipkin
分布式架构理论
CAP理论
-
CAP定理: 指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得
- 一致性(C):所有节点都可以访问到最新的数据
- 可用性(A):每个请求都是可以得到响应的,不管请求是成功还是失败
- 分区容错性(P):除了全部整体网络故障,其他故障都不能导致整个系统不可用
-
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡
CA: 如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的
CP: 如果不要求A(可用),每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统
AP:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。
CAP里面下的注册中心选择思考
- 常见注册中心:zk、eureka、nacos
- 那你应该怎么选择
Nacos | Eureka | Consul | Zookeeper | |
---|---|---|---|---|
一致性协议 | CP+AP | AP | CP | CP |
健康检查 | TCP/HTTP/MYSQL/Client Beat | 心跳 | TCP/HTTP/gRPC/Cmd | Keep Alive |
雪崩保护 | 有 | 有 | 无 | 无 |
访问协议 | HTTP/DNS | HTTP | HTTP/DNS | TCP |
SpringCloud集成 | 支持 | 支持 | 支持 | 支持 |
-
Zookeeper:CP设计,保证了一致性,集群搭建的时候,某个节点失效,则会进行选举行的leader,或者半数以上节点不可用,则无法提供服务,因此可用性没法满足
-
Eureka:AP原则,无主从节点,一个节点挂了,自动切换其他节点可以使用,去中心化
-
结论:
- 分布式系统中P,肯定要满足,所以只能在CA中二选一
- 没有最好的选择,最好的选择是根据业务场景来进行架构设计
- 如果要求一致性,则选择zookeeper/Nacos,如金融行业 CP
- 如果要求可用性,则Eureka/Nacos,如电商系统 AP
- CP:适合支付、交易类,要求数据强一致性,宁可业务不可用,也不能出现脏数据
- AP:互联网业务,比如信息流架构,不要求数据强一致,更想要服务可用
BASE理论 - 一致性和可用性的权衡结果
- 什么是Base理论
CAP 中的一致性和可用性进行一个权衡的结果,核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性, 来自 ebay 的架构师提出
-
Basically Available(基本可用)
- 假设系统,出现了不可预知的故障,但还是能用, 可能会有性能或者功能上的影响
-
Soft state(软状态)
- 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时
-
Eventually consistent(最终一致性)
- 系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值
线上部署(Docker)
环境部署
部署Nacos
- 拉取镜像:docker pull nacos/nacos-server
- 查看镜像:docker images
- 启动Nacos:docker run --env MODE=standalone --name xk857-nacos -d -p 8848:8848 ef8e53226440 (镜像id)
- 访问Nocos:http://公网ip:8848/nacos
部署Sentinel
- docker拉取镜像:docker pull bladex/sentinel-dashboard:latest
- 查看镜像:docker images
- 启动Sentinel:docker run --name sentinel -d -p 8858:8858 镜像id
- 访问Sentinel:http://公网ip:8858
登录密码默认sentinel/sentinel
环境准备
数据库准备
- 新建数据库cloud_video,新建video表
CREATE TABLE `video` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(524) DEFAULT NULL COMMENT '视频标题',
`summary` varchar(1026) DEFAULT NULL COMMENT '概述',
`cover_img` varchar(524) DEFAULT NULL COMMENT '封面图',
`price` int(11) DEFAULT NULL COMMENT '价格,分',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`point` double(11,2) DEFAULT '8.70' COMMENT '默认8.7,最高10分',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8;
INSERT INTO `video` (`id`, `title`, `summary`, `cover_img`, `price`, `create_time`, `point`)
VALUES
(30, '互联网架构之JAVA虚拟机JVM零基础到高级实战', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/maven/%E8%AF%A6%E6%83%85%E5%9B%BE.png', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/maven/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-mawen.png', 3980, '2021-06-24 22:14:00', 9.10),
(40, '全新微信小程序零基础到项目实战', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E8%AF%A6%E6%83%85%E5%9B%BE.png', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-%E5%B0%8F%E7%A8%8B%E5%BA%8F.png', 5980, '2021-01-18 22:14:00', 9.10),
(41, '玩转搜索框架ElasticSearch7.x实战', 'https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_backend/elasticsearch7_detail.jpeg', 'https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_backend/elasticsearch7.png', 4880, '2021-01-10 22:14:00', 8.70),
(45, 'Docker实战视频教程入门到高级dockerfile/compose-Harbor', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/Docker/%E8%AF%A6%E6%83%85%E5%9B%BE.jpeg', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/Docker/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-docker.png', 5980, '2021-01-10 22:14:00', 9.30),
(46, '新版javase零基础到高级教程小白自学编程', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/%E6%96%B0%E7%89%88javase/%E8%AF%A6%E6%83%85%E5%9B%BE.png', 'https://file.xdclass.net/video/2020/%E6%96%B0%E7%89%88javase/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-javase.png', 3980, '2021-01-24 22:14:00', 8.80),
(47, 'Nodejs教程零基础入门到项目实战前端视频教程', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/node/%E5%AE%98%E7%BD%91%E8%AF%A6%E6%83%85%E5%9B%BE-node.png', 'https://xdvideo-file.oss-cn-shenzhen.aliyuncs.com/video/2020/node/%E5%AE%98%E7%BD%91%E4%B8%BB%E5%9B%BE-node.png', 6980, '2021-01-24 22:14:00', 8.90);
- 新建数据库cloud_user,新建user表
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`phone` varchar(32) DEFAULT NULL,
`pwd` varchar(128) DEFAULT NULL,
`sex` int(2) DEFAULT NULL,
`img` varchar(128) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`role` int(11) DEFAULT NULL COMMENT '1是普通用户,2是管理员',
`username` varchar(128) DEFAULT NULL,
`wechat` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
INSERT INTO `user` (`id`, `phone`, `pwd`, `sex`, `img`, `create_time`, `role`, `username`, `wechat`)
VALUES
(1, '123', '666', 1, 'xdclass.net', '2021-09-09 00:00:00', 1, 'jack', 'xdclass6'),
(2, '2323432', '794666918', 1, 'wwwww', '2020-05-20 04:54:01', 1, '小滴Anna姐姐', 'xdclass-anna'),
(3, '2323432', 'xdclass-lw', 1, 'wwwww', '2020-05-20 04:54:42', 1, '二当家小D', 'xdclass1'),
(4, '2323432', '3232323', 1, 'wwwww', '2020-05-20 04:55:07', 1, '老王', 'xdclass-lw');
- 新建数据库cloud_order,新建video_order表
CREATE TABLE `video_order` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '订单唯一标识',
`state` int(11) DEFAULT NULL COMMENT '0表示未支付,1表示已支付',
`create_time` datetime DEFAULT NULL COMMENT '订单生成时间',
`total_fee` int(11) DEFAULT NULL COMMENT '支付金额,单位分',
`video_id` int(11) DEFAULT NULL COMMENT '视频主键',
`video_title` varchar(256) DEFAULT NULL COMMENT '视频标题',
`video_img` varchar(256) DEFAULT NULL COMMENT '视频图片',
`user_id` int(12) DEFAULT NULL COMMENT '用户id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8;
使用Maven创建聚合工程
- maven聚合工程
- xk857-common
- xk857-video-service
- xk857-user-service
- xk857-order-service
第一步:创建聚合工程
注意:聚合工程不需要src目录,直接删除即可
- pom文件示例
<groupId>com.xk857</groupId>
<artifactId>xk857-cloud</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 一般来说父级项目的packaging都为pom,packaging默认类型jar类型-->
<packaging>pom</packaging>
<!-- JDK版本等配置信息信息 -->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<!-- 锁定版本 -->
<dependencyManagement>
<dependencies>
<!--SpringBoot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringCloud版本-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--AlibabaCloud版本-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
第二步:创建工具模块
xk857-common
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
</dependencies>
第三步:创建3个子项目
添加子项目依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.xk857</groupId>
<artifactId>xk857-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
引入Mybatis
在common中创建实体类,例
@Data
public class VideoOrder {
private Integer id;
private String outTradeNo;
private Integer state;
private Date createTime;
private Integer totalFee;
private Integer videoId;
private String videoTitle;
private String videoImg;
private Integer userId;
}
在聚合工程添加Mybatis依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
在3个子工程添加依赖
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
3个子项目新建application.yml
配置数据库连接
server:
port: 9000
spring:
application:
name: xk857-video-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.100.177.78:12345/cloud_video?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
# 控制台输出sql、下划线转驼峰
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
创建启动类
@SpringBootApplication
@MapperScan("com.xk857,dao")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
Nacos注册中心
-
官网:https://nacos.io/zh-cn/
-
下载管理:
- https://download.fastgit.org/alibaba/nacos/releases/download/2.0.0-ALPHA.2/nacos-server-2.0.0-ALPHA.2.tar.gz
- https://download.fastgit.org/alibaba/nacos/releases/download/2.0.0-ALPHA.2/nacos-server-2.0.0-ALPHA.2.zip
-
Linux/Mac安装Nacos
- 解压安装包
- 进入bin目录
- 启动 sh startup.sh -m standalone
- 访问 localhost:8848/nacos
- 默认账号密码 nacos/nacos
- 关闭: sh shutdown.sh
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JJFi3tpq-1635235670363)(5587F1C3EAA84FF68E95487392264829)]
基本使用
模块中添加依赖
<!--添加nacos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
视频微服务配置
spring:
application:
name: xk857-video-service
cloud:
nacos:
discovery:
server-addr: nacos.xk857.com
启动类增加注解
@EnableDiscoveryClient
简单实现服务调用
获取nacos
服务列表
@Autowired
private DiscoveryClient discoveryClient;
获取服务
//获取指定服务的集合(实际中可能会有集群)
List<ServiceInstance> instances = discoveryClient.getInstances("k857-order-service");
//这里只有一个,获取那一个服务就好
ServiceInstance serviceInstance = instances.get(0);
Ribbon负载均衡
-
软硬件角度负载均衡的种类
- 通过硬件来进行解决,常见的硬件有NetScaler、F5、Radware和Array等商用的负载均衡器,但比较昂贵的
- 通过软件来进行解决,常见的软件有LVS、Nginx等,它们是基于Linux系统并且开源的负载均衡策略
-
常见的负载均衡策略(看组件的支持情况)
- 节点轮询
- 简介:每个请求按顺序分配到不同的后端服务器
- weight 权重配置
- 简介:weight和访问比率成正比,数字越大,分配得到的流量越高
- 固定分发
- 简介:根据请求按访问ip的hash结果分配,这样每个用户就可以固定访问一个后端服务器
- 随机选择、最短响应时间等等
- 节点轮询
AlibabaCloud集成Ribbon实现负载均衡
- 什么是Ribbon Ribbon是一个客户端负载均衡工具,通过Spring Cloud封装,可以轻松和AlibabaCloud整合
- 订单服务增加@LoadBalanced 注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
负载均衡Ribbon和OpenFeign
负载均衡策略调整
- Ribbon支持的负载均衡策略介绍
策略类 | 命名 | 描述 |
---|---|---|
RandomRule | 随机策略 | 随机选择server |
RoundRobinRule | 轮询策略 | 按照顺序选择server(默认) |
RetryRule | 重试策略 | 当选择server不成功,短期内尝试选择一个可用的server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值) |
WeightedResponseTimeRule | 响应时间加权重策略 | 根据server的响应时间分配权重,以响应时间作为权重,响应时间越短的服务器被选中的概率越大,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间 |
ZoneAvoidanceRule | 区域权重策略 | 综合判断server所在区域的性能,和server的可用性,轮询选择server |
订单服务增加配置
# 使用随机负载均衡
xdclass-video-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
-
注意:订单服务中api方法名那边个save方法,还原为findById,备课的时候忘记还原,不影响使用
-
策略选择:
- 如果每个机器配置一样,则建议不修改策略 (推荐)
- 如果部分机器配置强,则可以改为 WeightedResponseTimeRule
OpenFeign
官方文档:https://spring.io/projects/spring-cloud-openfeign
基本使用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
配置注解
启动类增加@EnableFeignClients
增加一个接口
订单服务增加接口,服务名称记得和nacos保持一样
@FeignClient(name="xdclass-video-service")
编写代码
@GetMapping(value = "/api/v1/video/find_by_id")
Video findById(@RequestParam("videoId") int videoId);
案例
GET方式
视频微服务
@RestController
@RequestMapping("api/v1/video")
public class VideoController {
@Autowired
private VideoService videoService;
@RequestMapping("find_by_id")
public Video findById(int videoId){
return videoService.findById(videoId);
}
}
订单微服务调用视频微服务
Controller
@RestController
@RequestMapping("api/v1/order")
public class OrderController {
@Autowired
private VideoService videoService;
@RequestMapping("/find_by_id")
public Video findOrderById(Integer id){
return videoService.findById(id);
}
}
Service
@FeignClient(name = "xk857-video-service")
public interface VideoService {
@GetMapping(value = "/api/v1/video/find_by_id")
Video findById(@RequestParam("videoId") int videoId);
}
POST方式
@PostMapping(value = "/api/v1/video/save")
Video saveVideo(@RequestBody Video video);
@PostMapping("save")
public Object save(@RequestBody Video video){
System.out.println(video.getTitle());
return video;
}
流量防卫兵-Sentinel
-
熔断:保险丝,熔断服务,为了防止整个系统故障,包含当前和下游服务 下单服务 -> 商品服务-> 用户服务 -> (出现异常-> 熔断风控服务
-
降级:抛弃一些非核心的接口和数据,返回兜底数据 旅行箱的例子:只带核心的物品,抛弃非核心的,等有条件的时候再去携带这些物品
-
隔离:服务和资源互相隔离,比如网络资源,机器资源,线程资源等,不会因为某个服务的资源不足而抢占其他服务的资源
-
熔断和降级互相交集
- 相同点:
- 从可用性和可靠性触发,为了防止系统崩溃
- 最终让用户体验到的是某些功能暂时不能用
- 不同点
- 服务熔断一般是下游服务故障导致的,而服务降级一般是从整体系统负荷考虑,由调用方控制
- 相同点:
-
想进行微服务的容错,业界目前有Sentinel、Hystrix,相对于AlibabaCloud而言,Sentinel是最好的搭配
什么是Sentinel
- 阿里巴巴开源的分布式系统流控工具
- 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性
- 丰富的应用场景:消息削峰填谷、集群流量控制、实时熔断下游不可用应用等
- 完备的实时监控:Sentinel 同时提供实时的监控功能
- 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合
- 官网:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9y6kVD3P-1635235670376)(3FDB0138685647F3B0393D57CD31B2B4)]
基本使用
- 微服务引入Sentinel依赖,三个子模块都引入
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<!--去除jackson-dataformat-xml,否则会返回xml文件,而不是JSON-->
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
- 配置
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
port: 9999
# dashboard: 8080 控制台端口
# port: 9999 本地启的端口,随机选个不能被占用的,与dashboard进行数据交互,会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互, 若被占用,则开始+1一次扫描
流控的使用和规则
使用
- 设置流控规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cFo8wdWp-1635235670378)(639D5F66F5A64822A5151E3A7E3F5425)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rMlCPCTq-1635235670380)(5FCCF3914621499D8796A343A50315D4)]
- 流控规则
-
两种规则
- 基于统计并发线程数的流量控制
并发数控制用于保护业务线程池不被慢调用耗尽
Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目)如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。- 基于统计QPS的流量控制
当 QPS 超过某个阈值的时候,则采取措施进行流量控制
流控规则效果
- 直接拒绝:默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝
- Warm Up:冷启动/预热,如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值
- 匀速排队:严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法,主要用于处理间隔性突发的流量,如消息队列,想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求
- 注意:
- 匀速排队等待策略是 Leaky Bucket 算法结合虚拟队列等待机制实现的。
- 匀速排队模式暂时不支持 QPS > 1000 的场景
熔断降级规则
-
熔断降级(虽然是两个概念,基本都是互相配合)
- 对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一
- 对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩
- 熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置
-
什么是Sentinel降级规则
- 文档:https://github.com/alibaba/Sentinel/wiki/熔断降级
- 就是配置一定规则,然后满足之后就对服务进行熔断降级
-
Sentinel 熔断策略
-
慢调用比例(响应时间): 选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用
- 比例阈值:修改后不生效-目前已经反馈给官方那边的bug
- 熔断时长:超过时间后会尝试恢复
- 最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
-
异常比例:当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
- 比例阈值
- 熔断时长:超过时间后会尝试恢复
- 最小请求数:熔断触发的最小请求数,请求数小于该值时,即使异常比率超出阈值也不会熔断
-
异常数:当单位统计时长内的异常数目超过阈值之后会自动进行熔断
- 异常数:
- 熔断时长:超过时间后会尝试恢复
- 最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
-
Sentinel自定义异常
Sentinel自定义异常降级-新旧版本差异
-
默认降级返回数据问题
- 限流和熔断返回的数据有问题
- 微服务交互基本都是json格式,如果让自定义异常信息
-
AlibabCloud版本升级,不兼容问题
- v2.1.0到v2.2.0后,Sentinel里面依赖进行了改动,且不向下兼容
-
自定义降级返回数据
- 【旧版】实现UrlBlockHandler并且重写blocked方法
@Component public class XdclassUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException { //降级业务处理 } }
- 【新版】实现BlockExceptionHandler并且重写handle方法
public class XdclassUrlBlockHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { //降级业务处理 } }
自定义降级异常
-
异常种类
FlowException //限流异常 DegradeException //降级异常 ParamFlowException //参数限流异常 SystemBlockException //系统负载异常 AuthorityException //授权异常
-
【新版】实现BlockExceptionHandler并且重写handle方法
@Component public class XdclassUrlBlockHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException { Map<String,Object> backMap=new HashMap<>(); if (e instanceof FlowException){ backMap.put("code",-1); backMap.put("msg","限流-异常啦"); }else if (e instanceof DegradeException){ backMap.put("code",-2); backMap.put("msg","降级-异常啦"); }else if (e instanceof ParamFlowException){ backMap.put("code",-3); backMap.put("msg","热点-异常啦"); }else if (e instanceof SystemBlockException){ backMap.put("code",-4); backMap.put("msg","系统规则-异常啦"); }else if (e instanceof AuthorityException){ backMap.put("code",-5); backMap.put("msg","认证-异常啦"); } // 设置返回json数据 httpServletResponse.setStatus(200); httpServletResponse.setHeader("content-Type","application/json;charset=UTF-8"); httpServletResponse.getWriter().write(JSON.toJSONString(backMap)); } }
Sentinel整合Open-Feign
简单理解:使用Open-Feign获取远程数据时,如果远程数据出现故障获取不到,我们可以实现这个Service接口,当没有远程数据时,会将兜底数据展现给用户。
整合步骤
- 加入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 开启Feign对Sentinel的支持
feign:
sentinel:
enabled: true
- 创建容错类, 实现对应的服务接口, 记得加注解 @Service
@Service
public class VideoServiceFallback implements VideoService {
@Override
public Video findById(int videoId) {
Video video = new Video();
video.setTitle("熔断降级数据");
return video;
}
@Override
public Video saveVideo(Video video) {
return null;
}
}
- 在Service层,配置feign容错类
@FeignClient(value = “xdclass-video-service”, fallback = VideoServiceFallback.class)
@FeignClient(value = "xdclass-video-service", fallback = VideoServiceFallback.class)
public interface VideoService {
@GetMapping(value = "/api/v1/video/find_by_id")
Video findById(@RequestParam("videoId") int videoId);
……
}
Gateway
基本使用
新建模块:xk857-gateway
依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
配置文件
server:
port: 8888
spring:
application:
name: api-gateway
cloud:
gateway:
routes: #数组形式
- id: order-service #路由唯一标识
uri: http://127.0.0.1:9001 #想要转发到的地址
order: 1 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发
- Path=/order-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀
启动类
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
此时访问:http://localhost:8888/order-server/api/v1/order/find_by_id?id=30
会转发到:http://localhost:9001/api/v1/order/find_by_id?id=30
Gateway配置Nocas
-
原先存在的问题
- 微服务地址写死
- 负载均衡没做到
-
添加Nacos服务治理配置
- 网关添加naocs依赖
<!--添加nacos客户端--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
- 启动类开启支持
@EnableDiscoveryClient
- 修改配置文件
server: port: 8888 spring: application: name: api-gateway cloud: nacos: discovery: server-addr: api.xk857.com:8848 gateway: routes: #数组形式 - id: order-service #路由唯一标识 uri: lb://xdclass-order-service # 从nacos获取名称转发,lb是负载均衡轮训策略 predicates: #断言 配置哪个路径才转发 - Path=/order-server/** filters: #过滤器,请求在传递过程中通过过滤器修改 - StripPrefix=1 #去掉第一层前缀 discovery: locator: enabled: true #开启网关拉取nacos的服务
内置断言实现接口定时下线
- 需求:接口需要在指定时间进行下线,过后不可以在被访问
- 使用Before ,只要当前时间小于设定时间,路由才会匹配请求
- 东8区的2020-09-11T01:01:01.000+08:00后,请求不可访问
- 为了方便测试,修改时间即可
predicates:
- Before=2021-09-01T01:01:01.000+08:00
GateWay过滤器
-
过滤器生命周期
- PRE: 这种过滤器在请求被路由之前调用,一般用于鉴权、限流等
- POST:这种过滤器在路由到微服务以后执行,一般用于修改响应结果,比如增加header信息、打点结果日志
-
网关过滤器分类
- 局部过滤器GatewayFilter:应用在某个路由上,每个过滤器工厂都对应一个实现类,并且这些类的名称必须以 GatewayFilterFactory 结尾
- 全局过滤器:作用全部路由上,
Gateway全局过滤器实现用户鉴权
@Component
public class UserGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求头中第一个"token",一般token也就是唯一的一个
String token = exchange.getRequest().getHeaders().getFirst("token");
if (StringUtils.isBlank(token)){
//如果为空代表不成功,返回状态码,实际开发中可以返回json数据进行交互
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// TODO 根据业务开发对应鉴权 如果token不为空代表成功,实际业务应该进行判断token是否正确
//继续往下执行
return chain.filter(exchange) ;
}
//过滤器优先级,数字越小,优先级越高
@Override
public int getOrder() {
return 0;
}
}
网关不建议加入太多的业务逻辑,否则会影响性能
链路追踪
-
抛两个常见的问题
- 微服务调用链路出现了问题怎么快速排查?
- 微服务调用链路耗时长怎么定位是哪个服务?
-
链路追踪系统
- 分布式应用架构虽然满足了应用横向扩展的需求,但是运维和诊断的过程变得越来越复杂,例如会遇到接口诊断困难、应用性能诊断复杂、架构分析复杂等难题,传统的监控工具并无法满足,分布式链路系统由此诞生
-
核心:将一次请求分布式调用,使用GPS定位串起来,记录每个调用的耗时、性能等日志,并通过可视化工具展示出来
-
注意:AlibabaCloud全家桶还没对应的链路追踪系统,我们使用Sleuth和zipking(内部使用的鹰眼)
Sleuth - 链路追踪
- 什么是Sleuth
- 一个组件,专门用于记录链路数据的开源组件
- 文档:https://spring.io/projects/spring-cloud-sleuth
-
各个微服务添加依赖(包括网关和3个微服务)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
ZipKin - 可视化链路追踪
-
什么是zipkin
- 官网
- https://zipkin.io/
- https://zipkin.io/pages/quickstart.html
- 大规模分布式系统的APM工具(Application Performance Management),基于Google Dapper的基础实现,和sleuth结合可以提供可视化web界面分析调用链路耗时情况
- 官网
-
同类产品
- 鹰眼(EagleEye)
- CAT
- twitter开源zipkin,结合sleuth
- Pinpoint,运用JavaAgent字节码增强技术
- StackDriver Trace (Google)
-
下载地址:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
-
开始使用:java -jar zipkin-server-2.12.9-exec.jar
- 访问入口:http://127.0.0.1:9411/zipkin/
- zipkin组成:Collector、Storage、Restful API、Web UI组成
Zipkin+Sleuth整合
- sleuth收集跟踪信息通过http请求发送给zipkin server
- zipkin server进行跟踪信息的存储以及提供Rest API即可
- Zipkin UI调用其API接口进行数据展示默认存储是内存,可也用mysql 或者elasticsearch等存储
- 微服务加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
- 配置地址和采样百分比配置(每个模块都配置)
spring:
application:
name: api-gateway
zipkin:
base-url: http://127.0.0.1:9411/ #zipkin地址
discovery-client-enabled: false #不用开启服务发现
sleuth:
sampler:
probability: 1.0 #采样百分比
默认为0.1,即10%,这里配置1,是记录全部的sleuth信息,是为了收集到更多的数据(仅供测试用)。
在分布式系统中,过于频繁的采样会影响系统性能,所以这里配置需要采用一个合适的值。
Zipkin持久化配置
-
现存在的问题
- 服务重启会导致链路追踪系统数据丢失
-
持久化配置:mysql或者elasticsearch
- sql文件:点击下载
- 启动命令
java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin_log --MYSQL_USER=root --MYSQL_PASS=xdclass.net
配置中心 - Nocas
- 现在微服务存在的问题
- 配置文件增多,不好维护
- 修改配置文件需要重新发布
- 什么是配置中心:
- 一句话:统一管理配置, 快速切换各个环境的配置
- 相关产品:
- 百度的disconf 地址:https://github.com/knightliao/disconf
- 阿里的diamand 地址:https://github.com/takeseem/diamond
- springcloud的configs-server: 地址:http://cloud.spring.io/spring-cloud-config/
- 阿里的Nacos:既可以当服务治理,又可以当配置中心,Nacos = Eureka + Config
开始使用
- 项目添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
-
官方文档
- https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config
-
配置文件优先级
- 不能使用原先的application.yml, 需要使用bootstrap.yml作为配置文件
- 配置读取优先级 bootstrap.yml > application.yml
-
配置实操
- 订单服务迁移配置
- 增加bootstrap.yml
spring: application: name: xdclass-order-service cloud: nacos: config: server-addr: 127.0.0.1:8848 #Nacos配置中心地址 file-extension: yaml #文件拓展格式 profiles: active: dev
启动微服务服务验证
- 测试是否可以获取配置
浏览器访问
http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=xdclass-order-service-dev.yaml&group=DEFAULT_GROUP
如果出现 config dta not exist 建议重启nacos