Redis series - Talking spike system

Foreword

  Before designing a system, we must first be sure to confirm what kind of systems business scene, following on the spike activity on a business platform for the scene, together to explore how to change a spike system design.

Scenes

  We now sell 100 disposable diapers , according to the user's system and the amount of past experience, such a visual spike activity will rob 10 million people participated in this 100 diapers.

 

 

  10 million people, which withstand the server where ah! If you hit the DB you certainly had to hang ah. But we do something thing. Here a good analysis, the system may spike any problems .

common problem

High concurrency:

  High concurrency it is beyond doubt, and spike the moment of the beginning of so many users access the system, certainly there will be high concurrency problems.

  Yes, this spike is characterized by a very short time, large users instantly .

  Normal store marketing is a very low price, coupled with precise push SMS, APP and other platforms, attracting a large number of users to participate in this spike activity, so much the amount of users, merchants definitely cool to heaven, and we just hard to force. . .

  We all know that the second injury promotional activities in place, the price is attractive, then the flow of hundreds of thousands is not a problem, but stand-alone Redis  generally 3-4 million for QPS (requests per second) or can withstand, and but no matter how high the way, and that just put forward a hot commodity this data spike 3-4 million more than the number of all.

  The influx of a large number of requests, the question we need to consider more than just parity, cache avalanche breakdown cache, the cache penetrate these are likely to occur, the emergence of these problems directly hit the DB, DB certainly could not carry so much traffic spike that this activity can be considered cold. . .

Stock oversold:

  但凡是秒杀,都怕库存超卖,我们这里只是卖尿不湿,顶多也就不赚钱而已,要是换成100台华为Mate30Pro,商家预算卖100台可以赚点零花钱,还能造势,结果写错程序多卖出去了200台,不发货用户会投诉你、平台也会关你店,你发货就裤衩都赔进去了,这可不是杀两个程序猿祭天就能解决的事。

 

 

 

 

 

  秒杀本来就是以低价吸引顾客来参与的,基本上都是不赚钱的,超卖就很恐怖了,所以库存超卖也是很关键的一个点。

恶意请求:

  秒杀这么低的价格,如果我抢到了,我转手一卖大保健的钱又有了。。。就算我不卖那我也不亏,用户知道,我们能知道,那些有心人(黑客、黄牛。。)肯定也知道的。

  这就简单了,我知道你什么时候活动开始(公开的),我搞几台服务器,再搞几个脚本,我也模拟出来十几万个人左右的请求,这是不是意味着80%的单子都来我这里了?

  真是情况可能远远不止,因为机器请求的速度比人的手速往往快很多很多,每年过年抢票我们我们抢不过黄牛也是有一定道理的(好像拖一头黄牛宰了祭天!!!)。。。

链接暴露:

  前面几个问题我们都比较好理解,但是这个链接暴露估计就有不少的兄die会比较迷惑了。

 

 

   相信我们对这个画面并不陌生,稍微懂点的都知道打开开发者模式,然后看看网页的代码,有的网页就写有url。对于一些查看不到源码的,我们可以点击一下查看对应的请求地址啊,不过好像这个可以对按钮在秒杀前置灰。

  不管怎么样子都是有危险的,姑且算你外面的所有东西你都能挡住了,如果你卖的这个东西实在是便宜,机具诱惑力,你能保证开发不动心?开发知道地址,在秒杀的时候自己提前请求。。。(开发:为什么TM又是我???)

 

 

 数据库:

  每秒上万甚至十几万的QPS(每秒请求数)直接打在数据库上,基本上都能把数据库打挂,而且你服务器不单单是做秒杀的,还涉及到其他的业务,你没有做降级、限流、熔断等处理,直接挂一片,小公司的话可能是全站崩溃404

  反正不管你秒杀怎么挂,别把我其他的业务整挂就行了。要真挂一片了,这就不是随便杀一两个程序猿就能糊弄过去的事了。

  程序猿:我。。。我TM挖谁祖坟了吗???

问题是讲出来了,但你别把问题丢给大佬呀,大佬要的是设计,你得给大佬讲清楚怎么设计,怎么解决这些问题呀。

  设疑一个能扛住高并发的系统,我觉得还是得单一职责

  什么意思呢?我们都知道现在设计都是微服务的设计思想,然后再用分布式的部署方式。

  也就是我们下单是有个订单服务,用户登录管理有用户服务等。既然这样,我们为何不能给秒杀也开一个服务呢?

  我们把秒杀的代码业务逻辑放一起。单独给他建立一个数据库,现在的互联网架构部署都是分库的,一样的就是订单服务对应订单库,秒杀我们也给他建立自己的秒杀库。

  至于表怎么设计,就看大家的设计习惯了,该设置索引的地方还是要设置索引的,建完后记得用 explain 看看 SQL 的执行计划。

  单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务器挂了,也不会影响到其他的服务。(勉强算是高可用吧)

秒杀链接加盐:

  我们上面提到了链接要是提前暴露出去就有可能被人直接访问 url 就提前秒杀了。这位兄die就优化说了:我做个时间的校验就好了。那我告诉你,知道链接地址比起页面人工点击的还是有很大优势的。

  我知道 url 了,那我可以通过程序不断地获取北京的最新时间,可以达到毫秒级别的,我就在00毫秒的时候请求,我敢说绝对比你人工点的成功率高很多,而且我可以一毫秒发送N次请求,一个不小心100个产品都是我的了。

 

 

   这种情况要怎么避免呢?

  这就比较简单了,把 url 动态化,就连写代码的人都不知道,你就通过MD5之类的加密算法,随机加密的字符串去做 url,然后通过前端代码获取 url 后台校验才能通过。

  下面放一个简单的 url 加密给各位参考一下。

 
 
/**
* url md5加密
* @param url
* @return url
*/
public static String getURLsign(String url) {
    try {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.update((url).getBytes("UTF-8"));
        byte[] b = md5.digest();

        int i ;
        StringBuffer buf = new StringBuffer();
        for (int offset = 0; offset < b.length; offset++) {
            i = b[offset];
            if (i < 0) {
                i += 256;
            }
            if (i < 16) {
                buf.append("0");
            }
            buf.append(Integer.toHexString(i));
        }
        url = buf.toString();
        System.out.println("result = " + url);

    } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
        logger.err("error:" ,e);
    }
    return url;
}

Redis集群:

  之前不是说了单机的Redis顶不住嘛,那简单啊,一个不行就找两个,两个不行就整三个嘛,多整几个兄弟一起扛(春哥:肆胸弟就来砍我。。。)。

  秒杀本来就是读多写少,这就需要用到我们提到过的 Redis集群、主从同步、读写分离了,我们还搞点哨兵,开启持久化直接无敌高可用了!

 

 

 Nginx:

  Nginx 相比大家都不陌生了,这东西是高性能的web服务器,并发也随便顶个几万不是梦,但是我们 Tomcat 只能订几百的并发呀。

  那简单呀,负载均衡嘛,一台服务几百,那就多搞几台就好了嘛(这是几台能解决的事?),在秒杀的时候多租点流量机

  Tip:据网传,我国内某大厂就是在去年春节活动期间,租光了亚洲所有的服务器,小公司也很喜欢在双十一期间买流量来顶住压力。

 

 

 

   这样一对比,是不是觉得你的集群能顶半边天了。

  恶意请求拦截也需要用到它,一般单个用户请求次数太夸张,不像人为的请求在网关那一层就得拦截掉,不然请求多了他抢不抢得到是一回事,服务器压力上去了,可能占用网络宽带或者把服务器打崩、缓存击穿等。

资源静态化:

  秒杀一般都是特定的商品和特定的页面模板,现在一般都是前后端分离的,所以页面一般都是不会经过后端的,但是前端也要自己的服务器啊,那就把能提前放入 cdn服务器 的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。

按钮控制:

  大家有没有发现没到秒杀前,一般按钮都是置灰的,只有时间到了,才能点击。

  这是因为怕用户在时间快到的最后几秒疯狂点击、请求服务器,然后一个不小心,秒杀还没开始基本上服务器就挂了。

  这个时候就需要前端的配合,定时去请求你的后端服务器,获取最新北京时间,到时间点再给按钮置成可用状态。

  按钮可以点击之后也得给他置灰几秒,不然用户一样在开始之后一直点。

限流:

  限流这点,可以分为前端限流后端限流

前端限流

  这个比较简单,一版秒杀不会让你一直点的,一般都是点击一下或者两下,然后几秒之后才可以继续点击,这也是保护服务器的一种手段。

后端限流:

  秒杀的时候肯定涉及到后续的订单生成和支付等操作,但是都只是成功的幸运儿才会走到那一步,那一旦100个产品卖完了,return 了一个 false,前端直接秒杀结束,然后你后端也关闭后续无效请求的接入了。

  Tip:真正的限流还会有限流组件的加入,例如:阿里的Sentinel、Hystrix等。这就就不展开,就说一下物理的限流。

库存预热:

  秒杀的本质,就是对库存的抢夺,每个秒杀的用户来,你都去查一下数据库校验库存,然后扣减库存,撇开性能因素,你不觉得这样很繁琐,对业务开发人员都不友好,而且数据库顶不住啊。

 那怎么办呢?

  我们都知道数据库顶不住,但是他的兄弟(非关系型数据库Redis)能顶啊!

  这就好说了,我们在开始秒杀前,通过定时任务或者运维同学提前把商品的库存加载都Redis中去,让整个流程都在Redis里面去做,然后等秒杀结束了,再异步的去修改库存就好了。

  但是用了Redis就有一个问题了,我们上面说了要采用主从,就是我们会去读取库存,然后再判断,之后有库存再去减库存,正常情况下是很OK的,但是高并发的情况下问题就很大了。

  现在库存只剩下1个了,高并发嘛,假设4个服务器一起查询了,发现都是还有1个,那大家都觉得是自己抢到了,就都去扣库存,结果就变成 -3。是的,有一个是真的抢到了,但别的都超卖了。咋整?赔钱咯。。。

 

 

Lua:

  不要慌,赔几个钱而已,问题不大。。。下面说个神器,连几个钱也不用赔。

   Lua 脚本功能是 Redis 在2.6版本的最大亮点,通过内嵌对 Lua 环境的支持,Reids 解决了长久以来不能高效地处理 CAS(check-and-set)命令的缺点,并且可以通过组合使用多个命令,轻松实现以前很难实现或者不能高效实现的模式。

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

  知道了原理,我们就写一个脚本把判断库存和扣减库存的操作都写在一个脚本,丢给 Redis 去做,到0后面的都 return false 了,一个失败了你修改一个开关,直接挡住了所有的请求,然后再做后面的事情嘛。

 限流&降级&熔断&隔离:

  这个为啥要做呢?不怕一万就怕万一,万一真顶不住了,限流,顶不住就挡一部分出去,但是不能说不行;降级,降级了还是被打挂了,熔断,至少不要影响别的系统,隔离,你本身就是独立的,但是你会调用其他系统嘛,你快不行了你也不能拖累其他兄弟呀。

削峰填谷:

  一说到这个词,很多兄die就知道了,对的,MQ,你买东西少了,你直接100个请求改库我觉得也没啥问题,但是万一秒杀一万个呢?服务器挂了,又要杀程序猿祭天了。。。

  Tip:可能有的兄die说,我们业务量达不到这个量级,没必要。但是我想说我们写代码,就不应该写出有逻辑漏洞的代码,至少以后公司体量上去了,代码居然不用改。小伙子这手代码写得真漂亮!

总结:

  到这里,我觉得基本上把该考虑的店,还有对应的解决方案都说了一下,也许还有没有考虑到的,欢迎补充哈。

  最后放个完整的流程图出来吧。

 

 

 最后:

  鸣谢博主:敖丙,本文参考公众号:JavaFamily

  附上传送门

Guess you like

Origin www.cnblogs.com/qiuhaitang/p/11997172.html