java_秒杀实现,redis+lua,已经生产环境试炼

参与人数:5000+
商品:课程300课位
注意的问题
1.前端页面的访问(前端占用带宽)
2.服务器承受的压力(接口占用带宽)
3.超卖(库存)问题

问题的解决思路
1.前端页面的访问(前端占用带宽)
前端页面的静态文件指向OSS,部署前端项目时,可将静态文件上传至OSS。
这样静态文件会从oss下载,不会从服务器下载。
服务器带宽峰值100M,静态文件如果20M,那5个人请求服务,便已占满带宽。
2.服务器承受的压力(接口占用带宽)
①减少非必要接口的请求次数
可以适当的从数据的时效性考虑,一些数据可以存入浏览器缓存,具体缓存过期时间可以根据业务评估。本人是直接存永久,只有在触发刷新,或是重新打开页面时进行获取。
负载均衡(分布式)
配置多台服务器,在访问压力过大时,可以租用服务器,应对一段时间。
服务器普遍8+16
3.超卖(库存)问题
Lua+redis
使用redis,不使用mysql的原因:
①mysql延迟高
redis数据存在内存中,mysql数据存在硬盘中,
内存的读写速度是硬盘的千倍,硬盘的读写性能不会超过内存

②redis可实现异步处理,秒杀只是一种短时间的业务,所以可以将数据先存入再进行其他操作

使用lua脚本
原因:lua是一种嵌入式脚本语言
①将多步的redis操作,一次提交给redis执行,减少连接redis的次数,提高性能。
②脚本执行时不会被其他命令插队,属于原子性操作,可做事务,数据一致。

具体实现

1.使用redisTemplate直接调用lua脚本实现

redisTemplate.execute(redisScript, keyList)

①创建脚本
在在项目文件中创建xxx.lua文件,具体内容见下:文件中代码其实都是一些redis的命令,通过lua语法来写执行逻辑

local userExists=redis.call("sismember",KEYS[3],KEYS[1]);
if tonumber(userExists)==1 then
  return 2;
end

local num= redis.call("get" ,KEYS[2]);
if tonumber(num)<=0 then
  redis.call("srem",KEYS[7],KEYS[4]);
  redis.call("srem",KEYS[7],KEYS[6]);
  redis.call("sadd",KEYS[7],KEYS[6]);
  return 0;
else
  redis.call("decr",KEYS[2]);
  redis.call("sadd",KEYS[3],KEYS[1]);

  redis.call("srem",KEYS[7],KEYS[4]);
  redis.call("srem",KEYS[7],KEYS[6]);
  redis.call("sadd",KEYS[7],KEYS[5]);
end
return 1;

②生成脚本对象(多个脚本配置多个bean)

@Configuration
public class RedisLuaConfig {

    @Bean
    public DefaultRedisScript<Long> redisScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("xxx.lua")));
        redisScript.setResultType(Long.class);
        return redisScript;
    }


    @Bean
    public DefaultRedisScript<Long> redisScriptDelete() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("yyy.lua")));
        redisScript.setResultType(Long.class);
        return redisScript;
    }

}

③传参执行
引入

    @Autowired
    DefaultRedisScript<Long> redisScript;

    @Autowired
    DefaultRedisScript<Long> redisScriptDelete;

参数

                    List<String> keyList = new CopyOnWriteArrayList<>();
                    //key[1]
                    keyList.add("user");//用户
                    //key[2]
                    String courseNumKey = "zzz";//redis中存放商品库存的key
                    keyList.add(courseNumKey);//商品总数
                    //key[3]
                    String courseList ="xxx" ;//redis中存放抢到的人列表key
                    keyList.add(courseList);//存放抢到的人列表
                    //key[4]
                    keyList.add("ccc-0");//待抢
                    //key[5]
                    keyList.add("ccc-1");//抢成功
                    //key[6]
                    keyList.add("ccc-2");//抢失败
                    //key[7]
                    String userCourseList = "vvv" ;//redis中存放每个人存放抢到的商品列表key
                    keyList.add(userCourseList);//每个人存放抢到的商品列表


                    // redisScript,key列表,arg(可多个),参考redis eval 命令
                    String result = redisTemplate.execute(redisScript, keyList).toString();
                    //0:抢失败,1:抢成功,2:已抢
                    log.info("状态result:" + result);

执行中遇到的问题
1.redis 集群版对lua 脚本的不支持
具体见阿里官方文档:https://help.aliyun.com/document_detail/145968.htm?spm=a2c4g.11186623.0.0.43c23732U14Pgh#concept-2353537

解决:
①所有key都应该由KEYS数组来传递,redis.call/pcall中调用的Redis命令,key的位置必须是KEYS array(不能使用Lua变量替换KEYS)
lua脚本中不定义keys数组,而用KEYS[0],数组下标的方式获取

②所有key必须在一个slot上
具体规则参考redis的官方文档
https://redis.io/commands/cluster-keyslot
方法:keySlot算法中,如果key包含{},就会使用第一个{}内部的字符串作为hash key,这样就可以保证拥有同样{}内部字符串的key就会拥有相同slot。
所以就可以在传入lua中的redis后面拼接同一个字符{ddd}
如:xxx{ddd},vvv{ddd},ccc-2{ddd}等

2.JVM性能问题
参考文档:https://www.cnblogs.com/lys_013/p/13185940.html

参考:
服务器参数:8+16+200,自动带宽100M,
压测工具:本地jmeter,可以勉强本地测试业务,可购买阿里性能测试服务
4台服务器可保证5000人无感秒杀,不过多多益善,一台也完全可以

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值