分库分表时一般有必要自定义生成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;
- }
- /**
- * 载入内存
- * @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+本地缓存的思路同样可以用于抢购时的库存计算。
原文链接:[http://wely.iteye.com/blog/2317423]
时间: 2025-01-17 14:26:57