本文共 6312 字,大约阅读时间需要 21 分钟。
用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_keylocal has_fetched = redis.call("sIsMember", log_key, uid)if (has_fetched ~= 0) then return response(-1, "已经抢过该商品了")endlocal result = false-- 遍历商品所有批次local quan_key = "QUAN_{" .. productId .. "}"local param = productId.."@";local product = redis.call("hgetall",param)if product==nil then return response(-1, "商品数据不存在")endlocal nums = redis.call("hget",param,"num");local n = tonumber(nums);if (n<=0)then return response(-1, "暂无库存")endredis.call("sAdd", log_key, uid)local num = n-1;local json = {};json["id"] = productId;json["num"] = n;result = {uid = uid, pid = productId, product = json}--把人员订单信息写入redisredis.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: */@Componentpublic class RedisUtils { private final Logger log = LoggerFactory.getLogger(this.getClass()); private final String expireTime = "50000"; @SuppressWarnings("rawtypes") @Autowired private StringRedisTemplate stringRedisTemplateDemo; private DefaultRedisScriptgetLockRedisScript; 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")@ResponseBodypublic 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("服务中断,请稍后重试!"); }}
转载地址:http://ylvbi.baihongyu.com/