• [Redis] Redis实战--EVAL


    ✨✨个人主页:沫洺的主页

    📚📚系列专栏: 📖 JavaWeb专栏📖 JavaSE专栏 📖 Java基础专栏📖vue3专栏 

                               📖MyBatis专栏📖Spring专栏📖SpringMVC专栏📖SpringBoot专栏

                               📖Docker专栏📖Reids专栏📖MQ专栏📖SpringCloud专栏     

    💖💖如果文章对你有所帮助请留下三连✨✨

    💌EVAL帮助文档

    EVAL-Redis命令参考

    传参数的格式

    其中 "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 是被求值的 Lua 脚本,数字 2 指定了键名参数的数量, key1 和 key2 是键名参数,分别使用 KEYS[1] 和 KEYS[2] 访问,而最后的 argv1 和 argv2 则是附加参数,可以通过 ARGV[1] 和 ARGV[2] 访问它们

    • redis.call()

    • eval执行scan

    源生scan命令

    1. //例如模糊迭代初始游标为0 前缀为'user.'Key 期望获取5条的命令
    2. scan 0 match user.* count 5

    使用eval执行脚本

    eval "return redis.call('scan',ARGV[1],'match',KEYS[1],'count',ARGV[2])" 1 user.* 0 5

    💌SpringBoot调用LUA脚本 

    1. @SpringBootTest
    2. public class AppTests_Lua {
    3. @Resource(name = "redisTemplate")
    4. private ValueOperations valueOperations;
    5. @Autowired
    6. private StringRedisTemplate stringRedisTemplate;
    7. @Test
    8. void test1() {
    9. for (int i =1 ;i<11;i++){
    10. User user = User.builder().name(i+"号张").age(18).build();
    11. valueOperations.set("user."+i,user);
    12. }
    13. }
    14. @Test
    15. void test2(){
    16. String lua = "return redis.call('scan',ARGV[1],'match',KEYS[1],'count',ARGV[2])";
    17. RedisScript redisScript = RedisScript.of(lua,List.class);
    18. List keys = new ArrayList<>();
    19. keys.add("user.*");
    20. List execute = stringRedisTemplate.execute(redisScript, keys, "0", "5");
    21. }
    22. }

    完整的迭代过程

    1. @Test
    2. void test2() {
    3. String lua = "return redis.call('scan',ARGV[1],'match',KEYS[1],'count',ARGV[2])";
    4. RedisScript redisScript = RedisScript.of(lua, List.class);
    5. List keys = new ArrayList<>();
    6. keys.add("user.*");
    7. String cursor = "0";
    8. do {
    9. List results = stringRedisTemplate.execute(redisScript, keys, cursor, "5");
    10. cursor = (String) results.get(0);
    11. List results_keys = (List) results.get(1);
    12. System.out.println(cursor);
    13. System.out.println(results_keys);
    14. } while (!"0".equals(cursor));
    15. }

     简单的get两种方式(KEYS提前预编译好了,与ARGV的区别只是效率不同)

    1. @Test
    2. void test3() {
    3. String lua = "return redis.call('mget',KEYS[1],KEYS[2])";
    4. RedisScript redisScript = RedisScript.of(lua, List.class);
    5. List keys = new ArrayList<>();
    6. keys.add("user.1");
    7. keys.add("user.10");
    8. List results = stringRedisTemplate.execute(redisScript, keys);
    9. }
    1. @Test
    2. void test3() {
    3. String lua = "return redis.call('mget',ARGV[1],ARGV[2])";
    4. RedisScript redisScript = RedisScript.of(lua, List.class);
    5. List keys = new ArrayList<>();
    6. List results = stringRedisTemplate.execute(redisScript, keys,"user.1","user.10");
    7. }

    💌解决库存负数问题

    1. @SpringBootTest
    2. class AppTests_DecrBy {
    3. @Resource(name = "redisTemplate")
    4. private ValueOperations valueOperations;
    5. @Autowired
    6. private StringRedisTemplate stringRedisTemplate;
    7. //定义一个产品
    8. private final static String productKey = "product01";
    9. @Test
    10. void test1() throws InterruptedException {
    11. //设置初始化库存5个
    12. valueOperations.set(productKey, 5);
    13. //获取线程池
    14. ExecutorService executorService = Executors.newCachedThreadPool();
    15. //模拟开启10个线程
    16. for (int i = 1; i <= 10; i++) {
    17. //模拟扣减i个库存量(qty)
    18. int finalI = i;
    19. //开启线程执行
    20. executorService.execute(() -> {
    21. doing(finalI);
    22. });
    23. }
    24. //防止主线程停止,导致模拟线程不执行
    25. Thread.sleep(60 * 1000);
    26. }
    27. //lua脚本语言
    28. private static String lua ;
    29. static {
    30. StringBuilder sb = new StringBuilder();
    31. sb.append(" local key = KEYS[1] ");
    32. sb.append(" local qty = ARGV[1] ");
    33. sb.append(" local redis_qty = redis.call('get',key) ");
    34. sb.append(" if tonumber(redis_qty) >= tonumber(qty) ");
    35. sb.append(" then ");
    36. sb.append(" redis.call('decrby',key,qty) ");
    37. sb.append(" return -1 ");
    38. sb.append(" else ");
    39. sb.append(" return tonumber(redis_qty) ");
    40. sb.append(" end ");
    41. lua = sb.toString();
    42. }
    43. private void doing(Integer qty){
    44. RedisScript redisScript = RedisScript.of(lua,Long.class);
    45. List keys = new ArrayList<>();
    46. keys.add(productKey);
    47. Long ret = stringRedisTemplate.execute(redisScript, keys,qty.toString());
    48. if(ret.intValue()==-1){
    49. System.out.println(StrUtil.format("{},扣减成功,扣减数量:{}",Thread.currentThread().getName(),qty));
    50. }else{
    51. System.out.println(StrUtil.format("{},库存不足,需求量:{},库存量:{}",Thread.currentThread().getName(),qty,ret));
    52. }
    53. }
    54. }

    核心代码逻辑

    1. //lua脚本语言
    2. //local定义变量的修饰符
    3. //tonumber将字符串转换为数字类型
    4. //redis.call('get',key)获取当前库存量
    5. //redis.call('decrby',key,qty)扣减所需的库存量
    6. //求量小于库存量时扣减成功返回-1,需求量大于库存量时返回所剩库存量
    7. //最后将lua语言转成字符串
    8. private static String lua ;
    9. static {
    10. StringBuilder sb = new StringBuilder();
    11. sb.append(" local key = KEYS[1] ");
    12. sb.append(" local qty = ARGV[1] ");
    13. sb.append(" local redis_qty = redis.call('get',key) ");
    14. sb.append(" if tonumber(redis_qty) >= tonumber(qty) ");
    15. sb.append(" then ");
    16. sb.append(" redis.call('decrby',key,qty) ");
    17. sb.append(" return -1 ");
    18. sb.append(" else ");
    19. sb.append(" return tonumber(redis_qty) ");
    20. sb.append(" end ");
    21. lua = sb.toString();
    22. }

  • 相关阅读:
    进入软件行业的几点建议
    Postgresql源码(88)column definition list语义解析流程分析
    【蓝桥杯真题练习】STEMA科技素养练习题库 答案版014 持续更新中~
    利用 clip-path 绘制不规则的图形
    Linux实用操作-----软件的安装
    云化Web IDE,在线开发新模式
    Maven 安装配置
    Android修行手册-POI操作Excel文档
    OA协同系统适合哪些企业
    ES6~ES13新特性(二)
  • 原文地址:https://blog.csdn.net/HeyVIrBbox/article/details/127839424