Redis集群 Lua Java 随机编号 用户编号
Posted 伍Wu哈Ha
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis集群 Lua Java 随机编号 用户编号相关的知识,希望对你有一定的参考价值。
用户id主键递增的情况下,要为用户生产随机的用户编号,效果如下:
这是一种伪随机的方案,前缀随机,后面的编号递增
reids脚本
local prefix=0
local exists = redis.call('EXISTS', KEYS[1])
if exists == 1 then
-- 随机出set的一个前缀
prefix= redis.call('SRANDMEMBER', KEYS[1])
else
-- 初始化set KEYS[1] 默认1至9 10个前缀
for i=1, 9 do redis.call('SADD', KEYS[1],i) end
prefix = redis.call('SRANDMEMBER', KEYS[1])
end
-- 随机出set的前缀 对应的值递增
local currentNum = redis.call('hIncrBy', KEYS[2], prefix, ARGV[1])
if (currentNum > tonumber(ARGV[2])) then
-- 有进位 前缀值加9,对应的旧前缀从set移除,增加新前缀
local newPrefix = prefix + 9
local newCurrentNum = redis.call('hIncrBy', KEYS[2], newPrefix, ARGV[1])
redis.call('srem', KEYS[1], prefix)
redis.call('SADD', KEYS[1], newPrefix)
return (newPrefix * tonumber(ARGV[2])) + newCurrentNum
else
-- 无进位 直接返回
return (prefix * tonumber(ARGV[2])) + currentNum
end
前缀
前缀对应的自增后的值
Redis客户端测试
把脚本的注释去掉
client.redis.rds.aliyuncs.com:1>eval "local prefix=0 local exists = redis.call('EXISTS', KEYS[1]) if exists == 1 then prefix= redis.call('SRANDMEMBER', KEYS[1]) else for i=1, 9 do redis.call('SADD', KEYS[1],i) end prefix = redis.call('SRANDMEMBER', KEYS[1]) end local currentNum = redis.call('hIncrBy', KEYS[2], prefix, ARGV[1]) if (currentNum > tonumber(ARGV[2])) then local newPrefix = prefix + 9 local newCurrentNum = redis.call('hIncrBy', KEYS[2], newPrefix, ARGV[1]) redis.call('srem', KEYS[1], prefix) redis.call('SADD', KEYS[1], newPrefix) return (newPrefix * tonumber(ARGV[2])) + newCurrentNum else return (prefix * tonumber(ARGV[2])) + currentNum end" 2 account_random_number_prefix_set account_random_number_hash 2332 10000
"ERR 'EVAL' command keys must in same slot"
client.redis.rds.aliyuncs.com:1>eval "local prefix=0 local exists = redis.call('EXISTS', KEYS[1]) if exists == 1 then prefix= redis.call('SRANDMEMBER', KEYS[1]) else for i=1, 9 do redis.call('SADD', KEYS[1],i) end prefix = redis.call('SRANDMEMBER', KEYS[1]) end local currentNum = redis.call('hIncrBy', KEYS[2], prefix, ARGV[1]) if (currentNum > tonumber(ARGV[2])) then local newPrefix = prefix + 9 local newCurrentNum = redis.call('hIncrBy', KEYS[2], newPrefix, ARGV[1]) redis.call('srem', KEYS[1], prefix) redis.call('SADD', KEYS[1], newPrefix) return (newPrefix * tonumber(ARGV[2])) + newCurrentNum else return (prefix * tonumber(ARGV[2])) + currentNum end" 2 account_random_number_prefix_set account_random_number_hash 2332 10000
"62332"
注意:Redis集群下,所有key必须带有 xxx,并且xxx内容一致,保证key都分到同一个slot中,例如account_random_number_prefix_set替换account_random_number_prefix_set,account_random_number_hash 替换为account_random_number_hash,共同部分为account_random_number
你可以通过在key前面增加带有 xxx 的部分来使这些key被分配到同一个slot中去。因为对于头部带有 xxx 的key,redis服务就不会对整个key做hash,只会对 xxx 做hash。
Java 代码
/**
* 五位数最大值(不包含)
*/
private static final Long FIVE_DIGITS_MAX = 100000L;
/**
* 用户随机ID 前缀
* 首批前缀序号为
* 递增规则如下:
* 1 --10 --19 --28
* 2 --11 --20 --29
* 3
* 4
* 5
* 6
* 7
* 8
* 9 --18 --27 --36
* 并且位数超过当前最大值 递增9为新前缀序号
*/
String ACCOUNT_RANDOM_NUMBER_PREFIX_SET = "account_random_number_prefix_set";
/**
* 用户前缀下的自增number
*/
String ACCOUNT_RANDOM_NUMBER_HASH = "account_random_number_hash";
/**
* lua脚本生成用户随机编号
*/
public Long doCreateUserNumberLua()
/*
* keys1 随机前缀set 默认1至9,超过
* keys2 hash 存储前缀set对应的递增后的值
* argv1 随机数
* argv2 进位数 这里是5位 超过99999前缀set对应一个set进位
* 例如:
* 1.随机到前缀1 随机数为222 则生成的编号为 100222 hash存储前缀为1--》 222
* 2.再次随机到前缀1 随机数为333 则生成的编号为 100555 hash存储前缀为1--》 222
* 进位的情况
* 3.再次随机到前缀1 随机数为555(hash存储前缀为1--》 99998)则删除前缀为1的set,生成1+9=10的前缀,生成的编号为 1000555
*/
String script = "local prefix=0\\n" +
"local exists = redis.call('EXISTS', KEYS[1])\\n" +
"if exists == 1 then\\n" +
" prefix= redis.call('SRANDMEMBER', KEYS[1])\\n" +
"else\\n" +
" for i=1, 9 do redis.call('SADD', KEYS[1],i) end\\n" +
" prefix = redis.call('SRANDMEMBER', KEYS[1])\\n" +
"end\\n" +
"local currentNum = redis.call('hIncrBy', KEYS[2], prefix, ARGV[1])\\n" +
"if (currentNum > tonumber(ARGV[2])) then\\n" +
" local newPrefix = prefix + 9\\n" +
" local newCurrentNum = redis.call('hIncrBy', KEYS[2], newPrefix, ARGV[1])\\n" +
" redis.call('srem', KEYS[1], prefix)\\n" +
" redis.call('SADD', KEYS[1], newPrefix)\\n" +
" return (newPrefix * tonumber(ARGV[2])) + newCurrentNum\\n" +
"else\\n" +
" return (prefix * tonumber(ARGV[2])) + currentNum\\n" +
"end";
return redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(ACCOUNT_RANDOM_NUMBER_PREFIX_SET, ACCOUNT_RANDOM_NUMBER_HASH), RandomUtil.randomInt(100, 500), FIVE_DIGITS_MAX);
测试方法
@Test
public void initUserIdNumber() throws InterruptedException
CopyOnWriteArraySet<Long> set = new CopyOnWriteArraySet<>();
CountDownLatch latch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++)
new Thread(() ->
long lon = accountRegisterService.doCreateUserNumberLua();
System.out.println("lon=" + lon);
set.add(lon);
latch.countDown();
).start();
latch.await();
System.out.println("initUserIdNumber 完成");
System.out.println(set.size());
System.out.println(JSON.toJSONString(set));
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<artifactId>lettuce-core</artifactId>
<groupId>io.lettuce</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.9.0</version>
</dependency>
yml配置文件
spring:
redis:
database: 2
host: xxxx.rds.aliyuncs.com
port: 6379
password: xxxxx
jedis:
pool:
min-idle: 0
max-active: 8
max-idle: 8
max-wait: -1ms
connect-timeout: 30000ms
@Configuration
public class RedisConfig
/**
* redis template.
*
* @param factory factory
* @return RedisTemplate
*/
@Bean
public StringRedisTemplate redisTemplate(RedisConnectionFactory factory)
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
以上是关于Redis集群 Lua Java 随机编号 用户编号的主要内容,如果未能解决你的问题,请参考以下文章