- 秒杀系统
- 前言
- 一、秒杀系统问题分析及解决方案探讨
- 二、效果展示
- 三、代码实现
- gateway网关
- sentinel
- 定义资源的方式
- 定义控制策略
- 定义异常处理策略
- skywalking链路追踪
- Reddssion分布式锁
- RabbitMQ
- Jmeter
- 总结
前言
秒杀是电商项目中的常见场景,如XX手机12点限量半价抢购等。该场景在极短的时间内涌入大量的请求,来同时访问有限的服务资源,从而造成系统负载压力大,甚至导致系统服务瘫痪以及宕机的可能。本文会介绍秒杀系统中存在的痛点以及针对这些点的优化思路。使用到的技术有:redis,redisson,gateway,nacos,rabbitmq,sentinel,jmeter,skywalking
一、秒杀系统问题分析及解决方案探讨
1.短时间内高并发下,会产生商品超卖问题。比如A商品200个库存,同一时间3个用户对数据库发起请求,发现有200个库存,然后大家都对库存减1,为199,实际库存应该为200-3=197.解决这个问题的方案之一是给mysql加锁,但是给mysql加锁会产生锁竞争问题,一个线程只有等另一个线程执行完 *** 作后才能访问数据库,从而导致用户等待时间延长,在高并发场景下该问题尤为严重。为解决该问题,提前将库存信息存入redis中,并加上redisson分布式锁,利用semaphore信号量来表示库存的数量。首先要让用户从redis中抢商品库存,只有抢到库存的用户才有资格下订单。还可以使用redis来保证接口幂等性,在redis中保存一个用户id和商品的映射,参与过秒杀的用户就不能再次秒杀。
2.此时系统负载压力依然很大。我们就要使用RabbitMQ来实现队列泄洪,确保数据库来的及处理消息。
3.通过网关gateway来拦截不必要的请求,并负载均衡路由到不同的服务器,分担每一个服务器的压力。
4.使用sentinel来进行限流,比如限流到8000qps。为保险起见,我们还要给系统加熔断处理,比如当消息队列的响应速度超过一定时间时,就直接熔断,给前端返回活动太火爆,请稍后再试,以免出现服务雪崩,造成严重后果。
5.每秒的流量依然巨大,为了确定限流熔断的参数设置,我们可以事先用jmeter进行压力测试,并通过skywalking链路追踪技术来分析性能瓶颈在哪里,然后做出进一步的优化。
流程图如下:
架构图如下:
效果如下(示例):
可以看到,该秒杀系统可单机承受每秒5000次的请求,并且平均时延只有588ms
https://www.bilibili.com/video/BV1Q44y1G7Cz/
秒杀系统
三、代码实现 gateway网关
gateway基本上不用写什么代码,只用配置一下就能实现各种功能。
以下是使用gateway实现负载均衡的核心配置
其中id就是服务名称,uri: lb://order/表示使用负载均衡策略进行路由匹配, predicates:就是断言,满足这断言的就会路由到负载均很的地址
routes:
- id: order
uri: lb://order/
predicates:
- Path=/order/**
sentinel
sentinel可对服务进行限流和熔断,支持对多种粒度的控制。核心就是定义资源,和对该资源的控制策略。
定义资源的方式1.抛出异常的方式定义资源
public static void main(String[] args) {
initFlowRules();
while (true) {
Entry entry = null;
try {
entry = SphU.entry("HelloWorld");
/*您的业务逻辑 - 开始*/
System.out.println("hello world");
/*您的业务逻辑 - 结束*/
} catch (BlockException e1) {
/*流控逻辑处理 - 开始*/
System.out.println("block!");
/*流控逻辑处理 - 结束*/
} finally {
if (entry != null) {
entry.exit();
}
}
}
}
这种定义资源的方式对代码侵入性高,但是控制粒度更细
2.使用注解的方式定义资源,Sentinel 支持通过 @SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理。
// 原本的业务方法.
@SentinelResource(blockHandler = "blockHandlerForGetUser")
public User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
public User blockHandlerForGetUser(String id, BlockException ex) {
return new User("admin");
}
对代码侵入性更低
3.sentinel控制台自动检测资源,对代码无侵入性
1.使用sentinel控制台来定义控制规则,对代码无侵入性
2.使用代码来定义控制规则,本秒杀系统使用的熔断规则代码如下:
//自定义熔断规则
@PostConstruct
private static void initDegradeRules() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("createOrder");
//设置熔断策略为慢调用比例模式
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
// 设置慢调用临界阈值,单位是ms,响应时间超过1s就视为慢调用
rule.setCount(1000);
//定义熔断时长5s
rule.setTimeWindow(5);
//设置慢调用比例阈值
rule.setSlowRatioThreshold(0.1);
//熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
rule.setMinRequestAmount(5);
//设置统计时长,1s
rule.setStatIntervalMs(1000);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
定义异常处理策略
比如限流以后直接返回给前端服务器限流,请稍后再试,参考代码如下:
@Component
public class SentinelBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
Map<String, Object> errors = new HashMap<>();
if (e instanceof FlowException) {
errors.put("code","403");
errors.put("message", "服务限流");
}
errors.put("path", httpServletRequest.getRequestURI());
httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setHeader("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getWriter(), errors);
}
}
skywalking链路追踪
核心就是定义探针,开启服务器,以及配置信息存储在哪里
在idea中使用skywalking,只需要在启动参数中做如下配置即可:
1.配置skywalking-agent.jar的位置
-javaagent:D:\SkyWalking\skywalking-agent/skywalking-agent.jar
2.定义服务的名称,和数据的收集地址
-Dskywalking.agent.service_name=XXX -Dskywalking.collector.backend_service=127.0.0.1:11800
然后就可以在控制台中看到微服务链路追踪及各种每个环节的数据分析了。
//使用redisson的Semaphore来实现抢商品功能
RSemaphore storage = redissonClient.getSemaphore("storage");
//使用非阻塞方式来抢redis中的semaphore信号量,每次抢1件商品
boolean success = storage.tryAcquire(1, 100, TimeUnit.MILLISECONDS);
if(success){
//秒杀成功,进入下一个环节,创建订单
}else{
//秒杀失败,直接返回
}
RabbitMQ
请参考我的其他文章
Jmeter压力测试工具
自定义线程数,在多少秒内完成,持续多少秒
下图所示就是每秒5000QPS,持续3秒
可从聚合报告中看到平均的响应时间,吞吐量等数据,以便分析系统的性能
以上就是秒杀系统的实现方案。要综合使用多种技术,包括网关来实现负载均衡,消息队列削峰泄洪,服务限流与熔断,分布式锁保证不出现超卖问题,压力测试工具整合链路追踪技术分析系统的性能瓶颈。要做好秒杀系统,其实还有很长的路要走,只有不断积累和精进各种技术,才能实现系统的高可用,高并发,高性能。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)