• Redis队列实现Java版秒杀系统(无脚本、可用于生产)


    需求是做一个秒杀系统,比如大家来抢100台手机,先到先得。

    查阅了网上很多用redis实现秒杀的demo(java语言),竟然没一个能用的!!!

    有些是php的,没闲心研究了,现在说说为什么不能用:

    • 绝大多数的DEMO都是基于redis的watch特性的事务实现①,
    • 个别是基于redis分布式锁实现②。
    • 当然还有些用了脚本的,我也没仔细看是lua还是调用redis指令,哪有那个闲心去研究哇。

    照顾一下小白,分析一下为什么这几种实现不行

    1.基于watch特性的 不靠谱 实现

    其实这两种实现方式,完全可以理解为乐观锁(watch)和悲观锁(加分布式锁)

    watch事务,相当于是乐观锁,这种方法在并发情况下极为不靠谱,假设有100个人同时尝试秒杀,那么极端情况下,有99个人都会失败,只有一个能修改成功。

    然而demo里甚至没写如果修改失败了就重试这个功能,那显然这失败的99个人,已经提示失败了,过一会回来,发现还剩了90多。那我是怎么失败的?我替他们问问了。

    并且使用这种方式实现呢,在并发量较大的时候,过多的重试线程应该会严重影响服务器性能。

    2.基于用redis做个分布式锁的 不靠谱 实现

    这种实现方式相当于一个悲观锁,每次执行减减操作之前,在redis中存入一个k,v键值对,使用特定的名称,并且使用setNX特性,确保抢锁没有安全问题,并在使用完成后释放锁。那么问题是,在100个人秒杀时,只有一个人抢到锁,剩下99个人怎么办?

    demo里同样没写个重试,抢不到锁就失败,醉了,不过就算写重新抢锁的机制,那么几十个上百个线程不断抢锁,想想是个挺恐怖的事,更别提高并发了。

    正题:使用spring操作redis的list队列实现

    我用的是springboot的StringRedisTemplate,至于如何整合jedis到spring等等,去查阅其他文章吧,我就不重复写了。

    贴工具类:

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;
    import java.time.Duration;
    import java.util.Collection;
    
    @Service
    public class RedisServiceImpl<T> implements RedisService<T> {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        //添加字符串并设置过期时间
        @Override
        public void addString(String key, String value, Duration duration) {
            stringRedisTemplate.opsForValue().set(key, value, duration);
        }
    
        //查找字符串
        @Override
        public String findString(String key) {
            return stringRedisTemplate.opsForValue().get(key);
        }
    
        //根据Key删除
        @Override
        public Boolean deleteByKey(String key) {
            return stringRedisTemplate.delete(key);
        }
    
     //在队列尾部减少一个对象
        @Override
        public String removeOneEntryOnListRight(String listName) {
            return stringRedisTemplate.opsForList().rightPop(listName);
        }
    
     //在队列头部新增对象
        @Override
        public Long addEntriesOnListLeft(String listName, Collection<String> args) {
            return stringRedisTemplate.opsForList().leftPushAll(listName, args);
        }
    
    }
    

      

    解释一下哈 这个类的父类是我自己写的service层,不是提供好的

    主要使用的是最后两个方法,最后一个方法,在队列头部新增对象,如果没有这个队列,他会创建出来这个队列,然后将一个集合统统塞到这个redis队列中。倒数第二个方法每调用一次,会删除队列中最后一个元素,然后返回这个元素的值,如果队列中已经没有元素了(队列已经没了)那么他会返回null,他们都是原子操作。

    如此,每个请求都无需经过加锁操作,直接利用redis的单线程特性,即可实现高并发下的秒杀:请求到达redis,redis会逐个执行,每一次执行要么返回一个值,要么返回null。很显然,返回值的就是抢到了,返回null的就是没抢到。而且可以灵活的为这个队列新加入一些元素(老板发话再加100台)或者直接把这个队列删了(老板说不行,不卖了)都不会对代码产生任何影响。

    其中对应的redis操作指令分别是:

    • 在队列左侧新增:lpush
    • 在队列右侧消费:rpop

    老板不卖了:del (笑)

    接下来贴出十分简单的使用方法

    先贴在任务开始时向redis中插入一个大队列

    List<String> entriesList = new LinkedList<>();
       for (int i = 0; i < 100; i++){
           entriesList.add("某个商品");
       }
       redisService.addEntriesOnListLeft("队列名",entriesList);
    

      

    突然想到这个实现即使秒杀100台不同型号的手机(并且在秒到时就通知用户秒到的是啥),也不用改代码。

    每次秒杀执行:

    String redisResult = redisService.removeOneEntryOnListRight("队列名");
        if (null == redisResult) {
            //说明没抢到
        }else{
     //说明抢到了 执行抢到逻辑
    }
    

      

    突然发现这个实现看起来甚至比那些所谓的秒杀demo还简单

    但他既没有并发问题,也没有为了解决并发问题而衍生的性能问题。

    虽然没经过测试,不过我认为就算秒杀10万台,放到redis队列里,应该也占用不了多少内存。

    赶工分布式秒杀,没想到如此基本的内容,竟没找到一个靠谱的实现,从上午写到现在(周末晚8点)一顿饭没吃,被网上过于demo的资源逼的,忍饿怒出此文。

  • 相关阅读:
    简单的MsChart使用与遇到的麻烦
    SQLServer中case when 与列拼接
    关于集成单点登录问题
    IIS部署网站后,只有本服务器才能登录
    获取本周的周一日期与本周的周日日期
    34个漂亮的应用程序后台管理系统界面(系列二)
    2011年最佳免费 PSD 用户界面素材揭晓
    编程你使用快捷键了吗?
    汉字转全拼音函数优化方案(SQLServer),值得你看看
    WinForm企业应用框架设计【四】动态创建业务窗体
  • 原文地址:https://www.cnblogs.com/SparkMore/p/16951642.html
Copyright © 2020-2023  润新知