openresty实践抢红包、秒杀之类的高并发场景

        这段时间由于即将换工作(工作交接),所以处于比较空闲,也有时间空闲下来整理下技术方面的东西,社区论坛也逛得多了点,看到了网上不少相关于秒杀和抢红包之类的文章,做了对比,分享下:

首先秒杀、抢红包或者投票之类都属于瞬间高并发,所以本文只针对NOSQL(redis)做后端,各个的方案的性能做了对比:

基本业务逻辑是这样的:每个用户只允许抢一次,保证余额等操作的原子性,最后保证数据的一致性;


基于redis乐观锁方案:下面的代码转摘自本社区的某位朋友,

 private int bid(HttpServletRequest request, HttpServletResponse response, Jedis jedis) throws Exception {        int flag = 0;// 1,成功,2已经购买,3已经没钱了,其他異常
        // 每个请求对应一个userId
        int userId = new Random().nextInt(999999);
         
        // 观察 总标值,每人抢购一元
        while ("OK".equals(jedis.watch("accountBalance"))) {
                // 判断是否购买过
                Boolean isBuy = RedisAPI.sismember("userIdSet", userId + "");
                if (isBuy) {
                    flag = 2;
                    return flag;
                }
                //投资额
            int r = 1;// new Random().nextInt(2);
            int lastAccount = 0;
            String balance = RedisAPI.get("accountBalance");
            if (StringUtils.isNotBlank(balance)) {
                lastAccount = Integer.valueOf(balance) - r;
            }
            if (lastAccount < 0) {
                flag = 3;
                break;
            }
            Transaction tx = jedis.multi();
            tx.set("accountBalance", lastAccount + "");
            List<Object> result = tx.exec();
            if (result == null || result.isEmpty()) {
                jedis.unwatch();
            } else {
                System.out.println("恭喜您," + userId + "已经中标" + r + "元,标余额" + lastAccount + "元");
                RedisAPI.set(Thread.currentThread().getName(), r + "");
                RedisAPI.sadd("userIdSet", userId + "");
                flag = 1;
                break;
            }
        }u
        return flag;
    }

我们看到,代码中对

  while ("OK".equals(jedis.watch("accountBalance"))) {

加了乐观锁,并且也判断了

  // 判断是否购买过
                Boolean isBuy = RedisAPI.sismember("userIdSet", userId + "");

那么是否真的如作者所想的,保证了每个用户只能领一次么?答案显然是否定!因为servlet中线程是不安全的,在并发下

  Boolean isBuy = RedisAPI.sismember("userIdSet", userId + "");

这是有漏洞的!而且即使这里做了线程安全,如果放在分布式环境中,跨JVM的线程安全也有隐患;

解决方法有两个:1.是方法前加同步锁,当然这样的性能是最差的不建议;ab测试结果:
不用太关注测试值的绝对值,cpu1.8GHz硬件环境不是太好,哈,只用关注各个方案之间的对比

224421_gjLo_2565243.png

 2.对userid乐观锁,也做这样能保证一个用户只能领一次;ab测试结果:

224504_RuNy_2565243.png

 那么,贴了这两种方案,其实从性能来看是没达到预期的;第一种是最暴力的java同步jizhi,以牺牲性能为代价的;
第二种性能有了很大的提高;
于是转悠中,发现了另一种比较好方案:

利用redis + lua解决抢红包高并发的方案


为尊重原作者的劳动成果,我这里只做转摘,原文网址:http://blog.csdn.net/hengyunabc/article/details/19433779/

里面写的相当的详细了,对于并发安全,操作原子性以及性能做相当细的分析;
正是基本lua脚本方式,以及redis服务端的方式形式,对类似于抢红包这样的高并发性能又了很显著的提升,原文作者是在java里面调用redis,我将其中代码部署在TOMCAT下,nginx做了反向代理;ab测试结果如下:


230153_l08r_2565243.png

1000RPS,哈哈,终于有了质的飞跃,接近了预期;

那么如果不放在tomcat下,而直接放到nginx下呢结果又会怎么样?我们都知道tomcat作为WEB容器性能是有瓶颈的,我pc试过放个最简单的动态页面性能也才1K多;

我们都知道openresty将nginx所用到模块做了很好的整合,异步非阻塞会大大提高服务端的并发性能:

230949_RB1Q_2565243.png

这里不对openresty环境如何搭建做进一步的展kai了;这里只贴下lua脚本,

local math = require("math")
userid=math.random(0, 10000000)
--连接redis  
local redis = require "resty.redis"  
cjson = require "cjson"
local cache= redis.new()  
local ok , err = cache.connect(cache,"127.0.0.1","6379")  
cache:set_timeout(60000)  

local sha1, err = cache:script("load",  "if redis.call('hexists',KEYS[3], KEYS[4]) ~= 0 then return nil    else local hongBao = redis.call('rpop', KEYS[1]) if hongBao then    local x = cjson.decode(hongBao)    local food=redis.call('get','accountBalance') food=food-1 redis.call('set','accountBalance',food) x['userId'] =KEYS[4]    local re = cjson.encode(x)  redis.call('hset',KEYS[3], KEYS[4], KEYS[4])   redis.call('lpush',KEYS[2], re)   return  re end end return nil");
if not sha1 then  
    ngx.say("load script error : ", err)  
         return close_redis(cache)  
   end  
ngx.say("sha1 : ", userid, "<br/>")  
local resp, err = cache:evalsha(sha1,4, "hongBaoList", "hongBaoConsumedList", "hongBaoConsumedMap",userid);

然后再用AB做下压力测试:

231501_esXJ_2565243.jpg

2.4K了是 刚才的1倍多,而且细心的同学会发现,我前面两种方案的AB压的是200个客户端;

那么我们来看下后台数据是否一致:

231737_40Ek_2565243.png

说明操作还是原子性的,因为放到了redis的服务端操作了;


总结:
OpenResty 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主kai发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web kai发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单ji并发连接的高性能 Web 应用系统。

有兴趣的同学可以百度openresty获取相关资料,对本文感兴趣的也可以私信我要代码;

 最后吐槽:第一次在oschina上写博客,编辑器的敏感词检查实在不敢恭维啊!!!

234057_op4R_2565243.png

234057_m90e_2565243.png

愣是找不到包含这样的敏感词,后来才发现 JI器、KAI发者,这些都当成了敏感词了,一片博客修改了半天,才发出去,希望官方能有所改进!!






转载于:https://my.oschina.net/dongzhecao/blog/599912

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值