Redisson读写锁

Redisson读写锁

在看Redis和mysql双写一致性时看到了使用Redisson读写锁达到强一致性。

介绍

双写一致性流程:

线程T1,T2。写锁:wLock;读锁:rLock。

T1修改数据库:先获取wLock,然后修改数据库数据,修改之后删除Redis中缓存。

T2读取缓存内容:先获取rLock,由于T1未释放wLock,获取锁失败,被阻塞。阻塞直至T1释放wLock,T2获取到rLock【此时任何线程想获取同一把写锁将被阻塞】,T2读取Redis发现没有数据,于是去数据库中读取最新数据。

使用读写锁虽然能达到一致性,但是性能较差。如果一致性要求不高,可以通过延时双删、MQ异步删除Redis数据、Canal方式实现

Canal 是阿里巴巴开源的一款基于数据库日志增量订阅&消费的解决方案,主要用于实时同步数据库变更到下游存储(如缓存、搜索引擎、数据仓库等)。它基于数据库的日志实现了增量数据订阅和消费,可以将数据库的变更操作(如插入、更新、删除)实时推送给其他数据存储或应用系统,实现数据的实时同步和消息传递。

SpringBoot项目中引入依赖

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.16.0</version>  
</dependency>
<dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.HashMap;
import java.util.concurrent.CountDownLatch;

说明:

读写锁使用Redisson

设置和获取键值使用springboot提供的RedisTemplate

流程:

使用两个线程分别执行读和写操作,调整线程优先级:

如果先执行写,写锁是排他锁,其余线程不能进行读写操作。写方法内部在设置key-value之后会等待1秒,观察这1秒内,读线程不能执行。

如果先执行读,读锁是共享锁,其余线程可读,但不能写。读方法内部在读取值之后,等待1秒,观察这1秒内,写线程不能执行。

当读,写线程都执行完毕之后,关闭Redisson客户端。(CountDownLatch)

具体代码实现

public class RedissonReadWriteLockExample {


    static RedissonClient redissonClient = getRedissonClient();

    static CountDownLatch countDownLatch = new CountDownLatch(2);//等待读、写线程执行完毕后,关闭redis客户端

    RedisTemplate<String, String> redisTemplate = getRedisTemplate();//用于进行key的设置和读取
    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("READ_WRITE_LOCK");//获取redisson读写锁;

    RLock readLock = readWriteLock.readLock();
    RLock writeLock = readWriteLock.writeLock();

    /**
     * 获取redisson客户端
     * @return
     */
    private static RedissonClient getRedissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }

    /**
     * 获取redisTemplate
     *
     * @return
     */
    private static RedisTemplate<String, String> getRedisTemplate() {
        //LettuceConnectionFactory使用此配置类
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost", 6379);

        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
        lettuceConnectionFactory.afterPropertiesSet();

        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    /**
     * 读操作
     */

    public void getKey() {
        try {
            readLock.lock();
            String name = redisTemplate.opsForValue().get("name");
            System.out.println("READ_KEY:" + name);
            Thread.sleep(1000);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            readLock.unlock();
            countDownLatch.countDown();
        }
    }

    /**
     * 写操作
     */
    public void setKey() {
        try {
            writeLock.lock();
            redisTemplate.opsForValue().set("name", "zhangsan");
            System.out.println("SET_KEY");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            writeLock.unlock();
            countDownLatch.countDown();
        }
    }

    public static void main(String[] args) {
        RedissonReadWriteLockExample example = new RedissonReadWriteLockExample();
        Thread thread = new Thread(example::getKey);
        Thread thread1 = new Thread(example::setKey);

        /**
         * 控制执行优先级,1代表最低优先级,10代表最高优先级。
         * 但不一定按照优先级顺序执行,看操作系统调度
          */
        thread.setPriority(10); //先读
        thread1.setPriority(1); //后写

//        thread.setPriority(1); //后读
//        thread1.setPriority(10); //先写


        thread.start();
        thread1.start();

        /**
         * 在使用完 Redisson 客户端后,需要显式地关闭它,以确保相关的线程资源被释放
         * 否则程序会一直显示在运行
         */
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        redissonClient.shutdown();
    }
}

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redisson提供了读写锁的功能。读写锁在并发场景中非常有用,特别是对于读操作远多于写操作的情况。读写锁允许多个线程同时持有读锁,但只允许一个线程持有写锁。 通过使用Redisson读写锁,你可以实现对某个关键字进行并发的读和写操作控制。当一个线程持有读锁时,其他线程也可以持有读锁,但是当有线程持有写锁时,其他的写锁和读锁都会被阻塞。 在实际应用中,如果有一个场景需要并发读取数据,然后并发进行扣减操作,可能会出现扣减失败的情况。这是因为在读取的时候还有库存,但是在扣减的时候库存已经没有了。为了避免这种情况,可以使用Redisson读写锁来保证并发读写的线程安全性。 在使用Redisson读写锁时,可以通过以下方式进行操作: - 使用`readLock()`方法获取读锁,并通过`lock()`方法加锁,然后使用`unlock()`方法释放读锁。 - 使用`writeLock()`方法获取写锁,并通过`lock()`方法加锁,然后使用`unlock()`方法释放写锁。 在你的代码示例中,可以看到使用Redisson读写锁的具体操作。通过`getReadWriteLock(key)`方法获取读写锁,然后使用`readLock()`方法获取读锁或使用`writeLock()`方法获取写锁。最后使用`lock()`方法加锁,使用`unlock()`方法释放锁。 总结起来,Redisson读写锁提供了一种并发控制机制,可以在并发读写场景下确保数据的一致性与线程安全性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [redission读写锁解决db和缓存双写不一致](https://blog.csdn.net/weixin_43944305/article/details/120191365)[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^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值