一、秒杀
1、秒杀业务
秒杀具有瞬时高并发的特点,针对这一特点,必须做限流+异步+缓存(页面静态化)+独立部署
限流方式:
- 前端限流:一些高并发网站直接在前端限流,例如:小米的验证码设计
- nginx限流:直接负载部分请求错误的静态页面;令牌算法,漏斗算法。
- 网关限流:限流的过滤器
- 代码中使用分布式信号量
- rabbitMq限流(能者多劳:channel.basicQos(1)),保证发挥所有服务器的性能。
2、定时任务(Quartz)
1)、cron表达式
https://cron.qqe2.com/ 可在该网站进行生成cron 表达式
spring自带的定时任务
/**
* 定时任务
* 1、@EnableScheduling 开启定时任务
* 2、@Scheduled 开启一个定时任务
* 3、自动配置类 TaskSchedulingAutoConfiguration
* 异步任务
* 1、@EnableAsync 开启异步任务功能
* 2、@Async 给方法加上注解
* 3、TaskExecutionAutoConfiguration 自动配置类
*/
@EnableScheduling
@Component
@Slf4j
@EnableAsync
public class HandSchedule {
/**
* 1、spring中 cron只能是六位 不能含有年
* 2、在周几的位置 1-7代表周一到周日:MON-SUN
* 3、定时任务不应该阻塞。默认是阻塞的
* 1)、可以让业务运行以异步的方式,自己提交到线程池
* CompletableFuture.runAsync(()->{
* },executor);
* 2)、支持定时任务线程池:设置 TaskSchedulingProperties
* 3)、让定时任务异步执行
* 异步任务
*
* 解决:使用异步+定时任务解决定时任务不阻塞的功能
*
*/
@Scheduled(cron = "* * * ? * 3")
@Async
public void hello(){
log.info("hello..");
}
}
2)、秒杀服务-幂等性问题
添加分布式锁:pom引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.24.3</version>
</dependency>
@Scheduled(cron = "* * * ? * 3")
@Async
public void hello() {
RLock lock = redissonClient.getLock("redis_key");
lock.lock(10, TimeUnit.SECONDS);
try {
log.info("hello.."); //业务逻辑
} finally {
lock.unlock();
}
}
二、高并发方法论
1、秒杀系统关注问题
-
服务单一职责+单独部署:
秒杀服务即使扛不住挂掉也不会影响其他服务。
-
秒杀链接加密:
防止恶意攻击,模拟秒杀请求,1000次/s攻击,防止链接暴漏,自己工作人员,提前秒杀商品
-
库存预热+快速扣减:
秒杀读多写少,无需每次实时校验库存。我们库存预热,放到redis中,信号量控制进来秒杀的商品
-
动静分离:
nginx做好动静分离。保证只有秒杀和商品详情页的动态请求打到后端的服务集群。使用CDN网络,分担本集群压力。
-
恶意请求拦截
识别非法请求并进行拦截,网关拦截
-
流量错峰
使用各种手段,将流量分担到更大宽度的时间点。比如验证码、购物车
-
限流&熔断&降级
前端限流+后端限流 。 限制次数,限制总量,快速失败降级运行,熔断隔离防止雪崩
-
队列削峰
1万个商品,每个1000件秒杀,双11所有秒杀成功的请求,进入队列,慢慢创建订单,扣减库存即可。
1)、熔断降级限流
什么是熔断:
A服务调用B服务的某个功能,由于网络不稳定,或者B服务卡机,导致功能时间超长。如果这样子的次数太多。我们就可以直接将B断路了(A不在请求B的接口),凡是调用B的直接返回降级数据,不必等B的超长执行。这样B的故障问题,就不会级联影响到A。
什么是降级:
整个网站都处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和页面进行有策略的降级【停止服务,所有的数据直接返回降级数据】。以此缓和服务器资源的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户得到正确的响应。
异同:
相同点:
1、为了保证集群大部分的可靠性和可用性,防止崩溃,选择了牺牲小我。
2、用户最终都是体验到某个功能不可用
不同点:
1、熔断是被调用方故障,触发系统保护规则。
2、降级是基于全局考虑,停止一些正常服务,释放资源。
什么是限流:
对打入服务的请求的流量进行控制,使服务器能够承担不超过自己能力的流量压力。
三、Sentinel
1、Sentinel简介
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
- 资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
- 规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
下载地址:https://github.com/alibaba/Sentinel/releases
启动:
java -jar D:\tools\sentinel-dashboard-2.0.0-alpha-preview.jar --server.port=8333
2、整合springboot
引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2022.0.0.0</version>
</dependency>
配置
spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.dashboard=localhost:8080
# 用于指定哪些端点应该暴露在web界面上
management.endpoints.web.exposure.include=*
自定义流控响应
@Component
public class SentinelUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code","1001");
jsonObject.put("message","访问次数过多");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().write(JSON.toJSONString(jsonObject));
}
}
如果引入的版本是2022.0.0.0之前的版本,那么写法是以下方式
@Configuration
public class SeckillSentinelConfig {
public SeckillSentinelConfig(){
WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler() {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code","1001");
jsonObject.put("message","访问次数过多");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().write(JSON.toJSONString(jsonObject));
}
});
}
}
3、Sentinel熔断降级
调用方熔断保护
feign.sentinel.enabled=true
远程调用添加fallback
@Component
public class InventoryServiceClientFallBack implements InventoryServiceClient {
@Override
public void insertInventory(Inventory inventory) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code","20001");
jsonObject.put("message","请求熔断");
System.out.println(jsonObject);
}
}
或者在sentinel控制台中手动添加 熔断机制
sentinel 会将远程调用服务做降级处理,并触发熔断保护机制。
新增熔断规则
- | 规则 | 说明 |
| :--------------- | :----------------------------------------------------------- |
| 资源名: | 唯一名称 |
| 熔断策略: | a.(默认值)慢调用比列
b.异常比列(秒级)
c.异常数(分钟级) |
| RT: | RT:平均响应时间,秒级。RT最大4900
(更大的需要通过-
Desp.sentinel.statistic.max=xxx才能生效) |
| 熔断时长: | 窗口期过后关闭断路器 |
| 慢调用比列阈值: | 仅慢调用比例模式有效(1.8.0有效),
比率的阈值范围是【0,0,1.0】,代表0%~100%。 |
| 最小请求数: | 熔断触发的最小请求数,请求数小于该值时即使异常
比率超出阈值也不会熔断(1.7.0引入)默认值5 |
| 统计时长: | 统计计入时长(单位ms),如60*1000代表分钟级
(1.8.0引入)默认值1000ms |
4、自定义保护资源
1)、基于代码方式
@Override
@Transactional
@GlobalTransactional
public List<Map<String, Object>> getList(Order order) {
try(Entry entry = SphU.entry("orderList")) {
// 受保护资源执行
}catch (Exception e){
log.info("限流控制,{}",e.getMessage());
}
return null;
}
2)、基于注解方式
@SentinelResource(value = "getList")
被流控后接口调用会报错。
解决:
@SentinelResource(value = "getList", blockHandler = "blockHandlerList")
可使用 blockHandler 指定流控后的限流方法。
注意:
- @SentinelResource这个注解的方法的返回值 与 blockHandlerList该方法返回值一致。
- 参数一致
- blockHandlerList 该方法应该多接收一个 BlockException 类型参数
示例
/**
* 在原方法降级/限流/系统保护的时候被调用 而fallback针对所有类型的异常
*/
@Override
@SentinelResource(value = "getList", blockHandler = "blockHandlerList")
public List<Map<String, Object>> getList(Order order) {}
public List<Map<String, Object>> blockHandlerList(Order order, BlockException t) {}
5、Sectinel网关流控
导入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.8.6</version>
</dependency>
配置
server:
port: 8090
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
enabled: true
routes:
- id: service-order
uri: lb://service-order
predicates:
- Path=/order/**
sentinel:
transport:
dashboard: localhost:8333
port: 8719
feign:
sentinel:
enabled: true
logging:
level:
com:
example: debug
创建流控
自定义限流返回数据:
@Configuration
public class SentinelGatewayConfig {
public SentinelGatewayConfig() {
GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
// 网关限流了请求,就会调用此回调
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code","20001");
jsonObject.put("message","限流");
return ServerResponse.ok().body(Mono.just(jsonObject), JSONObject.class);
}
});
}
}