分库分表时一般有必要自定义生成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+本地缓存的思路同样可以用于抢购时的库存计算。

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

  1. 数据库分库分表和带来的唯一ID、分页查询问题的解决

    需求缘起(用一个公司的发展作为背景) 1.还是个小公司的时候,注册用户就20w,每天活跃用户1w,每天最大单表数据量就1000,然后高峰期每秒并发请求最多就10,此时一个16核32G的服务器,每秒请求 ...

  2. EF多租户实例:快速实现分库分表

    前言 来到这篇随笔,我们继续演示如何实现EF多租户. 今天主要是演示多租户下的变形,为下图所示 实施 项目结构 这次我们的示例项目进行了精简,仅有一个API项目,直接包含所有代码. 其中Control ...

  3. 基于AOP和HashMap原理学习,开发Mysql分库分表路由组件!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 什么?Java 面试就像造火箭 单纯了! 以前我也一直想 Java 面试就好好面试呗 ...

  4. 分库分表的情况下生成全局唯一的ID

    分库分表情况下 跨库的问题怎么解决? 分布式事务怎么解决? 查询结果集集合合并的问题? 全局唯一的id怎么解决? 一般要求:1.保证生成的ID全局唯一,不可重复 2.生成的后一个Id必须大于前一个Id ...

  5. MySQL分库分表环境下全局ID生成方案 转

    在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...

  6. MySQL分库分表环境下全局ID生成方案

    在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...

  7. 【转】MySQL分库分表环境下全局ID生成方案

    转载一篇博客,里面有很多的知识和思想值得我们去思考. —————————————————————————————————————————————————————————————————————— 在大 ...

  8. 转数据库分库分表(sharding)系列(二) 全局主键生成策略

    本文将主要介绍一些常见的全局主键生成策略,然后重点介绍flickr使用的一种非常优秀的全局主键生成方案.关于分库分表(sharding)的拆分策略和实施细则,请参考该系列的前一篇文章:数据库分库分表( ...

  9. 数据库分库分表(sharding)系列(二) 全局主键生成策略

    本文将主要介绍一些常见的全局主键生成策略,然后重点介绍flickr使用的一种非常优秀的全局主键生成方案.关于分库分表(sharding)的拆分策略和实施细则,请参考该系列的前一篇文章:数据库分库分表( ...

随机推荐

  1. Linked List - leetcode

    138. Copy List with Random Pointer //不从head走 前面加一个dummy node 从dummy走先连head 只需记录当前节点 //这样就不需要考虑是先new ...

  2. python之实现缓存环

    看了CodeBokk 第二版通过python实现缓存环,吸收之后记录下,方便以后查阅. 任务: 定义一个固定尺寸的缓存,当它填满的时候,新加入的元素会覆盖第一个(最老的)元素.这种数据结构在存储日志和 ...

  3. 记录下 js各种证件的正则验证

    身份证 /(^\d{15}$)|(^\d{17}([0-9]|X)$)/    护照 /^[a-zA-Z0-9]{3,21}$/   /^(P\d{7})|(G\d{8})$/    军官证或士兵证 ...

  4. 转:【iOS开发每日小笔记(十一)】iOS8更新留下的“坑” NSAttributedString设置下划线 NSUnderlineStyleAttributeName 属性必须为NSNumber

    http://www.bubuko.com/infodetail-382485.html 标签:des   class   style   代码   html   使用   问题   文件   数据 ...

  5. liunx环境C、C++代码编译链接中间代码主要流程

    一个比较小的问题,可以直接看帖子: http://blog.csdn.net/gengyichao/article/details/6544266

  6. live555—VS2010/VS2013 下live555编译、使用及测试(转载)

    Ⅰ live555简介 Live555 是一个为流媒体提供解决方案的跨平台的C++开源项目,它实现了对标准流媒体传输协议如RTP/RTCP.RTSP.SIP等 的支持.Live555实现了对多种音视频 ...

  7. NEUQ1055谭浩强C语言(第三版)习题6.11

    //迭代公式不是很理解,写出来算了.. #include <stdio.h> #include <math.h> int main() { double x0,x1; int ...

  8. [Q]pdfFactory虚拟打印机的安装

    安装打图精灵过程中会提示是否安装pdfFactory虚拟打印机,建议选择安装. 若未安装,在安装打图精灵之后想安装pdfFactory,该软件可以在打图精灵应用程序文件夹下找到( 系统"开始 ...

  9. 关于pagerank算法的一点点总结

    1. PageRank算法每个顶点收敛的值与每个点的初值是没有关系的,每个点随便赋初值. 2.像q=0.8这样的阻尼系数已经解决了PageRank中处在的孤立点问题.黑洞效应问题. 3.当有那个点进行 ...

  10. .net导出不规则Excel

    using Hamp.App.BLL; using Hamp.App.Model; using Hamp.App.Model.QualityManagement; using System; usin ...