N位邀请码生成问题
1. 传统方式
采用Java库提供的基本Random类进行处理,考虑如下问题,生成n位邀请码,要求邀请码中的字符为0-9A-Z,这种方式只需要采用Random取n个0-35之间的随机数,然后分别与0-9A-Z进行对应。去重操作可以交给数据库来解决。
优点:
1. 实现简单
2. 对邀请码要求量小的情况下相当适用
缺点:
1. 后期去重十分不方便,极易出现重复情况
2. Redis解决
采用Redis解决邀请码问题,核心思想是采用Redis的位操作,以及Redis基本的哈希表数据结构做了很好的分桶操作。同样是上述问题,使用Redis处理基本思路如下:
- 在Redis中存储36的n次方个1bit组成的字符串,如果字符串过长,则对其进行分桶操作,将大value拆成小value。
- 采用lua脚本将bitpos和setbit进行结合,组成组合函数,函数的作用是输入x,取出距离x最近的1的index,并将其设置为0。返回index
- 使用Random函数先在总的桶中随机选取一个,然后再在桶内调用第二部的lua脚本。
- 将第二部的lua脚本中的返回值结合桶的index,计算为邀请码。
大致代码如下:
-
构造器初始化数据
/** *@param length 邀请码长度 *@save_key_prefix redis key的前缀 */ public CodeUtil(int length, String save_key_prefix) { if (length < MIN_LENGTH) { throw new RuntimeException(); } this.save_key_format = save_key_prefix + "_%d"; this.length = length; /* total用于记录邀请码可以出现的所有可能的情况 */ this.total = new BigDecimal(1); for (int i = 0; i < length; i++) { this.total = this.total.multiply(BigDecimal.valueOf(CHAR_RESOURCE.length)); } this.bulkLength = this.total.divide(new BigDecimal(BULK_SIZE)).intValue(); }
-
在Redis中存储36的n次方个1bit组成的字符串
/** * 初始化数据 * */ public void init(String addr, int port) { /*初始化Redis数据*/ jedisPool = new JedisPool(addr, port); Jedis jedis = jedisPool.getResource(); byte[] label = new byte[1024]; /* Redis的一个key只存放1KB个1 */ for (int i = 0; i < label.length; i++) { label[i] = (byte) 0xFF; } /* 填充Redis */ for (int i = 0; i < this.bulkLength; i++) { String key = String.format(this.save_key_format, i); byte[] data = jedis.get(key.getBytes()); if (data != null) { continue; } jedis.set(key.getBytes(), label); } jedis.close(); }
-
制作LUA脚本,使获取与设置统一成原子操作
/* Lua脚本,用于读取并设置位 */ private static final String script = "local index = redis.call('bitpos',KEYS[1],KEYS[2],KEYS[3]);\n" + "if index ~= -1 then\n" + " local result = redis.call('setbit',KEYS[1],index,'0')\n" + "end\n" + "return index;";
-
创建code:
/** * 创建Code * */ public String createCode() { Jedis jedis = jedisPool.getResource(); while (true) { /* 先分桶,在获取随机数 */ int bulkIndex = ThreadLocalRandom.current().nextInt(this.bulkLength); /* 再对桶内数据获取随机数 */ int offset = ThreadLocalRandom.current().nextInt(1024); System.out.println("bulkIndex:"+bulkIndex); System.out.println("offset:"+offset); String key = String.format(this.save_key_format,bulkIndex); /* 调用lua脚本获取index*/ Object result = jedis.eval(script,3,new String[]{key,"1",offset+""}); if(!result.equals("-1")){ /* 根据index转换成code */ String code = intToCode(bulkIndex,offset); jedis.close(); return code; } } } /** * 数字转Code * */ public String intToCode(int bulkIndex, int offset) { BigDecimal bigDecimal = new BigDecimal(bulkIndex).multiply(BULK_SIZE_BIGDICIMAL).add(BigDecimal.valueOf(offset)); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < this.length; i++) { BigDecimal[] result = bigDecimal.divideAndRemainder(new BigDecimal(36)); int index = result[1].intValue(); bigDecimal = result[0]; stringBuilder.append(CHAR_RESOURCE[index]); } return stringBuilder.toString(); }
整体代码如下:
package com.wdkid;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.math.BigDecimal;
import java.util.Scanner;
import java.util.concurrent.ThreadLocalRandom;
public class CodeUtil {
private final static int BULK_SIZE = 1024 * 8;
private final static BigDecimal BULK_SIZE_BIGDICIMAL = BigDecimal.valueOf(1024).multiply(BigDecimal.valueOf(8));
/* 邀请码最小长度 */
private int MIN_LENGTH = 4;
/* 邀请码中出现的字符 */
private static final String[] CHAR_RESOURCE = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
"U", "V", "W", "X", "Y", "Z"
};
/* redis桶的个数 */
private int bulkLength;
/* 指定长度的邀请码最多需要记录的数字的个数即CHAR_RESOURCE.length的length次方 */
private BigDecimal total;
/* 邀请码长度 */
private int length;
/* 保存邀请码的key的格式 */
private String save_key_format;
private JedisPool jedisPool;
/* Lua脚本,用于读取并设置位 */
private static final String script = "local index = redis.call('bitpos',KEYS[1],KEYS[2],KEYS[3]);\n" +
"if index ~= -1 then\n" +
" local result = redis.call('setbit',KEYS[1],index,'0')\n" +
"end\n" +
"return index;";
public CodeUtil(int length, String save_key_prefix) {
if (length < MIN_LENGTH) {
throw new RuntimeException();
}
this.save_key_format = save_key_prefix + "_%d";
this.length = length;
this.total = new BigDecimal(1);
for (int i = 0; i < length; i++) {
this.total = this.total.multiply(BigDecimal.valueOf(CHAR_RESOURCE.length));
}
this.bulkLength = this.total.divide(new BigDecimal(BULK_SIZE)).intValue();
}
/**
* 初始化数据
* */
public void init(String addr, int port) {
/*初始化Redis数据*/
jedisPool = new JedisPool(addr, port);
Jedis jedis = jedisPool.getResource();
byte[] label = new byte[1024];
for (int i = 0; i < label.length; i++) {
label[i] = (byte) 0xFF;
}
for (int i = 0; i < this.bulkLength; i++) {
String key = String.format(this.save_key_format, i);
byte[] data = jedis.get(key.getBytes());
if (data != null) {
continue;
}
jedis.set(key.getBytes(), label);
}
jedis.close();
}
/**
* 创建Code
* */
public String createCode() {
Jedis jedis = jedisPool.getResource();
while (true) {
/* 先分桶,在获取随机数 */
int bulkIndex = ThreadLocalRandom.current().nextInt(this.bulkLength);
int offset = ThreadLocalRandom.current().nextInt(1024);
System.out.println("bulkIndex:"+bulkIndex);
System.out.println("offset:"+offset);
String key = String.format(this.save_key_format,bulkIndex);
Object result = jedis.eval(script,3,new String[]{key,"1",offset+""});
if(!result.equals("-1")){
String code = intToCode(bulkIndex,offset);
jedis.close();
return code;
}
}
}
/**
* 数字转Code
* */
public String intToCode(int bulkIndex, int offset) {
BigDecimal bigDecimal = new BigDecimal(bulkIndex).multiply(BULK_SIZE_BIGDICIMAL).add(BigDecimal.valueOf(offset));
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < this.length; i++) {
BigDecimal[] result = bigDecimal.divideAndRemainder(new BigDecimal(36));
int index = result[1].intValue();
bigDecimal = result[0];
stringBuilder.append(CHAR_RESOURCE[index]);
}
return stringBuilder.toString();
}
public static void main(String[] args) {
CodeUtil codeUtil = new CodeUtil(4, "code");
codeUtil.init("127.0.0.1", 6379);
Scanner scanner = new Scanner(System.in);
for (int i = 0;i < 5000;i ++){
System.out.println("Please enter the new line:");
scanner.nextLine();
System.out.println(codeUtil.createCode());
}
}
}
优缺点分析以及优化步骤待明天补充。
更多笔记请查看