记录一次rediscluster 调用lua 脚本, 用于 list 批量获取

记录一次rediscluster 调用lua 脚本 用于 list 批量获取

需求:用redis list 当作消息队列(原来的流程固定不能变更),需要增加消费速度
改进方案的思想(不考虑业务逻辑变更优化,主要是原来的逻辑改不动,有些):
1、批量获取 2、多线程消费
改进中遇到的问题:
1.批量获取问题 2、多线程问题 3、分布式问题
解决方案:
1、批量获取问题
1)redis 6.2.2 版本前list 弹出队列只有单个出队列(lpop)变更版本不靠谱
2)搜索查询查看帖子 后了解到 通过lrange 结合ltrim 可以实现批量出队列
2、多线程问题
多线程的话就是加锁,给方法加锁
3、分布式问题
单点部署多线程加锁能保证单点上多线程对redis的lrange 和ltrim 是原子操作,但是分布式 就会出现问题,比如 服务器A 中的线程操作redis,redis 执行到了lrange 还没有执行到 ltrim,服务器B中的线程通过redis的执行了lrange,(redis内部是单线程执行),就会出现重复数据,然后再执行ltrim 会出现错误删除数据。
解决此情况: 就是让redis端保证 lrange 和ltrim 是原子操作,首先 想到redis 事务,可惜redis集群上不支持事务(需要改造,我觉得麻烦)
其次,lua 脚本 (通过redisson 了解到lua可以保证redis 原子操作)。开始尝试lua。
以下是代码 可以拿来直接用,

-------------------------------------------------
	maven  pom.xml 添加依赖jar
  <dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>2.9.1</version>
  </dependency>
  -----------------------------------------------
   JedisCluster jedisCluster= null;
   Set<HostAndPort> nodes = new HashSet<>();
   nodes.add(new HostAndPort("redis.host2","redis.port2"));
   nodes.add(new HostAndPort("redis.host3", "redis.port3"));
   if (jedisCluster== null) {
      jedisCluster= new JedisCluster(nodes);
   }
//  lua 脚本内容     
    public static String SCRIPT_LUA = 
             " local rlist = redis.call('lrange', KEYS[1],0,ARGV[1])\n" +
            "  redis.call('ltrim', KEYS[1],#rlist,-1)\n" +
            " return  rlist \n";
	//用于存储redis 对 lua脚本缓存
    static HashMap<String, String> cmap = new HashMap<>();
    public static synchronized List<String> batchList(String key, int size) {
        try {
            List<String> keys = new ArrayList<>();
            keys.add(key);
            List<String> params = new ArrayList<>();
            params.add(size + "");
            String UNSHA_LUA = cmap.get(key);
            if (StringUtils.isBlank(UNSHA_LUA)) {
            	//redis 加载lua脚本,返回唯一标识串
                UNSHA_LUA = jedisCluster.scriptLoad(SCRIPT_LUA, key);
                cmap.put(key, UNSHA_LUA);
            }
            return (List<String>) jedisCluster.evalsha(UNSHA_LUA, keys, params);//执行lua脚本
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

完结。。。。。
附 : 2个应用,1个应用 5个list,每个list 20线程获取,一次100,220W数据 测试可行

附2: redistemplate 操作lua(可用),主要摘自https://blog.csdn.net/u012899746/article/details/84314527

      List<String> keys = new ArrayList<String>();
      keys.add("test_key1");
      List<String> args = new ArrayList<String>();
      args.add("hello,key1");
      String LUA = "redis.call('SET', KEYS[1], ARGV[1]); return ARGV[1]";
        //spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本异常,此处拿到原redis的connection执行脚本
      String result = (String)redisTemplate.execute(new RedisCallback<String>() {
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式和单点模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                // 集群
                if (nativeConnection instanceof JedisCluster) {
                    return (String) ((JedisCluster) nativeConnection).eval(LUA, keys,args);
                };
                // 单点
                else if (nativeConnection instanceof Jedis) {
                    return (String) ((Jedis) nativeConnection).eval(LUA, keys, args);
                }
                return null;
            }
        });
 //或者    注意key 中的{}  多key 时集群操作 会出现如下 异常  
 // No way to dispatch this command to Redis Cluster because keys have different slots.
            String key = "key_{" + id + "}";
            String key2 = "key2_" + id2 + "_{" + id+ "}";
            String SCRIPT_LUA = "" +
                    " if redis.call('get',KEYS[2])  then  " +
                    "    return 'hiskey' " +
                    " else " +
                    "      if redis.call('exists',KEYS[1]) == 1 then  " +
                    "          local setObject =redis.call('spop', KEYS[1])  " +
                    "          redis.call('set', KEYS[2],setObject)  " +
                    "          return  setObject " +
                    "       else  return 'noelement' " +
                    "    end " +
                    "end";
            String result = (String) redisTemplate.execute(new RedisCallback<String>() {
                @Override
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    Object nativeConnection = connection.getNativeConnection();
                    if (nativeConnection instanceof JedisCluster) {
                        JedisCluster jedisCluster = (JedisCluster) nativeConnection;
                        //以下是不同点,evalsha 是redis端缓存脚本,2代表 key的个数
                        String UNSHA_LUA = jedisCluster.scriptLoad(SCRIPT_LUA, key);
                        return (String) ((JedisCluster) nativeConnection).evalsha(UNSHA_LUA, 2, key, key2);
                    } else {
                        return "";
                    }
                }
            });
//或者 如下,但也要保留 多key时中的{},多key的{} 内容要一致 
  			List<String> keys = new ArrayList<>();
            keys.add(key);
            keys.add(key2);
            List<String> params = new ArrayList<>();
    		String result = (String) redisTemplate.execute(new RedisCallback<String>() {
                @Override
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    Object nativeConnection = connection.getNativeConnection();
                    if (nativeConnection instanceof JedisCluster) {
                        JedisCluster jedisCluster = (JedisCluster) nativeConnection;
                        return (String) ((JedisCluster) nativeConnection).eval(SCRIPT_LUA, keys, params);
                    } else {
                        return "";
                    }
                }
            });

附3, lua 获取所有参数集合,"local args=ARGV " :
获取集合长度,"local length=#list "

  String LUA = " redis.call('DEL', KEYS[1])\n" +
            "local args=ARGV \n" +
            "local setNum=0 \n" +
            " for key,value in ipairs(args)\n" +
            " do\n" +
            "  local a=redis.call('sadd', KEYS[1],value)\n" +
            "  setNum=setNum+a \n" +
            " end " +
            " return  setNum \n";

你可能感兴趣的:(lua,redis,数据库)