系统设计原则
概述
目录结构参考《亿级流量网站架构核心技术》一书,结合工作中遇到的问题总结下,写下自己理解。绝大多数的工作中遇到的场景,其实已经有非常成熟的解决方案,多了解下,可以少踩些坑。比如redis分布式锁的实现方式,如果不参考下以前的方式,什么超时、释放时候的锁检查踩坑不可避免。
高并发
无状态
无状态后才好做成多个节点组成集群。其实很多场景是由状态的,对于无状态的处理可以有一些技巧,如:
状态放在配置文件中
- 根据不同的机房,部署的时候使用不同的配置文件。
放在客户端
- 比如用户的信息,尽量不要去管理session,直接从token中拿出用户的id,然后在通过rpc服务去查询其他需要的信息。
- 一些随机的内容获取,每个客户端自己生成并保留一个随机种子,每次请求都带上这随机种子(可以转化为一个固定列表中的位置)。否则为每个用户保存一份推荐数据是非常消耗内存的。
放在上下文中
- 比如一个操作流程中需要反复操作一个文件,可以先传文件,返回一个文件id。然后后续每次操作客户端都带着文件id。虽然可能每次都需要重新下载一次文件,但是可以做成无状态。并且,接口拆分后,能够单独进行优化。否则一个长的流程中都需要操作这个文件,如果所有的流程都放在一个接口中,后续如果某个步骤出现性能问题,就无法优化。
拆分
一定要省着点拆,尤其在服务的层面,不要看着满足条件就先拆出来个服务。代码中有过度设计,同样服务拆分也有过度拆分。拆分的原则大概有:
- 系统维度:产品、结算、订单、活动
- 功能维度:可以对一个系统在进行拆分,如活动可以分为抽奖、秒杀
服务化
演进为:进程内服务->单机远程服务->集自动注册和发现服务->服务的分组/隔离/路由->服务治理如限流/黑白名单。
目前微服务比较流程,从本质上看微服务是一种模块化的功能服务化,而前提是模块化时候就很清晰,如果模块化都没有最好,直接上来就服务化,会导致服务的不可控制。
消息队列
- 消息队列是应对高并发写的主要手段。比如打点信息,处理的方式直接写MQ即可。
- 解耦,如产品上线,可能有多个系统对于新的产品上线有关注,通过MQ的方式通知产品上线。还有比如订单和结算系统,新的完成订单通过MQ方式给结算系统,而不是结算的时候再去订单系统去查询订单这种耦合的方式。
- 另外,扣减库存,可以先从redis扣减库存,然后MQ的方式在去落库。订单也是,可以先写redis,并同时写MQ去落库。然后订单支付成功后,状态机去同时更新redis和DB(可能需要多次尝试)。
数据异构
通过数据异构,实现数据的自我控制,这样其他系统出现问题的时候不影响自己的系统。
如详情页的场景,需要使用到的数据特别多,影响服务稳定性因素就很多。比较好的方式是把数据进行异构存储。甚至,针对一些聚合查询的查询,还可以进行数据聚合操作,这样一次调用就能拿到所有的数据。比如:
- 如果设计师信息是外部的依赖的,可以在产品上线的时候,把外部的设计师信息异构存储到本系统中一份,然后变化更新。
- 还有,订单表的处理。正常来说订单表是按照订单的进行分表的,但是又有按照用户id查询所有订单的需求,这个时候可以重新按照用户id在建一份数据。唯品会的订单库就有这样的设计,这也是数据异构的一种方案。
PS:这数据异构,有两种方式,一种方式是为了避免对多个微服务的调用和查询,可以把所需要的数据本地保存的时候提前通过微服务方式查询,生成一张有冗余字段的宽表,表中包括了使用业务需要的字段,比如设计师站中主题的统计信息、曝光、点击、订单、订单转化、主题自身的信息等。
还有一种方式,有的场景需要跨微服务进行表关联,而且有A服务(甚至更多)的多个表需要依赖B服务的同一张表,这个时候可以通过小表广播的方式,把B服务中的表广播到其他服务。比如A级别和S级别的设计师信息,数据不多百多个,可以同步到产品服务进行一些榜单的业务。
缓存银弹
缓存是非常重要的解决读的手段。也有一些技巧,如APP缓存,在活动开始之前,就可以让客户端先进行一些资源的拉取操作。不要都等到活动开始的时候
并发化
高可用
降级
- 可以对多级服务进行降级,如服务调用降级为只读本地缓存,甚至只读默认的降级数据。
- 另外,也可以增加手动开关,在必要的时候,如推荐服务有问题,可以从默认的专题中取数据。
限流
限流的目的不仅仅是在重大活动的时候,防止流量超出系统峰值。还可以防止恶意的攻击与请求。
切流量
在整个机房出问题的时候,可以通过切换lvs接入层流量。
可回滚
最基础的就是发部版本的回滚。
##业务设计原则
防重
- 通常手段是防重的key、防重表。可以通过redis或数据库实现,比如第一次登陆能获取抽奖机会,在第一次进入的时候通过redis的setNX方式尝试写入防重key,第一次能写入成功,第二次写入就会失败。
- 还有种方式,如为了计算点赞数,即使不关心对哪些内容点赞,可以把所有的哪个用户对哪些内容进行了点赞进行记录,这样就天然有防重的key,然后在计算点赞数的时候去进行统计。对于订单来说更是重要。
- 对于防止重复下单,可以在下单前在redis生成一个临时防重key,在创建订单前获取这个key,然后在创建订单的时候从客户端在传回来,然后服务端创建的时候去redis验证这个key是否存在并且没有被使用过。
幂等
- 正常系统中,也会有回调重试,比如,支付回调。最理想方式有个能确定这次回调数据唯一的id(可以是组合的),比如订单id、商品id。如果没有,与防重设计思路一样,客户端使用一个随机的id或时间戳。
流程可定义
- 最理想是做到配置工作流
参考
亿级流量网站架构核心技术