• Redis脚本设计高并发 爱我


    需求场景:高并发接口场景

    一:设计思路

    ​ 利用redis设计多级缓存(假设有2级),当一级缓存用完后,到二级缓存中查询同时开启异步线程填充一级缓存。如此不断循环可保障缓存中一直有数据。

    1.多级缓存设计:共计创建3个list集合(2个为缓存集合,1个为维护缓存集合的状态)和1个hash集合(用于存放缓存中被消费的数据信息-key为被消费的id,value为其他信息)

    2.异步填充缓存,同时通知(MQ)更新hash集合中数据到数据库。

    步骤演示:

    步骤一:当请求过来时,先判断loopQueue的长度

    ​ 1.若大于0—》从左获取第一个数据(list的key),找到对应的list(q1),并判断改list的长度

    ​ 1.1若大于0—》从list中取出该值并返回

    ​ 1.2否则 —》从左取出loopQueue得第一个数据,并回到步骤一
    2.否则—》从数据库中获取

    二:实现:

    步骤一:预备数据——>创建缓存队列(lua脚本+redisTemplate)
    1.脚本定义如下
    local len = redis.call('LLEN', KEYS[1])
    if len ~= 0 then  
        return false //list缓存队列不存在
    end
    local len = redis.call('HLEN', KEYS[2])
    if len ~= 0 then
        return false //hash集合也不存在
        else
        redis.call('LPUSH', KEYS[1], ARGV[1],ARGV[2]) //创建缓存队列
        return true
    end
    
    2.redisTemplate调用脚本——createLoopQueue
    DefaultRedisScript<Boolean> redisScript2 = new DefaultRedisScript<Boolean>();
    List<String> keys = new ArrayList<>();
    List<String> argv = new ArrayList<>();
        {
            //指定脚本路径
            redisScript2.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redisScript2.lua")));
            // 指定返回类型
            redisScript2.setResultType(Boolean.class);
            // 
            keys.add("loopQueue");
            keys.add("waitingHash");
            argv.add("q1");
            argv.add("q2");
        }
    public void createLoopQueue() {
            if (redisTemplate.execute(redisScript2, keys, argv.toArray())) {
                System.out.print("新建循环队列");
            } else {
                System.out.print("循环队列已存在");
            }
        }
    
    步骤二:从缓存队列中获取数据——(lua脚本+redisTemplate)
    1.定义脚本
    local len = redis.call('LLEN', KEYS[1])
    if len == 0 then
        return '-1' //缓存数据已用完毕
    end
    
    local keySet = redis.call('LINDEX', KEYS[1], 0)
    local keySetLen = redis.call('LLEN', keySet)
    if keySetLen == 0 then
        return redis.call('LPOP', KEYS[1]) //缓存队列数据已用完毕
    else
        local value = redis.call('LPOP', keySet)
        redis.call('HMSET', KEYS[2], value,'0')
        return value //返回缓存中获取的数据
    end
    
    
    2.redisTemplate调用脚本——getByLoop
    DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
    {
            // 指定 lua 脚本
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redisScript.lua")));
            // 指定返回类型
            redisScript.setResultType(String.class);
          
     }
    /**
         * 循环从缓存中获取数据
         *
         * @return 从缓存中或数据库(缓存穿透)获取的数据
         */
        public String getByLoop() {
            String resultFromLua = getResultFromLua();
            while (true) {
                if (resultFromLua.equals("-1")) {
                    System.out.println("全部缓存已用完毕,从数据库获取数据");
                    // -1 全部缓存已用完毕,从数据库获取数据
                    resultFromLua = "mysql";
                    break;
                } else if (resultFromLua.equals("q1")) {// q1缓存已用完->循环获取
                    System.out.println("q1缓存已用完->循环获取");
                    //填充缓存
                    fillCache("q1");
                    resultFromLua = getResultFromLua();
                    continue;
                } else if (resultFromLua.equals("q2")) {// q2缓存已用完->循环获取
                    //填充缓存
                    System.out.println("q2缓存已用完->循环获取");
                    fillCache("q2");
                    resultFromLua = getResultFromLua();
                    continue;
                }
                break;
            }
            System.out.println("获取数据=" + resultFromLua);
            //记录缓存中数据被消费的其他信息(当前时间戳-真实情况可能是用户信息)
            redisTemplate.opsForHash().put(keys.get(1), resultFromLua, String.valueOf(System.currentTimeMillis()));
            return resultFromLua;
        }
    
        /**
         * 脚本获取缓存数据
         *
         * @return -1:表示缓存中的数据已被消费完,q1或q2表示缓存队列被消费
         */
        private String getResultFromLua() {
            return String.valueOf(redisTemplate.execute(redisScript, keys, keys.toArray()));
        }
    
        /**
         * 异步填充数据
         *
         * @param q : which name of  queue
         */
        @Async
        void fillCache(String q) {
            // TODO 从数据库中获取数据
            Integer integer = Integer.valueOf(this.ss2[0]);
            this.ss2[0] = String.valueOf(integer + 1);
            //填充缓存数据
            redisTemplate.opsForList().leftPushAll(q, this.ss2);
            //从相反的方向push到维护队列中
            redisTemplate.opsForList().rightPush(keys.get(0), q);
            //TODO 通知数据刷新
        }
    
  • 相关阅读:
    JQuery 学习总结及实例 !! (转载)
    JavaScript 学习笔记
    个人对JS的一些见解
    本博客欢迎交流,文章自由转载,保持署名!
    VSCode:源码编译运行,分析,踩坑
    ant design pro/前端/JS:实现本地运行https
    前端/JS/React/ES6:纯前端实现图片压缩技术
    云服务名词:软件即服务SaaS,怎么这个理解起来这么别扭
    React:effect应该怎么翻译比较合适??
    我给博客加了一个娃娃,一片雪花
  • 原文地址:https://www.cnblogs.com/jinliang374003909/p/15846784.html
Copyright © 2020-2023  润新知