redis+lua高同時商品秒殺を実現


マルチredisサービスノードがこの方法を推奨しない場合.マルチノードはredLockで分散ロックを実現
 redis+lua        ,     ,    ,    lua        ,        。redis       io  ,    。    。

redis+Lua  
1、      ,            redis,                  ,       
2、    ,redis  lua          ,           ,        ,      
3、  ,              redis ,             

            。999        。     (redis+lua    +         )tomcat    3500 (  ),tomcat      400/s,          ,          ,            ,   ,        。               ,          。           。                     。
         。       。         。     

1、  lua  
local productId = tostring(KEYS[1])
local uid = tostring(ARGV[1])
--     
local function successFun(success, msg, data)
    success = success or 1
    msg = msg or ""
    data = data or {}
    return cjson.encode({success = success, msg = msg, data = data})
end
--     
local function response(errno, msg, data)
    errno = errno or 0
    msg = msg or ""
    data = data or {}
    return cjson.encode({errno = errno, msg = msg, data = data})
end
--            
local log_key = "LOG_{" .. productId .. "}"
-- return log_key
local has_fetched = redis.call("sIsMember", log_key, uid)
if (has_fetched ~= 0) then
    return response(-1, "        ")
end
local result = false

--         
local quan_key = "QUAN_{" .. productId .. "}"
local param = productId.."@";
local product = redis.call("hgetall",param)
if product==nil then
   return response(-1, "       ")
end
local nums = redis.call("hget",param,"num");
local n = tonumber(nums);
if (n<=0)then
  return response(-1, "    ")
end
redis.call("sAdd", log_key, uid)
local num = n-1;
local json = {};
json["id"] = productId;
json["num"] = n;
result = {uid = uid, pid = productId,  product = json}
--         redis
redis.call("rPush", "DB_QUEUE", cjson.encode(result))
---    
redis.call("hset", param, "num",(num))
redis.call('rPush',"user",cjson.encode(result))
if (result == false) then
    return response(-1, "     ")
else
    return successFun(1, "    ", result)
end

 
2、redisツールのパッケージ
package com.bus.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;

/**
 * @author wwz
 * @date 2020-03-06
 * @descrption:
 */
@Component
public class RedisUtils {

    private final  Logger log = LoggerFactory.getLogger(this.getClass());
    private final  String expireTime = "50000";

    @SuppressWarnings("rawtypes")
    @Autowired
    private StringRedisTemplate stringRedisTemplateDemo;

    private DefaultRedisScript getLockRedisScript;
    private DefaultRedisScript releaseLockRedisScript;
    private DefaultRedisScript realRedisScript;

    private StringRedisSerializer argsStringSerializer = new StringRedisSerializer();
    private StringRedisSerializer resultStringSerializer = new StringRedisSerializer();
    private StringRedisSerializer realStringSerializer = new StringRedisSerializer();

    private final String EXEC_RESULT = "1";
    @SuppressWarnings("unchecked")
    @PostConstruct
    private void init() {
        getLockRedisScript = new DefaultRedisScript();
        getLockRedisScript.setResultType(String.class);
        releaseLockRedisScript = new DefaultRedisScript();
        realRedisScript = new DefaultRedisScript();
        releaseLockRedisScript.setResultType(String.class);
        realRedisScript.setResultType(String.class);

        //       lua   
        getLockRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/getLock.lua")));
        releaseLockRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/releaseLock.lua")));
        realRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/real.lua")));

    }

   
    /**
     *     
     * @param key
     * @param requestId
     * @param retryTimes
     * @return
     */
    public JSONObject set(String key, String requestId, int retryTimes) {
        try {
            int count = 0;
            while (true) {
                String result = stringRedisTemplateDemo.execute(realRedisScript, argsStringSerializer, realStringSerializer,
                        Collections.singletonList(key), requestId);
                JSONObject object = JSON.parseObject(result);
                log.debug("result:{},type:{}", result, result.getClass().getName());
                return object;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public boolean get(String key, String requestId) {
        String result = stringRedisTemplateDemo.execute(releaseLockRedisScript, argsStringSerializer, resultStringSerializer,
                Collections.singletonList(key), requestId);
        if (EXEC_RESULT.equals(result)) {
            return true;
        }
        return false;
    }


}

 
コントロールレイヤ呼び出し
@RequestMapping("buy")
@ResponseBody
public Object orderBy(String productId){
    String requestId = UUID.randomUUID().toString();
    try{
        //Executors.newFixedThreadPool()
        JSONObject object = redisUtils.set(productId,requestId,0);
        if(object == null){
            return JsonResult.Fail("    ,     !");
        }
        String success = object.getString("success");
        if("1".equals(success)){
            taskExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    orderService.createOrder(object.getJSONObject("data"),requestId);
                }
            });
            return JsonResult.OK("      ");
        }
        return JsonResult.Fail(object.getString("msg"));
    }catch (Exception e){
        e.printStackTrace();
        return JsonResult.Fail("    ,     !");
    }
}