几乎所有的大型项目都涉及到分库分表(使用关系型数据库),为了应对递增的数据增长采用分库分表的策略,分库分表后面临的首要问题就是主键的生成。主键的关系涉及几个重要的因素:
1,如果只是做数据存储,没有其他的意义,这样的主键设计会很简单,面临的是后期查询的问题,需要选择是选择什么样的数据类型来存储主键,比如uuid的varchar,序列的bigint等,又或者拼接的结果最终以varchar来存储,考虑的要点是需要怎么最好的使用索引性能来设计实现快速的查询。
2,确保整个系统的分库分表的主键数据唯一,可以采用主键生成器的方式来确保主键唯一,该方案也有很多的方式实现,最常见的就是我们使用mysql的一个表来控制主键的生成,你可以使用序列也可以使用其他的,该方案的缺点是高并发的时候性能会压在该数据库服务器,也可以采用不同的步长增长使用不同的数据库。还有一种是动态的生成Twitter的snowflake算法,该算法已经有很多实现方法,中心思想是一样的,可以上网搜索很多的实现方法。我这里介绍的是基于redis来实现的一个小方案。
3,基于redis实现分布式主键的策略。
redis首先是支持主从复制的,可以确保高可用的,采用服务器的双redis和keepalive实现灾难自动转移。根据情况可以分成不同的主键成成类型采用负载均衡的方法,平摊服务器的压力。并且redis本身是支持事物的,顺便再讲解一下,在分布式系统中使用的分布式锁安全的一种,这里只是小提一下,具体的可以参考redis的官方文档。具体的实现方式有很多种,这里使用一种简单的方式来实现:
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;
}
}
该方法只是一个抛砖引玉,你可以采用其他的方式来实现。