redis实现CAS

CAS

  • mysql的UPDATE,hbase的checkAndPut提供CAS操作。
  • redis基于watch和multi也可以实现CAS乐观锁。

multi

  • multi:multi开启事务,包含多个命令。
    • 保证一致性、隔离性
    • 命令全部成功时保证原子性,有失败则无法保证
    • 无法保证持久性
  • exec触发。
  • 可以理解为打包的批量执行脚本。某条命令执行失败不会导致之前的命令回滚,后续命令也会继续执行。
  • 对比Pipeline
    • multi使用服务端缓冲,每个命令发送一次给服务端,执行过程不会有其他命令穿插。
    • pipeline使用客户端缓冲,多个命令一次性发给服务端,执行过程可能有其他命令穿插。

watch

  • watch:监视key,如事务开始前key被改动,终止事务。
  • 原理
    • 服务端维护key->watch这个key的client列表。
    • client1完成操作后会将watch这个key的列表中其他client状态设置为CLIENT_DIRTY_CAS,并把自己从列表中删除。
    • client2执行时状态为CLIENT_DIRTY_CAS直接终止事务返回失败。

Jedis实现

  • 先开watch,再开multi。
  • 根据 transaction.exec()执行结果是否都是OK,判断操作是否成功。
    • 失败则重试或者抛异常。
    • 成功则继续操作。
@Service
public class RedisCAS {

    @Autowired
    private JedisPool jedisPool;

    private String redisKey = "cas_key";

    private int threadNum = 5;

    private ExecutorService service = Executors.newFixedThreadPool(threadNum);

    private CountDownLatch latch = new CountDownLatch(threadNum);

    private AtomicInteger successCount = new AtomicInteger(0);

    private AtomicInteger failCount = new AtomicInteger(0);

    public void execute() {
        //初始化key
        Jedis cli = jedisPool.getResource();
        cli.set(redisKey, String.valueOf(0));
        //开启5个线程
        for (int i = 0; i < threadNum; i++) {
            service.submit(new AddThread("thread-" + i));
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(redisKey + ": " + cli.get(redisKey));
        System.out.println("success: " + successCount.get() + ", fail: " + failCount.get());
    }

    public class AddThread implements Runnable {

        private Jedis client;
        private String name;

        public AddThread(String name) {
            client = jedisPool.getResource();
            this.name = name;
        }

        @Override
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    atomicAdd();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                client.close();
                latch.countDown();
            }
        }

        private void atomicAdd() {
            while (true) {
                //开启watch
                client.watch(redisKey);
                int target = Integer.parseInt(client.get(redisKey)) + 1;
                //开启事务
                Transaction transaction = client.multi();
                transaction.set(redisKey, String.valueOf(target));
                List<Object> result = transaction.exec();
                //事务结果
                if (ok(result)) {
                    System.out.println(this.name + " multi succussful, value = " + target);
                    successCount.addAndGet(1);
                    break;
                }
                System.out.println(this.name + " multi fail.");
                failCount.addAndGet(1);
            }
        }

        private boolean ok(List<Object> result) {
            return CollectionUtils.isNotEmpty(result) && result.stream().allMatch("OK"::equals);
        }
    }
}

@Configuration
public class RedisConfig {

    @Bean
    public JedisPool jedisPool() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(10);
        config.setMaxWaitMillis(1000);
        config.setMaxTotal(30);
        JedisPool jedisPool = new JedisPool(config, "127.0.0.1", 6379);
        return jedisPool;
    }
}

备选方案

  • redis悲观锁:setnx
  • 原因:
    • 由于watch需要维护key的客户端观察者列表,key修改之后修改客户端状态会有一定的开销,不适合高并发场景。
    • 某些中间件不支持watch+multi
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值