多产品架构升级
当系统有多个产品,且由多个团队分别开发时,产品间的隔离就需要提上日程了。如果继续在一个大单体上开发,很容易造成同一模块多方维护,导致功能无规划的混乱增长,系统逻辑复杂,冲突变多,质量问题频发。对于多产品的隔离,需要从业务、数据、部署等方面进行分析和设计。
多产品业务隔离
在新产品发展初期,一般规模较小,大部分能力还是基于原有的产品,领域的判断也不是很清晰。但随着产品的发展,新产品的功能会越来越丰富,产品间的领域也会越来越清晰。一般当迭代需要花很多时候讨论功能界线、开发冲突越来越多的时候,就是一个彻底进行隔离的明显信号。随着业务的发展,隔离的形态我们可以按三个阶段进行描述:
- 阶段一:新产品体现为单独的模块
- 阶段二:新产品独立,公共部分依赖原产品
- 阶段三:公共业务独立,新旧产品平等。
多产品数据隔离
在系统的设计过程中,不同模块应当尽量避免直接依赖数据表,而应该通过接口来进行交互。因为数据库层面的依赖在开发时不容易识别,需要调整的时候也比较复杂,容易出现影响点评估遗漏,改动大等容易出BUG的情况,而且改动工作量大,团队沟通协作成本高。随着业务隔离的清晰,不同产品可以放到不同的数据库中,最终形成按租户+业务分库的数据架构。
对于需要查询大量数据,且需要联表查询的业务来说,完全不依赖数据库,都通过代码来实现会带来挑战。对于不同的阶段,解决方案也不同。
- 初期未分库时,可以约定新增和修改数据必须通过接口,查询取数可以联表查询。在代码中建议将查询的部分独立出来,进行关联影响分析时,可以针对这部分代码中涉及的表进行查找分析。
- 后期按产品分库后,可以使用数据清洗的方式对数据进行冗余。一般来说,公共业务的数据和各产品关联性较强,查询也比较频繁。对于数据量大的,可以考虑将数据分发冗余到不同的业务库中;对于数据量不大,但使用频繁的部分,也可以考虑放到缓存中获取。产品间的数据一般只有在数据分析的场景才会有大数据量的关联(像单页取数,使用关联数据的ID通过接口获取基本上都能满足性能要求),可以考虑使用异步的方式将同一租户的所有业务宽表清洗到同一个分析库中,分析功能统一从分析库进行取数。
数据的隔离还会对事务操作形成挑战。同样对于不同的阶段,可以采用不同的方案来保障事务操作。
- 初期未分库,针对需要在同一事务完成的操作,可以使用数据库的嵌套事务或子事务来实现,需要注意的是不要在开启事务后进行数据查询、远程调用等耗时的操作,避免锁时间过长带来性能问题。
事务处理对于不同的语言,实现方式可能需要一定的技巧,大体来说可以先将逻辑处理依赖的数据先获取出来(包括远程调用),逻辑处理完成后再开启事务,调用不同模块的数据操作接口然后结束事务。数据处理完成后,再调用处理结束后影响的远程接口。
- 后期按产品分库后,尽量使用数据最终一致性的方案,只有在约束特别强的情况下才考虑分布式事务。
多产品部署隔离
对于不同的产品,可能会有类似于不同租户的隔离需求。比如某个产品主要是面向大量C端用户的,有的产品主要是面向内部管理的。为了隔离产品间的相互影响,针对不同类型的产品采取更好的流量控制策略,可以将原有的部署架构由支持租户隔离升级为支持产品+租户隔离。当然是否有必要这样做,取决于产品的用户量和性能、可用性需求。
多产品中间件隔离
- 缓存隔离:随着多产品概念的出现,我们应当针对不前的产品对缓存的Key进行隔离,比如不同产品使用不同的前缀,以利于分析和迁移。如果单个缓存集群不能满足性能要求,或者基于可用性要求需要隔离,通过产品的部署隔离,使得我们能够更简单的对不同的产品服务配置不同的缓存服务,从而达到按业务扩展缓存的目的。
- 对象存储隔离:根据前期的多租户架构设计,我们能够很好的对不同产品的存储进行区分,如果单个对象存储实例不能满足性能或安全的需求,我们可以针对不同产品使用不同的对象存储实例。
- 任务调度隔离:在按前期按租户隔离的基础上,需要添加按产品进行隔离的能力。任务的颗粒度尽量细分,不同产品的任务、跨多产品的任务相互独立,有利于使用更灵活的调度策略。
架构重构的时机
对于有经验的团队和架构师来说,上面描述的很多过程可能是自然而然发生的,当我们的迭代因为架构原因进度变慢、复杂度上升、沟通成本太高、质量变差等情况时,自然会找到恰当的时机考虑对架构进行重构。而架构重构的风险也相对也比较大,难易程度也取决于团队前期对业务理解的深度,以及架构和代码设计和业务的匹配度。对于没有类似经验的团队,我们有以下几点建议:
-
架构的合理性取决于架构和业务的匹配度,一切以对业务的理解和洞察为基础。随着系统的建设和用户使用的深入,业务和最初的设想可能会发生很大的变化,团队在过程中也会对业务和用户有更深入的理解,能够做出更好的系统演进的预判。所以当我们认识到现有的架构与业务偏离较大时,就应该考虑找机会进行重构。
往往我们在意识到问题的时候,已经有了比较重的负担和技术债务。甚至已经发现了问题,但由于业务压力,得不到业务认可等各种原因找不到机会进行重构。这个时候我们需要认识到一次性的大重构往往是“奢侈”的,业务不会停下来等,而且也是高风险的。因此建议是在意识到问题后,确定逐步重构的方案:先打好新基础,新功能在新的基础上实现,然后在迭代中逐步迁移旧的能力。需要注意的是我们需要有一个相对完整的计划,避免烂尾导致的后患无穷。
-
在迭代达不到预期时,争取重构机会。产品经理和老板往往很难理解为什么要花时间、冒风险进行重构。特别是在业务快速发展的时候。做为技术Leader,有义务说明原因,争取重构机会。首先应当摆正心态,站在对方角度考虑问题。重构不是为了技术完美主义,而是为了更快、更好的支撑业务和公司的发展,更好的服务客户。当产品经理认为一个很简单的修改为什么需要那么长时间的时候、当多次迭代都在复杂模块发现未识别到的缺陷的时候、当跨团队沟通需要花大量迭代时间的时候,都是团队产能和质量下降的信号。这个时候分析根因为架构和业务不匹配时,和业务方进行说明是很好的时机。
-
事故驱动设计变革。虽然我们不愿意,也应该尽量避免生产事故的发生,但它还是不可避免的会到来。如果团队的工程工作相对较好,导致事故发生的深层次原因往往在架构层面。如果确定是架构层面的原因,团队可以基于事故复盘和后续的行动计划来进行架构的变革和完善。
避免过度设计,没有一劳永逸的完美方案。基于当前对业务的了解,以及对可预见变化的洞察进行恰当的设计。过度设计往往和现有情况不匹配,使问题变得复杂和难以理解,而且你担心的问题可能经常不会发生。当然针对特定的问题域,我们可以参考已有的架构设计模式,特别是我们经验不足的时候。但我们需要充分理解设计背后的目的,与我们现有场景的匹配程度,模式本身的缺陷等情况,避免盲目套用,适得其反。