原则
容忍一个缺点
缓存
分层
数据分区
展开/折起 - 头部

架构设计经验杂谈

原则

做一件事情,总得有个原则,它可以帮助我们进一步评价几个相差不多的事物。架构设计时的原则是什么?种豆得豆,种瓜得瓜,你秉持的原则直接决定后续的成效。

我的答案是:

可用性第一。这个没有什么争议。系统再NB,跑不起来,完不成预定需求也是扯淡。

可维护第二。是争论比较多的一个地方。而我觉得,可维护真的非常非常重要。一段无法维护的代码就是一颗定时炸弹, 而你却不知道其爆炸时间。对于团队来说,如果以牺牲可维护性为代价,去死扣一点性能而引入过于复杂的算法、组件是非常危险的, 看似赢得了一时,却输掉了以后。代码肯定有bug,团队即将面对出问题无法调试的时刻。

此外,可维护性一定要与自己团队实力结合起来。可维护这个事情评估起来非常难,因为团队彼此有差异。 某项具体设计或实现的难度,对一个团队没有问题,对另一个团队则可能完全无法维护,只能使用。 而作为设计者,则一定要把握好这个尺度,自己的团队能承受多大难度的问题,没有什么特殊问题,千万 不要盲目的因为别人的只言片语就霸王硬上弓。

只要代码可维护性良好,即便他有其它方面问题,随着时间的推移,肯定会被fix掉。而没有维护性的设计则没有生命,出生即死亡,以后只能抛弃掉重构。

在团队中,我一直反对团队自己些php扩展解决一些问题。因为相关工作无人维护,写完,爽完,就真的完了。这是很恐怖的,当后续问题 出现时,当初写的人可能离职了,或者无能力完成新的修改,这,都是致命的事情。 而公司有一定规模后,各司其职,有专门的团队(是团队,不是个人)来做这件事情时,就OK了。

容忍一个缺点

容忍缺点,似乎不应该让这么一个不断追求完美的人群听见。

但是,如果你找不到一个合适的点妥协,那设计永远是设计。

高可用、无单点、高性能、数据一致性、实时性等,不要妄求在设计中解决一切问题。 要清楚,CAP原则是无法同时实现的,千万不要让自己钻到牛角尖中。

容忍一个问题,说不定会让你豁然开朗,从而快速完成架构其它部分的设计。

没有不出问题的架构,当我们想不出更好的方案时,就包容它,转而为其寻找出问题时候的快速修复方案。除非,你的时间是无限的,公司允许你一直这样的想下去,试验下去。

年初(2014)时我需要设计一个通用的异步服务,服务本身系统保证所有的任务都能收得到。但网络与系统,总是会出问题的。 没有系统敢宣称自己100%的在线率,google牛逼吧,在china照样无法正常使用,这里就扯到另外一件事情——做高可用 时,不要把眼光局限在服务本身。

为了保证高可用,接收到所有的消息,需要做重试、灾备切换等很多事情,但线上服务等着急用呢,另外,灾备切换太废机器。 最后,选择了客户端调用时,如失败则打日志,后续收集重新请求的方式来解决。这样,server-client的实现都简单了很多。 当然,如此妥协,与这本身是一个异步服务有关,其它服务不能照搬方法。

缓存

缓存是所有“读操作”性能问题的通用解决方案,它是为程序运行服务的,不是为工程师服务的,因为他会增加代码的复杂度。架构里面有两个概念非常关键:一是缓存,二是分层。缓存主要解决性能问题,分层主要解决耦合(复杂性)问题。 引入缓存的代价是逻辑复杂性的上升,必须去维护缓存关系。

缓存的常见使用方式有写时同步构建、读时按需构建、异步构建三种,各有各的好处。 对于QPS比较高的接口,慎用读时按需构建,因为这么做很有可能会被缓存构建过程卡死。 对于数据一致性要求比较高的场合,慎用异步构建,以防上下文相关的业务逻辑异常。

衡量缓存价值的主要指标是缓存利用率,具体多少利用率合适,需要根据业务实际情况而定。

分层

分层和缓存正好相反,它简化了业务的复杂性,使其更易被人类理解,就像给一部长长的小说编排了一个清晰的目录。但它却降低了程序的运行性能。 它是两类问题的标准答案:

  1. 逻辑复杂性。
  2. 各种适配问题(更确切的答案,应该是增加中间层)。

引入分层,必然会遇到层次之间如何通信的问题。通信分为两类:内部调用与rpc。

内部调用,适合业务逻辑还不是太庞大的阶段,一般通过各种库(lib、动态、静态库)来完成,整个系统在操作系统层面还是一个程序。

网络RPC,一般代码级别的分层无法解决问题时,我们便会着手进行更彻底的分层,将程序分为多个独立存在的程序,彼此间通过网络进行通讯。网络rpc会碰到协议问题,这时候应该首选HTTP。选择HTTP能为以后节省相当多的配套工具开发时间。不同层次之间通讯还会碰到同步与异步的问题。同步容易理解,但容易被卡住。异步增加复杂性,却提高了性能。之前语言层次没有很好的解决这个问题,同步和异步需要自己在业务层解决,而golang却在语言层解决了,用异步的模式构建语言实现基础,工程师却仍然用同步的方式来写程序,堪称伟大。

分层会带来性能问题。 这个时候可以在分层体系的上层增加缓存来解决;或者通过对后端数据的并发访问来缓解,不过对后端数据的并发访问会对逻辑表达造成不利的影响,而且使用场景有限,因为大部分逻辑都是串行为主的,未必能准确的预测出可以并发获取的数据是哪些。

此外,分层还需要解决循环调用的问题。

数据分区

分区算法有两类:按严格递增的区间,或者按照hash。

按照区间,可以非常灵活的处理后续增长问题。但在某些业务场景下带来旧区间冷数据过多的问题。可以通过定期合并旧区间来解决。

按照hash,比较好的解决了冷热分布不均的问题,但在扩展区间时简直就是噩梦(缓存类的数据到可以通过一致性hash来解决,但持久数据就没这么幸运了)。

如果数据量非常大,先hash再在每个hash段里分区存储,可以缓解两者的弊端,但也不是最终完美的解决办法,最终都会走到通过导数据(手动or自动)来调整分区的路子上来。

知识共享许可协议 本作品采用知识共享署名 3.0 未本地化版本许可协议进行许可。
comments powered by Disqus