后端风云

​ 本文简单概况了一下刘欣老师的《码农翻身》的后端风云章,这本书强烈推荐给大家,每看一遍都有不同的感觉,通俗易懂且知识面大而全。居家旅行,必备良药!!!

早期初级阶段:

image-20220820220959059

​ 在我们早期学习 javaWeb阶段,将订单模块儿,购物车模块儿,支付模块儿都写在了一个项目中,并访问一个Mysql数据库,这在自己练手项目中是没问题的,但如果系统上线,有大量的用户同时访问该系统,Tomcat服务器首先需要考虑能不能支撑住大的并发量(默认150,当超过250时,就应该考虑的服务器的集群),其次是Mysql数据库,在高并发两三千也就差不多了,要知道Mysql数据库读取数据是很慢的操作,它本质是从硬盘中读取文件的。

优化点1:使用缓存

image-20220820120628253

可以看到我们给中间加了一个Redis中间件作为缓存,现在服务器需要获取数据,先到缓存中找数据,如果找不到再去Mysql中找数据,找到数据后再写回Redis中,这样下次请求数据可以直接在缓存中获取数据,那缓存的好处是啥,缓存是加载在内存中的,读取数据那肯定是快的多。这样就减轻Mysql数据库的压力。但是如果用户每次请求先去缓存找,如果都没找到,那请求还是落在了Mysql上,这个可能导致缓存击穿,缓存雪崩。

提问:如果数据库中数据修改了,但是缓存中还是之前的数据该怎么办?

为了保证数据的一致性下面的方案是否可行?

  1. 先更新数据库中,再更新缓存
  2. 先删除缓存,再更新数据库
  3. 先更新数据库,再删缓存
  4. 数据设置过期时间

这里参考文章:https://blog.csdn.net/diweikang/article/details/94406186

优化点2:多台缓存

之前我们在服务器与数据库中间加上一个缓存,来减轻数据库的压力,和提高数据读取速度,但如果只有一台缓存,后期也会因为内存用完而耗尽,缓存要是挂机了,又是数据库直面大量请求了,那我们多部署几个缓存服务器来分摊一下压力。

image-20220820131146797

当部署多个缓存服务器时,又出现问题了,我读取和存放数据的时候,我用哪个服务器啊,我们先不说具体用啥算法,我们先想想我们需要啥?

  1. 我存的地方和我找的地方得一致,不能我存在缓存1,结果去缓存3找,那肯定是没数据
  2. 我存放数据的均匀的落在这三个缓存上,不能说饿的饿死,撑得撑死。

余数算法

最简单:在找数据或者写数据时,先求Hash值,然后对Hash值进行取余,这样可以均匀的将请求落在三个缓存中。

image-20220820162701218

​ 缺点也很明显:之前我用三个缓存好好的,数据存储的也没问题,现在要再加一个缓存,那现在读数据和写数据取余可就变成4了,之前的数据可就都访问不到了,请求一下子又干到数据库了

image-20220820162729067

一致性Hash算法

一致性Hash算法不能完全的解决这个问题,但是可以做到大部分数据可以照常工作

image-20220820163202097

​ 比方说我们在这个圆上标注 0-2^32-1个点,三台redis服务器均匀的落在这个图上,当有数据要查找或插入时,还是先取Hash值,使落在这个圆的范围上,如果超出了那就取余。比方说这是插入的这个点落在了A到B之前的位置,那就让它顺时针找它最近的那个服务器点,数据存放在那里,这时候就找到了B,如果是落在了C到A的范围,那还是顺时针走,走到A的位置。

​ 假如说突然又添加了一个服务器D,假如插在了B和C之间。

image-20220820163842694

​ 那么现在要查询数据时,点位落在了B到C之前,原来是到C去读取数据,现在就看是不是在B和D之间了,如果在这之间那就去D找,如果落在了D到C之间,那还是去C找。这时候就发现到D找的数据肯定是找不到的,但是其他区域的数据都不会被影响到啊,这样就避免了全部缓存穿透导致的缓存雪崩。

这里的一个前提条件就是服务器经过Hash计算后需要均匀的落在这个区间内,否则就会有部分的缓存负责过高,有的负载过低,也就是数据倾斜

​ 比方说我们个人开发的小项目,就用两个缓存服务器,那这么大的一个圆就放两个点,可能导致数据都集中到了一个服务器上,导致压力过大

解决这个问题的办法有:虚拟服务器(虚拟节点映射)

image-20220820165011054

就是将一个真实服务器看做成多态虚拟的服务器,均匀的分布到圆上。

保证数据均匀的存放在真实的缓存服务器上

Hash槽算法

槽可以理解为分区,数据都是存放在分区中的,然后分区与机器进行动态绑定。

image-20220820200524038

​ 比方说现在要存储(Key,Value),先对Key求Hash值,然后对16384求余数,看到余数落在那个槽位,就放到哪个服务器。是不是感觉第三种算法和余数算法和一致性Hash算法都有点儿像,类似于一个综合的算法

​ 那假如我现在要新增一个节点怎么办,现在有三个服务器,假如再来一个服务器4,就从服务器1,服务器2,服务器3中各取出一部分槽和数据。

image-20220820201220590

​ 那相应的我请求请求的时候选择哪个节点,这里客户端可以向任意一个节点发送请求,例如:get(key1)这个请求我发送给了服务器1,但是数据是在服务器2,则服务器会将请求重定向到服务器2。这也叫做Redis Cluster

为什么redis集群不采用一致性哈希算法?

​ 一个直观的区别就是一致性Hash,无法很好的控制数据的分布,比方说我是有几个服务器,但是各个硬件条件并不一样,想让配置比较好的多放一些数据,配置差点儿的就少部署一些,可以做到精确控制。

优化点3:故障转移

​ 比方说我原先有三个缓存服务器,每个服务器负责一部分Hash槽,假如这时候一个服务器挂了,那这部分数据就算丢失了,这部分的请求也就又干到数据库了,现在我们利用Redis提供的 master-slave功能,就是一个大哥带几个小弟,如果大哥被干掉了,小弟中一个人就去当大哥管事儿。大哥与小弟之前的数据是同步的,以确保小弟可以顺利的继位

image-20220820205141078

优化点4:高可用的Tomcat

​ 在文章的最开始我们就说了Tomcat默认支持的并发量并不大,且如果它挂掉了,那完蛋了,服务直接用不了了,所以索性我们学习一下Redis,多部署几个Tomcat服务器,让请求均摊到各个Tomcat服务器之间,这样就可以有效的降压,如果有一个服务器挂了,其他的服务器还能继续干。

问题:Tomcat的Session信息是不能服务共享的,假如用户A在Tomcat服务器1上进行登陆了,并将登陆信息保存在Session中,现在用户又请求了,被轮训到了服务器2上,但是服务器2是没有该用户的Session信息的,这又得让用户去登陆

可以使用Redis缓存来处理。

image-20220820211730816

优化点5:使用Nginx

其实在之前我一直对Nginx都是懵逼状态,明明可以直接访问Tomcat,为啥要中间加一个Nginx。我对Nginx最直观的使用就是通过端口去访问服务器静态文件,去做个人博客,但现在按照我们优化点4所说的部署多个Tomcat服务器,那前端到底访问哪个Tomcat呢?总不能写死到前端代码里去轮询吧,如果我们在中间加一个Nginx去做代理,就会舒服的多。

image-20220820212437837

这里我就不说文绉绉的定义了,可以去官网上查

正向代理

image-20220820212745193

​ 这里说一个例子就行了,VPN翻墙,你想访问国外网址,但是电脑受国内管控,无法访问,那就需要一个代理,你请求代理,代理请求国外服务器,然后数据返回。

反向代理

image-20220820213214154

比方说我们现在上网搜索www.baidu.com,我们只知道这个域名,但实际上它可能只是一个代理,真实的请求是转发到内部的其他服务,这样也可以保护真实的服务器。和正向代理的区别是,正向代理是知道我要找谁,反向代理是我也不知道谁真实的服务。

负载均衡

就是将请求均匀的发送到各个服务器间

其中有轮训方式

image-20220820213520428

有权重方式

image-20220820213640770

动静分离

很直观的,我们可以通过Nginx直接访问静态文件。那Tomcat也可以访问静态文件啊,但是低,且一般Tomcat是作为业务逻辑服务器的,如果将动态资源和静态资源都放在Tomcat中,就会消耗额外的一部分性能,

image-20220820214118620

高可用的Nginx

​ 我们已经发现如果一条链路中如果某个地方只有一个工作的,那一旦它挂机了,后续工作都无法进行了。所以按照之前的设计我们再部署一个Nginx服务器,但是与Tomcat不同的是,Nginx是直面客户端的,所以即使是多个服务,对外也只能暴露一个Ip地址,从外面看就只有一个服务,通过 Keepalived将多个Nginx服务形成master-slave关系,一个挂了,另一个补上,那我们的结构图又得更新了。

image-20220820214735780

优化点6:数据库的读写分离

​ 我们看上面的系统架构图后发现似乎就剩数据库是孤家寡人了,但如果把数据多部署几个,如果每个都可以进行读写操作,那服务之间的数据一致性就很难再确定了,而对数据库的大部分操作都是读操作,写操作相比要少的多。而且我们都知道数据库的共享锁和写锁互斥,这也是影响效率的,如果能分开的话,速度方面,安全方面肯定更稳健。

image-20220820215503380

假如有一天master挂掉了,那在剩余的slave中选一个,是不是就发现,多服务部署的思想很多都是一样的。就是为了保证高可用性。

但是Tomcat可就犯难了,难倒我每次请求前都判断一下是读操作还是写操作,再根据不同的类型,发送不同的数据库吗?

image-20220820215954215

使用MySQL Proxy

发挥我们抽象的大脑,只要有这种不属于我们应该管的事,我们就把活儿抛出去,我们再增加一个抽象层。将这些脏活累活统一交给Mysql Proxy去管理

image-20220820220324413

小总结

我们的系统已经可见性的复杂的多了,但为了保证服务不会突然挂掉了,这些也是必须的。至于后期还有更多的优化,例如ES,MQ,熔断降级,分布式注册中心,配合中心,等各种骚操作后面再不断加呗,一口吃不成胖子。

image-20220820220832414