分庫分表時一般有必要自定義生成uuid,大企業一般有自己的uuid生成服務,其他它的實作很簡單。我們以訂單号為例,組成可以是"業務辨別号+年月日+當日自增數字格式化",如0001201608140000020。當然,如果我們用"業務辨別号+使用者唯一辨別+目前時間"也是可以達到uuid的目的的,但使用者唯一辨別是敏感資訊且可能不太友善處理為數字,是以弄一套uuid生成服務是很有必要的。本文就來研究下怎麼實作自增數字,且性能能滿足企業中的多方業務調用。起初,我想的是db+redis,後來想想用redis不僅會相對降低穩定性,更是一種舍近求遠的做法,是以,我最終的做法是db+本地緩存(記憶體)。不說了,直接上代碼。

public class uuidmodel implements serializable {
private static final long serialversionuid = 972714740313784893l;
private string name;
private long start;
private long end;
// above is db column
private long oldstart;
private long oldend;
private long now;

package com.itlong.bjxizhan.uuid;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import java.util.list;
import java.util.concurrent.concurrenthashmap;
import java.util.concurrent.concurrentmap;
/**
* created by shenhongxi on 2016/8/12.
*/
public class uuidcontext {
private static final logger log = loggerfactory.getlogger(uuidcontext.class);
// 緩存db中的截止數
public static concurrentmap<string, long> endcache = new concurrenthashmap<string,long>();
// 緩存目前增加到的數值
public static concurrentmap<string, long> nowcache = new concurrenthashmap<string,long>();
// 緩存共享對象
public static concurrentmap<string, uuidmodel> uuidcache = new concurrenthashmap<string, uuidmodel>();
// 緩存配置
public static concurrentmap<string, config> configcache = new concurrenthashmap<string, config>();
static uuiddao uuiddao;
/**
* 根據名稱更新号段 直至成功
* @param um
* @return
*/
public static uuidmodel updateuuid(uuidmodel um, int length){
boolean updated = false;
do{
uuidmodel _um = uuiddao.findbyname(um.getname());
int cachesize = 1000;
config config = getconfig(um.getname());
if (config != null) {
cachesize = config.getcachesize();
}
// 判斷是否需要重置 條件為:1.配置的重置數<新段的截止數 則需要重置
// 2.新段的截止數大于需要擷取的位數 則需要重置
long resetnum = config.getresetnum();
// 取得新段的截止數
long newend = _um.getend() + cachesize;
um.setoldend(_um.getend());
um.setoldstart(_um.getstart());
if ((resetnum < newend) || (string.valueof(newend).length() > length)) {
// 需要重置為0開始段
um.setstart(0);
um.setend(cachesize);
} else {
// 取新段
um.setstart(_um.getend());
um.setend(_um.getend() + cachesize);
// 最終的更新成功保證了多執行個體部署時,各執行個體持有的号段不同
updated = uuiddao.update(um);
} while (!updated);
return um;
}
* 載入記憶體
public static void loadmemory(uuidmodel um){
endcache.put(um.getname(), um.getend());
nowcache.put(um.getname(), um.getstart());
uuidcache.put(um.getname(), um);
public static config getconfig(string name) {
config config = configcache.get(name);
if (config == null) {
config = configcache.get("default");
}
return config;
}

import java.text.simpledateformat;
import java.util.date;
public class uuidserviceimpl implements uuidservice {
private static final logger log = loggerfactory.getlogger(uuidserviceimpl.class);
private uuiddao uuiddao;
@override
public string nextuuid(string name) {
// 日期 + format(nextuuid(name, cachesize, length))
private synchronized long nextuuid(string name, int cachesize, int length) {
uuidmodel um = uuidcontext.uuidcache.get(name);
long nowuuid = null;
try {
if (um != null) {
synchronized (um) {
nowuuid = uuidcontext.nowcache.get(name);
config cm = uuidcontext.getconfig(name);
// 判斷是否到達預警值
if (uuidcontext.nowcache.get(name).intvalue() == cm.getwarnnum()) {
log.warn("警告:" + name + "号段已達到預警值.");
}
log.info("dbnum:" + uuidcontext.endcache.get(name)
+ ",nownum:" + uuidcontext.nowcache.get(name));
// 判斷記憶體中号段是否用完
if (uuidcontext.nowcache.get(name).compareto(uuidcontext.endcache.get(name)) >= 0) {
// 更新号段
uuidcontext.updateuuid(um, length);
nowuuid = um.getstart() + 1;
uuidcontext.endcache.put(name, um.getend());
uuidcontext.nowcache.put(name, nowuuid);
} else {
nowuuid += 1;
// 是否需要重置 判斷自增号位數是否大于length參數
if (string.valueof(nowuuid).length() > length) {
// 更新号段,需要重置
nowuuid = 1l;
uuidcontext.updateuuid(um, 0);
uuidcontext.endcache.put(name, um.getend());
uuidcontext.nowcache.put(name, nowuuid);
uuidcontext.uuidcache.put(name, um);
} else {
// 直接修改緩存的值就可以了
}
}
synchronized (this) {
um = uuidcontext.uuidcache.get(name);
if (um != null) {
return nextuuid(name, cachesize, length);
nowuuid = 1l;
// 如果緩存不存在,那麼就新增到資料庫
uuidmodel um2 = new uuidmodel();
um2.setname(name);
um2.setstart(0);
um2.setend(cachesize);
uuiddao.insert(um2);
// 還要同時在緩存的map中加入
uuidcontext.endcache.put(name, um2.getend());
uuidcontext.nowcache.put(name, nowuuid);
uuidcontext.uuidcache.put(name, um2);
} catch (exception e) {
log.error("生成uuid error", e);
if (e.getmessage() != null && (e.getmessage().indexof("unique key") >= 0 ||
e.getmessage().indexof("primary key") >= 0)) {
uuidmodel _um = new uuidmodel();
_um.setname(name);
// 更新号段
uuidcontext.updateuuid(_um, length);
// 載入緩存
uuidcontext.loadmemory(_um);
// 繼續擷取
return nextuuid(name, cachesize, length);
throw new runtimeexception("生成uuid error");
return nowuuid;
值得一提的是,db+本地緩存的思路同樣可以用于搶購時的庫存計算。
原文連結:[http://wely.iteye.com/blog/2317423]