`
IXHONG
  • 浏览: 446533 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

分库分表下uuid的生成

阅读更多

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

1
1
分享到:
评论
2 楼 IXHONG 2016-08-17  
lizhenghua168 写道
请问楼主,Config类的代码能麻烦贴一下吗?

  
/**
     * 业务名称
     */
    private String name;
    /**
     * 内存运算个数
     */
    private int cacheSize;
    /**
     * 生成自增数长度
     */
    private int length;
    /**
     * 填充字符
     */
    private String fillChar;
    /**
     * 前缀
     */
    private String prefix;
    /**
     * 后缀
     */
    private String suffix;
    /**
     * 自增预警数
     */
    private long warnNum;
    /**
     * 重置数
     */
    private long resetNum;
    /**
     * 1 uuid=前缀+自增数+后缀
     */
    private int strategy;
1 楼 lizhenghua168 2016-08-17  
请问楼主,Config类的代码能麻烦贴一下吗?

相关推荐

    47_来来来!咱们聊一下你们公司是怎么玩儿分库分表的?.zip

    总的来说,分库分表是解决大数据量下数据库性能问题的有效手段,但同时也需要考虑其带来的复杂性和运维挑战。在Java开发中,合理地利用像ShardingSphere这样的中间件,可以简化这一过程并确保系统的高效运行。

    50_一个关键的问题!分库分表之后全局id咋生成?.zip

    在IT行业中,数据库设计是至关重要的,特别是在大型系统中,分库分表是解决高并发、大数据量场景下的常见策略。然而,随着数据的分散,如何生成全局唯一ID(Global Unique Identifier)成为一个关键问题。本话题将...

    分库分表入门级-lzg

    分库分表是应对大数据量、高并发场景下的数据库优化策略,旨在解决单表数据量过大、并发处理能力不足等问题。本文将深入探讨分库分表的基本概念、关键问题及其解决方案,并介绍相关开源工具和实际应用案例。 **一、...

    第六节课交易分库分表详解二1

    分库分表是为了应对高并发、大数据量场景下的性能瓶颈,但是它带来了如分布式事务、分布式主键、跨库查询以及数据迁移等问题。 分布式事务是其中的一大难点,因为它涉及到多个数据库间的协调与一致性保证。解决这个...

    对分库分表的一些想法

    7. ID生成策略:分库分表后,自增ID可能会冲突,需要设计全局唯一ID生成策略,例如雪花算法、UUID等。 8. 数据迁移与备份:分库分表后,数据迁移和备份也需要考虑各个库表之间的关系,确保数据完整性。 9. 监控与...

    46_体验一下面试官对于分库分表这个事儿的一个连环炮.zip

    在这个主题下,我们将深入探讨分库分表的概念、原因、实现方式以及面试中可能涉及的相关问题。 首先,分库分表是为了应对单个数据库在数据量增大时性能下降的问题。当数据量达到一定程度,查询效率会显著降低,影响...

    10-发号器:如何保证分库分表后ID的全局唯一性?_For_group_share1

    在分布式数据库环境中,确保分库分表后的ID全局唯一性是一项关键挑战。传统的自增ID在单库单表中能够很好地工作,但在分布式系统中,由于数据分散在多个数据库或表中,简单的自增策略无法满足全局唯一性的需求。在...

    Java实现分布式雪花ID生成代码

    在大数据量的时候,会涉及分库分表,使用自增ID可能会导致ID重复,使用UUID是无序的,在创建主键索引的时候会频繁的修改索引树内的索引位置,让索引更新的效率很低等问题。索引此时就引入了雪花ID,它既能保证ID的...

    分库分表

    分库分表是一种数据库扩展策略,用于解决单个数据库在高并发、大数据量场景下的性能瓶颈问题。在大型系统中,随着用户数量和业务规模的增长,数据库的压力会急剧增加,这时就需要采用分库分表的方式来分散负载,提高...

    MySQL数据库优化之分表分库操作实例详解

    Spring Boot等框架提供了对分库分表的支持,如ShardingSphere等中间件可以帮助开发者实现这些策略,简化数据库的扩展和维护工作。 总之,MySQL数据库的分表分库优化是应对高并发、大数据量场景的有效手段。通过对...

    java面试题总结.docx

    Java编程语言在面试中常常涉及的关键知识点包括方法的重载(Overloading)和重写(Overriding)、多态性、Synchronized关键字的理解以及数据库的分库分表策略。下面将详细阐述这些概念。 1. **方法重载(Overloading)**...

    interview.zip

    - 分库分表是解决大数据量下数据库性能瓶颈的方法,通过水平拆分降低单表压力。 - “分库分表之后全局id咋生成?”涉及到分布式ID生成器,如Snowflake、Twitter的UUID等方案。 - “46_体验一下面试官对于分库分表...

    分布式ID生成器的解决方案总结.docx

    分库分表或数据迁移会导致问题。 4. 批量生成ID 一次性生成多个ID,减少数据库访问次数,但可能导致ID不连续,且不便于水平扩展。 5. Redis自增 Redis的单线程特性可以保证ID的唯一性和顺序,但增加了中间件的...

    分布式系统ID生成器解决方案.docx

    随着数据量的不断增长,数据库的分库分表策略需要一个全局唯一的ID来标识每条记录,传统的数据库自增ID不再适用。因此,引入专门的分布式ID生成器解决方案成为必要。 ### UUID方案 UUID(Universally Unique ...

    Aooms是基于SpringCloud生态的微服务开发平台,不止于简单的框架集成

    框架核心包,其他工程均依赖此包,核心特性如下:极简Controller,基于sharding-sphere的多数据源、分库分表支持,基于Mybatis 实现的 Db + Record 极简模式,附带物理分页实现,基于Consul的服务注册、发现,服务...

    Java开发面试必备知识技能总结视频合集

    - **具体实现**:结合分库分表的场景,探讨如何在实际项目中实现全局唯一ID的生成与分配。 #### 6. RPC底层通讯原理之Netty线程模型源码分析 - **RPC通信基础**:了解远程过程调用(RPC)的基本概念和工作原理。 -...

Global site tag (gtag.js) - Google Analytics