分庫分表時通常有必要自定義生成uuid,大企業通常有本身的uuid生成服務,其餘它的實現很簡單。咱們以訂單號爲例,組成能夠是"業務標識號+年月日+當日自增數字格式化",如0001201608140000020。固然,若是咱們用"業務標識號+用戶惟一標識+當前時間"也是能夠達到uuid的目的的,但用戶惟一標識是敏感信息且可能不太方便處理爲數字,因此弄一套uuid生成服務是頗有必要的。本文就來研究下怎麼實現自增數字,且性能能知足企業中的多方業務調用。起初,我想的是DB+Redis,後來想一想用Redis不只會相對下降穩定性,更是一種捨近求遠的作法,因此,我最終的作法是DB+本地緩存(內存)。不說了,直接上代碼。java
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; } /** * 載入內存 * @param 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; } }
package com.itlong.bjxizhan.uuid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.SimpleDateFormat; import java.util.Date; /** * Created by shenhongxi on 2016/8/12. */ 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 { // 直接修改緩存的值就能夠了 UuidContext.nowCache.put(name, nowUuid); } } } } 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+本地緩存的思路一樣能夠用於搶購時的庫存計算。數據庫