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

转载于:https://my.oschina.net/javahongxi/blog/1523746

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值