分享

lua+redis业务场景举例

 vnxy001 2020-07-31

       lua脚本可以实现redis命令的原子性,也就是在脚本中的redis命令不会插入其他redis命令。和pipline、redis事务相似。需要注意的是,它并不会保证同时成功或者同时失败。当然,解决办法除了lua脚本外,还可以用分布式锁,分布式锁可以用setnx(需要注意集群中的时间统一问题),redission以及zookeeper。

       这个业务场景非常的多,比如抢红包中的判断重复等。这里举一个业务场景,redis做队列时,判断是否入队成功。

       redis是否入队成功的判断方法很简单,push完以后会返回当前队列目前的长度,另外通过llen命令获得队列长度,如果相同,那么入队成功。

       但是,在队列中同时有消费者同时在出队,那么很可能在push和llen之间调用了pop命令,那么即使入队成功,但是也会判断为失败。比如:

  1. public static void main(String[] args) {
  2. Thread inQueue = new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. for(int i=0; i<100; i++) {
  6. // long success = (long) JedisUtil.evalScript(LuaSysContants.IN_QUEUE.getName(), LuaSysContants.IN_QUEUE.getScript(), 1, "result_list", String.valueOf(i));
  7. // if(success == 0) {
  8. // System.err.println("入队出错!");
  9. // }
  10. long retValue = JedisUtil.rpush("result_list", String.valueOf(i));
  11. long len = JedisUtil.llen("resut_list");
  12. if(retValue != len) {
  13. System.err.println("入队出错!");
  14. }
  15. }
  16. }
  17. });
  18. Thread outQueue = new Thread(new Runnable() {
  19. @Override
  20. public void run() {
  21. try {
  22. TimeUnit.SECONDS.sleep(1);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. for(int i=0; i<10; i++) {
  27. JedisUtil.lpop("result_list");
  28. }
  29. }
  30. });
  31. inQueue.start();
  32. outQueue.start();
  33. }
              这里一个入队,一个出队线程,那么我们可以看到结果很不靠谱

               但是我们改成lua脚本就不会出现这个问题了

               先来看一下lua脚本

  1. public enum LuaSysContants {
  2. IN_QUEUE("inQueue",
  3. "redis.log(redis.LOG_NOTICE, \"in queue start\") " +
  4. "local retValue = redis.call(\"rpush\", KEYS[1], ARGV[1]) " +
  5. "local len = redis.call(\"llen\", KEYS[1]) " +
  6. "if(retValue == len) then " +
  7. " redis.log(redis.LOG_NOTICE, \"in queue success\") " +
  8. " return 1 " +
  9. "else " +
  10. " redis.log(redis.LOG_NOTICE, \"in queue failure\") "+
  11. " return 0 " +
  12. "end");
  13. private String name;
  14. private String script;
  15. private LuaSysContants(String name, String script) {
  16. this.name = name;
  17. this.script = script;
  18. }
  19. public String getName() {
  20. return name;
  21. }
  22. public void setName(String name) {
  23. this.name = name;
  24. }
  25. public String getScript() {
  26. return script;
  27. }
  28. public void setScript(String script) {
  29. this.script = script;
  30. }
  31. }
          
              执行lua脚本的方法
  1. public static Object evalScript(String scriptName,String script,int keyCount,String ... params) {
  2. Jedis jedis = new Jedis(HOST, PORT);
  3. jedis.auth(PASSWORD);
  4. String sha = jedis.get("script-sha-"+scriptName);
  5. if(sha==null || "".equals(sha) || !jedis.scriptExists(sha)) {
  6. sha = jedis.scriptLoad(script);
  7. jedis.set("script-sha-"+scriptName, sha);
  8. }
  9. Object retValue = jedis.evalsha(sha, keyCount, params);
  10. return retValue;
  11. }
             最后改造我们的例子
  1. public static void main(String[] args) {
  2. Thread inQueue = new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. for(int i=0; i<100; i++) {
  6. long success = (long) JedisUtil.evalScript(LuaSysContants.IN_QUEUE.getName(), LuaSysContants.IN_QUEUE.getScript(), 1, "result_list", String.valueOf(i));
  7. if(success == 0) {
  8. System.err.println("入队出错!");
  9. }
  10. // long retValue = JedisUtil.rpush("result_list", String.valueOf(i));
  11. // long len = JedisUtil.llen("resut_list");
  12. // if(retValue != len) {
  13. // System.err.println("入队出错!");
  14. // }
  15. }
  16. }
  17. });
  18. Thread outQueue = new Thread(new Runnable() {
  19. @Override
  20. public void run() {
  21. try {
  22. TimeUnit.SECONDS.sleep(1);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. for(int i=0; i<10; i++) {
  27. JedisUtil.lpop("result_list");
  28. }
  29. }
  30. });
  31. inQueue.start();
  32. outQueue.start();
  33. }

   

              不存在入队失败的问题,再看一下数据,完全正确


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多