spring boot + redis cluster + redisson 实现分布式锁示例

一,spring boot 使用redis cluster 实现分布式缓存

1)maven依赖jedis客户端

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.4.3</version>
        </dependency>

2)properties配置文件配置

#redis cluster config
#RedisCluster集群节点及端口信息
share.redis.jedis.cluster.nodes=127.0.0.1:6380,127.0.0.1:6381,127.0.0.1:6382,127.0.0.1:6383,127.0.0.1:6384,127.0.0.1:6385
#Redis密码
share.redis.jedis.cluster.password=
#在群集中执行命令时要遵循的最大重定向数目
share.redis.jedis.cluster.maxredirects=5
#Redis连接池在给定时间可以分配的最大连接数。使用负值无限制
share.redis.jedis.cluster.maxactive=1000
#以毫秒为单位的连接超时时间
share.redis.jedis.cluster.timeout=2000
#池中“空闲”连接的最大数量。使用负值表示无限数量的空闲连接
share.redis.jedis.cluster.maxidle=8
#目标为保持在池中的最小空闲连接数。这个设置只有在设置max-idle的情况下才有效果
share.redis.jedis.cluster.minidle=5
#连接分配在池被耗尽时抛出异常之前应该阻塞的最长时间量(以毫秒为单位)。使用负值可以无限期地阻止
share.redis.jedis.cluster.maxwait=1000
#redis cluster只使用db0
share.redis.jedis.cluster.index=0
#从jedis连接池获取连接时,校验并返回可用的连接
share.redis.jedis.cluster.testborrow=true
share.redis.jedis.cluster.maxtotal=2000

3)程序集成jedis客服端实现缓存操作

①redis cluster配置自动装配

@Component
@ConfigurationProperties(prefix = "share.redis.jedis.cluster")
public class JedisConfig {

    private String nodes;
    private String password;
    private String maxredirects;
    private String maxactive;
    private String timeout;
    private String maxidle;
    private String minidle;
    private String maxwait;
    private String index;
    private String testborrow;
    private String maxtotal;
    private String testreturn;


    public String getNodes() {
        return nodes;
    }

    public void setNodes(String nodes) {
        this.nodes = nodes;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getMaxredirects() {
        return maxredirects;
    }

    public void setMaxredirects(String maxredirects) {
        this.maxredirects = maxredirects;
    }

    public String getMaxactive() {
        return maxactive;
    }

    public void setMaxactive(String maxactive) {
        this.maxactive = maxactive;
    }

    public String getTimeout() {
        return timeout;
    }

    public void setTimeout(String timeout) {
        this.timeout = timeout;
    }

    public String getMaxidle() {
        return maxidle;
    }

    public void setMaxidle(String maxidle) {
        this.maxidle = maxidle;
    }

    public String getMinidle() {
        return minidle;
    }

    public void setMinidle(String minidle) {
        this.minidle = minidle;
    }

    public String getMaxwait() {
        return maxwait;
    }

    public void setMaxwait(String maxwait) {
        this.maxwait = maxwait;
    }

    public String getIndex() {
        return index;
    }

    public void setIndex(String index) {
        this.index = index;
    }

    public String getTestborrow() {
        return testborrow;
    }

    public void setTestborrow(String testborrow) {
        this.testborrow = testborrow;
    }

    public String getMaxtotal() {
        return maxtotal;
    }

    public void setMaxtotal(String maxtotal) {
        this.maxtotal = maxtotal;
    }

    public String getTestreturn() {
        return testreturn;
    }

    public void setTestreturn(String testreturn) {
        this.testreturn = testreturn;
    }
}

②创建JedisCluster用以操做redis集群

@Component
public class RedisShardedPool {
    @Autowired
    private JedisConfig jedisConfig;
    private ShardedJedisPool pool;



    public JedisCluster initPool(){
        System.out.println("-------------------------------------------------------------------------------------------------");
        JedisPoolConfig config=new JedisPoolConfig();
        config.setMaxIdle(Integer.parseInt(jedisConfig.getMaxidle()));
        config.setMaxTotal(Integer.parseInt(jedisConfig.getMaxtotal()));
        config.setMinIdle(Integer.parseInt(jedisConfig.getMinidle()));

        config.setTestOnBorrow(Boolean.parseBoolean(jedisConfig.getTestborrow()));
        config.setTestOnReturn(Boolean.parseBoolean(jedisConfig.getTestreturn()));
        //whether block when resource is exhausted: false will throw a exception,true will block until timeout, default is true
        config.setBlockWhenExhausted(true);
        String[] clusters = jedisConfig.getNodes().split(",");//形成node结点的数组

//        List<JedisShardInfo> jedisShardInfos=new ArrayList<>();
        Set<HostAndPort> nodes = new HashSet<>();
        for(String cluster : clusters){
            String host = cluster.split(":")[0];//获取ip
            int port = Integer.parseInt(cluster.split(":")[1]);//获取端口
            nodes.add(new HostAndPort(host,port));

//            JedisShardInfo jedisShardInfo1=new JedisShardInfo(host,port,1000*2);
//            jedisShardInfos.add(jedisShardInfo1);
        }
        return new JedisCluster(nodes,2000,1000,1,new GenericObjectPoolConfig());
//        pool = new ShardedJedisPool(config,jedisShardInfos, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
    }

}

③创建redis cluster集群操作工具类

@Component
public class RedisShardedPoolUtil {
    private static Logger log = LoggerFactory.getLogger(RedisShardedPoolUtil.class);
    @Autowired
    private RedisShardedPool redisShardedPool;
    
    public JedisCluster getJedis(){
        return redisShardedPool.initPool();
    }

    /**
     * 设置key的有效期,单位是秒
     * @param key
     * @param exTime
     * @return
     */
    public  Long expire(String key,int exTime){
        JedisCluster jedis = null;
        Long result = null;
        try {
            jedis = getJedis();
            result = jedis.expire(key,exTime);
        } catch (Exception e) {
            log.error("expire key:{} error",key,e);
            return result;
        }
        return result;
    }

    /**
     * 设置key value exTime的单位是秒
     * @param key
     * @param value
     * @param exTime
     * @return
     */
    public  String setEx(String key,String value,int exTime){
        JedisCluster jedis = null;
        String result = null;
        try {
            jedis = getJedis();
            result = jedis.setex(key,exTime,value);
        } catch (Exception e) {
            log.error("setex key:{} value:{} error",key,value,e);
            return result;
        }
        return result;
    }

    /**
     * 获取
     * @param key
     * @return
     */
    public  String get(String key){
        JedisCluster jedis = null;
        String result = null;
        try {
            jedis = getJedis();
            result = jedis.get(key);
        } catch (Exception e) {
            log.error("get key:{} error",key,e);
            return result;
        }

        return result;
    }

    /**
     * 设置值
     * @param key
     * @param value
     * @return
     */
    public  String set(String key,String value){
        JedisCluster jedis = null;
        String result = null;

        try {
            jedis = getJedis();
            result = jedis.set(key,value);
        } catch (Exception e) {
            log.error("set key:{} value:{} error",key,value,e);

            return result;
        }

        return result;
    }

    /**
     * 获取旧的value,设置新的value;可用于锁操作
     * @param key
     * @param value
     * @return
     */
    public  String getSet(String key,String value){
        JedisCluster jedis = null;
        String result = null;

        try {
            jedis = getJedis();
            result = jedis.getSet(key,value);
        } catch (Exception e) {
            log.error("getset key:{} value:{} error",key,value,e);

            return result;
        }

        return result;
    }

    /**
     * 删除key
     * @param key
     * @return
     */
    public  Long del(String key){
        JedisCluster jedis = null;
        Long result = null;
        try {
            jedis = getJedis();
            result = jedis.del(key);
        } catch (Exception e) {
            log.error("del key:{} error",key,e);

            return result;
        }

        return result;
    }

    /**
     * 当且仅当key不存在,将key的值设置为value,并且返回1;
     * 若是给定的key已经存在,则setnx不做任何动作,返回0。
     * 可用于锁操作
     * @param key
     * @param value
     * @return
     */
    public Long setnx(String key,String value){
        JedisCluster jedis = null;
        Long result = null;

        try {
            jedis = getJedis();
            result = jedis.setnx(key,value);
        } catch (Exception e) {
            log.error("setnx key:{} value:{} error",key,value,e);

            return result;
        }

        return result;
    }


    /**
     * 查询有多少KEY
     * @param pattern
     * @return
     */
    public int keys(final String pattern) {
        try {
            final Set<String> keySet = new HashSet();
            final Map<String, JedisPool> nodes = getJedis().getClusterNodes();
            for (String k : nodes.keySet()) {
                JedisPool pool = nodes.get(k);
                //获取Jedis对象,Jedis对象支持keys模糊查询
                Jedis jedis = pool.getResource();
                final Set<String> set = jedis.keys(pattern);
                keySet.addAll(set);
            }

            return keySet.size();
        } catch (Exception e) {
            log.error("异常信息", e);
            throw e;
        }
    }
    /**
     * 模糊查询给定的pattern的所有keys列表
     *
     * @param pattern 模糊查询
     * @return 返回当前pattern可匹配的对象keys列表
     */
    public Set<String> keyList(final String pattern) {

        try {
            final Set<String> keySet = new HashSet<String>();
            final Map<String, JedisPool> nodes = getJedis().getClusterNodes();
            for (String k : nodes.keySet()) {
                JedisPool pool = nodes.get(k);
                //获取Jedis对象,Jedis对象支持keys模糊查询
                Jedis jedis = pool.getResource();
                final Set<String> set = jedis.keys(pattern);
                keySet.addAll(set);
            }
            return keySet;
        } catch (Exception e) {
            log.error("异常信息", e);
            throw e;
        }
    }
}

4)控制器中实际应用jediscluster操作缓存

@Controller
@RequestMapping("/account-info")
public class AccountInfoController {
    @Autowired
    private IAccountInfoService accountInfoService;
    @Autowired
    private RedisShardedPoolUtil redisShardedPoolUtil;

    @Value("${server.port}")
    private String port;


    /**
     * 存储缓存数据
     * @param jsonObject
     * @return
     */
    @ResponseBody
    @PostMapping(value = "/redisOption")
    public String redisOption(@RequestBody JSONObject jsonObject){
        Map<String,String> map = JSONObject.toJavaObject(jsonObject,Map.class);
        //循环转换
        for (Map.Entry<String, String> entry : map.entrySet()) {
            redisShardedPoolUtil.setEx(entry.getKey(), entry.getValue(),1111111111);
        }
        return null;
    }

    /**
     * 获取缓存数据
     * @param jsonObject
     * @return
     */
    @ResponseBody
    @PostMapping(value = "/redisOptionGet")
    public String redisOptionGet(@RequestBody JSONObject jsonObject){
        Map<String,String> map = JSONObject.toJavaObject(jsonObject,Map.class);
        //循环转换
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(redisShardedPoolUtil.get(entry.getKey()));
        }
        return "111111111111111";

    }

二,在使用JedisCluster操作分布式缓存的基础上,使用redisson实现分布式锁

1)使用maven依赖redisson jar包

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.15.1</version>
        </dependency>

2)redis cluster节点配置

#redisson
spring.redisson.cluster.nodes[0]=127.0.0.1:6380
spring.redisson.cluster.nodes[1]=127.0.0.1:6381
spring.redisson.cluster.nodes[2]=127.0.0.1:6382
spring.redisson.cluster.nodes[3]=127.0.0.1:6383
spring.redisson.cluster.nodes[4]=127.0.0.1:6384
spring.redisson.cluster.nodes[5]=127.0.0.1:6385

3)redisson自动装配

①redisson配置自动装配

@ConfigurationProperties(prefix = "spring.redisson")
//@ConditionalOnProperty("redisson.cluster")
public class RedissonClusterProperties {

    private String password;
    private cluster cluster;

    public static class cluster {
        private List<String> nodes;

        public List<String> getNodes() {
            return nodes;
        }

        public void setNodes(List<String> nodes) {
            this.nodes = nodes;
        }
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public RedissonClusterProperties.cluster getCluster() {
        return cluster;
    }

    public void setCluster(RedissonClusterProperties.cluster cluster) {
        this.cluster = cluster;
    }
}

②redisson客户端自动装配

@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonClusterProperties.class)
public class RedissonAutoConfiguration {
    @Autowired
    private RedissonClusterProperties redssionProperties;


    /**
     * 哨兵模式自动装配
     * @return
     */
//    @Bean
//    @ConditionalOnProperty(name="redisson.master-name")
//    RedissonClient redissonSentinel() {
//        Config config = new Config();
//        SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses())
//                .setMasterName(redssionProperties.getMasterName())
//                .setTimeout(redssionProperties.getTimeout())
//                .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
//                .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());
//
//        if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
//            serverConfig.setPassword(redssionProperties.getPassword());
//        }
//        return Redisson.create(config);
//    }

    /**
     * 单机模式自动装配
     * @return
     */
//    @Bean
//    @ConditionalOnProperty(name="redisson.address")
//    RedissonClient redissonSingle() {
//        Config config = new Config();
//        SingleServerConfig serverConfig = config.useSingleServer()
//                .setAddress(redssionProperties.getAddress())
//                .setTimeout(redssionProperties.getTimeout())
//                .setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
//                .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
//
//        if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
//            serverConfig.setPassword(redssionProperties.getPassword());
//        }
//
//        return Redisson.create(config);
//    }

    @Bean
//    @ConditionalOnProperty(name="redisson.cluster")
    public RedissonClient redissonClient() {
        //redisson版本是3.5,集群的ip前面要加上“redis://”,不然会报错,3.2版本可不加
        List<String> clusterNodes = new ArrayList<>();
        for (int i = 0; i < redssionProperties.getCluster().getNodes().size(); i++) {
//            clusterNodes.add("redis://" + redssionProperties.getCluster().getNodes().get(i));
            clusterNodes.add("redis://" + redssionProperties.getCluster().getNodes().get(i));
        }
        Config config = new Config();
        // 添加集群地址
        ClusterServersConfig clusterServersConfig = config.useClusterServers().addNodeAddress(clusterNodes.toArray(new String[clusterNodes.size()]));
        // 设置密码
//        clusterServersConfig.setPassword(redssionProperties.getPassword());
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
//    /**
//     * 装配locker类,并将实例注入到RedissLockUtil中
//     * @return
//     */
//    @Bean
//    DistributedLocker distributedLocker(RedissonClient redissonClient) {
//        RedissonDistributedLocker locker = new RedissonDistributedLocker();
//        locker.setRedissonClient(redissonClient);
//        RedissLockUtil.setLocker(locker);
//        return locker;
//    }
}

4)控制器实现

①不加锁代码

@ResponseBody
    @PostMapping(value = "/redisOptionReduce")
    public String redisOptionReduce(@RequestBody JSONObject jsonObject) throws ExecutionException, InterruptedException {
        String returnStr = "";
        //RLock lock = redissonClient.getLock("xxxxxxxxxxxxxxxxxx");
        //boolean isLock = lock.tryLock(3,5,TimeUnit.SECONDS);
        //if(isLock){
            AccountInfo accountInfo = accountInfoService.getById(1);
            if(accountInfo.getAccountBalance() > 0){
                Thread.sleep(30); //延长程序操作时间增加线程并发操作时,多个线程获取相同余额的可能性
                double accountBalance = accountInfo.getAccountBalance() - 1;
                UpdateWrapper<AccountInfo> accountInfoUpdateWrapper2 = new UpdateWrapper<>();
                accountInfoUpdateWrapper2.setSql("account_balance = account_balance - 1");
                accountInfoService.update(accountInfo,accountInfoUpdateWrapper2);
                returnStr = "当前剩余余额:" + accountBalance;
            }else {
                returnStr = "当前剩余余额: 0";
            }
            //lock.unlock(); //释放锁
        //}
        return returnStr;
    }

并发操作前账户余额信息

30个线程并发操作后,根据账户余额信息和jmter运行结果我们可以看出该程序代码造成了,库存超卖情况

②代码加上分布式锁实现

程序代码

@Autowired
private RedissonClient redissonClient;

 @ResponseBody
    @PostMapping(value = "/redisOptionReduce")
    public String redisOptionReduce(@RequestBody JSONObject jsonObject) throws ExecutionException, InterruptedException {
        String returnStr = "";
        RLock lock = redissonClient.getLock("xxxxxxxxxxxxxxxxxx");
        boolean isLock = lock.tryLock(3,5,TimeUnit.SECONDS);
        if(isLock){
            AccountInfo accountInfo = accountInfoService.getById(1);
            if(accountInfo.getAccountBalance() > 0){
                Thread.sleep(30); //延长程序操作时间增加线程并发操作时,多个线程获取相同余额的可能性
                double accountBalance = accountInfo.getAccountBalance() - 1;
                UpdateWrapper<AccountInfo> accountInfoUpdateWrapper2 = new UpdateWrapper<>();
                accountInfoUpdateWrapper2.setSql("account_balance = account_balance - 1");
                accountInfoService.update(accountInfo,accountInfoUpdateWrapper2);
                returnStr = "当前剩余余额:" + accountBalance;
            }else {
                returnStr = "当前剩余余额: 0";
            }
            lock.unlock(); //释放锁
        }
        return returnStr;
    }

程序运行前账户余额情况

30个线程并发运行后,根据账户余额和jmter运行结果可知分布式锁成功避免了线程超卖情况

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值