java -Redis分布式锁学习

什么是分布式锁

分布式锁是一种用于分布式系统的锁机制,它能够保证在分布式环境下对共享资源进行互斥访问,防止并发访问时出现数据竞争和不一致问题。

在分布式系统中,多个节点可能同时修改共享资源,为了保证数据的正确性和一致性,需要对共享资源进行串行化访问。传统的单节点锁无法满足分布式系统的要求,因此需要使用分布式锁来保证在整个分布式系统中只会有一个节点可以获取到锁,从而保证共享资源的独占性。

实现分布式锁的方法有很多种,包括利用数据库、缓存系统等。

为什么要使用分布式锁

分布式锁用于在分布式系统中协调多个进程或线程访问共享资源的顺序和互斥性。因为在分布式系统中,多个进程同时竞争同一资源时会出现一些问题,如死锁、数据不一致等。

使用分布式锁可以保证在分布式环境下的多个进程或线程按照预期的顺序进行操作,并且只有一个进程或线程能够成功地获取到锁,并且避免了多个进程或线程同时修改同一资源所导致的数据不一致问题。

同时,分布式锁还可以解决某些场景下的并发问题,比如防止重复提交等。如果不使用分布式锁,在分布式系统中的多个进程或线程操作一个共享资源时可能会产生竞争,从而使得某些操作被重复执行,导致数据错误或不一致。因此,使用分布式锁可以有效地避免这些问题的发生。

使用redis做分布式锁的好处

使用redis做分布式锁有以下几个好处:

1.高可靠性:redis是一款高度可靠的数据库,提供了诸多机制来保证数据的稳定性和可用性。在使用redis做分布式锁时,只要确保正确配置和使用,就可以保证分布式系统的高度可靠性。

2.高效性:redis是一款内存数据库,在读写方面都具有极高的效率,适合作为分布式锁使用。并且redis还支持多种数据结构,如字符串、哈希表、列表等,可以灵活地根据实际需求进行调整。

3.简单易用:redis提供了简单易用的命令集合,通过这些命令即可完成锁的获取、释放等操作。开发人员可以快速上手使用,也可以根据自己的业务需求进行二次封装。

4.支持多语言:redis支持多种编程语言,如java、python、c#、php等,因此可以在不同的应用环境中使用,并且方便进行语言之间的交互。

5.分布式性:分布式锁的实现需要考虑分布式环境下的数据同步问题,使用redis做分布式锁能够解决这个问题。redis提供的事务、乐观锁等机制可以保证数据同步性,并且支持分布式环境下的节点故障恢复。

spring boot + redis做分布式锁怎么做

使用 Redis 实现分布式锁可以保证分布式系统的并发情况下数据的正确性。Spring Boot 中,可以通过 Spring Data Redis 来使用 Redis, 下面是实现分布式锁的代码示例:

1.首先定义一个 DistributedLock 接口
public interface DistributedLock {

    boolean lock(String key, String value, long expire);

    boolean unlock(String key, String value);

}

2.然后实现 DistributedLock 接口
@Component
public class RedisDistributedLock implements DistributedLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public boolean lock(String key, String value, long expire) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);
        if (result != null && result) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return result != null && result;
    }

    @Override
    public boolean unlock(String key, String value) {
        String lockValue = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(lockValue) && StringUtils.equals(lockValue, value)) {
            redisTemplate.delete(key);
            return true;
        }
        return false;
    }
}

3.使用分布式锁

在需要使用分布式锁的地方注入 DistributedLock 对象,并调用 lock 方法获取锁,调用 unlock 方法释放锁。

@Autowired
private DistributedLock distributedLock;

public void doSomething() {
    String key = "lock_key";
    String value = Math.random();
    long expire = 30; // 锁的过期时间,单位 s
    boolean locked = false;
    try {
        // 获取锁
        locked = distributedLock.lock(key, value, expire);
        if (locked) {
            // 执行业务逻辑
        } else {
            // 获取锁失败
        }
    } finally {
        // 释放锁
        if (locked) {
            distributedLock.unlock(key, value);
        }
    }
}

spring boot使用redis分布式锁事务超时或线程堵塞了怎么办

对于 spring boot 使用 redis 实现分布式锁的情况中,若存在事务超时或者线程堵塞的情况,建议采取以下措施:

1.确认问题

首先需要确认是事务超时还是线程阻塞引起的。
如果是事务超时,需要检查业务逻辑,尽量缩短异步任务运行时间或考虑单独分离出来处理。
如果是线程阻塞,需要分析原因并解决。

2.配置 redis

确认 redis 的配置是否正确。
检查 redis 连接池和线程池是否设置合理,例如连接总数等。

3.优化代码

可以使用 redission 工具类进行优化。
尝试使用 redis 哨兵模式或集群配置等其他方式优化实现。

4.重试机制

为了防止因为 redis 超时等一些不可避免的情况,我们可以实现一个重试机制,使得在 redis 再次可用之后,可以重新执行任务。

5.引入限流机制

引入限流机制,将访问 redis 的请求数量限制在可承受范围内,防止过多的请求导致 redis 不可用等情况。

超时问题解决方案

1.问题描述

redis实现分布式锁,主要是通过SETNX命令,设置一组 key,value。在同一时刻只能有一个线程设置成功,该线程获得了分布式锁。
分布式可能会出现的超时问题:
1、误删除别人的锁。
A获取锁后,事务执行超时。锁到期自动释放,B线程获取到锁。A完成事务后,释放锁时,把B的锁释放了
解决方案:
每个线程在获取锁式,在写key-value时,将自己生成的一个独一无二的id写入value。先校验value,再删除锁。这两个操作要保证原子性

2、A到期自动释放锁后,B获得锁。出现两个锁并行的情况。
A获取锁后,开启一个守护线程,定期为A刷新锁的时间。这样A就是一直保持锁的情况。在redission源码中有相应的程序。

通过redission解决
1. Redission介绍

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。
Redission解决redis超时问题的原理大概意思就是为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。

2.具体操作
1.引入依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId> 
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId> 
        </dependency> 
2.注入配置
redisson:
  server:
    # 单机:stand-alone
    host: 192.168.0.1
    port: 28888
    database: 0
    type: stand-alone
  #     哨兵:sentinel ;主从: 或者 master-slave  集群:cluster
  #        master: mymaster
  #        nodes: 192.168.0.1:28888,192.168.0.2:28888,192.168.0.3:28888
  #        password: test1@164
  #        database: 0
  #        type: sentinel
  #     集群:cluster
  #    nodes: 192.168.0.1:28888,192.168.0.2:28888,192.168.0.3:28888
  #    password: test1@164
  #    database: 0
  #    type: cluster
  #获取锁最长等待时间,单位秒
  waitTime: 10
  #锁过期最长时间,单位秒
  leaseTime: 10 
3.添加配置管理类

@Component
@ConfigurationProperties(prefix = "redisson.server")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RedissonProperties {
    /**
     * redis主机地址
     */
    private String host;

    /**
     * PORT
     */
    private String port;

    /**
     * 连接类型,支持stand-alone-单机节点,sentinel-哨兵,cluster-集群,master-slave-主从
     */
    private String type;

    /**
     * redis 连接密码
     */
    private String password;

    /**
     * 选取那个数据库
     */
    private String database;

    /**
     * 节点信息
     */
    private String nodes;

    /**
     * 主节点
     */
    private String master;

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

    public RedissonProperties setDatabase(String database) {
        this.database = database;
        return this;
    }
}
 
 
@Configuration
@RequiredArgsConstructor
public class RedissonConfig {

    private final RedissonProperties redissonProperties;

    @Bean
    public RedissonClient getRedisson() {
        return StandAlone.getInstance(redissonProperties);
    }
}

/**
* redis单机配置
*/
public class StandAlone {

    public static volatile RedissonClient standAloneClient = null;

    public static synchronized RedissonClient getInstance(RedissonProperties redissonProperties) {

        String type = "stand-alone";

        if (!type.equalsIgnoreCase(redissonProperties.getType().trim())) {
            return Sentinel.getInstance(redissonProperties);
        }
        //实例没创建,才会进入内部的 synchronized 代码块,提高性能,防止每次都加锁
        if (standAloneClient == null) {
            //可能第一个线程在synchronized 代码块还没创建完对象时,第二个线程已经到了这一步,所以里面还需要加上判断
            synchronized (StandAlone.class) {
                //也许有其他线程已经创建实例,所以再判断一次
                if (standAloneClient == null) {

                    Config config = new Config();

                    SingleServerConfig singleServerConfig = config.useSingleServer()
                            .setAddress("redis://" + redissonProperties.getHost() + ":" + redissonProperties.getPort());

                    if (StringUtils.hasText(redissonProperties.getPassword())) {
                        singleServerConfig.setPassword(redissonProperties.getPassword());
                    }
                    if (StringUtils.hasText(redissonProperties.getDatabase())) {
                        singleServerConfig.setDatabase(Integer.parseInt(redissonProperties.getDatabase()));
                    }

                    standAloneClient = Redisson.create(config);
                }
            }
        }
        return standAloneClient;
    }
}

/** 
 *   主从 
 */
public class MasterSlave {

    public static volatile RedissonClient masterSlaveClient = null;

    public static synchronized RedissonClient getInstance(RedissonProperties redissonProperties) {

        String type = "master-slave";

        if (!type.equalsIgnoreCase(redissonProperties.getType())) {

            return Cluster.getInstance(redissonProperties);
        }
        if (masterSlaveClient == null) {

            synchronized (MasterSlave.class) {

                if (masterSlaveClient == null) {

                    Config config = new Config();

                    String[] adds = redissonProperties.getNodes().split(",");

                    Set<String> nodeAddress = new HashSet<>();

                    for (int i = 0; i < adds.length; i++) {

                        StringBuilder stringBuilder = new StringBuilder("redis://");

                        adds[i] = stringBuilder.append(adds[i]).toString();

                        nodeAddress.add(adds[i]);

                    }
                    MasterSlaveServersConfig masterSlaveServersConfig = config.useMasterSlaveServers().setMasterAddress(adds[0]);

                    masterSlaveServersConfig.setSlaveAddresses(nodeAddress);

                    if (StringUtils.hasText(redissonProperties.getPassword())) {
                        masterSlaveServersConfig.setPassword(redissonProperties.getPassword());
                    }
                    if (StringUtils.hasText(redissonProperties.getDatabase())) {
                        masterSlaveServersConfig.setDatabase(Integer.parseInt(redissonProperties.getDatabase()));
                    }
                    masterSlaveClient = Redisson.create(config);
                }
            }
        }
        return masterSlaveClient;
    }
}
 
/**
 * 哨兵
 */
public class Sentinel {

    public static volatile RedissonClient sentinelClient = null;

    public static synchronized RedissonClient getInstance(RedissonProperties redissonProperties) {

        String type = "sentinel";

        if (!type.equalsIgnoreCase(redissonProperties.getType())) {
            return Cluster.getInstance(redissonProperties);
        }
        if (sentinelClient == null) {

            synchronized (Sentinel.class) {

                if (sentinelClient == null) {

                    Config config = new Config();

                    String[] adds = redissonProperties.getNodes().split(",");

                    for (int i = 0; i < adds.length; i++) {

                        StringBuilder stringBuilder = new StringBuilder("redis://");

                        adds[i] = stringBuilder.append(adds[i]).toString();
                    }
                    SentinelServersConfig serverConfig = config.useSentinelServers()
                            .addSentinelAddress(adds)
                            .setMasterName(redissonProperties.getMaster());

                    if (org.springframework.util.StringUtils.hasText(redissonProperties.getPassword())) {
                        serverConfig.setSentinelPassword(redissonProperties.getPassword());
                    }
                    if (StringUtils.hasText(redissonProperties.getDatabase())) {
                        serverConfig.setDatabase(Integer.parseInt(redissonProperties.getDatabase()));
                    }
                    sentinelClient = Redisson.create(config);
                }
            }
        }
        return sentinelClient;
    }
}


package com.microservice.stock.utils.redisson;

import com.microservice.stock.config.RedissonProperties;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.springframework.util.StringUtils;

/**
 * 集群 
 */
public class Cluster {

    public static volatile RedissonClient clusterClient = null;

    public static synchronized RedissonClient getInstance(RedissonProperties redissonProperties) {

        String type = "cluster";

        if (!type.equalsIgnoreCase(redissonProperties.getType().trim())) {
            return MasterSlave.getInstance(redissonProperties);
        }

        if (clusterClient == null) {

            synchronized (Cluster.class) {

                if (clusterClient == null) {

                    String[] nodesList = redissonProperties.getNodes().split(",");

                    for (int i = 0; i < nodesList.length; i++) {

                        StringBuilder stringBuilder = new StringBuilder("redis://");

                        nodesList[i] = stringBuilder.append(nodesList[i]).toString();
                    }
                    Config config = new Config();

                    ClusterServersConfig clusterServersConfig = config.useClusterServers().addNodeAddress(nodesList);

                    if (StringUtils.hasText(redissonProperties.getPassword())) {
                        clusterServersConfig.setPassword(redissonProperties.getPassword());
                    }

                    clusterClient = Redisson.create(config);
                }
            }
        }
        return clusterClient;
    }
}
代码测试
@Autowired
RedissonClient redissonClient;

@RequestMapping(value = "/showRedisValue", method= RequestMethod.GET)
public Result showRedisValue() {

      final String lockKey = "helloword";

      final RLock lock = redissonClient.getLock(lockKey);

      if (lock.tryLock()) {
          try {
              System.out.println("加锁成功,等待观察redis过期时间是否自动续期");

              TimeUnit.SECONDS.sleep(60);

              System.out.println("休眠结束");
          } finally {
              if (lock.isLocked() && lock.isHeldByCurrentThread()) {

                  System.out.println("释放当前执行线程的锁");

                  lock.unlock();
              }
              System.out.println("解锁成功");
          }
      }
      System.out.println(stopWatch.prettyPrint()); 
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java实现Redis分布式可以通过使用Redis的SETNX命令来实现。SETNX命令可以在Redis中设置一个键值对,但只有在键不存在的情况下才会设置成功。因此,可以将某个键作为的标识,当某个线程成功执行SETNX命令并获得时,其他线程执行SETNX命令时会失败,从而实现了分布式的效果。在释放时,可以使用Redis的DEL命令来删除对应的键。 另外,Java中也可以使用显式(Lock)来实现分布式。通过使用ReentrantLock类,可以在代码中显式地加和解。在同一个线程中,当外层方法获取后,再进入内层方法时会自动获取,不会因为之前已经获取过而阻塞。这种可重入的特性可以一定程度上避免死的发生。 总结起来,Java实现Redis分布式可以通过使用Redis的SETNX命令或者使用显式(Lock)来实现。具体的实现方式可以根据实际需求和场景选择。\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [Java --- redis7实现分布式](https://blog.csdn.net/qq_46093575/article/details/130661856)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Redis分布式的正确实现方式(Java版)](https://blog.csdn.net/zth_killer/article/details/106853052)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值