Redis实现CAS的乐观锁

2024-05-11 18:38
文章标签 实现 redis cas 乐观

本文主要是介绍Redis实现CAS的乐观锁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

对于经常开发Web的Coder们,经常会有这样的需求,就是在多机的分布式环境下,有时候需要限制多台机器上的请求修改同一份资源。对于单机的环境下,我们通常可以用同步或者锁去避免多线程下的竞态条件。以java为例,我们可以用synchronized或者ReentrantLock,去做资源访问的同步。但这是JVM和操作系统提供给我们的特性,但是对于分布式环境下我们没有这些便利条件。所以我们需要引入一个外部的Observer去实现这样的一个分布式锁,Zookeeper是一个比较好的解决方案,但是Zookeeper还是比较重的,我们可以用Redis实现这样一个锁。
乐观锁基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,但是需要反复的重试,但也是因为重试的机制,能比较快的响应。在实现CAS之前,需要了解一下Redis的事务机制。
Redis事务:
我们可以用Mysql事务机制来理解Redis的事务机制,但也有所不同,Mysql的事务的形式如下:
openSession()
update()
insert()
commit()
如果在update和insert之间出现错误,那么会触发rollback(),Redis的事务用到了MULTI和EXEC命令,事务的形式如下:
MULTI
SET
HSET
EXEC
和Mysql的事务不同,Redis会将所有EXEC命令之前的命令放入一个QUEUE中,当遇到EXEC时批量执行QUEUE中的命令,但是 Redis的事务是不支持回滚的,它只是顺序的执行命令,并批量返回结果,但是对于极端情况下,事务在没有完全执行完时宕机,导致事务日志只写入部分,这样在重启时会产生错误,用aof的修复工具修复后可以进行启动。
在了解了事务机制后,我们还不足以实现乐观锁,还需要了解一个命令——Watch,Watch命令可以监控Redis中的一个key,当Key发生变化时终止事务的提交。先看一个正确的例子:

127.0.0.1:6379> set locktest 1
OK
127.0.0.1:6379> get locktest
"1"
127.0.0.1:6379> watch locktest
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set locktest 3
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get locktest
"3"
127.0.0.1:6379> 

但是在multi的过程中如果locktest的值发生变化又会怎样?

127.0.0.1:6379> set locktest 1
OK
127.0.0.1:6379> get locktest
"1"
127.0.0.1:6379> watch locktest
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set locktest 3
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get locktest
"2"
127.0.0.1:6379> 

redis 不支持事务的一个回滚

127.0.0.1:6379> get a
QUEUED
127.0.0.1:6379> set a 4
QUEUED
127.0.0.1:6379> lpop 3
QUEUED
127.0.0.1:6379> exec
1) (nil)
2) OK
3) (nil)

这里我们用另一个Client在Multi之后将locktest修改为2,课件在执行事务的时候返回为nil,表示执行失败。
那么我们就可以用上述两种命令实现一个乐观锁,代码如下:

package com.redis.lock;import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;
/*** topic:利用redis的事务,实现一个乐观锁* * @author zhiming**/
public class RedisWatchLock {private static final String redisHost = "10.0.5.86";private static final int port = 6381;private static JedisPoolConfig config;private static JedisPool pool;private static ExecutorService service;private static int ThLeng=10;private static CountDownLatch latch;private static AtomicInteger Countor = new AtomicInteger(0);static{//利用Redis连接池,保证多个线程利用多个连接,充分模拟并发性config = new JedisPoolConfig();config.setMaxIdle(10);config.setMaxWaitMillis(1000);config.setMaxTotal(30);pool = new JedisPool(config, redisHost, port);//利用ExecutorService 管理线程service = Executors.newFixedThreadPool(10);//CountDownLatch保证主线程在全部线程结束之后退出latch = new CountDownLatch(ThLeng);}public static void main(String args[]){int ThLeng = 10;String ThreadNamePrefix = "thread-";Jedis cli = pool.getResource();cli.del("redis_inc_key");//先删除既定的keycli.set("redis_inc_key", String.valueOf(1));//设定默认值for(int i =0;i<ThLeng;i++){Thread th = new Thread(new TestThread(pool));th.setName(ThreadNamePrefix+i);System.out.println(th.getName()+"inited...");service.submit(th);}service.shutdown();try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("all sub thread sucess");System.out.println("countor is "+Countor.get());String countStr = cli.get("redis_inc_key");System.out.println(countStr);}public static class TestThread implements Runnable {private String incKeyStr = "redis_inc_key";private Jedis cli;private JedisPool pool;public TestThread(JedisPool pool) {cli = pool.getResource();this.pool = pool;}public void run() {try{for (int i = 0; i < 100; i++) {actomicAdd();}}catch(Exception e){pool.returnBrokenResource(cli);}finally{pool.returnResource(cli);latch.countDown();}}public void actomicAdd(){boolean flag =true;while(flag){cli.watch(incKeyStr);String countStr = cli.get("redis_inc_key");int countInt = Integer.parseInt(countStr);int expect = countInt+1;Transaction tx = cli.multi();                   tx.set(incKeyStr, String.valueOf(expect));List<Object> list = tx.exec();//如果事务失败了exec会返回nullif(list==null){System.out.println("multi shut down");continue;}else{//如果达到期望值那么结束while循环flag=false;}System.out.println("my expect num is "+expect);         System.out.println("seting....");   }Countor.incrementAndGet();  }}}

这样我们就利用Redis实现了一个类似于Java 的原子类的功能。在实际的Web开发中,我们可以利用redis来解决资源重复修改或争用的问题。



作者:一只小哈
链接:https://www.jianshu.com/p/d777eb9f27df
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

这篇关于Redis实现CAS的乐观锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/980339

相关文章

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

Spring Boot 3.4.3 基于 Spring WebFlux 实现 SSE 功能(代码示例)

《SpringBoot3.4.3基于SpringWebFlux实现SSE功能(代码示例)》SpringBoot3.4.3结合SpringWebFlux实现SSE功能,为实时数据推送提供... 目录1. SSE 简介1.1 什么是 SSE?1.2 SSE 的优点1.3 适用场景2. Spring WebFlu

基于SpringBoot实现文件秒传功能

《基于SpringBoot实现文件秒传功能》在开发Web应用时,文件上传是一个常见需求,然而,当用户需要上传大文件或相同文件多次时,会造成带宽浪费和服务器存储冗余,此时可以使用文件秒传技术通过识别重复... 目录前言文件秒传原理代码实现1. 创建项目基础结构2. 创建上传存储代码3. 创建Result类4.

SpringBoot日志配置SLF4J和Logback的方法实现

《SpringBoot日志配置SLF4J和Logback的方法实现》日志记录是不可或缺的一部分,本文主要介绍了SpringBoot日志配置SLF4J和Logback的方法实现,文中通过示例代码介绍的非... 目录一、前言二、案例一:初识日志三、案例二:使用Lombok输出日志四、案例三:配置Logback一

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

idea中创建新类时自动添加注释的实现

《idea中创建新类时自动添加注释的实现》在每次使用idea创建一个新类时,过了一段时间发现看不懂这个类是用来干嘛的,为了解决这个问题,我们可以设置在创建一个新类时自动添加注释,帮助我们理解这个类的用... 目录前言:详细操作:步骤一:点击上方的 文件(File),点击&nbmyHIgsp;设置(Setti

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

MySQL大表数据的分区与分库分表的实现

《MySQL大表数据的分区与分库分表的实现》数据库的分区和分库分表是两种常用的技术方案,本文主要介绍了MySQL大表数据的分区与分库分表的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录1. mysql大表数据的分区1.1 什么是分区?1.2 分区的类型1.3 分区的优点1.4 分