本文主要是介绍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的乐观锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!