优化与灾备-后段架构一些总结
什么是CDN静态数据优化
本质上就是减少流量直接流向后端,且CDN提供多种防护机制,ddos攻击之类的。
将内容分发到CDN服务器上,进行缓存(网页,视频,图片,文件之类的),当用户请求则DNS重定向到引导到物理距离较近的边缘节点上。
如果缓存有则直接返回,没有则再向源服务器请求并缓存内容。
前端频率拦截
设置请求频率,比如5秒才能发送一次请求,随后置灰按钮。(客户端侧)
但是有人直接访问接口,调用api,染过频率检查,则只有在进行频率检查才会产生一个参数,访问服务器带上该参数。否则视作非法请求(服务端)
缓存层+预热数据
如果直接读取数据库,则数据库承载巨大流量不可取。查看数据库前,先查找缓存实例,如果缓存找不到了,才去数据库进行查找。
但是引入缓存层,通常会有几个问题。
1 缓存击穿:
说的是某个热门数据失效过期后,突然大量的请求穿过缓存层直接到数据库,压力剧增。
解决方式:
设置互斥锁,缓存失效后,只能一个线程访问数据库,
不然则是延长热点数据的过期时间,
不然进行双重检查锁,缓存失效前,提前异步更新缓存。
2 缓存穿透:
查询本就不存在缓存和数据库的值,造成每次访问都穿透到数据库层。
解决方式:
缓存空指,对于本来就不存在的数据,缓存一个空结果。
使用布隆过滤器,查询缓存前,先检查数据存在与否?
3 缓存雪崩:
同一时间大量的缓存失效,导致请求直接打到数据库上。
解决方式:
为过期时间随机增加偏移量,保证不是大量缓存同时过期。
提前更新缓存。
4 缓存预热:
系统运行前,提前将一些热点数据加载到缓存里面。避免冷启动带来的高延迟。
解决方式:
手动脚本加载已知的热点数据
根据历史访问记录,自动加载热点数据
MQ异步处理
一般业务流程,都是有不同阶段的,不同阶段通过异步消息队列进行解耦。
比如下单后,即可往消息队列里投放一个消息,在由订单系统消费该消息。
限流&熔断&降级
限流就是设置流量阈值,超过的直接拒绝请求
熔断则是,请求错误的次数超过阈值,则不再用到后端服务,直接返回失败,但是隔一段时间放请求去重试后端。
降级则是服务失败后,直接返回默认指定的信息。
业务侧优化
12306一开始只有很短的时间前才能进行抢购,每次都卡死。
后面业务侧,直接放长购票周期为20天,压力下降n倍
风控管理
验证码,app指纹,之类的,谷歌识图
系统的可用性
三大原则,高性能,高可用,易拓展。高性能意味着,1秒处理百万请求,respone时间3ms内。
易拓展意味着在迭代新功能的时候,还是扩容系统的时候,改动的代价非常小。
高可用,则需要一些计算指标去衡量:
1 平均故障间隔时间MTBF,即两次故障的间隔时间,也就是系统运行正常的时间,越长越好。
2 故障恢复时间MTTR,系统故障后发生回复的时间,越小则使用者越不感知。
可用性=MTBF/(MTBF+MTTR)*100%
故障的原因很多: 硬件层面(CPU,内存,磁盘,网卡等等)。软件层面(代码bug)。还有不可抗力(地震,水灾,火宅)
单机架构
服务,数据库,缓存等等都是单机部署的,有一个致命的缺点,在于一旦遭受意外,磁盘损坏,误删数据,等等,意味着Game Over。很容易想到:使用备份。
定期的吧数据cp到另一台服务器上,即使源服务器丢失数据,也可以备份回来原来的数据。但是存在一些问题。
问题1:恢复数据要时间,业务不可能中断等恢复数据。
问题2:定期备份的,数据不是最新的,可能是一两天前的数据。
主从架构(master-slave)
在另一台及其上,部署多一个数据库示例,让该示例成为源示例的副本,两者实时保持同步。
- 1 数据由于是实时同步的,所以slave实例的数据是新的。
- 2 抗故障能力具备了,主库有问题,随时切换到从库,继续服务。
- 3 读性能提升,流量多的时刻,业务可以被分流到从库去读取数据,分担主库的读压力。
同时当发展下去,会发现,业务层也可以部署多个服务器上,当这样的业务应用多了之后。
那为什么不做一个接入层,专门拿台机器使用nginx,做反向代理,负载均衡的工作呢?
当台业务机器死了后,接入层会让其他机器接管所有流量,持续服务。
PS:(多个机器的冗余提高了容错性)
但是即使这样,依旧不够,因为灾难可能是机柜级别,或者机房级别的,这类新闻非常多。
有可能整个机房都断电了,那还是死。
同城灾备
冷备,就是同一个城市但是不同机房,机房A的数据会定期备份到机房B,只有机房A断电了才启用机房B,但是依旧是数据不是新的。依旧依赖主从架构,机房A成为master,机房B成为slave,实时同步数据。
但是依旧不够,如果机房A不行了,还得做如下操作:
- 1 B机房的从库升级主库
- 2 B机房部署应用,启动服务
- 3 部署接入层,配置转发规则
- 4 DNS指向B机房,接入流量,业务恢复
所以还是得提前吧应用,接入层全部部署好在B机房,一旦变故,则只需要升级从库,DNS指向B机房即可,这种方式下来B机房已经完全随时可以上场的状态了,叫热备。
同城双活
但是构建了一个随时可以上场的机房,但是没有经过实践,难以确认它能够正常地帮忙抵御流量之洪,因为部署服务存在各种细节问题,版本管理,系统资源不足,参数不一,部署两个机房问题只会增加,不会减少。而且从成本的角度看,部署的机房开销巨大却不使用,很浪费。
所以idea变成
- 1 构建一个达到源机房水平的机房B
- 2 让B也接入流量分担压力
所以把B机房接入DNS层,让流量也进来B机房,但是B机房是A的从库,从库是不可写,B机房的写请求不可以写到本机房的存储上。B机房只能分担的是读操作(读操作比写操作的流量高)
所以由此衍生出了中间件,在应用层操作数据库时候,这一层中间件用来区分读写分离。
机房A允许读操作也允许写操作,机房B只允许读操作,不允许写操作,需要经有中间件去A机房执行写操作。
慢慢的,B机房承担一定比例的流量。而且一旦A机房挂了,可以马上切换到B机房。
这种方案称作,同城双活。
两地三中心
如果万一发生了严重自然灾害,某个城市的机房全军覆没,这也是经常有的事情。
所以两个机房的距离1000公里以上,称作异地。
比如说有城市A,和1000公里外的城市B,城市A弄了个同城双活,城市B则做了数据备灾。
伪异地双活
按照同城双活的方式,把主机房A部署A城市,从机房B部署B城市。虽然这样容错性比同城双活高了。但是带来一个棘手的问题,网络延迟。
试想一下,一个客户端请求到了B城市机房,B城市机房要去写A城市的机房存储,一次跨城市访问就达到了30ms, 是机房内部延迟(0.5ms)的60倍,如果请求的跳转次数,而且还来回跨城市访问,整体延迟可能到底秒的级别,惨不忍睹。
真异地双活
如果说不允许B机房去读写A机房的存储,就近读写的话,又违反了主从机房的约定。
所以到了需要在存储层改造的时刻了,这个时候应该些数据双向同步中间件。
让A,B机房都是主库本身,都拥有全量数据。除了数据双向同步之前,消息队列需要双向同步。
类似RabbitMQ, kafka的。
其实案例的Canal,RedisShake,MongoShake,很多公司也有自研的同步中间件。
于是在中间件的加持下,两地机房都是主库。
达到了以下的效果:
- A城机房写入X=1
- B城机房写入Y=2
数据中间件双向同步
A城如今有数据(X=1,Y=2),B城有了(X=1,Y=2)
但是迎来一些问题,如果
- A城写入X=1
- B城写入X=2
在近乎同时的情况下,中间件除了要合并数据,更要会解决冲突数据。
一种想法是,以实践戳为标准,比如:
A城迎来写操作(X=1,时间上午10:01:59秒)
B城迎来写操作(X=2,时间上午10:01:58秒)
虽然这里可以严格按照时间排序,59秒那个操作生效,所以最终X=1
但是还有一种更好的方式
异地双活next-level
直接区分上层用户,把部分用户固定引导到A城,部分用户固定引导到B城,从根源避免跨机房。在接入层上引入一个路由层解决该问题。
那么到底要根据什么原则来分割用户呢?
- 1 业务类型分割
- 2 直接哈希分割
- 3 地理位置分割(游戏里的华北一区,华南二区)
1 业务类型:
比如说说第一种按业务分割,比如应用1,应用2都是和订单支付有关的,那么部署都在A机房。
应用2,应用3都是和发帖和社交功能有关的,则部署在B机房。尽可能让互相依赖的应用部署一个地方。
2 直接哈希分割:
比如0-1000的用户id会到A城市机房,10001-2000的会到B城市的机房0。
3 按地理分割:
这种方案特别适合于地理位置密切关系的业务,比如说uber,深圳人肯定不会打到上海的车。
直接在上层把B城市及其周边的用户导流导B机房,A也如是。
这些分割都是为了让用户一个请求尽可能在一个机房内闭环,不需要出现跨机房访问。
这种方式叫“单元化”
不仅如此,这种按地理的方式,还能优化系统性能。
(PS 如果有些数据比如全局数据,依旧得再主机房A做主从,不做双活,保证核心业务双活,而非所有业务)
这样就有了两个相距1000公里,都有全量数据,一个挂了另一个马上接管的,双活局面。
中心机房
当部署的地点越来越多后,双明显不够,需要多活。
不再是A B同步数据,此时已经出现了A,B,C,D,E,F,G。 那么难道任意两点之间都要同步数据么?不需要,这个时候需要设定一个主心骨,中心机房,比如A。
任意的机房写入数据后,都会与中心机房保持同步,而中心机房又会同步到其他机房。
即使运气不好,中心机房A挂了,这个时候迅速的升级另一个B机房作为中心机房,这样的星状架构,可以实现异地多活。