当前位置: 首页 > article >正文

Spring Retry + Redis Watch实现高并发乐观锁

为什么不用分布式锁
分布式锁属于悲观锁,不利于并发优化
在这里插入图片描述
能不能用Redis+Lua
利用Redis+Lua单线程原子性特性,可以解决高并发且无锁,单对于复杂业务逻辑,例如加入数据库业务逻辑判断,Lua非常不友好,且不容易调试

package com.itlaoqi.redislua;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;

@RestController
public class LuaController {
    private static final String LUA_SCRIPT = """
        if tonumber(redis.call('exists', KEYS[1])) == 0 then
            redis.call('set', KEYS[1],'10')
        end
        
        if tonumber(redis.call('exists', KEYS[2])) == 0 then
            redis.call('sadd', KEYS[2],'-1')
        end
        
        if tonumber(redis.call('get', KEYS[1])) > 0 and tonumber(redis.call('sismember', KEYS[2] , ARGV[1])) == 0  then 
            redis.call('incrby', KEYS[1],'-1') 
            redis.call('sadd',KEYS[2],ARGV[1])
            return 1
        else 
            return 0 
        end
    """;

    @Autowired
    private StringRedisTemplate redisTemplate;
    @GetMapping("/sk")
    public Map secKill(String pid){
        Map resp = new HashMap();
        String uid = String.valueOf(new Random().nextInt(100000000));
        List keys = new ArrayList();
        keys.add("P" + pid); //P1010 String类型 用于保存1010产品库存量
        keys.add("U" + pid);//U1010 SET类型 用于保存秒杀确权的UID
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LUA_SCRIPT,Long.class);
        Long result = redisTemplate.execute(redisScript, keys,uid);
        resp.put("uid", uid);
        resp.put("result", result);
        return resp;
    }
}

Spring Retry + Redis Watch实现乐观锁
通过多次重试实现最小程度锁定,开发模式利用Java语言接口
Redis中的事务是指在单个步骤中执行一组命令,围绕着MULTI、EXEC、DISCARD和WATCH命令展开。
引入依赖

<dependencies>
  <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>
  <version>3.23.5</version>
</dependency>-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>2.0.0</version>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
  </dependency>
</dependencies>

启用spring-retry

@SpringBootApplication
@EnableRetry
public class RedisClientSideApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisClientSideApplication.class, args);
    }

}

业务逻辑

package com.itwenqiang.redisclientside;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class SampleService {
    @Autowired
    private RedisTemplate redisTemplate;

    @Retryable(retryFor = IllegalStateException.class, maxAttempts = 2)
    @Transactional
    public String sa(){
        System.out.println("executing sa()");
        List execute = (List)redisTemplate.execute(new SessionCallback<List>() {
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                redisTemplate.watch("sa001");
                redisTemplate.multi();
                redisTemplate.opsForValue().set("pri001",-100);
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                redisTemplate.opsForValue().set("sa001",100);
                return redisTemplate.exec();
            }
        });
        if(execute.size()==0){
            System.out.println("发现并发冲突:" + execute);
            throw new IllegalStateException("Retry");
        }else{
            System.out.println("exec执行成功:" + execute);
        }
        return "success";
    }
}

控制器

package com.itwenqiang.redisclientside;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleController {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private SampleService sampleService;

    @GetMapping("/test")
    public String testWatch(){
        sampleService.sa();
        return "success";
    }

    @GetMapping("/setSA")
    public String setSA(){
        redisTemplate.opsForValue().set("sa001",300);
        return "success";
    }

}

测试代码

GET http://localhost:8080/test
GET http://localhost:8080/setSA

执行结果

executing sa()
发现并发冲突:[]
executing sa()
exec执行成功:[true, true]

http://www.kler.cn/a/458687.html

相关文章:

  • AI 智能助手对话系统
  • Node.js 函数
  • 三大行业案例:AI大模型+Agent实践全景
  • ubuntu安装qt creator 并配置交叉编译环境
  • Mybatis-Plus updateById 方法更新无效及空值处理
  • 006-Jetpack Compose for Android之传感器数据
  • UI页面布局分析(5)- 评分弹窗的实现
  • 【PCIe 总线及设备入门学习专栏 5.1 -- PCIe 引脚 PRSNT 与热插拔】
  • Edge Scdn是用来干什么的?
  • 用户界面的UML建模05
  • element-plus在Vue3中开发相关知识
  • AI文献阅读ChatDOC 、ChatPDF 哪个好?
  • 如何在Linux上配置SSH密钥以实现免密登录
  • PostgreSQL 初始化配置设置
  • Unity功能模块一对话系统(4)实现个性文本标签
  • 2024-12-29-sklearn学习(25)无监督学习-神经网络模型(无监督) 烟笼寒水月笼沙,夜泊秦淮近酒家。
  • Leetcode 3405. Count the Number of Arrays with K Matching Adjacent Elements
  • 【LangChain】Chapter1 - Models, Prompts and Output Parsers
  • 【开源免费】基于SpringBoot+Vue.JS网上摄影工作室系统(JAVA毕业设计)
  • PostgreSQL中FIRST_VALUE、LAST_VALUE、LAG 和 LEAD是窗口函数,允许返回在数据集的特定窗口(或分区)内访问行的相对位置
  • 软件测试之单元测试
  • 技术周总结 12.23~12.29 周日(C#异步线程及调试)
  • 网络畅通无阻:计算机网络知识点复习指南
  • Empire Lupin One靶机
  • AIGC多模态生成模型的演进:图像与视频生成算法的协同发展(附代码与详细教程)
  • .net core 的软件开发方法论