06谷粒商城-高级篇六
前言
人到洛阳花似锦,偏我来时不逢春
17.商城业务-秒杀服务
17.1后台添加秒杀商品
主要步骤:
- 导入每日秒杀前端界面
gulimall-gateway
网关服务配置gulimall-coupon
路由- 优化秒杀关联商品接口
导入每日秒杀前端界面
gulimall-gateway
网关服务配置gulimall-coupon
路由
优化秒杀关联商品接口
界面
17.2定时任务&Cron表达式
创建gulimall-seckill
秒杀服务
创建gulimall-seckill
秒杀服务
导入依赖,主要有rabbitmq
、redis
、springsession
配置application.yaml
gulimall-seckill
开启SpringSession
、OpenFeign
、nacos
,禁用datasource
定时任务
语法:秒 分 时 日 月 周 年(Spring 不支持)
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html
cron 示例
17.3SpringBoot整合定时任务与异步任务
开启定时任务
@EnableScheduling
【spring 默认是使用自己的定时任务,如果想整合Quartz,参考官方】@Scheduled
- 定时任务配置类:
TaskSchedulingAutoConfiguration
@Slf4j
@Component
@EnableScheduling
public class HelloScheduled {
// 每s执行
@Scheduled(cron = "0/1 * * * * ? ")
public void hello() {
log.info("hello...");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
定时任务+异步任务
- 方案1:业务方法自己作异步编排【
CompletableFuture.runAsync
】 - 方案2:修改定时任务线程池的线程个数【
spring.task.scheduling.pool.size=5
】(不一定生效,有BUG) - 方案3:让定时任务异步执行
方案3:让定时任务异步执行
- 1.
@EnableAsync
- 2.
@Async
- 3.异步配置类:
TaskExecutionAutoConfiguration
spring.task.execution.pool.core-size=5
:核心线程数spring.task.execution.pool.max-size=50
:最大线程数
@EnableAsync
和@Async
@Slf4j
@Component
@EnableAsync
@EnableScheduling
public class HelloScheduled {
// @Scheduled(cron = "*/5 * * ? * 4")
// 每s执行
@Async
@Scheduled(cron = "0/1 * * * * ? ")
public void hello() {
log.info("hello...");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
异步配置类
spring:
task:
execution:
pool:
core-size: 5 # 核心线程数
max-size: 50 # 最大线程数
17.4时间日期处理
主要步骤:
gulimall-seckill
秒杀服务远程调用gulimall-coupon
优惠卷服务查询最近三天的活动gulimall-coupon
优惠卷服务实现查询最近三天的活动- 处理最近三天的日期
gulimall-seckill
秒杀服务远程调用gulimall-coupon
优惠卷服务查询最近三天的活动
gulimall-coupon
优惠卷服务实现查询最近三天的活动
- 处理最近三天的日期
测试代码
@Test
public void testTime(){
LocalDate now = LocalDate.now();
LocalDate plus1 = now.plusDays(1);
LocalDate plus2 = now.plusDays(2);
System.out.println(now);
System.out.println(plus1);
System.out.println(plus2);
LocalTime min = LocalTime.MIN;
LocalTime max = LocalTime.MAX;
System.out.println(min);
System.out.println(max);
LocalDateTime start = LocalDateTime.of(now,min);
LocalDateTime end = LocalDateTime.of(plus2,max);
System.out.println(start);
System.out.println(end);
// 格式化最近三天的日期
String format1 = start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String format2 = end.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("开始时间:"+format1);
System.out.println("结束时间:"+format2);
}
17.5秒杀商品上架-1
主要步骤:
- 远程调用
gulimall-coupon
查询最近三天的活动 - 在
redis
中缓存活动信息 - 在
redis
中缓存活动关联的商品信息- 商品信息,远程调用
gulimall-product
,获取商品sku
信息
- 商品信息,远程调用
在redis
中缓存活动信息
在redis
中缓存活动关联的商品信息
- 商品信息
17.6秒杀商品上架-2
主要步骤:
- 远程调用
gulimall-coupon
查询最近三天的活动 - 在
redis
中缓存活动信息 - 在
redis
中缓存活动关联的商品信息- 商品信息,远程调用
gulimall-product
,获取商品sku
信息 - 秒杀信息
- 秒杀时间信息
- 设置商品的随机码(防止恶意攻击)
- 使用库存作为分布式
Redisson
信号量(限流)
- 商品信息,远程调用
17.7秒杀商品上架-3
主要步骤:
- 测试秒杀商品上架
启动定时任务开启秒杀商品上架,设置的cron
表达式每分钟执行一次
@Slf4j
@Service
public class SeckillScheduled {
@Autowired
private SeckillService seckillService;
// TODO 保证幂等性问题
// @Scheduled(cron = "*/5 * * * * ?")
@Scheduled(cron = "0 * * * * ?")
public void uploadSeckillSkuLatest3Days() {
log.info("上架秒杀的商品...");
seckillService.uploadSeckillSkuLatest3Days();
}
}
缓存活动信息的key
:SESSION_CACHE_PREFIX + startTime + "_" + endTime
缓存活动信息的value
:活动场次Id + "_" + 商品skuId
缓存秒杀商品的的key
:seckill:skus
缓存秒杀商品的的value
key
:活动场次Id + "_" + 商品skuId
value
:商品sku
信息、秒杀信息、秒杀时间信息、商品的随机码(防止恶意攻击)、使用秒杀总量作为分布式Redisson
信号量(限流)
使用秒杀总量作为分布式Redisson
信号量(限流)
分布式Redisson
信号量的key
:seckill:stock:
分布式Redisson
信号量的value
:秒杀总量
17.8幂等性保证
主要步骤:
- 秒杀商品上架时添加分布式锁
redis
保存活动信息,判断seckill:sessions: + startTime + "_" + endTime
是否已经存在redis
保存秒杀商品信息时,判断seckill:skus
中是否存在活动场次Id + "-" + 商品skuId
秒杀商品上架时添加分布式锁
redis
保存活动信息,判断seckill:sessions: + startTime + "_" + endTime
是否已经存在
redis
保存秒杀商品信息时,判断seckill:skus
中是否存在活动场次Id + "-" + 商品skuId
17.9查询秒杀商品
主要步骤:
-
管理后台创建一个当前时间范围内的秒杀活动,并关联商品
-
实现获取到当前可以参加秒杀商品的信息
- 获取当前时间,获取活动场次的所有
key
,因为key
中包含活动场次开始时间和结束时间,查询出活动场次中包含的所有秒杀商品的key
(场次Id + "-" + 商品skuId
) - 根据开始时间和结束时间获取所有秒杀商品的
key
,再去seckill:skus
获取所有的秒杀商品
- 获取当前时间,获取活动场次的所有
-
秒杀商品界面渲染
-
管理员运行
SwitchHost
,配置seckill.gulimall.com
转发 -
gulimall-gateway
网关服务配置gulimall-seckill
秒杀服务路由
管理后台创建一个当前时间范围内的秒杀活动,并关联商品
实现获取到当前可以参加秒杀商品的信息
- 获取当前时间,获取活动场次的所有
key
,因为key
中包含活动场次开始时间和结束时间,查询出活动场次中包含的所有秒杀商品的key
(场次Id + "-" + 商品skuId
) - 根据开始时间和结束时间获取所有秒杀商品的
key
,再去seckill:skus
获取所有的秒杀商品
秒杀商品界面渲染
主界面秒杀商品渲染完成
管理员运行SwitchHost
,配置seckill.gulimall.com
转发
gulimall-gateway
网关服务配置gulimall-seckill
秒杀服务路由
- id: gulimall-seckill_route
uri: lb://gulimall-seckill
predicates:
- Host=seckill.gulimall.com
Bug:数据库时间和界面显示时间相差8小时
主要配置serverTimezone=Asia/Shanghai
和time-zone
spring:
application:
name: gulimall-coupon
cloud:
nacos:
discovery:
server-addr: 192.168.188.180:8848 # nacos地址
datasource:
url: jdbc:mysql://192.168.188.180:3306/mall_sms?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
17.10秒杀页面渲染
主要步骤:
gulimall-seckill
实现skuId
查询商品秒杀信息- 查询
seckill:skus
(所有秒杀商品信息),获取所有keys
- 遍历
seckill:skus
,判断key
是否包含skuId
- 如果包含
skuId
,判断当前时间是否在秒杀活动时间区间
- 查询
gulimall-product
使用OpenFeign
添加gulimall-seckill
的商品秒杀信息接口gulimall-product
的skuItem
方法获取sku
商品详情时,异步编排调用gulimall-seckill
查询商品秒杀信息item.html
商品详情界面添加秒杀信息
gulimall-seckill
实现skuId
查询商品秒杀信息
- 查询
seckill:skus
(所有秒杀商品信息),获取所有keys
- 遍历
seckill:skus
,判断key
是否包含skuId
- 如果包含
skuId
,判断当前时间是否在秒杀活动时间区间
gulimall-product
使用OpenFeign
添加gulimall-seckill
的商品秒杀信息接口
gulimall-product
的skuItem
方法获取sku
商品详情时,CompletableFuture
异步编排调用gulimall-seckill
查询商品秒杀信息
item.html
商品详情界面添加秒杀信息
商品详情界面
17.11秒杀系统设计
秒杀系统设计
- 单一职责
- 秒杀链接加密
- 随机码,秒杀开始才暴露
- 库存预热+快速扣减(redis存储库存信号量,最终正常进入购物车的流量最多是库存数)
- 按照库存信号量原子扣减
- 动静分离
- nginx/CDN
- 恶意请求拦截
- 网关层按照访问次数拦截脚本请求【异常请求】
- 流量错峰
- 【最重要是体现在秒杀开始的那一刻的错峰】判断登录状态、输入验证码、加入购物车、提交订单
- 限流&熔断&降级
- 前端限流:间隔1秒允许点击
- 后端限流:
- 限制次数:同一个用户10次放行2次
- 限制总量:秒杀服务峰值处理能力10万,网关层放行不得超过10万,超过的等待两秒放行
- 熔断:A->B->C,链路中B总是失败,则下次调用时直接返回错误不调用B
- 降级:流量太大,秒杀模块将流量引导到降级页面,服务繁忙页【正常请求】
- 队列削峰(杀手锏)
- 扣减库存信号量成功的秒杀信息存入队列,订单系统监听队列创建订单(按照自己的处理能力消费
17.12登录检查
主要步骤:
gulimall-product
商品服务的item.html
商品详情页面,如果商品在秒杀活动时段显示立即抢购,否则显示加入购物车- 立即抢购需要参数:
killId
(场次活动Id + 商品skuId
)、秒杀随机码、商品数量 gulimall-seckill
调用秒杀接口/kill
,需要登录操作,因此需要配置拦截器和springsession
gulimall-product
商品服务的item.html
商品详情页面,如果商品在秒杀活动时段显示立即抢购,否则显示加入购物车
立即抢购需要参数:killId
(场次活动Id + 商品skuId
)、秒杀随机码、商品数量
gulimall-seckill
调用秒杀接口/kill
,需要登录操作,因此需要配置拦截器和springsession
配置登录拦截器
配置springsession
17.13秒杀流程
主要步骤:
- 实现秒杀接口
- 从
从Redis中获取
中获取当前秒杀商品的详细信息 - 合法性效验:判断当前这个秒杀请求是否在活动时间区间内(效验时间的合法性)
- 效验随机码和商品id
- 验证购物数量是否合理和库存量是否充足:判断信号量是否大于0,并且买的数量不能超过限购数量
- 验证这个人是否已经买过了(幂等性处理),如果秒杀成功,就去占位。
userId-sessionId-skuId
- 从
17.14秒杀效果完成
流程图:
两种方案:
- 方案一:
- 加入购物车(仍然走购物车流程,但价格按照秒杀价格计算),创建订单、锁定库存
- 优点:只需要做好适配,无大改动
- 缺点:将秒杀的流量带给了其他模块
- 方案二:【采用方案二,队列削峰】
- 直接发送MQ消息,订单根据消息创建订单(不需要锁定库存,库存预热了【信号量】),订单关闭增加信号量
-
加信号量
- 优点:没有将秒杀的压力分担给其他模块,只有校验合法性没有远程调用、db操作
- 缺点:订单等模块需要提供监听消费信息创建订单,如果订单崩了,会导致支付失败
-
结果:
- 假设一个请求50ms,一个线程1s能处理20个请求
- Tomcat开启500个线程,1s能处理10000个请求
主要步骤:
-
实现秒杀接口
- 从
从Redis中获取
中获取当前秒杀商品的详细信息 - 合法性效验:判断当前这个秒杀请求是否在活动时间区间内(效验时间的合法性)
- 效验随机码和商品id
- 验证购物数量是否合理和库存量是否充足:判断信号量是否大于0,并且买的数量不能超过限购数量
- 验证这个人是否已经买过了(幂等性处理),如果秒杀成功,就去占位。
userId-sessionId-skuId
- 完成以上验证,向
order-event-exchange
发送消息,routingKey
设置为order.seckill.order
- 从
-
gulimall-order
订单服务创建队列order.seckill.order.queue
,并和order-event-exchange
建立绑定关系 -
gulimall-order
监听order.seckill.order.queue
创建秒杀订单
实现秒杀接口
- 从
从Redis中获取
中获取当前秒杀商品的详细信息 - 合法性效验:判断当前这个秒杀请求是否在活动时间区间内(效验时间的合法性)
- 效验随机码和商品id
- 验证购物数量是否合理和库存量是否充足:判断信号量是否大于0,并且买的数量不能超过限购数量
- 验证这个人是否已经买过了(幂等性处理),如果秒杀成功,就去占位。
userId-sessionId-skuId
- 完成以上验证,向
order-event-exchange
发送消息,routingKey
设置为order.seckill.order
gulimall-order
订单服务创建队列order.seckill.order.queue
,并和order-event-exchange
建立绑定关系
gulimall-order
监听order.seckill.order.queue
创建秒杀订单
17.15秒杀页面完成
主要步骤:
- 秒杀成功,跳转到秒杀成功页面,然后进行支付-
gulimall-seckill
导入thymeleaf
,并关闭缓存- 秒杀成功界面渲染
秒杀成功,跳转到秒杀成功页面,然后进行支付
gulimall-seckill
导入thymeleaf
,并关闭缓存
秒杀成功界面渲染
18.Sentinel
18.1高并发方法论&简介
熔断
A 服务调用 B 服务的某个功能,由于网络不稳定问题,或者 B 服务卡机,导致功能时 间超长。如果这样子的次数太多。我们就可以直接将 B 断路了(A 不再请求 B 接口),凡是 调用 B 的直接返回降级数据,不必等待 B 的超长执行。 这样 B 的故障问题,就不会级联影 响到 A。
降级
整个网站处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和 页面进行有策略的降级[停止服务,所有的调用直接返回降级数据]。以此缓解服务器资源的 的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户的得到正确的相应。
异同:
相同点:
-
1、为了保证集群大部分服务的可用性和可靠性,防止崩溃,牺牲小我
-
2、用户最终都是体验到某个功能不可用
不同点:
- 1、熔断是被调用方故障,触发的系统主动规则
- 2、降级是基于全局考虑,停止一些正常服务,释放资源
限流
对打入服务的请求流量进行控制,使服务能够承担不超过自己能力的流量压力
18.2基本概念
Sentinel 简介
官方文档:https://github.com/alibaba/Sentinel/wiki/介绍
项目地址:https://github.com/alibaba/Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场 景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集 群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入 应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如 与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配 置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过 实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时 对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
Sentinel 基本概念
资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提 供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文 档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下, 可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规 则。所有规则可以动态实时调整。
Hystrix 与 Sentinel 比较
18.3整合SpringBoot
主要步骤:
-
docker
安装sentinel
-
SpringBoot
整合sentinel
-
测试
docker安装sentinel
如果拉取镜像失败,更换一下镜像地址,这种方式最方便
拉取到镜像后,后面可以把sentinel
添加到docker-compose.yml
cat <<EOF > /etc/docker/daemon.json
{
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 5,
"default-shm-size": "1G",
"debug": true,
"experimental": false,
"registry-mirrors":[
"https://x9r52uz5.mirror.aliyuncs.com",
"https://dockerhub.icu",
"https://docker.chenby.cn",
"https://docker.1panel.live",
"https://docker.awsl9527.cn",
"https://docker.anyhub.us.kg",
"https://dhub.kubesre.xyz"
]
}
EOF
systemctl daemon-reload
systemctl restart docker
docker pull bladex/sentinel-dashboard
docker run --name sentinel -p 8858:8858 -td bladex/sentinel-dashboard
访问http://你的虚拟机ip:8858/#/dashboard
用户名:sentinel
密码:sentinel
整合SpringBoot
Spring Cloud Alibaba Sentinel
文档说明:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
这里以gulimall-seckill
秒杀服务为主,正常来说所有服务都需要引用sentinel
导入依赖
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
不知道为什么这里现在需要导入hibernate-validator
,要不会报错,记录一下
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
配置application.yaml
,sentinel
在spring.cloud
节点下
spring:
cloud:
sentinel:
transport:
# 控制台信息
dashboard: 192.168.188.180:8858
测试
启动项目,设置/getCurrentSeckillSkus
的QPS
为1,就是每s只允许1个请求
访问接口http://seckill.gulimall.com/getCurrentSeckillSkus,连续多点几次,发现请求会直接返回失败
后台控制台发现接口只被调用2次,说明收到了流控控制
18.4自定义流控响应
主要步骤:
-
自定义流控响应
-
application.yaml
配置,暴露的endpoint
路径为/actuator/sentinel
-
/getCurrentSeckillSkus
添加QPS
官方地址:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel#endpoint-支持
bug:导入Sentinel无WebCallbackManager包
https://blog.csdn.net/m0_70651612/article/details/125365554
自定义流控响应
我用的最新版的sentinel
,之前的WebCallbackManager
实现的自定义响应好像不太好使,查询资料后发现需要实现sentinel-spring-webmvc-adapter
包的BlockExceptionHandler
导入spring-boot-starter-actuator
,是审计框架,计算springboot应用健康状况信息、请求的调用信息,dashboard可以拿到actuator数据作实时监控统计
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
导入sentinel-spring-webmvc-adapter
,实现自定义响应
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
</dependency>
实现BlockExceptionHandler
@Configuration
public class MySentinelConfig implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
R error = R.error(BizCodeEnum.TO_MANY_REQUEST.getCode(), BizCodeEnum.TO_MANY_REQUEST.getMsg());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().write(JSON.toJSONString(error));
}
}
application.yaml
配置,暴露的 endpoint
路径为 /actuator/sentinel
management:
endpoints:
web:
exposure:
include: '*'
/getCurrentSeckillSkus
添加QPS
因为Sentinel
保存在服务内存中,每次重启服务都需要重新配置
访问接口:http://seckill.gulimall.com/getCurrentSeckillSkus,F5
多刷新几次
审计界面
18.5全服务引入
主要步骤:
- 所有的服务导入
sentinel
相关包 - 所有的服务
application.yml
配置sentinel
- 把商城服务的所有的流程走一遍
所有的服务导入sentinel
相关包
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--sentinel统计审计信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
</dependency>
所有的服务配置sentinel
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.188.180:8848 # nacos地址
sentinel:
transport:
# 应用开启端口,接收dashboard限流规则
# port: 8719
# 控制台信息
dashboard: 192.168.188.180:8858
management:
endpoints:
web:
exposure:
include: '*'
把服务的所有的流程走一遍,到支付即可
Sentinel
成功监控了所有的服务
18.6流控模式&效果
官方文档:https://github.com/alibaba/Sentinel/wiki/流量控制
18.7熔断降级
主要步骤:
- 调用方的熔断保护:
feign.sentinel.enable=true
- 调用方手动指定远程服务的降级策略。远程服务被降级处理。触发我们的熔断回调方法
- 超大流量的时候,必须牺牲一些远程服务。在服务的提供方(远程服务)指定降级策略;提供方是在运行,但是不允许自己的业务逻辑,返回的是默认的降级数据(限流的数据)
调用方的熔断保护
Feign
支持文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel#feign-支持
导入依赖,使 Sentinel starter
中的自动化配置类生效
<!--sentinel Feign支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
添加配置,打开 Sentinel
对 Feign
的支持
feign:
sentinel:
enabled: true
我们发现gulimall-product
商品服务中调用的openfeign
远程和接口也显示出来了
创建SeckillFeignServiceFallBack
,实现SeckillFeignService
当远程调用SeckillFeignService
失败时,会调用这个接口进行返回
因为商品详情会远程调用gulimall-seckill
判断是够是秒杀商品,关掉gulimall-seckill
服务模拟宕机,发现界面依然显示,没有出错
调用方手动指定远程服务的降级策略。远程服务被降级处理,触发我们的熔断回调方法
熔断策略文档:https://github.com/alibaba/Sentinel/wiki/熔断降级#熔断策略
修改测试代码,/sku/seckill/{skuId}
睡眠500ms,使该接口触发熔断
配置熔断规则:
-
最大 RT:这个值表示请求的最大响应时间阈值(单位为毫秒)。在这个配置中,最大 RT 设置为
1 ms
,这意味着如果某个请求的响应时间超过 1 毫秒,就会触发统计。 -
熔断时长:熔断器触发后的持续时间。在这里,熔断时长设置为
5 秒
,即一旦熔断触发,该资源在 5 秒内将会被熔断,所有请求将直接返回异常。 -
统计时长:这是用于统计的时间窗口(单位为毫秒)。在这个配置中,统计时长设置为
1000 ms
(即 1 秒)。在这个时间窗口内,Sentinel 将会统计请求的响应时间,并根据配置的阈值来决定是否触发熔断。 -
比阈值:表示在统计时长内,请求的 RT 超过最大 RT 阈值的比例阈值。在这里设置为
0.1
(10%),意味着如果在统计时长内,有 10% 的请求的响应时间超过了最大 RT,则触发熔断。 -
最小请求数:这是触发熔断所需的最小请求数。在这个配置中,设置为
1
,意味着只要有一个请求超过了最大 RT 且满足比阈值的条件,熔断就会触发。
总结来说,这个配置的意思是:如果在 1 秒内有至少 1 个请求且其中 10% 以上的请求的响应时间超过了 1 毫秒,那么这个资源将被熔断 5 秒。
超大流量的时候,必须牺牲一些远程服务。在服务的提供方(远程服务)指定降级策略;提供方是在运行。但是不运行自己的业务逻辑,返回的是默认的降级数据(限流的数据)
我们给gulimall-seckill
服务的/getCurrentSeckillSkus
的接口配置熔断策略
- 最大 RT:设置为
1 ms
,表示如果请求的响应时间超过 1 毫秒,这个请求将会被纳入统计。 - 熔断时长:设置为
5 秒
,表示一旦触发熔断,这个资源会在接下来的 5 秒内直接返回异常,不会真正执行请求。 - 统计时长:设置为
1000 ms
,表示 Sentinel 会在 1 秒的时间窗口内统计请求的响应时间。 - 比阈值:设置为
0.3
(30%),表示在统计时长内,如果有 30% 以上的请求响应时间超过了最大 RT(1 ms),将会触发熔断。 - 最小请求数:设置为
3
,表示在统计时长内,至少有 3 个请求时,才会开始计算并判断是否触发熔断。
总结来说,这个配置表示:如果在 1 秒内,至少有 3 个请求被处理,并且其中 30% 的请求响应时间超过了 1 毫秒,那么这个资源将会熔断 5 秒。
快速刷新请求会触发熔断策略
给所有的服务配置Sentinel
对 Feign
的支持和Sentinel
的配置
18.8自定义受保护资源
主要步骤:
- 基于代码 :
try (Entry entry = SphU.entry("seckillSkus")) {}catch (BlockException e) { log.error("资源被限流{}", e.getMessage()); }
- 基于注解:
@SentinelResource(value = "getCurrentSeckillSkusResource", blockHandler = "blockHandler")
基于代码 :try (Entry entry = SphU.entry("seckillSkus")) {}catch (BlockException e) { log.error("资源被限流{}", e.getMessage()); }
配置seckillSkus
流控规则,每s最多一个请求
官方文档
基于注解:@SentinelResource(value = "getCurrentSeckillSkusResource", blockHandler = "blockHandler")
配置getCurrentSeckillSkusResource
流控规则,每s最多一个请求
官方文档
18.9网关流控
主要步骤:
-
导入
spring-cloud-alibaba-sentinel-gateway
-
配置网关流控
官网地址:https://github.com/alibaba/Sentinel/wiki/网关限流
导入spring-cloud-alibaba-sentinel-gateway
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
配置网关流控
快速访问http://seckill.gulimall.com/getCurrentSeckillSkus,发现
配置gulimall-seckill_route
请求Header
必须带上hello
,并且值必须是world
浏览器请求http://seckill.gulimall.com/getCurrentSeckillSkus
不会被拦截
使用postman
带上Header
的hello=world
请求会被限流
18.10定制网关流控返回
主要步骤:
- 官方文档:https://github.com/alibaba/Sentinel/wiki/网关限流
gulimall-gateway
网关服务重写GatewayCallbackManager.setBlockHandler
- 配置
gulimall-seckill_route
流控规则 - 测试
gulimall-gateway
网关服务重写GatewayCallbackManager.setBlockHandler
配置gulimall-seckill_route
流控规则
测试
19.Sleuth-链路追踪
19.1基本概念&整合
sleuth文档: https://spring.io/projects/spring-cloud-sleuth
started: https://docs.spring.io/spring-cloud-sleuth/docs/3.1.0/reference/html/getting-started.html#getting-started
为什么用
微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务 单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要 体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以 定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与, 参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。
链路追踪组件有 Google 的 Dapper,Twitter 的 Zipkin,以及阿里的 Eagleeye (鹰眼)等,它 们都是非常优秀的链路追踪开源组件。
基本术语
- Span(跨度):基本工作单元,发送一个远程调度任务 就会产生一个 Span,Span 是一 个 64 位 ID 唯一标识的,Trace 是用另一个 64 位 ID 唯一标识的,Span 还有其他数据信 息,比如摘要、时间戳事件、Span 的 ID、以及进度 ID。
- Trace(跟踪):一系列 Span 组成的一个树状结构。请求一个微服务系统的 API 接口, 这个 API 接口,需要调用多个微服务,调用每个微服务都会产生一个新的 Span,所有 由这个请求产生的 Span 组成了这个 Trace。
- Annotation(标注):用来及时记录一个事件的,一些核心注解用来定义一个请求的开 始和结束 。这些注解包括以下:
- cs - Client Sent -客户端发送一个请求,这个注解描述了这个 Span 的开始
- sr - Server Received -服务端获得请求并准备开始处理它,如果将其 sr 减去 cs 时间戳 便可得到网络传输的时间。
- ss - Server Sent (服务端发送响应)–该注解表明请求处理的完成(当请求返回客户 端),如果 ss 的时间戳减去 sr 时间戳,就可以得到服务器请求的时间。
- cr - Client Received (客户端接收响应)-此时 Span 的结束,如果 cr 的时间戳减去 cs 时间戳便可以得到整个请求所消耗的时间。
如果服务调用顺序如下
那么用以上概念完整的表示出来如下:
整合Sleuth
所有的服务提供者与消费者导入依赖
<!-- sleuth 链路追踪-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
打开 debug 日志
logging:
level:
com.peng: debug
org.springframework.cloud.openfeign: debug
org.springframework.cloud.sleuth: debug
19.2整合Zipkin效果
docker安装zipkin
安装命令
docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin
整合zipkin
导入依赖
我用的spring-cloud
版本没有Zipkin
所以要自己指定版本
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
zipkin
中包含sleuth
,所以只需要配置zipkin
即可
配置zipkin
和sleuth
访问:http://192.168.188.180:9411
zipkin数据持久化
持久化类型选择elasticsearch
,因为elasticsearch
查询速度更快
docker run --env STORAGE_TYPE=elasticsearch --env ES_HOSTS=192.168.188.180:9200 openzipkin/zipkin-dependencies
19.3Zipkin界面分析
查询界面
提交订单
订单完成,向rabbitmq
发送消息
订单确认页,显示异步编排调用接口
查看所有服务依赖
查看gulimall-gateway
服务依赖
20.分布式高级篇总结
01-EleasticSearch、商品上架、首页、nginx动静分离、Apache JMeter使用、缓存、Redisson、SpringCache
02-检索服务、CompletableFuture异步编排、商品详情
03-认证服务、SpringSession、单点登录、购物车
04-RabbitMQ、订单确认
05-Seata、RabbitMQ延时队列、支付宝支付、订单服务
06-秒杀服务、Sentinel、Sleuth、Zipkin
创作不易,感谢支持。