互联网分库分表主键的生成-一个小思路

几乎全部的大型项目都涉及到分库分表(使用关系型数据库),为了应对递增的数据增加采用分库分表的策略,分库分表后面临的首要问题就是主键的生成。主键的关系涉及几个重要的因素: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

相关文章
相关标签/搜索