天天看點

分庫分表下uuid的生成

    分庫分表時一般有必要自定義生成uuid,大企業一般有自己的uuid生成服務,其他它的實作很簡單。我們以訂單号為例,組成可以是"業務辨別号+年月日+當日自增數字格式化",如0001201608140000020。當然,如果我們用"業務辨別号+使用者唯一辨別+目前時間"也是可以達到uuid的目的的,但使用者唯一辨別是敏感資訊且可能不太友善處理為數字,是以弄一套uuid生成服務是很有必要的。本文就來研究下怎麼實作自增數字,且性能能滿足企業中的多方業務調用。起初,我想的是db+redis,後來想想用redis不僅會相對降低穩定性,更是一種舍近求遠的做法,是以,我最終的做法是db+本地緩存(記憶體)。不說了,直接上代碼。

分庫分表下uuid的生成

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;  

分庫分表下uuid的生成

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;  

    }  

     * 載入記憶體 

    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;  

}  

分庫分表下uuid的生成

import java.text.simpledateformat;  

import java.util.date;  

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 {  

                            // 直接修改緩存的值就可以了  

                        }  

                }  

                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+本地緩存的思路同樣可以用于搶購時的庫存計算。

原文連結:[http://wely.iteye.com/blog/2317423]