分库分表下uuid的生成

简介:

    分库分表时一般有必要自定义生成uuid,大企业一般有自己的uuid生成服务,其他它的实现很简单。我们以订单号为例,组成可以是"业务标识号+年月日+当日自增数字格式化",如0001201608140000020。当然,如果我们用"业务标识号+用户唯一标识+当前时间"也是可以达到uuid的目的的,但用户唯一标识是敏感信息且可能不太方便处理为数字,所以弄一套uuid生成服务是很有必要的。本文就来研究下怎么实现自增数字,且性能能满足企业中的多方业务调用。起初,我想的是DB+Redis,后来想想用Redis不仅会相对降低稳定性,更是一种舍近求远的做法,所以,我最终的做法是DB+本地缓存(内存)。不说了,直接上代码。

Java代码   收藏代码
  1. public class UuidModel implements Serializable {  
  2.     private static final long serialVersionUID = 972714740313784893L;  
  3.   
  4.     private String name;  
  5.   
  6.     private long start;  
  7.   
  8.     private long end;  
  9.   
  10.     // above is DB column  
  11.   
  12.     private long oldStart;  
  13.   
  14.     private long oldEnd;  
  15.   
  16.     private long now;  

 

Java代码   收藏代码
  1. package com.itlong.bjxizhan.uuid;  
  2.   
  3. import org.slf4j.Logger;  
  4. import org.slf4j.LoggerFactory;  
  5.   
  6. import java.util.List;  
  7. import java.util.concurrent.ConcurrentHashMap;  
  8. import java.util.concurrent.ConcurrentMap;  
  9.   
  10. /** 
  11.  * Created by shenhongxi on 2016/8/12. 
  12.  */  
  13. public class UuidContext {  
  14.   
  15.     private static final Logger log = LoggerFactory.getLogger(UuidContext.class);  
  16.   
  17.     // 缓存DB中的截止数  
  18.     public static ConcurrentMap<String, Long> endCache = new ConcurrentHashMap<String,Long>();  
  19.     // 缓存当前增加到的数值  
  20.     public static ConcurrentMap<String, Long> nowCache = new ConcurrentHashMap<String,Long>();  
  21.     // 缓存共享对象  
  22.     public static ConcurrentMap<String, UuidModel> uuidCache = new ConcurrentHashMap<String, UuidModel>();  
  23.     // 缓存配置  
  24.     public static ConcurrentMap<String, Config> configCache = new ConcurrentHashMap<String, Config>();  
  25.   
  26.     static UuidDao uuidDao;  
  27.   
  28.     /** 
  29.      * 根据名称更新号段 直至成功 
  30.      * @param um 
  31.      * @return 
  32.      */  
  33.     public static UuidModel updateUuid(UuidModel um, int length){  
  34.         boolean updated = false;  
  35.         do{  
  36.             UuidModel _um = uuidDao.findByName(um.getName());  
  37.             int cacheSize = 1000;  
  38.             Config config = getConfig(um.getName());  
  39.             if (config != null) {  
  40.                 cacheSize = config.getCacheSize();  
  41.             }  
  42.             // 判断是否需要重置 条件为:1.配置的重置数<新段的截止数 则需要重置  
  43.             // 2.新段的截止数大于需要获取的位数 则需要重置  
  44.             long resetNum = config.getResetNum();  
  45.             // 取得新段的截止数  
  46.             long newEnd = _um.getEnd() + cacheSize;  
  47.             um.setOldEnd(_um.getEnd());  
  48.             um.setOldStart(_um.getStart());  
  49.             if ((resetNum < newEnd) || (String.valueOf(newEnd).length() > length)) {  
  50.                 // 需要重置为0开始段  
  51.                 um.setStart(0);  
  52.                 um.setEnd(cacheSize);  
  53.             } else {  
  54.                 // 取新段  
  55.                 um.setStart(_um.getEnd());  
  56.                 um.setEnd(_um.getEnd() + cacheSize);  
  57.             }  
  58.   
  59.             // 最终的更新成功保证了多实例部署时,各实例持有的号段不同  
  60.             updated = uuidDao.update(um);  
  61.         } while (!updated);  
  62.   
  63.         return um;  
  64.     }  
  65.   
  66.     /** 
  67.      * 载入内存 
  68.      * @param um 
  69.      */  
  70.     public static void loadMemory(UuidModel um){  
  71.         endCache.put(um.getName(), um.getEnd());  
  72.         nowCache.put(um.getName(), um.getStart());  
  73.         uuidCache.put(um.getName(), um);  
  74.     }  
  75.   
  76.     public static Config getConfig(String name) {  
  77.         Config config = configCache.get(name);  
  78.         if (config == null) {  
  79.             config = configCache.get("default");  
  80.         }  
  81.         return config;  
  82.     }  
  83. }  

 

Java代码   收藏代码
  1. package com.itlong.bjxizhan.uuid;  
  2.   
  3. import org.slf4j.Logger;  
  4. import org.slf4j.LoggerFactory;  
  5.   
  6. import java.text.SimpleDateFormat;  
  7. import java.util.Date;  
  8.   
  9. /** 
  10.  * Created by shenhongxi on 2016/8/12. 
  11.  */  
  12. public class UuidServiceImpl implements UuidService {  
  13.   
  14.     private static final Logger log = LoggerFactory.getLogger(UuidServiceImpl.class);  
  15.   
  16.     private UuidDao uuidDao;  
  17.   
  18.     @Override  
  19.     public String nextUuid(String name) {  
  20.         // 日期 + format(nextUuid(name, cacheSize, length))  
  21.     }  
  22.   
  23.     private synchronized long nextUuid(String name, int cacheSize, int length) {  
  24.         UuidModel um = UuidContext.uuidCache.get(name);  
  25.         Long nowUuid = null;  
  26.         try {  
  27.             if (um != null) {  
  28.                 synchronized (um) {  
  29.                     nowUuid = UuidContext.nowCache.get(name);  
  30.                     Config cm = UuidContext.getConfig(name);  
  31.                     // 判断是否到达预警值  
  32.                     if (UuidContext.nowCache.get(name).intValue() == cm.getWarnNum()) {  
  33.                         log.warn("警告:" + name + "号段已达到预警值.");  
  34.                     }  
  35.   
  36.                     log.info("dbNum:" + UuidContext.endCache.get(name)  
  37.                             + ",nowNum:" + UuidContext.nowCache.get(name));  
  38.                     // 判断内存中号段是否用完  
  39.                     if (UuidContext.nowCache.get(name).compareTo(UuidContext.endCache.get(name)) >= 0) {  
  40.                         // 更新号段  
  41.                         UuidContext.updateUuid(um, length);  
  42.   
  43.                         nowUuid = um.getStart() + 1;  
  44.                         UuidContext.endCache.put(name, um.getEnd());  
  45.                         UuidContext.nowCache.put(name, nowUuid);  
  46.                     } else {  
  47.                         nowUuid += 1;  
  48.                         // 是否需要重置 判断自增号位数是否大于length参数  
  49.                         if (String.valueOf(nowUuid).length() > length) {  
  50.                             // 更新号段,需要重置  
  51.                             nowUuid = 1l;  
  52.                             UuidContext.updateUuid(um, 0);  
  53.                             UuidContext.endCache.put(name, um.getEnd());  
  54.                             UuidContext.nowCache.put(name, nowUuid);  
  55.                             UuidContext.uuidCache.put(name, um);  
  56.                         } else {  
  57.                             // 直接修改缓存的值就可以了  
  58.                             UuidContext.nowCache.put(name, nowUuid);  
  59.                         }  
  60.                     }  
  61.                 }  
  62.             } else {  
  63.                 synchronized (this) {  
  64.                     um = UuidContext.uuidCache.get(name);  
  65.                     if (um != null) {  
  66.                         return nextUuid(name, cacheSize, length);  
  67.                     }  
  68.                     nowUuid = 1l;  
  69.   
  70.                     // 如果缓存不存在,那么就新增到数据库  
  71.                     UuidModel um2 = new UuidModel();  
  72.                     um2.setName(name);  
  73.                     um2.setStart(0);  
  74.                     um2.setEnd(cacheSize);  
  75.                     uuidDao.insert(um2);  
  76.                     // 还要同时在缓存的map中加入  
  77.                     UuidContext.endCache.put(name, um2.getEnd());  
  78.                     UuidContext.nowCache.put(name, nowUuid);  
  79.                     UuidContext.uuidCache.put(name, um2);  
  80.                 }  
  81.             }  
  82.         } catch (Exception e) {  
  83.             log.error("生成uuid error", e);  
  84.             if (e.getMessage() != null && (e.getMessage().indexOf("UNIQUE KEY") >= 0 ||  
  85.                     e.getMessage().indexOf("PRIMARY KEY") >= 0)) {  
  86.                 UuidModel _um = new UuidModel();  
  87.                 _um.setName(name);  
  88.                 // 更新号段  
  89.                 UuidContext.updateUuid(_um, length);  
  90.                 // 载入缓存  
  91.                 UuidContext.loadMemory(_um);  
  92.                 // 继续获取  
  93.                 return nextUuid(name, cacheSize, length);  
  94.             }  
  95.             throw new RuntimeException("生成uuid error");  
  96.         }  
  97.   
  98.         return nowUuid;  
  99.     }  
  100.   
  101. }  

 值得一提的是,DB+本地缓存的思路同样可以用于抢购时的库存计算。



原文链接:[http://wely.iteye.com/blog/2317423]

相关文章
|
Java 数据库
如何使用JPA的UUID主键生成策略
这篇文章只写给主键用uuid并且用jpa的小伙伴。 1. 数据实体类 @Entity @Table(name = "ip_user") @GenericGenerator(name = "jpa-uuid", strategy = "uuid") ...
3459 0
|
8天前
|
存储 SQL 算法
搞定了 6 种分布式ID,分库分表哪个适合做主键?
在《ShardingSphere5.x分库分表原理与实战》系列的第七篇文章中,作者探讨了分布式ID在分库分表中的重要性,以及如何利用`ShardingSphere-jdbc`的多种主键生成策略。文章介绍了`UUID`、`NanoID`、自定义雪花算法和`CosId`等策略的优缺点,并警告不要在SQL中手动拼接主键字段。此外,文章还展示了如何配置这些策略,并提醒读者`CosId`在5.2.0版本可能不可用。最后,文章讨论了如何自定义分布式主键生成算法,并强调选择策略时要考虑全局唯一性、性能和易用性。
|
30天前
|
缓存 关系型数据库 MySQL
为啥MySQL官方不推荐使用uuid或者雪花id作为主键
为啥MySQL官方不推荐使用uuid或者雪花id作为主键
23 1
|
2月前
|
存储 关系型数据库 MySQL
用雪花 ID 和 UUID 做 MySQL 主键,可以吗?
用雪花 ID 和 UUID 做 MySQL 主键,可以吗?
33 0
用雪花 ID 和 UUID 做 MySQL 主键,可以吗?
|
9月前
|
存储 算法 安全
UUID为什么不用在分布式系统
分布式和大数据场景下,为什么不推荐使用UUID做为程序主键
|
7月前
|
算法 关系型数据库 MySQL
MySQL分库分表id主键处理
MySQL分库分表id主键处理
64 0
|
9月前
|
存储 监控 安全
详解:MySQL自增ID与UUID的优缺点及选择建议,MySQL有序uuid与自定义函数实现
详解:MySQL自增ID与UUID的优缺点及选择建议,MySQL有序uuid与自定义函数实现
921 0
|
10月前
|
算法 安全 Java
数据库如何合理生成主键:UUID、雪花算法
1.使用自增主键的弊端 首先在实际工程中我们很少用1,2,3......这样的自增主键,原因如下: 主键冲突 性能问题 安全问题 主键冲突: 比如我要跨数据库进行数据同步、或者在分布式系统中跨“分区”进行数据同步,不难想象,1,2,3......这种递增的单数字是极容易产生冲突的。
258 0
|
10月前
|
SQL 存储 缓存
聊聊分库分表后非Sharding Key查询的三种方案~(建议收藏)
聊聊分库分表后非Sharding Key查询的三种方案~(建议收藏)
523 0
|
10月前
|
SQL 关系型数据库 MySQL
mysql表中怎么将主键的类型设置为UUID
mysql表中怎么将主键的类型设置为UUID