redis是單線程的,他提供了一個單線程的自增方法increment供我們使用。
現在有一個業務需求,id需要自增生成,且生成速度要求一秒一千以上。廢話不多說,直接上代碼
public class IncrIdUtils {
private final String REDIS_KEY_TASK_ID = "AUTO_TASK_ID";
private final String REDIS_KEY_PRO_INS_ID = "AUTO_PRO_INS_ID";
@Resource
private RedisUtils redisUtils;
@Value("${task.id.last}")
private Long taskIdLast = 900000000000L;
@Value("${pro.ins.id.last}")
private Long proInsIdLast = 900000000000L;
/**
* 生成一個長度為12的純數字字符串作為任務id,規則如下
* 1.以9開頭
* 2.后11位數字從1開始自增
*
* @return 如:"900000000001"
*/
public String getTaskId() {
String taskIdRedis;
long incr = redisUtils.incr(REDIS_KEY_TASK_ID, 1);
taskIdRedis = String.valueOf(incr + taskIdLast);
return taskIdRedis;
}
}
下面是測試代碼
@Resource
IncrIdUtils incrIdUtils;
public Response testTaskIdCreate(@ApiParam(name = "num", value = "生成數量", required = true) @RequestParam("num") Integer num) throws InterruptedException {
long start = System.currentTimeMillis();
Map<String, Integer> dataMap = new ConcurrentHashMap<>();
ExecutorService executorService = Executors.newFixedThreadPool(100);
for (int i = 0; i < num; i++) {
executorService.submit(() -> {
String taskId = "";
taskId = incrIdUtils.getTaskId();
System.out.println(taskId);
dataMap.put(taskId, 1);
});
}
executorService.shutdown();
while (true) {
if (executorService.isTerminated()) {
Map<String, Integer> resultMap = new HashMap<>();
resultMap.put("map", dataMap.size());
resultMap.put("time", (int) (System.currentTimeMillis() - start));
return Response.success(resultMap);
}
}
}
運行代碼測試,並發量可以達到每秒一萬以上,並且生成的id不會重復,搞定
開發過程中遇到的坑:注意在測試的時候一定不能用hashmap,hashmap是線程不安全的,因為一開始用了hashmap,老師出現數量對不上的情況,換了concurrenthashmap馬上好了
用過的其他方法:
在代碼中進行自增運算,然后寫入的時候用cas算法校驗。
這種方法效率低,增加了redis的讀寫次數,並且單純的cas算法並沒有保證線程安全,還容易導致內存溢出。再加上synchronized代碼鎖之后,效率更低,每秒只能生成150個左右。
換成lock鎖之后效率更低,只有每秒100個左右。頻繁的加鎖釋放鎖也是很浪費時間