zoukankan      html  css  js  c++  java
  • Spring Boot基于redis分布式锁模拟直播秒杀场景

    摘要:Spring Boot基于redis分布式锁模拟秒杀场景,未完待续

    §前言

      在Java中,关于锁我想大家都很熟悉,例如synchronized和Lock等。在并发编程中,我们通过加锁来保证数据一致。但是Java中的锁,只能保证在同一个JVM进程内中执行,如果在分布式集群环境下,就缴械投降,那如何处理呢?使用Redis锁来处理。

      测试用例所用软件开发环境如下:

      ♦ java version 13.0.1
      ♦ IntelliJ IDEA 2019.3.2 (Ultimate Edition)
      ♦ Spring Boot 2.3.0.RELEASE
      ♦Redis 5.0.10(暂时使用单台部署,后面使用集群)

    §案例分析

      模拟一个比较常见的秒杀场景,假如只有1000件商品上架,这时候就需要用到锁。在《Spring Boot 整合Jedis连接Redis和简单使用》的JedisUtil基础上增加加锁和解锁函数:

    package com.eg.wiener.utils;
    
    import com.eg.wiener.config.JedisPoolFactory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.params.SetParams;
    
    import java.util.Collections;
    import java.util.Map;
    
    @Component
    public class JedisUtil {
        private static Logger logger = LoggerFactory.getLogger(JedisUtil.class);
    
        private static String lock_key = "lock_"; //锁键
        private static String lock_ok = "OK"; //锁键
        protected static long internalLockLeaseTime = 30000;//锁过期时间
        private long timeout = 999999; //获取锁的超时时间
        private static String lua_script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    
        //SET命令的参数
        private final static SetParams params = SetParams.setParams().nx().px(internalLockLeaseTime);
    
        @Autowired
        private JedisPool jedisPool;
        @Autowired
        private JedisPoolFactory jedisPoolFactory;
    
        /**
         * 存储字符串键值对,永久有效
         * @param key
         * @param value
         * @return
         * @author hw
         * @date 2018年12月14日
         */
        public String set(String key, String value) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.set(key, value);
            } catch (Exception e) {
                return "-1";
            } finally {
                jedis.close();
            }
        }
    
        /**
         * 根据传入key获取指定Value
         * @param key
         * @return
         * @author hw
         * @date 2018年12月14日
         */
        public String get(String key) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.get(key);
            } catch (Exception e) {
                return "-1";
            } finally {
                jedis.close();
            }
        }
    
        /**
         * 删除字符串键值对
         * @param key
         * @return
         * @author hw
         * @date 2018年12月14日
         */
        public Long del(String key) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.del(key);
            } catch (Exception e) {
                return -1L;
            } finally {
                jedis.close();
            }
        }
        /**
         * 校验Key值是否存在
         */
        public Boolean exists(String key) {
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                return jedis.exists(key);
            } catch (Exception e) {
                return false;
            } finally {
                jedis.close();
            }
        }
        /**
         * 分布式锁
         * @param key
         * @param value
         * @param time 锁的超时时间,单位:秒
         *
         * @return 获取锁成功返回"OK",失败返回null
         */
        public String getDistributedLock(String key,String value,int time){
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                return jedis.set(key, value, new SetParams().nx().ex(time));
            } catch (Exception e) {
                return null;
            } finally {
                jedis.close();
            }
        }
    
        /**
         * 加锁
         *
         * @param business_code 业务编码
         * @param id
         * @return
         */
        public boolean lock(String business_code, String id) {
            Jedis jedis = jedisPool.getResource();
            Long start = System.currentTimeMillis();
            try {
                while (true) {
                    //SET命令返回OK ,则证明获取锁成功
                    String lock = jedis.set(lock_key.concat(business_code), id, params);
                    if (lock_ok.equals(lock)) {
                        return true;
                    }
                    //否则循环等待,在timeout时间内仍未获取到锁,则获取失败
                    long waitTime = System.currentTimeMillis() - start;
                    if (waitTime >= timeout) {
                        return false;
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        logger.error("sleep失败,", e);
                    }
                }
            } finally {
                jedis.close();
            }
        }
    
        /**
         * 解锁
         *
         * @param id
         * @return
         */
        public boolean unlock(String business_code, String id) {
            Jedis jedis = jedisPool.getResource();
            try {
                Object result = jedis.eval(lua_script,
                        Collections.singletonList(lock_key.concat(business_code)),
                        Collections.singletonList(id));
                if ("1".equals(result.toString())) {
                    return true;
                }
                return false;
            } finally {
                jedis.close();
            }
        }
        public Map<String, String> getMap(String key) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.hgetAll(key);
            } catch (Exception e) {
                return null;
            } finally {
                jedis.close();
            }
        }
        public Long setMap(String key, Map<String, String> value) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.hset(key, value);
            } catch (Exception e) {
                logger.info("-------向Redis存入Map失败--------", e);
                return -1L;
            } finally {
                jedis.close();
            }
        }
    }
    

    解锁是通过jedis.eval来执行一段LUA来实现的,这是当下流行的靠谱方案,它把锁的Key键和生成的字符串当做参数传入。

    在UserController中添加测试函数,模拟直播秒杀场景,上架商品数量右主播设置。由于是模拟是否加锁解锁成功,为了简化,故在程序中自动生成客户id。

        
        private int count = 0;
    
        /**
         * 模拟直播秒杀场景,上架商品数量限指定件
         *
         * @param productNum 上架商品数量
         * @return
         * @throws InterruptedException
         */
        @ApiOperation(value = "测试Redis分布式锁")
        @GetMapping("/testRedisLock")
        @ResponseBody
        public String testRedisLock(Integer productNum) throws InterruptedException {
            CountDownLatch countDownLatch = new CountDownLatch(productNum);
    
            ExecutorService executorService = Executors.newFixedThreadPool(100);
            long start = System.currentTimeMillis();
            for (int i = 0; i < productNum; i++) {
                executorService.execute(() -> {
                    String businessCode = "test";
                    // 模拟下单用户,其实作为入参,为了简化,故自动生成
                    String id = UUID.randomUUID().toString();
                    try {
    //                    jedisUtil.lock(businessCode, id);
                        jedisUtil.getDistributedLock(businessCode, id, 1);
                        count++;
                    } finally {
                        jedisUtil.unlock(businessCode, id);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            String ret = String.format("执行线程数:%s,总耗时:%s,count数为:%s", productNum, System.currentTimeMillis() - start, count);
            logger.info(ret);
            return ret;
        }    
    

    由代码可知,商品售罄即止Swagger3测试效果如图所示:

    大家可以试试另外一种加锁方式,你有Redis分布式锁的更佳实现方案吗?欢迎留言!

    §小结

      对于上述应用单节点Redis分布式锁模拟直播秒杀场景,您怎么看?欢迎参与话题互动讨论,分享你的观点和看法, 评论区留言哦,喜欢小编文章的朋友请点赞,谢谢你的参与!


      读后有收获,小礼物走一走,请作者喝咖啡。

    赞赏支持

  • 相关阅读:
    sas中一些小的选项的含义
    C++变量学习点
    sas,log,output,ods输出管理(html output_object output_statement)
    matlab统计函数
    sas条件判断语句where,if的区别,以及where选项
    sas数组,数组的语法与一些特殊定义,获取维度大小
    sas赋值语句,累加语句,keep,drop,rename,(retain/sum statement)
    解决Xcode 4.3.2的"Could not insert new outlet connection"问题
    网络数据的XML解析
    将UIView中的图像保存到相册
  • 原文地址:https://www.cnblogs.com/east7/p/14459668.html
Copyright © 2011-2022 走看看