• 分库分表下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+本地缓存的思路同样可以用于抢购时的库存计算。

    京东技术
  • 相关阅读:
    字体辉光效果
    C# 读写 Photoshop PSD文件 操作类
    SQL Server日期时间格式转换字符串详解
    用DataTable填充实体类List
    C#控件的闪烁问题解决方法总结
    .NET Framework的属性类对控件的支持功能
    Aspose破解版本dll
    整理的C#屏幕截图,控件截图程序
    C#绘制渐变背景
    VS2010编译的时候出现fatal error LNK1146: 没有用选项“/out:”指定的参数
  • 原文地址:https://www.cnblogs.com/wely/p/6198735.html
Copyright © 2020-2023  润新知