系统设计的一些原则
在设计系统时,应该多思考墨菲定律
- 任何事情都没有表面看起来那么简单
- 所有的事情都会比你预计的时间?
- 可能会出错的事一定会出错
- 如果你担心某种情况发生,那么它就更有可能发生
在系统划分时,也要思考康威定律
- 系统架构是公司组织架构的反映
- 应该按照业务闭环进行系统拆分/组织架构划分,实现闭环/高内聚/低耦合,减少沟通成本
- 如果沟通出现问题,那么就应该考虑进行系统和组织架构的调整
- 在合适时机进行系统拆分,不要一开始就把系统/服务拆得非常细,虽然闭环,但是每个人维护的系统多,维护成本高
高并发原则
-
无状态 如果设计的应该是无状态的,那么应用比较容易进行水平扩展。实际生产环境可能是这样的:应用无状态,配置文件有状态。比如,不同的机房需要读取不同的数据源,此时,就需要通过配置文件或配置中心指定。 -
拆分 在系统设计初期,是做一个大而全的系统还是按功能模块拆分系统,这个需要根据环境进行权衡。 -
服务化 总结为:进程内服务—>单价远程服务—>集群手动注册服务—>自动注册和发现服务—>服务的分组/隔离/路由—>服务治理 如限流/黑白明白,服务降级 -
消息队列 消息队列是用来解耦一些不需要同步调用的服务或者订阅一些自己系统的变化。使 用消息队列可以实现服务解耦(一对多消费)、异步处理、流量削峰/缓冲等。但是订 阅者太多,那么订阅单个消息队列就会成为瓶颈,此时需要考虑对消息队列进行多个镜 像复制。 使用消息队列时,需要注册生产消息失败,以及消息重复接收时的场景。有些消息 队列产品会提供生产重试功能,在达到指定重试次数还未生产成功时,会对外通知生产 失败。这对于不能容忍生产失败的业务场景来说,一定要做好后续的数据处理工作,比 如持久化数据同时要增加日志、报警等,或者在生产失败后发送http请求来保证成功。 还有消息重复问题,特别是一些分布式消息队列,出于对性能和开销的考虑,在一些场 景下会发送消息重复接收,需要在代码层面进行防重处理。 -
数据异构 数据异构:订单分库分表一般按照订单ID进行分,如果要查询某个用户的订单列表,则需要聚 合多个表的数据才能返回,这样会导致订单表的读性能很低。此时需要对订单表进行异 构,异构一套用户订单表,按照用户ID进行分库分表。另外,还需要考虑对历史订单数 据进行归档处理,以提升服务的性能和稳定性。而有些数据异构的意义不大,如库存架 构,可以考虑异步加载,或者合并并发请求。 数据闭环:数据闭环如用户的聊天记录,因为数据来源太多,影响服务稳定性的因素就非常多了。因此,最好的办法是把使用到的数据进行异构存储,形成数据闭环,基本步骤如下: 1: 数据异构:通过MQ机制接收数据的变更,然后原子化存储到合适的存储引擎,如 redis,Elasticsearch等。 2:数据聚合:这步是可选的,数据异构的目的是把数据从多个数据源拿过来,数据聚 合的目的是把这些数据做个聚合,这样前端可以一次请求拿到所有数据,此步骤一般存 储到KV存储中 3:前端通过一次或少量几次请求拿到所需要的数据。 这种方式的好处就是数据的闭环,任何依赖的系统出问题了,还是能正常工作,只是更新会有积压,单是不影响前端展示。 数据闭环和数据异构其实是一个概念,目的都是实现数据的自我控制,当其他系统 出现问题时不影响自己的系统,或者自己出问题时不影响其他系统。一般通过MQ来实 现数据分发。 -
缓存银弹 缓存对读服务来说,是扛流量的银弹,可总结为下表:
高可用原则
-
降级 对于一个高可用服务,很重要的一个设计就是降级开关,在设计降级开关时,主要依据如下思路: 1:开关集中化管理:通过推送机制把开关推送到各个应用。 2:可降级的多级读服务:比如服务调用降级为只读本地缓存、只读NoSql缓存 、只读 默认降级数据 3:开关前置化: 如架构是Nginx—>Apache,可以将开关前置到Nginx接入层,在Nginx 层做开关,请求流量汇源后端应用或者只是一小部分流量回源 4:业务降级:当高并发流量来袭,保证核心业务是正常的,并保障数据最终一致性即 可。这样就把一些同步调用改成异步调用,优先处理高优先级数据或特殊特征的数据,合 理分配进入系统的流量,以保障系统可用。 -
限流 限流的目的是防止恶意请求流量,恶意攻击,或者防止流量超出系统峰值。思路如下: 1:恶意请求只访问到cache 2:对于穿透到后端应用的流量可用考虑使用Nginx的limit模块处理 3:对于恶意IP可以使用nginx deny进行屏蔽 原则是限制流量穿透到后端薄弱的应用层
业务设计原则
总结
一个系统的设计,不仅需要考虑实现业务功能,还要保证系统高并发、高可用等。在系 统容量规划(流量、容量等)、SLA制定(吞吐量、响应时间、可用性、降级方案等)、 压测方案(线上、test等)、监控报警(机器负载、响应时间、可用率等)、应急预案(容 灾、降级、限流、隔离、切流量、可回滚)等方面,也要有一些原则来进行设计。
负载均衡与反向代理
当我们的应用单实例不能支撑用户请求时,此时就需要扩容,从一台服务器扩容到两 台,几十台,上百台,甚至更多。然后用户访问是通过一个域名的方式访问,在请求时, 浏览器首先会查询DNS服务器获取对应IP,然后通过此IP访问对应服务。
因此一种方式是将域名解析到多个IP,但是存在一个简单的问题,假设某台服务重启或 者出现故障,DNS会有一定的缓存时间,故障切换时间?,而且没有对后端服务进行心跳 检查和失败重试的机制。
因此,外网DNS应该用来实现GSLB(全局负载均衡)进行流量调度,如将用户分配到离他最近的服务器上以提升体验。而且当某一区域的机房出现问题时(如断网),可以通过 DNS指向其他区域的IP来是服务可用。
对于内外DNS,可以实现简单的轮询负载均衡。但是,还是会有一定缓存时间并且没有失败重试机制。因此我们可以使用HaProxy和Nginx。
而且对于一般应用来说,有Nginx就可以了。但Nginx一般用户7层负载均衡,其吞吐量 是有一定限制的。为了提升整体吞吐量,会再DNS和Nginx之间引入接入层,如使用 LVS(软件负载均衡)、F5(硬件负载均衡器)可以做4层负载均衡,即首先DNS解析到 LVX/F5,返回LVS/F5转发给Nginx,再由Nginx转发给后端Server。
Nginx负载均衡算法
负载均衡用来解决用户请求到来时如何选择upstream server进行处理,默认采用round-robin(轮询),同时支持其他几种算法。
- round-robin:轮询,默认负均衡算法,即以轮询的方式将请求转发到上游服务器,通过配合weight配置可以实现基于权重的轮询。
- ip_hash:根据客户IP进行负载,即相同的IP负载均衡到同一个server。
- hash key:对某一个key进行哈希或者使用一致性哈希算法进行负载均衡。使用hash算 法存在的问题是,当添加/删除一台服务器时,将导致很多key被重新负载均衡到不同的服 务器(从而导致后端可能出现问题);因此建议考虑使用一致性哈希算法,这样当添加/删 除一台服务器时,只有少数key将被重新负载均衡到不同的服务器。
- 哈希算法:此处是根据请求uri进行负载均衡,可以使用Nginx变量,因此可以实现复 杂的算法
- least_conn:将请求负载均衡到最少活跃连接的上游服务器。如果配置的服务器较少, 则将转而使用基于权重的轮询算法
失败重试
主要有两部分配置:upstream server 和proxy_pass
upstream api{
server 172.31.72.157:80 max_fails=2 fial_timeout=10s weight=1;
server 172.31.72.155:80 max_fails=2 fial_timeout=10s weight=1;
}
通过配置上游服务器的max_fails和fial_timeout,来指定每个上游服务器,当fail_timeout 时间内失败了max_fails次请求,则认为该上游服务器不可用/不存活,然后将摘掉该上游服 务器,fail_timeout时间后会再次将该服务器加入到存活上游服务器列表进行重试。
健康检查
Nginx对上游服务器的健康检查默认采用的是惰性策略,Nginx商业版提供了 health_check进行主动健康检查。当然也可以集成nginx_upstream_check_module模块来进 行主动健康检查。
nginx_upstream_check_module支持TCP心跳和HTTP心跳来实现健康检查
TCP心跳检查
upstream api{
server 172.31.72.157:80 max_fails=2 fial_timeout=10s weight=1;
server 172.31.72.155:80 max_fails=2 fial_timeout=10s weight=1;
check interval=3000 rise=1 fall=3 timeout=2000 type=tcp;
}
- interval:检测间隔时间,此处配置每3s检测一次
- fall:检测失败多少次后,上游服务器被标识为不存活
- rise:检测成功多少次后,上游服务被标识为存活,并可以处理请求
- timeout:检查请求超时时间
HTTP心跳检查
upstream api{
server 172.31.72.157:80 max_fails=2 fial_timeout=10s weight=1;
server 172.31.72.155:80 max_fails=2 fial_timeout=10s weight=1; check interval=3000 rise=1 fall=3 timeout=2000 type=htpp;
check_http_send "GET /check HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx | http_3xx;
}
HTTP心跳检查有如下两个参数需要额外配置
-
check_http_send:检查时发的HTTP请求内容 -
check_http_expect_alive:当上游服务器返回匹配的响应状态码时,则认为上游服务器存活 需要注意,检查间隔时间不能太短,否则可能因为心跳检查包太多造成上游服务器挂掉,同时要设置合理的超时时间。
备份上游服务器
upstream api{
server 172.31.72.157:80 weight=1;
server 172.31.72.152:80 weight=2 backup;
}
将172.31.72.152服务器80端口的上游服务器配置为备上游服务,当所有主上游服务器都 不存活时,请求会转发给备上游服务器。
不可用上游服务器
upstream api{
server 172.31.72.157:80 weight=1;
server 172.31.72.152:80 weight=2 down;
}
将172.31.72.152服务器80端口的上游服务器配置为永久不可用,当测试或者机器出现故障 时,暂时通过该配置临时摘掉机器。
隔离
隔离是指将系统或资源分割开,系统隔离是为了在系统发生故障时,能限定传播范围和影响范围,即发生故障后不会出现滚雪球效应,从而保证只有出问题的服务不可用,其他服务还是可用的。资源隔离通过隔离来减少资源竞争,保障服务间的相互不影响和可用性。
常规的隔离手段有线程隔离、进程隔离、集群隔离、机房隔离、读写隔离、快慢隔离、 动静隔离、爬虫隔离等。出现系统问题时,可以考虑负载均衡路由、自动/手段切换分组或 者降级等收到来保障可用性。
线程隔离
线程隔离主要是指线程池隔离,在实际使用时,我们会把请求分类,然后交给不同的线程池处理。当一种业务的请求处理发送问题时,不会将故障扩散到其他线程池,从而保证其他服务可用。
进程隔离
一般一个系统刚开始构建,一般是先进行从零到一,不会一开始就进行系统拆分,这样 就会开发出一个大而全的系统,系统的一个模块/功能出现问题,整个系统就不可用了。首 先,相到的解决方案是通过部署多个实例,通过负载均衡进行路由转发。但是这种情况无 法避免某个模块因BUG而导致整个系统不可用的?险。因此,这种方案只是一个过渡,好 的解决方案是通过将系统拆分为多个子系统来实现物理隔离。通过进程隔离使得某一个子 系统出现问题时不会影响到其他子系统。微服务架构模式就是解决进程隔离的方式之一。
集群隔离
随着系统的发展,单实例服务无法满足需求,此时需要服务化技术,通过部署多个服务形成服务集群,来提升系统容量。
机房隔离
随着对系统可用性的要求,会进行多机房部署,每个机房的服务都有自己的服务分组, 本机房的服务应该只调用本机房服务,不进行跨机房调用。其中,一个机房服务发送问题 时,可用通过DNS/负载均衡将请求全部切到另一个机房,或者考虑服务能自动重试其他机 房的服务,从而提升系统可用性。
读写隔离
通过主从模式将读和写分离,读服务只向从机读取数据,当主机出现问题,从机还是可 用的,从而不影响用户访问。而当从机出现问题时,可用直接读取主机,或者读取其他从 机。一般来说 mysql主从,redis主从都是可以实现。
动静隔离
当用户访问一个?面时,如果js/css/image等静态资源和动态资源在一个机器中,很可能因为访问量太大导致带宽被打满,从而出现不可用。
因此,应该将动态内容和静态资源分类,一般应该将静态资源放到oss对象存储上,开启 CDN缓存。
爬虫隔离
在实际业务中,会有爬虫来抓取我们的?面,爬虫和正常流量的比例能达到5:1,甚至更 高。一些系统就是因为爬虫访问量太大而导致服务不可用。一种解决办法是通过限流解 决,另一种解决办法是在负载均衡层面将爬虫路由到单独集群,从而保证正常流量可用, 爬虫流量尽量可用。
热点隔离
秒杀,抢购属于非常合适的热点例子,对于这种热点,是能提前知道的,所以可以将秒 杀和抢购做成独立系统或服务进行隔离,从而保证秒杀/抢购流程出现问题时不影响主流程。
还存在一些热点,可能是因为活动或突发事件引起的。对于读热点,可以使用多级缓存 来搞定,而写热点一般通过缓存+消息队列模式削峰。
资源隔离
最常?的资源,如磁盘、CPU、网络,这些资源都会存在竞争问题。
使用Hystrix实现隔离
Hystrix是Netflix开源的一款容错框架,包含常用的容错方法:线程池隔离、信号量隔 离、熔断、降级回退。在高并发访问下,系统所依赖的服务的稳定性对系统的影响非常 大,依赖有很多不可控的因素,比如网络连接变慢,资源突然繁忙,暂时不可用,服务脱机等。我们要构建稳定、可靠的分布式系统,就必须要有这样一套容错方法。
限流详解
在开发高并发系统时,有很多方法来保护系统,如缓存,降级,限流等。缓存的目的是提升系统访问速度和增大系统处理能力,可谓是扛高并发流量的银弹。而降级是当服务出 问题或者影响到核心流程的性能,需要暂时屏蔽掉,待高峰过去或者问题解决后在打开的 场景。而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务 (如评论,下单)、频繁的复杂查询等。因此,需要一种手段来限制这些场景下的并发/请 求量,这种手段就是限流。
限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级。在压测时我们能找出每个 系统的处理峰值,然后通过设定峰值阀值,来防止当系统过载时,通过拒绝处理过载的请求来保障系统可用。另外,也应根据系统的吞吐量、响应时间、可用率来动态调整限流阀值。
一般开发高并发系统常?的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬发时并发数(如Nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口 内的平均速率(如Nginx的limit_req模块,用来限制每秒的平均速率),以及限制远程接口 调用速率、限制MQ的消费速率等。另外,还可以根据网络连接数、网络流量、CPU或内存负载等来限流。
先有缓存这个银弹,后有限流来应对高并发流量,在处理高并发问题上可以说是如?添翼,不用担心瞬间流量而导致系统挂掉或雪崩,最终做到有损服务而不是整个服务停掉。限流需要评估好,不可乱用,否则正常流量会出现一些奇怪的问题,而导致用户抱怨。
在实际应用时,也不要太纠结算法问题,因为一些限流算法实现是一样的,只是描述不一样。具体使用哪种限流技术,还是要根据实际场景来选择,不要盲目去找最佳模式,最终解决了问题就好。
限流算法
常?的限流算法有:令牌桶、漏桶。计数器也可以用来进行粗暴限流实现。
-
令牌桶算法
令牌桶算法,是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:
-
设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌
-
桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝
-
当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上
-
如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么 在缓冲区等待)
-
漏桶作为计量工具时,可以用于流量整形和流量控制,漏桶算法的描述如下:
-
一个固定容量的漏桶,按照常量固定速率流出水滴
-
如果桶是空的,则不需要流出水滴
-
可以以任意速率流入水滴到漏桶
-
如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的
应用级限流
-
限流总并发/连接/请求数 一个应用系统,一定会员极限并发/请求数,即总有一个TPS/QPS阀值,如果超了阀值,则系统就会不响应用户请求或响应速度非常慢,因此最好进行过载保护,防止大量请求涌入击垮系统。 -
限流总资源数 如果有的资源是稀缺资源(如数据库连接、线程),而且可能有多个系统会去使用 它,那么需要加以限制。可以使用池化技术来限制总资源数,如连接池,线程池。假设分 配个某个应用的数据库连接是100,那么该应用最多可以使用100个资源,超出可以等待或者抛异常。 -
限流某个接口的总并发数/请求数 如果接口可能会有突发访问情况,但又担心访问量太大造成崩溃,那么这个时候就需要限制这个接口的总并发数/请求数了。因为粒度比较细,可以为每个接口设置相应的阀值。 -
限流某个接口的时间窗请求数 即一个时间窗口内的请求数,如想限制某个接口/服务每秒/每分钟/每天的请求数/调用 量。如一些基础服务会被很多其他系统调用,比如用户的搜索服务会被其他的服务调用,但是更新量比较大有可能将基础服务打挂。这时,我们要对每秒/每分钟的调用量 进行限速。 -
平滑限流某个接口的请求数 之前的限流方式都不能很好的应对突发请求,即瞬间请求可能都被允许,从而导致 一些问题。因此,在一些场景中需要对突发请求进行整形,整形为平均速率请求处理 (比如5r/s,则每隔200毫秒处理一个请求,平滑了速率)。这个时候有两种算法满足 我们的场景:令牌桶和漏桶算法。
分布式限流
分布式限流最关键的是要将限流服务做成原子化,而解决方案可以使用Redis+Lua或者 Nginx+Lua技术进行实现,通过这俩中技术可以实现高并发和高性能。
接入层限流
接入层通常指请求流量的入口,该层主要的目的有:负载均衡、非法请求过滤、请求聚 合、缓存、降级、限流、A/B测试、服务质量监控等。
对应Nginx接入层限流可以使用Nginx自带的两个模块:连接数限流模块 ngx_http_limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module来实现。
降级
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如聊天,支付)。降级也需要根据系统的吞吐量、响应时间、可用率等条件进行手工降级或自动降级。
降级预案
在进行降级之前要对系统进行梳理,看看系统是不是可以弃?保帅,从而梳理出哪些必须誓死保护,哪些可降级。比如,可以参考日志级别设置预案。
- 一般:比如,有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级。
- 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送警告。
- 错误:可用率低于90%,或者数据库连接池用完了,或者访问量突然剧增达到系统能 承受的最大阀值,此时可以根据情况自动降级或者人工降级。
- 严重错误:因为特殊原因数据出现错误,此时需要紧急人工降级。
- 降级按照是否自动化可分为:自动开关降级和人工开关降级。
- 降级按照功能可分为:读服务降级和写服务降级
- 降级按照处于的系统层次可分为:多级降级
-
降级的功能点主要从服务器端链路考虑,即根据用户访问的服务调用链路来梳理哪里需要降级:
-
?面降级:在大的促销或者其他特殊情况下,某些?面占用了一些稀缺资源,在紧急 情况下可以对其整个降级,以达到弃?保帅的目的
-
?面片段降级:比如?面中,某个展示内容因为数据错误,此时需要对其进行降级
-
?面异步请求降:某些?面上的信息访问速度缓慢,可以进行异步请求,进行降级
-
服务功能降级:一些不重要的服务,加载速度又比较慢,这些服务在异常情况下可以直接不获取,降级即可
-
读降级:比如多级缓存,如果后端服务有问题,则可以降级为只读缓存,这种情况适 用于对读一致性要求不高的场景
-
写降级:比如,用户聊天,我们可以只进行存储缓存,然后异步同步数据至DB即可
自动开关降级
自动降级是根据系统负载、资源使用情况、SLA等指标进行降级
-
超时降级 当访问的数据库/http服务/远程调用响应慢或者?时间响应慢,且该服务不是核心服务 的话,可以在超时后自动降级。 -
统计失败次数降级 有时依赖一些不稳定的api,比如,调用微信or阿里云接口,当失败次数达到一定阀值自动降级(即熔断)。然后通过异步线程检测服务是否恢复了,恢复则取消降级 -
故障降级 比如,调用某个服务挂掉了(如网络故障、RPC服务抛出异常、HTTP状态码异 常),则可以直接降级。降级后的处理方案有:默认值(比如数据库挂了,返回默认的 数据),兜底数据、缓存。 -
限流降级 当春节或者十一假期我们去12306购买火?票,因为访问量太大导致系统崩溃,此 时,就会使用限流来限制访问量,当达到阀值时,后续请求会被降级。降级后的处理方 案就是:排队,或者直接告知无票。
人工开关降级
在特殊情况下,通过监控发现一些服务存在问题,此时就需要暂时将这项服务摘掉。
-
读服务降级 对于读服务降级一般采用的策略有:暂时切换读(降级到读取缓存,降级到走静态 化)、暂时屏蔽读(屏蔽读入口、屏蔽某个读服务)。 -
写服务降级 写服务在大多数场景下是不可降级的,不过可以通过一些迂回策略来解决问题。比如,将同步操作切换为异步操作,或者限制写的频率。 -
多级降级 缓存离用户越近越高效,而降级是离用户越近越对系统保护得好。因为业务的复杂性导 致越到后端QPS/TPS越低。 ?面JS降级开::主要控制?面功能的降级,在?面中,通过JS脚本部署功能降级开 关,在适当时机开启/关闭开关 接入层降级开关:主要控制请求入库的降级,请求进入后,会首先进入接入层,在接 入层可以配置功能降级开发,根据时间情况进行自动/人工降级。 应用层降级开发:主要控制业务的降级,在应用总配置响应的功能开关,根据实际业 务情况进行自动/人工降级
超时与重试机制
在实际开发中,很多故障是因为没有设置超时或者设置的不合适而造成的。这些故障都是因为没有设置超时而造成的。如果应用不设置超时,则可能会导致响应慢,慢请求导致 的连锁反应,甚至造成应用的雪崩。有些中间件与框架在超时后会进行重试,读服务非常 适合重试,但是写服务大多不能设置重试(如下单,发送消息,支付等,如果写服务是幂 等的,则允许重试),但是重试的次数太多或导致多倍的请求流量,就像模拟了DDoS攻 击,后果可能是灾难,因此重试的机制一定要设置合理,并且应该和熔断、快速失败机制配合。
代理层超时与重试
对于代理层以Nginx来介绍:
Nginx主要有4类超时设置:客户端超时设置、DNS解析超时设置、代理超时设置,如果使用ngx_lua,则还有Lua相关超时设置
-
客户端超时设置 client_header_timeout:设置读取客户端请求头超时时间,默认为60s,如果在超时 时间内,客户端没发送任何东?,nginx返回HTTP状态码408(“Request timed out”) client_body_timeout:设置读取客户端内容提请求超时时间,默认为60s,.如果客 户端在指定时间内没有发送任何内容,Nginx 返回 HTTP 408(Request Timed Out)。 send_timeout:设置发送响应到客户端时间,默认为60s,在两次客户端读取操作之 间。如果在这段时间内,客户端没有读取任何数据,Nginx就会关闭连接。 keepalive_timeout:设置客户端分配HTTP链接超时时间。服务器将在这个时间过 后关闭链接,我们将它设置低些可以让Nginx持续工作的时间更?,默认为75s。 -
DNS解析超时设置 resolver_timeout:设置DNS解析超时时间,默认为30s。
web容器超时
比如php的容器是php-fpm,那么可以在php-fpm中配置超时设置:
- fastcgi_connect_timeout:指定同FastCGI服务器的连接超时时间,这个值不能超过75秒
- fastcgi_read_timeout:指定接收FastCGI应答的超时时间,这个值是已经完成俩次握手后 介绍FastCGI应答的超时时间。
数据库 | NoSql连接超时
在连接数据库,NoSql时,我们会设置数据库的连接超时时间。
业务超时
业务超时分为2类:
- 任务型:比如12306购买了火?票,需要在指定时间内完成支付,否则火?票将被取消。
- 服务调用型:在调用某个服务时会设置一个超时时间。
回滚机制
回滚是指当程序or数据库出错时,将程序或数据恢复到最近一个正确的版本的行为。最 常?的回滚如:事务回滚,代码回滚,部署版本回滚,数据库版本回滚,静态资源版本回滚。
事务回滚
在某些需要保持数据一致的情况下,我们会使用数据库的事务,从而保证数据的一致性,该事务要么全部执行成功,要么全部执行失败,进行回滚。
代码回滚
在开发中,我们会将代码提交到代码仓库,进行版本管理。比如 代码版本控制工具git。 比如当前版本出现问题,那么可以回滚代码到指定的版本。
部署版本回滚
每次发布上线,都会有上线版本,如果本次上线版本有问题,则切换到上一版本。
数据版本回滚
设计版本化数据结构时,有2种方式:全量和增量。全量化版本指即使只变更了一个字 段也将整体记录进行历史版本化,保存的数据量比较多,但是回滚方便。而增量指记录保 存变化的字段,保存的数据量较少,但是回滚起来很麻烦,需要回溯。
静态资源版本回滚
在前端开发中,静态资源版本也是经常变更,如JS/CSS,而每次发布上线时,都会备份 之前版本,从而保证版本可回滚。
压测与预案
系统压测
压测一般就是性能压力测试,用来评估系统的稳定性和性能,通过压测数据进行系统容量评估,从而决定是否需要进行扩容或缩容。
压测之前要有压测方案,常?的压测是:接口压测、并发量、压测策略(突发、逐步加 压、并发量)、压测指标(机器负载、QPS/TPS、响应时间),然后出压测报告{压测方 案、机器负载、QPS/TPS、响应时间(平均、最小、最大)、成功率、相关参数(nginx参 数、php-fpm参数)等},最后根据压测报告分析的结果进行系统优化和容灾。
应用级缓存
缓存就是让数据更接近用户,目的是让访问速度更快。工作机制是先从缓存中读取数据,如果没有,在从db中读取数据,并同步至缓存中。
缓存命中率
缓存命中率是从缓存中读取数据的次数与总读取次数的比率,命中率越高越好。缓存命 中率=从缓存读取次数 / (总读取次数「缓存中读取次数+从db中读取次数」)。这是一个非 常重要的监控指标,如果做缓存,则应通过监控这个指标来看缓存是否工作良好。
缓存回收策略
-
基于空间 基于空间值缓存设置了存储空间,例如设置100MB,当达到存储上线,按照一定策略移除数据 -
基于容量 基于容量值缓存设置了最大大小,当缓存的条目超过了最大大小时,按照一定策略移除就数据 -
基于事件 TTL(Time To Live):存活期,即缓存数据的一个有效时间。一般创建缓存的时候会设 置一个缓存过期时间 TTI(Time to Idle):空闲期,即缓存数据多久没有被访问后移除缓存的时间。
多级缓存
所谓多级缓存,是指在整个系统架构的不同系统层级进行数据缓存,以提升访问效率,这也是应用最广的方案之一。
连接池线程池
在系统开发过程中,我们经常会用到池化技术,如对象池、连接池、线程池等。通过池 化来减少一下消耗,以提升性能。对象池通过复用对象来减少创建对象,垃圾回收的开 销,但是池不能太大,太大会影响GC时的扫码时间。连接池如数据库连接池,redis连接池,http连接池,通过复用TCP连接来减少创建和释放连接的时间来提升性能。线程池也是 类似,通过复用线程提升性能。
队列
应用场景
-
异步处理 使用队列的一个主要原因是进行异步处理。比如用户注册成功后,需要发送注册成功 邮件,赠送卡券等;通过异步处理提升主流程响应速度,而非主流程/非重要处理可以集中 处理,这样还可以将任务聚合批量处理。因此可以使用消息队列/任务队列来进行异步处理。 -
系统解耦 一个完善系统是需要根据不同语言的特性来做不同的应用处理,使用消息队列/任务队列可以进行系统上解耦。 -
数据同步 比如,想把MySql的数据同步至redis,或者让机房之间的数据同步,或者主从数据同步 等,此时可以使用databus、canal、otter等。数据数据总线队列进行数据同步的好处是 可以保证数据修改的有序性。 -
流量削峰 系统瓶颈一般在数据库上,比如下单,扣减库存等。此时可以考虑使用队列将变更请求暂时放入队列,通过缓存+队列暂存的方式将数据库流量削峰。
任务队列
使用任务队列可以将一些不需要与主线程同步执行的任务扔到任务队列进行异步处理。 比如 go语言中channel就可以实现一个个任务队列。比如socket聊天,当用户A给用户B发 送消息时, 后端接收到用户A发送的消息,需要下发给用户B,并告知用户A发送成功,此时 就可以将消息放入到channel中异步处理,告知用户A发送消息成功,使用goroutine从 channel中有序的获取数据,执行操作,下发到用户B。
消息队列
通常使用开源系统,Kafak、Nsq、redis等。使用消息队列存储各业务数据,其他系统根 据需要订阅即可。常?的订阅模式是:点对点(即一个消息只有一个消费者)、发布订阅 (一个消息可以有多个消费者)。
请求队列
请求队列是指在web环境下对用户请求进行排队,从而进行一下特殊控制:流量控制、 请求分级、请求隔离。
数据总线队列
一般消息队列中的消息都是业务维度的简单数据,如用户更改了基础信息。如果要进行 数据维度的同步,使用消息队列方式很难只进行变更部分的推送并保证数据的有序性。例 如数据库的字段发生了变化,或者将一个机房的数据同步至另一个机房,数据维度的同 步,此时需要是用数据总线队列,如阿里的Canal、LinkedIn的databus。使用数据总线队列 的好处是,可以保证数据的有序性。
|