redis资源池优化以及分布式锁
前言
redis单线程,压力测试的时候会出现各种问题,以下是做过优化后的一套东西,redis 操作工具类,雪花算法获取分布式ID,分布式锁的正确用法。如果是电商平台建议用redisson做分布式锁。
1. pom 添加依赖
<!-- jedis客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- spring2.X集成redis所需common-pool2,使用jedis必须依赖它-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.7.0</version>
</dependency>
2. RedisPoolConfig
redis 资源池参数配置
package com.hc.common.config.redis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Configuration; /** * redis 资源池参数配置 * * @author summer.chou * @date 2021/07/08 */ @Configuration @EnableCaching public class RedisPoolConfig { private Logger logger = LoggerFactory.getLogger(RedisPoolConfig.class); //最大连接数 public static int CONNECTION_MAX_TOTAL = 1000; //最大空闲连接数 public static int CONNECTION_MAX_IDLE = 50; //初始化连接数(最小空闲连接数) public static int CONNECTION_MIN_IDLE = 10; //等待连接的最大等待时间 public static int CONNECTION_MAX_WAIT = 2000; //borrow前 是否进行alidate操作,设置为true意味着borrow的均可用 public static boolean TEST_ON_BORROW = true; //return前 是否进行alidate操作 public static boolean TEST_ON_RETURN = true; }
3.MyRedisPool
资源池配置
package com.hc.common.config.redis; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * redis 资源池配置 * * @author summer.chou * @date 2021/07/08 */ public class MyRedisPool { private static MyRedisPool myRedisPool = null; //redis 自带的 连接池 private static JedisPool jedisPool = null; public MyRedisPool( String host,Integer pool ,String password) { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(RedisPoolConfig.CONNECTION_MAX_TOTAL); jedisPoolConfig.setMaxIdle(RedisPoolConfig.CONNECTION_MAX_IDLE); jedisPoolConfig.setMinIdle(RedisPoolConfig.CONNECTION_MIN_IDLE); jedisPoolConfig.setMaxWaitMillis(RedisPoolConfig.CONNECTION_MAX_WAIT); jedisPoolConfig.setTestOnBorrow(RedisPoolConfig.TEST_ON_BORROW); jedisPoolConfig.setTestOnReturn(RedisPoolConfig.TEST_ON_RETURN); if ("".equals(password.trim())) { //无密码 jedisPool = new JedisPool(jedisPoolConfig, host, pool); } else { //有密码 int waitTime = 10000; jedisPool = new JedisPool(jedisPoolConfig, host, pool, waitTime, password); } } /** * 双重锁定获取 my redis pool 实例 * 在生成的过程中 生成了 redis pool 实例 * * @return */ public static MyRedisPool getRedisPoolInstance(String host,Integer pool ,String password) { if (myRedisPool == null) { synchronized (MyRedisPool.class) { if (myRedisPool == null) { myRedisPool = new MyRedisPool(host,pool,password); } } } return myRedisPool; } /** * 获取JedisPool 实例 * * @return */ public static JedisPool getJedisPool() { return jedisPool; } /** * 获取一个jedis * * @return */ public Jedis borrowJedis() { return jedisPool.getResource(); } /** * 返还一个jedis * * @param jedis */ public void returnJedis(Jedis jedis) { jedis.close(); } }
4.RedisUtil
redis工具类,对数据进行操作
package com.hc.common.utils; import com.hc.common.config.redis.MyRedisPool; import redis.clients.jedis.Jedis; import java.util.List; import java.util.Set; /** * redis 操作字符串 * * @author summer.chou * @version 1.0 * @date 2021/1/20 * @description: */ public class RedisUtil { /** * 根据key 获取 string * * @param redisPool * @param key * @return */ public static String get(MyRedisPool redisPool, String key) { Jedis jedis = redisPool.borrowJedis(); String value = jedis.get(key); redisPool.returnJedis(jedis); return value; } /** * 根据 key 和 value 添加一条数据 * 如果key 已经存在,会覆盖原来的值 * * @param redisPool * @param key * @param value * @return */ public static String set(MyRedisPool redisPool, String key, String value) { Jedis jedis = redisPool.borrowJedis(); String returnStatus = jedis.set(key, value); redisPool.returnJedis(jedis); return returnStatus; } /** * 根据 key 和 value 添加一条数据 * 如果key 已经存在,则不添加 * * @param redisPool * @param key * @param value * @return */ public static long setOnlyKeyNotExist(MyRedisPool redisPool, String key, String value) { Jedis jedis = redisPool.borrowJedis(); Long returnStatus = jedis.setnx(key, value); redisPool.returnJedis(jedis); return returnStatus; } /** * 批量插入数据 * * @param redisPool * @param keysValues 格式为: "key1","value1","key2","value2" * @return */ public static String set(MyRedisPool redisPool, String... keysValues) { Jedis jedis = redisPool.borrowJedis(); String status = jedis.mset(keysValues); redisPool.returnJedis(jedis); return status; } /** * 根据 keys 批量获取 value * 返回一个list 如果某一个key的值不存在, * 则在list对应的位置为null * * @param redisPool * @param keys * @return */ public static List<String> get(MyRedisPool redisPool, String... keys) { Jedis jedis = redisPool.borrowJedis(); List<String> result = jedis.mget(keys); redisPool.returnJedis(jedis); return result; } /** * 设置 有时间限制 即生存时间的 key * 将value关联到key, 并将key的生存时间设为seconds(以秒为单位)。 * 如果key 已经存在,SETEX将重写旧值 * * @param redisPool * @param key * @param time * @param value * @return */ public static String setLifeTime(MyRedisPool redisPool, String key, int time, String value) { Jedis jedis = redisPool.borrowJedis(); String status = jedis.setex(key, time, value); redisPool.returnJedis(jedis); return status; } public static long setList(MyRedisPool redisPool, String key, List<String> list) { Jedis jedis = redisPool.borrowJedis(); long size = 0L; for (String each : list) { size = jedis.lpush(key, each); } redisPool.returnJedis(jedis); return size; } /** * lrange 第二个参数为 -1 * 读取 list中的所有数据 * * @param redisPool * @param key * @return */ public static List<String> getList(MyRedisPool redisPool, String key) { Jedis jedis = redisPool.borrowJedis(); List<String> size = null; size = jedis.lrange(key, 0, -1); redisPool.returnJedis(jedis); return size; } /** * 存入set类型的数据 * * @param redisPool * @param key * @param set * @return */ public static long setSet(MyRedisPool redisPool, String key, Set<String> set) { Jedis jedis = redisPool.borrowJedis(); for (String each : set) { jedis.sadd(key, each); } Set<String> result = jedis.smembers(key); redisPool.returnJedis(jedis); return result.size(); } /** * 取出set类型的数据 * * @param redisPool * @param key * @return */ public static Set<String> getSet(MyRedisPool redisPool, String key) { Jedis jedis = redisPool.borrowJedis(); Set<String> size = jedis.smembers(key); redisPool.returnJedis(jedis); return size; } }
5.雪花算法分布式ID
package com.hc.common.utils; /** * 雪花算法获取分布式ID * * @author summer.chou * @2021/06/02 */ public class SnowFlakeUtils { // 起始的时间戳 private final static long START_STMP = 1480166465631L; // 每一部分占用的位数,就三个 private final static long SEQUENCE_BIT = 12;// 序列号占用的位数 private final static long MACHINE_BIT = 5; // 机器标识占用的位数 private final static long DATACENTER_BIT = 5;// 数据中心占用的位数 // 每一部分最大值 private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); // 每一部分向左的位移 private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; // 数据中心 private long machineId; // 机器标识 private long sequence = 0L; // 序列号 private long lastStmp = -1L;// 上一次时间戳 public SnowFlakeUtils(long datacenterId, long machineId) { if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); } this.datacenterId = datacenterId; this.machineId = machineId; } //产生下一个ID public synchronized long nextId() { long currStmp = getNewstmp(); //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过 这个时候应当抛出异常 if (currStmp < lastStmp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currStmp == lastStmp) { //if条件里表示当前调用和上一次调用落在了相同毫秒内,只能通过第三部分,序列号自增来判断为唯一,所以+1. sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列数已经达到最大,只能等待下一个毫秒 if (sequence == 0L) { currStmp = getNextMill(); } } //时间戳改变,毫秒内序列重置 else { //不同毫秒内,序列号置为0 //执行到这个分支的前提是currTimestamp > lastTimestamp,说明本次调用跟上次调用对比,已经不再同一个毫秒内了,这个时候序号可以重新回置0了。 sequence = 0L; } lastStmp = currStmp; //就是用相对毫秒数、机器ID和自增序号拼接 //移位 并通过 或运算拼到一起组成64位的ID return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分 | datacenterId << DATACENTER_LEFT //数据中心部分 | machineId << MACHINE_LEFT //机器标识部分 | sequence; //序列号部分 } private long getNextMill() { long mill = getNewstmp(); //使用while循环等待直到下一毫秒。 while (mill <= lastStmp) { mill = getNewstmp(); } return mill; } private long getNewstmp() { return System.currentTimeMillis(); } public static void main(String[] args) { // 构造方法设置机器码:第9个机房的第20台机器 SnowFlakeUtils snowFlake = new SnowFlakeUtils(9, 20); //循环生成2^12个ID for (int i = 0; i < (1 << 12); i++) { System.out.println(snowFlake.nextId()); } } }
6.RedisTool 分布式锁
package com.hc.common.utils; import redis.clients.jedis.Jedis; import java.util.Collections; /** * 分布式锁 * * @author summer.chou * @2021/06/02 */ public class RedisTool { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; private static final Long RELEASE_SUCCESS = 1L; /** * 尝试获取分布式锁 * * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } /** * 释放分布式锁 * * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { try { //lua 脚本 确保原子性 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end;"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return false; } }
7.使用的案例
@GetMapping("/test")
@ApiOperation("测试接口")
public CommonResult getTest() {
Map<String, Object> map = new HashMap<>();
//redis 数据操作
RedisUtil.set(MyRedisPool.getRedisPoolInstance(HOST, PORT, PASSWORD), "dddddd", "111111111");
//获取分布式ID
SnowFlakeUtils snowFlake = new SnowFlakeUtils(0, 0);
Long uuid = snowFlake.nextId();
Jedis jedis = MyRedisPool.getJedisPool().getResource();
String LOCK_KEY = "key";
try {
//上锁
RedisTool.tryGetDistributedLock(jedis, LOCK_KEY, String.valueOf(uuid), 20);
//业务逻辑代码
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
RedisTool.releaseDistributedLock(jedis, LOCK_KEY, String.valueOf(uuid));
}
map.put("tile", "测试");return CommonResult.ok(map);
}

浙公网安备 33010602011771号