前言:我们在做系统开发的时候,特别要清楚我们要做的是什么东西,我们想要的是什么,我们将来应该怎么做,这些都是我们开发的时候非常重要的事情,如果不能更好的了解这个,对于后期的开发会带来巨大的不确定性。当然这些很多属于业务和老板的想法决定。我们介绍来说的是软件开发中的问题。
一:设计思路
1:要有高内聚低耦合的设计思想。
在软件开发的过程中,我们遇到的大部分都是快速迭代模型的软件开发。因此我们本着高内聚,低耦合的设计原则。
2:单一责任原则(The single responsibility Principle)SRP
即是:修改一个类的原因应该只是一个。
换句话说就是让一个类只负责一件事,当这个类需要做过多事情的时候,就需要分解这个类。
如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱这个类完成其他职责的能力。
3:开放封闭原则
即是:类应该对扩展开放,对修改关闭。
扩展就是添加新功能的意思,因此该原则需要在添加新功能时不需要修改代码
符合开闭原则最典型的设计模式是装饰者模式,它可以动态的将责任附加到对象上,而不用去修改类的代码
4:里氏替换原则
即是:子类对象必须能够替换掉所有对象父类。
继承是一种IS-A关系。子类需要能够当成父类来使用。并且需要比父类更特殊。
如果不满足这个原则,那么各个子类的行为上就会有很大的差异,增加继承体系的复杂度。
5:接口分离原则。
即是:不应该强迫客户依赖它们不用的方法。
因此使用多个专门的接口比使用单一的总接口要好。
6:依赖倒置原则。
即是:高层模块不应该依赖于底层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。
高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于底层模块,那么底层模块的改动就会直接影响到高层模块。从而迫使高层模块也需要改动。
依赖于抽象意味着:
任何变量都不应该持有一个指向具体类的指针或者引用。
任何类都不应该从具体类派生。
任何方法都不应该覆写它的任何基类中的已经实现的方法。
二:秒杀系统设计
1:场景
我们现场要卖100件单一商品,然后我们根据以往这样秒杀活动的数据经验来看,目测来抢这100件纸尿裤的人足足有10万人。如果在同一时间内,高达10w的点击率,普通的服务完全受不了。
2:问题。
(1):高并发
秒杀特点就是时间短,瞬间用户量大,
大量的请求进来,我们需要考虑的点就很多了,缓存雪崩,缓存击穿,缓存穿透等等。
(2):超卖
如果没处理好库存问题,当商品只有100件,但是你却卖出去200件,这个问题怎么处理,发货还是不发货呢。
(3):恶意请求
我知道什么时候抢,我搞个几十台机器搞点脚本,我也模拟出来十几万个人左右的请求,那我是不是意味着我基本上有80%的成功率了。
要知道机器触发的速度完全超过人的速度。
(4):秒杀链接暴露
我们通常是点击按钮触发请求,这样懂行的用开发者模式就能看到请求,这样就可以模拟请求。
(5):数据库极限
每秒上万甚至十几万的QPS(每秒请求数)直接打到数据库,基本上都要把库打挂掉,而且你服务不单单是做秒杀的还涉及其他的业务,你没做降级、限流、熔断啥的,别的一起挂,小公司的话可能全站崩溃404。
3:解决问题。
(1):单一原则
我们在说设计思路的时候,有一个单一原则。这样可以解决很多问题。
大家都知道现在设计都是微服务的设计思想,然后再用分布式的部署方式
也就是我们下单是有个订单服务,用户登录管理等有个用户服务等等,那秒杀也开个服务,我们把秒杀的代码业务逻辑放一起。单独给他建立一个数据库,现在的互联 架构部署都是分库的,一样的就是订单服务对应订单库,秒杀我们也给他建立自己的秒杀库。至于表就看大家怎么设计了,该设置索引的地方还是要设置索引的,建完后记得用explain看看SQL的执行计划。(不了解的小伙伴也没事,MySQL章节我会说的)单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务挂了,也不会影响到其他的服务。(强行高可用)
(2):秒杀链接加盐
我们上面说了链接要是提前暴露出去可能有人直接访问url就提前秒杀了,那又有小伙伴要说了我做个时间的校验就好了呀,那我告诉你,知道链接的地址比起页面人工点击的还是有很大优势。我知道url了,那我通过程序不断获取最新的北京时间,可以达到毫秒级别的,我就在00毫秒的时候请求,我敢说绝对比你人工点的成功率大太多了,而且我可以一毫秒发送N次请求,搞不好你卖100个产品我全拿了。
那这种情况怎么避免/strong>
简单,把URL动态化,就连写代码的人都不知道,你就通过MD5之类的加密算法加密随机的字符串去做url,然后通过前端代码获取url后台校验才能通过。
(3):Redis集群化。
单个Redis是有极限的通常能处理4-6w的数据,但是我们的数据量过大的时候,我们就无法处理了。这个时候需要对Redis进行集群化处理。Redis集群,主从同步、读写分离,我们还搞点哨兵,开启持久化等等!
(4):Nginx的负载均衡。
Nginx是高性能的web服务,并发也随便顶几万不是梦,但是我们的Tomcat只能顶几百的并发呀,那简单呀负载均衡嘛,一台服务几百,那就多搞点,在秒杀的时候多租点流量机。
恶意请求拦截也需要用到它,一般单个用户请求次数太夸张,不像人为的请求在 关那一层就得拦截掉了,不然请求多了他抢不抢得到是一回事,服务器压力上去了,可能占用 络带宽或者把服务器打崩、缓存击穿等等。
(5):资源静态化:
秒杀一般都是特定的商品还有页面模板,现在一般都是前后端分离的,所以页面一般都是不会经过后端的,但是前端也要自己的服务器啊,那就把能提前放入cdn服务器的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。
(6):按钮控制:
大家有没有发现没到秒杀前,一般按钮都是置灰的,只有时间到了,才能点击。
这是因为怕大家在时间快到的最后几秒秒疯狂请求服务器,然后还没到秒杀的时候基本上服务器就挂了。
这个时候就需要前端的配合,定时去请求你的后端服务器,获取最新的北京时间,到时间点再给按钮可用状态。
按钮可以点击之后也得给他置灰几秒,不然他一样在开始之后一直点的。你敢说你们秒杀的时候不是这样的/strong>
(7):限流:
限流这里我觉得应该分为前端限流和后端限流。
前端限流:这个很简单,一般秒杀不会让你一直点的,一般都是点击一下或者两下然后几秒之后才可以继续点击,这也是保护服务器的一种手段。
后端限流:秒杀的时候肯定是涉及到后续的订单生成和支付等操作,但是都只是成功的幸运儿才会走到那一步,那一旦100个产品卖光了,return了一个false,前端直接秒杀结束,然后你后端也关闭后续无效请求的介入了。
(8):库存预热
秒杀的本质,就是对库存的抢夺,每个秒杀的用户来你都去数据库查询库存校验库存,然后扣减库存。
我们都知道数据库顶不住大量的请求,但是他的兄弟非关系型的数据库Redis能顶的住!
我们要开始秒杀前你通过定时任务或者运维同学提前把商品的库存加载到Redis中去,让整个流程都在Redis里面去做,然后等秒杀介绍了,再异步的去修改库存就好了。
但是用了Redis就有一个问题了,我们上面说了我们采用主从,就是我们会去读取库存然后再判断然后有库存才去减库存,正常情况没问题,但是高并发的情况问题就很大了。
但是我们还有个问题,解决超卖的问题。
Lua脚本是类似Redis事务,有一定的原子性,不会被其他命令插队,可以完成一些Redis事务性的操作。这点是关键。
知道原理了,我们就写一个脚本把判断库存扣减库存的操作都写在一个脚本丢给Redis去做,那到0了后面的都Return False了是吧,一个失败了你修改一个开关,直接挡住所有的请求,然后再做后面的事情嘛。
(9):削峰填谷
你可以把它放消息队列,然后一点点消费去改库存就好了嘛,不过单个商品其实一次修改就够了,我这里说的是某个点多个商品一起秒杀的场景,像极了双十一零点。
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览92349 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!