互聯網分庫分表主鍵的生成-一個小思路

幾乎全部的大型項目都涉及到分庫分表(使用關係型數據庫),爲了應對遞增的數據增加採用分庫分表的策略,分庫分表後面臨的首要問題就是主鍵的生成。主鍵的關係涉及幾個重要的因素:java

1,若是隻是作數據存儲,沒有其餘的意義,這樣的主鍵設計會很簡單,面臨的是後期查詢的問題,須要選擇是選擇什麼樣的數據類型來存儲主鍵,好比uuid的varchar,序列的bigint等,又或者拼接的結果最終以varchar來存儲,考慮的要點是須要怎麼最好的使用索引性能來設計實現快速的查詢。mysql

2,確保整個系統的分庫分表的主鍵數據惟一,能夠採用主鍵生成器的方式來確保主鍵惟一,該方案也有不少的方式實現,最多見的就是咱們使用mysql的一個表來控制主鍵的生成,你可使用序列也可使用其餘的,該方案的缺點是高併發的時候性能會壓在該數據庫服務器,也能夠採用不一樣的步長增加使用不一樣的數據庫。還有一種是動態的生成Twitter的snowflake算法,該算法已經有不少實現方法,中心思想是同樣的,能夠上網搜索不少的實現方法。我這裏介紹的是基於redis來實現的一個小方案。redis

3,基於redis實現分佈式主鍵的策略。算法

redis首先是支持主從複製的,能夠確保高可用的,採用服務器的雙redis和keepalive實現災難自動轉移。根據狀況能夠分紅不一樣的主鍵成成類型採用負載均衡的方法,平攤服務器的壓力。而且redis自己是支持事物的,順便再講解一下,在分佈式系統中使用的分佈式鎖安全的一種,這裏只是小提一下,具體的能夠參考redis的官方文檔。具體的實現方式有不少種,這裏使用一種簡單的方式來實現:spring

public class SequenceNum {

    //關於redis的使用
    private static JedisPool jedisPool;
    private static Map<String,Integer> dbMap = new ConcurrentHashMap<String,Integer>();

    //單例的安全實現以下
    private SequenceNum() {
        //spring整整合redis的連接工程模式
        JedisConnectionFactory jedisConnectionFactory = (JedisConnectionFactory) SpringUtil.getBean("jedisConnectionFactory");
        jedisPool = new JedisPool(jedisConnectionFactory.getPoolConfig(),
                                    jedisConnectionFactory.getHostName(),jedisConnectionFactory.getPort());
        dbMap.put("default", jedisConnectionFactory.getDatabase());
        System.out.print("init one");
    }
    private static class SequenceHelper {
        private static SequenceNum instance = new SequenceNum();
    }
    public static SequenceNum getInstance() {
        return SequenceHelper.instance;
    }


    //下面是須要進行內部緩存處理的數據
    private static Map<String, ConcurrentLinkedQueue<Long>> sequenceMap = new ConcurrentHashMap<String, ConcurrentLinkedQueue<Long>>();
    private static final long SEQUENCE_NUM = 50;


    //得到分佈式序列的方法
    public Long getSequenceNum(String sequence) {
        Long sequenceNum = -1l;

        ConcurrentLinkedQueue<Long> sequenceQuene = sequenceMap.get(sequence);

        if (null != sequenceQuene) {
            if (sequenceQuene.isEmpty()) {
                getAndSetQuene(sequence, sequenceQuene);
            }
        } else {
            sequenceQuene = new ConcurrentLinkedQueue<Long>();
            sequenceMap.put(sequence, sequenceQuene);
            getAndSetQuene(sequence, sequenceQuene);
        }
        sequenceNum = sequenceQuene.poll();
        return sequenceNum;
    }

    /**
     * 重構本地緩存的數據
     * @param sequence
     * @param sequenceQuene
     */
    private synchronized void getAndSetQuene(String sequence, ConcurrentLinkedQueue<Long> sequenceQuene) {
        if (!sequenceQuene.isEmpty()) return;
        try {
            Jedis jedis = getJedis(sequence);

            byte [] keys = sequence.getBytes("utf-8");
            //使用redis的事物管理
            Transaction trans =jedis.multi();
            //該redis的健值自增1,是本次的開始位置
            Response<Long> start = trans.incr(keys);
            //redis的最後序列,可使用區間的方式得到本地的緩存序列
            Response<Long> end = trans.incrBy(keys, SEQUENCE_NUM);
            //提交事務,保證本次的操做是一致性的,固然也能夠採用redis的管道來實現
            trans.exec();

            closeRedis(jedis);
            for (long i = start.get(); i <= end.get(); i++) {
                sequenceQuene.add(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //釋放redis的鏈接源
    private void closeRedis(Jedis jedis) {
        jedisPool.returnResourceObject(jedis);
    }
    //得到redis的操做源
    private Jedis getJedis(String sequence) {
        Jedis jedis = jedis = jedisPool.getResource();
        jedis.select(dbMap.containsKey(sequence) ? dbMap.get(sequence) : dbMap.get("default"));
        return jedis;
    }


}

 

該方法只是一個拋磚引玉,你能夠採用其餘的方式來實現。sql

相關文章
相關標籤/搜索