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); }

 

 
posted @ 2021-07-10 09:36  峡谷小短腿  阅读(289)  评论(0)    收藏  举报