【高并发】Redis如何助力高并发秒杀系统,看完这篇我完全懂了!!

写在前面

以前,咱们在《【高并发】高并发秒杀系统架构解密,不是全部的秒杀都是秒杀!》一文中,详细讲解了高并发秒杀系统的架构设计,其中,咱们介绍了可使用Redis存储秒杀商品的库存数量。不少小伙伴看完后,以为一头雾水,看完是看完了,那如何实现呢?今天,咱们就一块儿来看看Redis是如何助力高并发秒杀系统的!java

有关高并发秒杀系统的架构设计,小伙伴们能够关注 冰河技术 公众号,查看《【高并发】高并发秒杀系统架构解密,不是全部的秒杀都是秒杀!》一文。面试

秒杀业务

在电商领域,存在着典型的秒杀业务场景,那何谓秒杀场景呢。简单的来讲就是一件商品的购买人数远远大于这件商品的库存,并且这件商品在很短的时间内就会被抢购一空。好比每一年的61八、双11大促,小米新品促销等业务场景,就是典型的秒杀业务场景。redis

秒杀业务最大的特色就是瞬时并发流量高,在电商系统中,库存数量每每会远远小于并发流量,好比:天猫的秒杀活动,可能库存只有几百、几千件,而瞬间涌入的抢购并发流量可能会达到几十到几百万。设计模式

因此,咱们能够将秒杀系统的业务特色总结以下。缓存

(1)限时、限量、限价bash

在规定的时间内进行;秒杀活动中商品的数量有限;商品的价格会远远低于原来的价格,也就是说,在秒杀活动中,商品会以远远低于原来的价格出售。微信

例如,秒杀活动的时间仅限于某天上午10点到10点半,商品数量只有10万件,售完为止,并且商品的价格很是低,例如:1元购等业务场景。markdown

限时、限量和限价能够单独存在,也能够组合存在。数据结构

(2)活动预热架构

须要提早配置活动;活动还未开始时,用户能够查看活动的相关信息;秒杀活动开始前,对活动进行大力宣传。

(3)持续时间短

购买的人数数量庞大;商品会迅速售完。

在系统流量呈现上,就会出现一个突刺现象,此时的并发访问量是很是高的,大部分秒杀场景下,商品会在极短的时间内售完。

秒杀三阶段

一般,从秒杀开始到结束,每每会经历三个阶段:

  • 准备阶段:这个阶段也叫做系统预热阶段,此时会提早预热秒杀系统的业务数据,每每这个时候,用户会不断刷新秒杀页面,来查看秒杀活动是否已经开始。在必定程度上,经过用户不断刷新页面的操做,能够将一些数据存储到Redis中进行预热。
  • 秒杀阶段:这个阶段主要是秒杀活动的过程,会产生瞬时的高并发流量,对系统资源会形成巨大的冲击,因此,在秒杀阶段必定要作好系统防御。
  • 结算阶段: 完成秒杀后的数据处理工做,好比数据的一致性问题处理,异常状况处理,商品的回仓处理等。

Redis助力秒杀系统

咱们能够在Redis中设计一个Hash数据结构,来支持商品库存的扣减操做,以下所示。

seckill:goodsStock:${goodsId}{
	totalCount:200,
	initStatus:0,
	seckillCount:0
}
复制代码

在咱们设计的Hash数据结构中,有三个很是主要的属性。

  • totalCount:表示参与秒杀的商品的总数量,在秒杀活动开始前,咱们就须要提早将此值加载到Redis缓存中。
  • initStatus:咱们把这个值设计成一个布尔值。秒杀开始前,这个值为0,表示秒杀未开始。能够经过定时任务或者后台操做,将此值修改成1,则表示秒杀开始。
  • seckillCount:表示秒杀的商品数量,在秒杀过程当中,此值的上限为totalCount,当此值达到totalCount时,表示商品已经秒杀完毕。

咱们能够经过下面的代码片断在秒杀预热阶段,将要参与秒杀的商品数据加载的缓存。

/** * @author binghe * @description 秒杀前构建商品缓存代码示例 */
public class SeckillCacheBuilder{
    private static final String GOODS_CACHE = "seckill:goodsStock:"; 
    private String getCacheKey(String id) { 
        return  GOODS_CACHE.concat(id);
    } 
    public void prepare(String id, int totalCount) { 
        String key = getCacheKey(id); 
        Map<String, Integer> goods = new HashMap<>(); 
        goods.put("totalCount", totalCount); 
        goods.put("initStatus", 0); 
        goods.put("seckillCount", 0); 
        redisTemplate.opsForHash().putAll(key, goods); 
     }
}
复制代码

秒杀开始的时候,咱们须要在代码中首先判断缓存中的seckillCount值是否小于totalCount值,若是seckillCount值确实小于totalCount值,咱们才可以对库存进行锁定。在咱们的程序中,这两步其实并非原子性的。若是在分布式环境中,咱们经过多台机器同时操做Redis缓存,就会发生同步问题,进而引发“超卖”的严重后果。

在电商领域,有一个专业名词叫做“超卖”。顾名思义:“超卖”就是说卖出的商品数量比商品的库存数量多,这在电商领域是一个很是严重的问题。那么,咱们如何解决“超卖”问题呢?

Lua脚本完美解决超卖问题

咱们如何解决多台机器同时操做Redis出现的同步问题呢?一个比较好的方案就是使用Lua脚本。咱们可使用Lua脚本将Redis中扣减库存的操做封装成一个原子操做,这样就可以保证操做的原子性,从而解决高并发环境下的同步问题。

例如,咱们能够编写以下的Lua脚本代码,来执行Redis中的库存扣减操做。

local resultFlag = "0" 
local n = tonumber(ARGV[1]) 
local key = KEYS[1] 
local goodsInfo = redis.call("HMGET",key,"totalCount","seckillCount") 
local total = tonumber(goodsInfo[1]) 
local alloc = tonumber(goodsInfo[2]) 
if not total then 
    return resultFlag 
end 
if total >= alloc + n  then 
    local ret = redis.call("HINCRBY",key,"seckillCount",n) 
    return tostring(ret) 
end 
return resultFlag
复制代码

咱们可使用以下的Java代码来调用上述Lua脚本。

public int secKill(String id, int number) { 
    String key = getCacheKey(id); 
    Object seckillCount =  redisTemplate.execute(script, Arrays.asList(key), String.valueOf(number)); 
    return Integer.valueOf(seckillCount.toString()); 
}
复制代码

这样,咱们在执行秒杀活动时,就可以保证操做的原子性,从而有效的避免数据的同步问题,进而有效的解决了“超卖”问题。

重磅福利

关注「 冰河技术 」微信公众号,后台回复 “设计模式” 关键字领取《深刻浅出Java 23种设计模式》PDF文档。回复“Java8”关键字领取《Java8新特性教程》PDF文档。回复“限流”关键字获取《亿级流量下的分布式限流解决方案》PDF文档,三本PDF均是由冰河原创并整理的超硬核教程,面试必备!!

好了,今天就聊到这儿吧!别忘了点个赞,给个在看和转发,让更多的人看到,一块儿学习,一块儿进步!!

写在最后

若是你以为冰河写的还不错,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习高并发、分布式、微服务、大数据、互联网和云原生技术,「 冰河技术 」微信公众号更新了大量技术专题,每一篇技术文章干货满满!很多读者已经经过阅读「 冰河技术 」微信公众号文章,吊打面试官,成功跳槽到大厂;也有很多读者实现了技术上的飞跃,成为公司的技术骨干!若是你也想像他们同样提高本身的能力,实现技术能力的飞跃,进大厂,升职加薪,那就关注「 冰河技术 」微信公众号吧,天天更新超硬核技术干货,让你对如何提高技术能力再也不迷茫!

相关文章
相关标签/搜索