redis生成自增长的ID
当我们web系统处于初期系统的时候,用户量比较小,我们的数据库的数据生成主键的时候可以采用increment自增策略,简单的做id的唯一生成器,
这种模式我们的web服务器不需要做额外的操作就可以保证数据库中主键是唯一的.但是随着业务量和用户量增长,我们就会做web集群和数据库集群
如下图所示
在图中,我们发现当数据库集群化,就不能在使用increment自增了(这里暂时不考虑oracle的sequence的方案,因为oracle要钱啊,手动滑稽).因为数据库自增长是依赖表的,分表分库之后就不行了. 这时候我们需要一个策略可以实现id的自增且不唯一,这里给出一个方案使用redis的方案,因为redis是单线程的.不会有线程安全问题,redis提供了incr 和incrby两种安全的自增方法.我们可以在web服务器端直接调用redis的方案.在业务层就给出一个唯一自增的id. 如下图所示:
如图中所示我们可以使用同一的redis服务器生成id,具体的代码实现 这里使用java实现 使用jedis作为通信的工具包,封装两个工具类
第一个类是用来封装跟redis获取连接相关工具类
package com.itheima.utils;
import java.util.Properties;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisUtil {
private static JedisPoolConfig poolConfig = new JedisPoolConfig();
private static JedisPool pool ;
static{
//解析配置文件
try {
Properties properties = new Properties();
//使用类加载器 加载配置文件
properties.load(RedisUtil.class.getClassLoader().getResourceAsStream("redis.properties"));
int maxIdle =Integer.parseInt( properties.getProperty("maxIdle"));
int maxTotal =Integer.parseInt( properties.getProperty("maxTotal"));
int minIdle =Integer.parseInt( properties.getProperty("minIdle"));
String host =properties.getProperty("host");
int port =Integer.parseInt( properties.getProperty("port"));
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxTotal(maxTotal);
poolConfig.setMinIdle(minIdle);
pool= new JedisPool(poolConfig,host, port);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static Jedis getConnection(){
return pool.getResource();
}
public static void close(){
pool.close();
}
}
这里需要用到一个配置文件:
maxIdle=20
maxTotal=100
minIdle=10
host=127.0.0.1
port=6379
接下来是redis生成id工具类
package com.itheima.utils;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import redis.clients.jedis.Jedis;
public class IDGenerator {
private static Map<String, Queue<Long>> queues=new ConcurrentHashMap<>();
private static long section = 20l; //区间 一次取出多少个放在缓存中
private static final String PREFIX="ids_";
public static String next(String idKey){
return _get(idKey);
}
private static String _get(String idKey){
Queue<Long> queue=getQueue(idKey);
//此处队列肯定是存在的
Long poll = queue.poll();
if (poll!=null) {
return String.valueOf(poll);
}else{
synchronized (queue) {
//用完了 此时加锁 去refill数据
refill(queue,idKey);
}
return String.valueOf(queue.poll());
}
}
/**
* 重新装填数据
* @param queue
* @param idKey
*/
private static void refill(Queue<Long> queue, String idKey) {
Jedis connection = RedisUtil.getConnection();
Long end = connection.incrBy(PREFIX+idKey, section);
Long start=end-section+1;
initQueue(queue,start,end);
connection.close();
}
private static void initQueue(Queue<Long> queue, Long start, Long end) {
for (long i= start; i <=end; i++) {
queue.add(i);
}
}
private static Queue<Long> getQueue(String idKey) {
if(queues.containsKey(idKey)){
return queues.get(idKey);
}else{
synchronized (IDGenerator.class) {
//初始化队列的概率小 所有这里就直接使用 synchronized加锁了 没有多少性能损耗
Queue<Long> queue = new ConcurrentLinkedQueue<>();
queues.put(idKey, queue);
return queue;
}
}
}
}
我们就可以直接用IDGenerator的next方法传入一个字符串作为key生成id 比如说要为用户生成id,可以给出一个 "user"字符串