分库分表下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]

时间: 2025-01-17 14:26:57

分库分表下uuid的生成的相关文章

水平分库分表的关键问题及解决思路

分片技术的由来 关系型数据库本身比较容易成为系统性能瓶颈,单机存储容量.连接数.处理能力等都很有限,数据库本身的"有状态性"导致了它并不像Web和应用服务器那么容易扩展.在互联网行业海量数据和高并发访问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding.分片).同时,流行的分布式系统中间件(例如MongoDB.ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小异的. 分布式全局唯一ID 在很多中小项目中,我们往往直接使用数据库自

水平分库分表的关键步骤和技术难点

在之前的文章中,我介绍了分库分表的几种表现形式和玩法,也重点介绍了垂直分库所带来的问题和解决方法.本篇中,我们将继续聊聊水平分库分表的一些技巧. 分片技术的由来 关系型数据库本身比较容易成为系统性能瓶颈,单机存储容量.连接数.处理能力等都很有限,数据库本身的"有状态性"导致了它并不像Web和应用服务器那么容易扩展.在互联网行业海量数据和高并发访问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding.分片).同时,流行的分布式系统中间件(例如MongoDB.Elas

【转】微服务MySQL分库分表数据到MongoDB同步方案

需求背景 近年来,微服务概念持续火热,网络上针对微服务和单体架构的讨论也是越来越多,面对日益增长的业务需求是,很多公司做技术架构升级时优先选用微服务方式.我所在公司也是选的这个方向来升级技术架构,以支撑更大访问量和更方便的业务扩展. 发现问题 微服务拆分主要分两种方式:拆分业务系统不拆分数据库,拆分业务系统拆分库.如果数据规模小的话大可不必拆分数据库,因为拆分数据看必将面对多维度数据查询,跨进程之间的事务等问题.而我所在公司随着业务发展单数据库实例已经不能满足业务需要,所以选择了拆分业务系统同时

MySQL分库分表的实现过程详解介绍

MySQL分库分表基础表介绍 表基本模型结构 这里我们模拟一个商城的基本的表结.此结构由(用户.门店.导购.门店商品.订单.订单对应的商品).其中,导购也是一个用户,门店是只属于一个店主的,同时店主本身也是一个导购也是一个普通用户. 结构图:   构造数据脚本 MySQL分库分表(1)-脚本 对业务场景进行模拟 场景1:购买者下订单. 1.从session中获得客户ID. 2.可以通过时间戳等拼凑一个订单ID(在创建表的时候为了方便我用自增的,在以下我们一直就吧订单ID看成不是自增的,是用程序生

大众点评订单分库分表实践之路

本文是关于大众点评订单分库分表实践的一个具体分享,包含对订单库的具体切分策略,以及我个人的一些思考. 背景   订单单表早已突破两百G,因查询维度较多,即使加了两个从库,各种索引优化,依然存在很多查询不理想的情况.加之去年大量的抢购活动的开展,数据库达到瓶颈,应用只能通过限速.异步队列等对其进行保护.同时业务需求层出不穷,原有的订单模型很难满足业务需求,但是基于原订单表的DDL又非常吃力,无法达到业务要求.随着这些问题越来越突出,订单数据库的切分就愈发急迫了. 我们的目标是未来十年内不需要担心订

详解yii2实现分库分表的方案与思路

前言 大家可以从任何一个gii生成model类开始代码上溯,会发现:yii2的model层基于ActiveRecord实现DAO访问数据库的能力. 而ActiveRecord的继承链可以继续上溯,最终会发现model其实是一个component,而component是yii2做IOC的重要组成部分,提供了behaviors,event的能力供继承者扩展. (IOC,component,behaviors,event等概念可以参考http://www.digpage.com/学习) 先不考虑上面的

订单表的分库分表方案设计(大数据)

    原创文章,转载注明出处     一.两种方案分库分表    一般业界,对订单数据的分库分表,笔者了解,有两类思路:按照订单号来切分.按照用户id来切分.   方案一.按照订单号来做hash分散订单数据        把订单号看作是一个字符串,做hash,分散到多个服务器去.      具体到哪个库.哪个表存储数据呢?订单号里面的数字来记录着.     现在的微信红包.它的订单分库分表,是对订单号进行hash计算.不是什么取模.取整数.这样数据是均匀分散的.     然后订单号的末尾3个数

透明的分库分表方案

问题提出 随着应用规模的不断扩大,单机数据库就慢慢无法满足应用的需要了,这主要表现在如下方面: 存量数据越来越大,查询速度越来越慢 访问并发越来越大,磁盘IO.网络IO.CPU都慢慢成为瓶颈 事务数越来越多,事务冲突越来越严重,导致TPS越来越少 这个时候,有的人采用了换商用数据库的方案比如Oracle,然后用Oracle的RAC方式进行水平扩展.但是带来的缺点也比较明显,第一是成本太高,一般人吃不消:第二,管理复杂度较单节点有非常大的提升,风险及管理成本也相应增加:第三,对人员的水平要求更高,

mysql 分库分表的方法

分表后怎么做全文搜索 1.merge方式分表(不好) 2. 使用 sql union 3 使用Sphinx全文检索引擎 一,先说一下为什么要分表 当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. 根据个人经验,MySQL执行一个sql的过程如下: 1,接收到sql;2,把sql放到排队队列中 ;3,执行sql;4,返回执行结果.在这个执行过程中最花时间在什么地方呢?第一,是排队等待的时间,第二,