记录一次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";