文章目录

  • 一、前期准备
  • 二、开始模拟
  • 三、连接池解决超时问题
  • 四、乐观锁解决超卖问题
  • 五、LUA脚本解决库存遗留问题
    • 1. LUA脚本
    • 2. LUA脚本在Redis中的优势
    • 3. LUA脚本实现

一、前期准备

为了模拟Redis事务中的并发操作,需要安装工具

CentOS6 默认安装
CentOS7需要手动安装

本机是CentOS7所以手动安装

联网情况下:

联网:yum install httpd-tools

无网络情况下:

(1) 进入cd  /run/media/root/CentOS 7 x86_64/Packages(路径跟centos6不同)
(2) 顺序安装apr-1.4.8-3.el7.x86_64.rpmapr-util-1.5.2-6.el7.x86_64.rpmhttpd-tools-2.4.6-67.el7.centos.x86_64.rpm

查看命令参数:

主要参数:

-n:请求的数量
-c:请求中的并发数量
-p:post提交需要的参数放到文件里
-T:参数的类型,post或者put提交需要设置

建立参数文件postfile


编写代码:

public class SecKill_redis {//秒杀过程public static boolean doSecKill(String uid,String prodid) throws IOException {//1 uid和prodid非空判断if(uid == null || prodid == null) {return false;}//2 连接redisJedis jedis = new Jedis("192.168.176.128",6379);//3 拼接key// 3.1 库存keyString kcKey = "sk:"+prodid+":qt";// 3.2 秒杀成功用户keyString userKey = "sk:"+prodid+":user";//4 获取库存,如果库存null,秒杀还没有开始String kc = jedis.get(kcKey);if(kc == null) {System.out.println("秒杀还没有开始,请等待");jedis.close();return false;}// 5 判断用户是否重复秒杀操作if(jedis.sismember(userKey, uid)) {System.out.println("已经秒杀成功了,不能重复秒杀");jedis.close();return false;}//6 判断如果商品数量,库存数量小于1,秒杀结束if(Integer.parseInt(kc)<=0) {System.out.println("秒杀已经结束了");jedis.close();return false;}//7.1 库存-1jedis.decr(kcKey);//7.2 把秒杀成功用户添加清单里面jedis.sadd(userKey,uid);System.out.println("秒杀成功了..");jedis.close();return true;}
}

二、开始模拟

执行命令:

ab -n 1000 -c 100 -k -p ~/postfile -T application/x-www-form-urlencoded http://10.15.22.207:8080/Seckill/doseckill


出现冲突,库存为空了,还能有成功的

库存数量为负:

除了这个问题,还会出现连接超时问题

三、连接池解决超时问题

节省每次连接redis服务带来的消耗,把连接好的实例反复利用。
通过参数管理连接的行为

连接池参数:

  • MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
  • maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
  • MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;
  • testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;
public class JedisPoolUtil {private static volatile JedisPool jedisPool = null;private JedisPoolUtil() {}public static JedisPool getJedisPoolInstance() {if (null == jedisPool) {synchronized (JedisPoolUtil.class) {if (null == jedisPool) {JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(200);poolConfig.setMaxIdle(32);poolConfig.setMaxWaitMillis(100 * 1000);poolConfig.setBlockWhenExhausted(true);poolConfig.setTestOnBorrow(true);  // ping  PONGjedisPool = new JedisPool(poolConfig, "192.168.176.128", 6379, 60000);}}}return jedisPool;}public static void release(JedisPool jedisPool, Jedis jedis) {if (null != jedis) {jedisPool.returnResource(jedis);}}
}

四、乐观锁解决超卖问题

public class SecKill_redis {public static void main(String[] args) {Jedis jedis =new Jedis("192.168.176.128",6379);System.out.println(jedis.ping());jedis.close();}//秒杀过程public static boolean doSecKill(String uid,String prodid) throws IOException {//1 uid和prodid非空判断if(uid == null || prodid == null) {return false;}//2 连接redis// Jedis jedis = new Jedis("192.168.176.128",6379);//通过连接池得到jedis对象JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();Jedis jedis = jedisPoolInstance.getResource();//3 拼接key// 3.1 库存keyString kcKey = "sk:"+prodid+":qt";// 3.2 秒杀成功用户keyString userKey = "sk:"+prodid+":user";//监视库存jedis.watch(kcKey);//4 获取库存,如果库存null,秒杀还没有开始String kc = jedis.get(kcKey);if(kc == null) {System.out.println("秒杀还没有开始,请等待");jedis.close();return false;}// 5 判断用户是否重复秒杀操作if(jedis.sismember(userKey, uid)) {System.out.println("已经秒杀成功了,不能重复秒杀");jedis.close();return false;}//6 判断如果商品数量,库存数量小于1,秒杀结束if(Integer.parseInt(kc)<=0) {System.out.println("秒杀已经结束了");jedis.close();return false;}//7 秒杀过程//使用事务Transaction multi = jedis.multi();//组队操作multi.decr(kcKey);multi.sadd(userKey,uid);//执行List<Object> results = multi.exec();if(results == null || results.size()==0) {System.out.println("秒杀失败了....");jedis.close();return false;}System.out.println("秒杀成功了..");jedis.close();return true;}
}

五、LUA脚本解决库存遗留问题

已经秒光,可是还有库存。

原因,就是乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了。

也就是2000个人购买物品,第一买成功,修改版本号,可能导致剩余的1999个人都购买失败

1. LUA脚本

Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。

很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。
https://www.w3cschool.cn/lua/

2. LUA脚本在Redis中的优势

将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。

LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。

但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。

利用lua脚本淘汰用户,解决超卖问题。

redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。

3. LUA脚本实现

local userid=KEYS[1];
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local usersKey="sk:"..prodid.":usr';
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then return 2;
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then return 0;
else redis.call("decr",qtkey);redis.call("sadd",usersKey,userid);
end
return 1;

加入代码中:

public class SecKill_redisByScript {private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;static String secKillScript ="local userid=KEYS[1];\r\n" + "local prodid=KEYS[2];\r\n" + "local qtkey='sk:'..prodid..\":qt\";\r\n" + "local usersKey='sk:'..prodid..\":usr\";\r\n" + "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + "if tonumber(userExists)==1 then \r\n" + "   return 2;\r\n" + "end\r\n" + "local num= redis.call(\"get\" ,qtkey);\r\n" + "if tonumber(num)<=0 then \r\n" + "   return 0;\r\n" + "else \r\n" + "   redis.call(\"decr\",qtkey);\r\n" + "   redis.call(\"sadd\",usersKey,userid);\r\n" + "end\r\n" + "return 1" ;static String secKillScript2 = "local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +" return 1";public static boolean doSecKill(String uid,String prodid) throws IOException {JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();Jedis jedis=jedispool.getResource();//String sha1=  .secKillScript;String sha1=  jedis.scriptLoad(secKillScript);Object result= jedis.evalsha(sha1, 2, uid,prodid);String reString=String.valueOf(result);if ("0".equals( reString )  ) {System.err.println("已抢空!!");}else if("1".equals( reString )  )  {System.out.println("抢购成功!!!!");}else if("2".equals( reString )  )  {System.err.println("该用户已抢过!!");}else{System.err.println("抢购异常!!");}jedis.close();return true;}
}

【Redis 学习】Redis事务秒杀案例相关推荐

  1. redis学习——redis事务

    Redis事务的概念: Redis 事务的本质是一组命令的集合.事务支持一次执行多个命令,一个事务中所有命令都会被序列化.在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会 ...

  2. Redis 学习---Redis 安装(2)

    Redis 安装 Window 下安装 下载地址:https://github.com/MSOpenTech/redis/releases. Redis 支持 32 位和 64 位.这个需要根据你系统 ...

  3. Redis学习手册(事务)

    一.概述: 和众多其它数据库一样,Redis作为NoSQL数据库也同样提供了事务机制.在Redis中,MULTI/EXEC/DISCARD/WATCH这四个命令是我们实现事务的基石.相信对有关系型数据 ...

  4. redis学习-redis事务

    是什么 可以一次执行多个命令,本质是一组命令的集合.一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞. 一次执行多个redis命令. 能干嘛 一个队列中,一次性.顺序性 ...

  5. Redis 学习 - 06 漂流瓶案例

    案例介绍 微信有几亿的用户群,某一时刻可能有几千上万人同时在玩漂流瓶,对于这种高并发数据量小的服务,使用 Node.js 和 Redis 绝对是一个推荐的选择. 接口设计 扔一个漂流瓶 请求方法:PO ...

  6. redis学习-redis五大数据类型

    五大数据类型简介 String(字符串) string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value. 也就是例子中的k1 v1,k2 v2 s ...

  7. redis学习-redis入门概述及简介

    是什么 Redis:REmote DIctionary Server(远程字典服务器)是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运 ...

  8. Redis学习---Redis操作之String

    set(name, value, ex=None, px=None, nx=False, xx=False) 在Redis中设置值,默认,不存在则创建,存在则修改 参数: ex,过期时间(秒) px, ...

  9. Redis学习笔记——快速入门

    @ NoSQL数据库简介 技术发展 技术的分类 1.解决功能性的问题:Java.Jsp.RDBMS.Tomcat.HTML.Linux.JDBC.SVN 2.解决扩展性的问题:Struts.Sprin ...

最新文章

  1. 提速20倍!谷歌AI发布TensorFlow 3D
  2. Licia:最全最实用的 JavaScript 工具库
  3. 常见操作系统调度算法研究(2)
  4. 【xargs使用】查询包含某字符串的所有文件
  5. ITester软件测试小栈,点击领取你的能量值!
  6. Guava学习笔记(四):复写的Object常用方法
  7. 【vscode】vscode插件学习(五)
  8. 一分钟解决微信小程序截图(截屏问题)
  9. Android音频压缩分析
  10. 【React】报错:Error: Element type is invalid: expected a string (for built-in components) or a class/fun
  11. 【网络】MTU理解、MTU对上层协议的影响
  12. Jira+Confluence+Fisheye+Crucible安装步骤清单
  13. K'ed by TNT team是什么意思?
  14. 计算机应用基础原文,计算机应用基础(本) - 平时作业
  15. java接口防刷_API 接口防刷
  16. Ubuntu磁盘分区和内存查看
  17. cryengine3中lua脚本模块集成笔记
  18. GitHub开源协议
  19. 计算机基础课程思政建设的应用,计算机基础教学中心|课程思政教学研讨会
  20. IOS 10 推送

热门文章

  1. Nginx 日志配置
  2. Erlang函数与模式匹配
  3. Armadillo_OpenBLAS_IntelMKL安装
  4. 我用YOLOv5做情感识别
  5. iOS开发 - ANPs推送通知 标签: 推送通知ANPs远程推送、本地推送
  6. mac下 MATLAB安装
  7. java自定义异常类的父类_Java 自定义异常类
  8. PAT 甲级1129
  9. EM 管理 - 基础简介
  10. 施耐德lxm23du使用说明书_施耐德还出钢笔?简评德系入门钢笔施耐德BASE