优化与灾备-后段架构一些总结

什么是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机房作为中心机房,这样的星状架构,可以实现异地多活。