互联网系统应用架构
负载均衡器
当正常的请求通过防火墙之后,最先到达的就是负载均衡器
功能
决定这个请求是否到达web服务器,比如使用nginx可以配置哪些是无效的请求,还有就是可以屏蔽一些作弊的ip
负载均衡算法,可以根据不同的需要,把请求分发到不同的web服务器,这样单台web服务器的压力就会减小
限流,请求量太大,我们不加以限制的时候,假如这些请求冲垮了一台机器,在我们整体承受能力变弱的情况下,仍然要处理这些请求,无疑会造成其他服务器宕机,而后雪崩。所以,我们在请求过多的时候,经常看到系统繁忙的提示
通过,我们还会通过c语言或者Lua脚本对请求进行判断,无效请求的话,直接剔除
有效请求和无效请求
利用脚步刷新网页,访问接口,或者用脚步抢票,这种都要按无效的请求来处理,我们不能让这种请求影响到用户的访问。如果我们能直接把这些恶意请求拦截下来,那么我的性能就会从源头上提高,那么我们怎么拦截这些请求呢
无效请求
验证码
验证码技术可以减慢这种请求的速度,甚至复杂的验证码让这些脚本无法正常工作,这就是过淘宝的验证码值那么多钱的原因,我们不要在前几次请求就加验证码,因为我们会拦住正常人,一般是重复的请求超过几次就给验证码拦截一下,如果通过的话,那么就继续无验证码几次,但是有的时候,我验证码都出来了,你还在发请求,我觉得你这明显就是脚本在操作了。同理,短信也属于验证码的一种。
其实,这些技术是不应该放在web服务器中实现的,应该放在负载均衡器上完成,所以我们负载均衡器就要是保证每一条发过来的请求都是正常用户的访问,一般而言,我们会把验证码放到redis中,然后就是通过c语言或者lua脚步来判断,要注意,这里要包括验证码和单个用户请求量综合判断,也就是说,用户量在这里就要进行一定的限制。
身份证或者手机号
注意一点,如果你是用户,怎么作弊?我直接开多个小号,怎么办?我直接不让你开,正常人谁会开小号,所以,我要通过手机号码去卡你,甚至安全级别高的,我要通过身份证,通过人脸识别,你不可能为了开几个小号找一堆人吧?而且后期如果通过身份证或者手机号码实名追踪的话,对你的影响是很不利的。
僵尸账号
万一,我还真就用了多个人的账号呢,然后作为黄牛的我,再把这些票高价买给别人。怎么处理,当然是根据特点处理,僵尸号的特点就是只买高需求的票,普通的票都不买,平时也不登陆,乘坐火车当天也不看看自己的火车是什么启动。反正就是不符合正常用户的习惯,这个时候,分析起来就比较难了
系统设计
业务划分
有些业务模块,牛马不相关,或者关联的情况很少,我们可以把这些模块分开。而且,有的时候,不得不说,没有办法只能拆分了,因为一个系统代码量太大了,如果我们要整体更换一个服务太不稳定了,不能说,我订单的业务升级,结果购买的业务也要下线,准备一起升级。所以拆分开来,让一个一个小组来实现功能的话,决定是没有办法中的最好的办法。从工程学的角度考虑,比如制造一辆汽车,开始简单的时候,一个工厂生产,现在复杂了,车载电脑一家公司很难实现,就让别的公司做,最后在组装在一起,这样拆开有问题的吗?很明显,运费(RPC)的成本上来了,而且得互相直接制定好规约(接口,DDD)。
有一点注意的是,实际过程中的开发不是只有你一个人的,而是好多个人一起写的,所以业务拆分对于你来说可能不爽,但是一但拆分开,你负责的模块就简单了,而且有的时候,你开发的软件非常大型,肯定也是需要拆分的,不然会很乱。我们要做的就是利用对外的RPC接口,利用别人提供的RPC接口。这种情况,就是叫做,水平拆分,比如把一个好几个字段的数据表拆分成好几个表,就是水平拆分。
书竟然说集群是垂直拆分,不过从类比数据库的角度来看确实如此。不过通常来讲,大型互联网公司的架构都是集群+RPC
数据库设计
分表
本来可以储存在一张表的数据储存在多张表里面
水平分表
比如用户表,有些字段是常用的,比如用户名,密码,姓名然后有些字段是不常用的,比如我自己定义的找回密码的问题,这些数据的特点就是不经常被查询到,还有就比如文章,文章有很多信息,但是一篇文章存储的数据量可能会非常大,我们可以把标题,作者名称,点赞数存在一个表里面,用来显示的文章前半部分(参考****),然后具体的文章存在一个表。
垂直分表
比如交易的表我们分成transaction_2019,transaction_2020,transaction_2021,因为以前的交易数据一般没有人看,而且有时候,写入的数据量太大,一张表承受不过来,我们可以用垂直分表,比如我们的雪花算法,所谓分布式唯一ID生成器
分库
把数据存到不同的数据库里面,然后通过redis来决定从哪一个库里面查找。而且还有一个活跃用户的概念,比如我们统计了上个月的访问量,把那些活跃的用户平均的分配到不同的数据库里面,这样就不会导致一个数据库被查询的太多。
不得不说,把这些弄出来的话,一个是我感觉会很难,而且还要保证不出错,后端的复杂度在这个时候才体现出来。
而且,你也应该想到了,这种情况下,事务怎么处理,事务的一致性怎么处理
动静分离
说的直观一点,就是前后端分离,而且可以用CDN技术,内容分发网络,从最近的节点找静态资源,然后给用户,而且可以用cookie等技术让客户端缓存起来,不要每次都访问我的静态资源,而且现在企业的开发框架都比较接近,比如vue,element ui,那么,你从一个网站下载之后,就不要从这个网站再访问一次了,比较你地址都一样,访问的东西怎么可能不一样,让浏览器多做一些处理,这些处理对服务器是没有性能影响的,这就是端计算,而且这样的计算结果是不可信的,如果这些仅仅是暂时给用户的,那么完全无所谓。但是,一般而言,一般不会向服务器发送太过复杂的内容。
所以,你不要在用自己的服务器处理静态资源了,至少,不要再用spring来实现静态文件的转发,直接用nginx来实现,而且最好,你能用阿里的oss来处理文件,包括上传的文件。其实比自己做的话,经济成本要低很多。
锁和高并发
就是相当于并发的请求想要同时处理相同的数据,我们在平时开发的时候,也要注意这些内容,一个接口,重复访问会怎么样,同时访问会怎么样?你要把这些特殊的情况都处理掉,安全性比什么都重要,然后是性能,我在付钱的时候,慢,我可以接受,但是要多扣我的钱,那肯定是绝对不行的。
实际业务
悲观锁
select … for update 如果select用主键查询,那么就只会加行锁,如果不是主键,就得看是不是给全表加锁了,然后其他所有与这个有关的SQL都会被停住,包括查询的阻塞,但结果就是性能下降了不少,因为其他线程都被挂起了,所以悲观锁,会造成大量线程的挂起和恢复(抢夺),悲观锁,给一个好理解的名字,就是独占锁
乐观锁
乐观锁不会阻塞其他线程并发,非阻塞锁,CAS,ABA问题,说一下补充的地方,就是ABA是利用业务逻辑字段判断才导致的,所以我们想了一个业务逻辑无关的字段,version,强制让他的修改不回退,ABA问题不就是回退导致的吗?
看一下大体的逻辑,首先是查询,查的时候,版本号就出来了,改的时候也要有查出来的version,这样就没有问题了,但为什么我总觉得有点问题,哈哈哈,对的,确实是有问题,有的红包明明我先抢的,却因为中途的问题,失败了,而我后来那个人却抢到了
乐观锁重入
为了解决上面的问题,那就让乐观锁重新进入,比如我们在100ms内,会不断的抢红包,直到抢到,或抢关,或者到时间退出。有的则是按次数来计算的,没有太大的差别。
redis
这个比用数据库快了5-6倍,确实非常让人兴奋,但是这个需要lua脚本的基础,我这里说一下思路,大概就是用内存
来判断和记录抢红包的情况,最后再统一保存到数据库里面,这个肯定是异步的,说不定用的是mq,所以并发和redis还是需要深入学习的