赞
踩
最近回忆了一下过去,经历了很多开会,出方案,开发,测试,联调,写checklist,提单,灰度,验证,再灰度,再验证的过程,发现了一些好的地方和不好的地方,就我之前的经历分享一下,怎么样提升研发质量和效率。
从方案设计,开发,测试,上线计划等几个阶段来讨论一些原则类的问题。
为何要做方案设计?它不仅仅是一个留存的文档,让新人来熟悉业务和代码,而是为了让其他同同事把关,帮忙确认风险和不足。
一个好的方案需要充分考虑到系统的 可靠性 可拓展性 易于运维,你必须认识到,方案评审的目的不是别人来给你挑刺找茬,而是为你想将来可能出现的问题。
一个业务系统的某一个业务场景中,会有一到多个业务流程,可以按业务场景分类把所有的业务流程整理出来,这样更清晰。
每个业务流程会有多个步骤和依赖,确认流程中哪些是核心的,哪些是非核心的。核心步骤失败意味着整个流程失败,所有核心步骤成功意味着业务流程成功。非核心流程失败不影响核心流程的结果。
核心步骤的依赖就是核心依赖,我们需要重点关注核心步骤的结果和核心依赖的可靠性。
如果核心依赖是一个分布式基础组件,分布式数据库,zk这种高可用的组件,可以不用过于关注,除非它的SLA无法满足需求。
如果核心依赖是其他服务,需求确认接口是否可以重试,是否支持幂等,是否支持事务,是否可能存在中间状态,如果存在中间状态需要能自动修正。
非核心步骤一般是对于内/外部系统的通知,当流程的非核心步骤失败时,流程可以继续执行,但是其他系统可能服务获取通知,导致数据不一致,这里一般把消息写入mq或者db,通过定时重试来解决。
核心依赖一般不允许降级,但是当确认核心依赖完全不可用时,再确保影响尽量小的情况下,实行短时间的降级。比如飞书套件之前并不是免费的,企业付费套餐信息的license记录再数据库中,某天数据库出现问题,查询失败,导致降级的结果是企业全是免费套餐,导致文档数量等受限,无法创建文档。后来改为降级为付费用户。
业务降级是一个业务问题,不是技术问题,需要在业务层讨论和决策。
在不影响业务逻辑的情况下的降级,是技术降级,在技术层决策。
所有用户输入都是不可信的,参数必须校验。
充分评估性能,做好限流,限速。
当核心依赖出现性能问题时,根据业务的优先级,通过动态配置,只保证重要的流程的执行,中低优先级的流程直接拒绝。
对外保证SLA的时候要确认对应的前提。
核心步骤的执行次数,结果和执行时间都需要打点,所有修改db的操作必须有流水日志。
配置系统容量的告警,提醒及时扩容
测试用例需要覆盖所有的业务场景的所有业务流程,需要有异常流程的用例。
系统如何划分为多个子系统,一个服务内部应该分那些模块?首先需要确认边界。
系统一般是根据业务特性来划分,首先确认这个系统有哪些职责,确立这些职责的边界,系统可以可以划分称多个子系统(业务域)了。确认了每个子系统该做什么不该做什么,该暴露什么,不该暴露什么。
每个子系统的技术复杂度不同,可以根据技术实现方式或者内部在做进一步的业务划分,分成多个服务。每个服务再根据各自的业务场景设计接口,要确保接口的职责范围,尽量单一,参数的灵活性,调用方通过多个接口的组合可以实现所有业务场景即可。
如果需求方提一个接口解决所有问题的需要时直接拒绝,对外提供查询的聚合类服务除外。
服务内部模块划分和服务的划分原则基本一致。
大部分系统拓展性有问题的原因都是前期设计时分工不明确,多个子系统做了相同的事情,或者本该一个子系统完成的工作让多个子系统参与,子系统暴露了不该暴露的数据被其他子系统应用,彼此耦合,改一处。确认好职责和边界后,整个系统的拓展性就会很好。
业务配置动态化,可以动态调整业务参数,确认服务运行的更好。
核心指标监控,服务健康状态易于观察,便于快速发现问题。
日志信息详细,需要确保出现问题时,日志提供的内容能够帮助定位问题。
背景
介绍为什么做这件事,是为了解决什么问题,突出做这件事的价值是什么,做了有什么好处,不做有什么坏处
业务的大致介绍,画一下业务架构
专有名词介绍
达成目标(在什么前提下达成)
短期目标
长期目标
系统业务架构
根据业务职责和边界,划分出业务架构
突出每个业务域的职责和关系
技术架构
每个业务域的技术实现,划分成了哪几个服务,每个服务的职责是什么
使用了那些基础组件,数据流程是怎么样的
突出重点和难点,详细介绍解决方法
可能出现异常流程和依赖失效的场景和对应的解决方法
详细设计
每个服务的接口设计,每个字段需要有注释
模块设计,类图,时序图
存储设计
关系数据库的表设计,每个字段必须有注释
需要罗列相关sql的查询场景和频率,用于评估索引设计的合理性
每天数据量的评估
缓存数据结构的设计,失效时间如何配置,缓存命中统计等
测试用例
正对以上提到的业务场景和业务流程,设计测试用例
明确每个用例的前置准备和输入,确认期望输出或行为
后期改进计划
当前设计是否有不足的地方,后期改进计划
长期规划
灾备方案设计等
评审主要是相关同事一起从业务和技术层来看一个方案,目的是发现可能存在问题,把控风险,不是挑刺。
多个方案可选择时,结合当前的实际情况,如人力和时间,短期目标和长期方案复杂度的情况下,选择当前最适宜的的方案,而不是最全面的方案。
好的设计是演变而来的,业务是多变的,很难一开始就设计出一个方案能应对未来多年的业务发展。也不存在一个完美的方案解决所有问题,只能确保在当前的有限的资源下,给出当下最优秀的方案,在多个因素之间权衡。
最终的产品质量和开发时间和人力权衡,服务性能和数据一致性之间的权衡,使用读写分离时数据延迟的影响等等。
方案评审过后,就可以分工开发了。
代码一般要求可读性,可拓展性和可测试性。一般来说拓展性好的代码,可读性和可测试性都不会差。
代码拓展性好的原则只有一个,解耦合,依赖于抽象(接口),不依赖具体(实现),这个大家都知道,但是。
这里推荐一个比较简单的代码结构,可以兼顾可读性,拓展性且易于测试。
这里Service表示核心逻辑,也就是实现业务流程的逻辑代码,一个服务可以由多个Service。
Service的实现往往需要依赖外部组件,如数据库,配置信息,消息队列,调用其他服务等,所有service可以单向依赖这些外部组件的接口,这些外部组件除了可以使用common库和依赖配置接口之外,彼此之间不能有任何依赖,核心逻辑service通过调用外部依赖的接口实现所有有核心流程的逻辑。
一个微服务本质上需要处理数据输入,实现自己的RPC接口或处理事件消息,事件消息一般来自于mq,当然也可以是rpc或者轮询数据库,这都不重要,对service来说都只是事件消息,service不用关注消息具体是来自于哪里。
Service实现了服务的核心逻辑,可以处理所有业务请求,它不关注业务请求是来自于rpc,http还是mq,还是tcp自定义协议,只有按它的要求传参数,它就能返回相应的结果。
在我们的rpc框架生成的代码XXXXImpl函数中,只需要把rpc request转换成Service要求的入参,然后调用Service的接口,将service返回的结果在写入rpc response中,rpc接口就实现了。这么看来框架生成的XXXXImpl本质上是一个适配器,它把外部输入转换成一个service可识别的数据结构,使用service处理,再把结构转换成对应的输入返回。一般称之为适配层或反腐层。
RPC框架 调用Service处理请求,Service依赖外部组件的接口处理请求。Service内部可以由多个模块组成,这里不展开讨论了。
禁止把RPC/FIX的request/response直接作为参数传给service,这会造成service对RPC/FIX的不必要的耦合。当请求的接口参数很多时,额外的数据转化会增加一些代码量,但是对于服务的长期维护来说是一本万利的。
这块打算单独写一个文档分享。
上文提到结合业务场景的业务流程做系统架构划分,做服务接口设计,写测试用例,而服务的核心逻辑是Service,本质上我们只需要把Service测试全面,就可以保证服务的逻辑是准确的。
大公司内部的上线流程已经很全面,这里不在过多讲述,简单介绍一下曾经用到的灰度流程。
对于无状态的服务,可以有一个权重很小的小流量的部署组,发布时,必须线发布小流量部署组,确保正常运行超过半天以上,才能继续发布
可以搭建一个线上专有集群,用于线上灰度,适用于修特别大或风险高的需求发布。支持使用自定义策略,将拥有某种特征的流量路由到灰度集群,确保出问题的时候影响可控。
比如终端链路是否可以作为灰度集群?
把整个系统垂直划分为多个单元(set),每个set都可以提供完整的业务能力,set之间完全独立,对等,都是多机房部署,彼此之间流量隔离。将用户/流量按一定规则分散到多个set中,让set之间做互备,用于容灾,故障隔离。当某个set中的服务出现故障时,可以快速切断故障set的流量入口,让故障set中的用户/流量自动迁移到其他的set。
系统set化之后,可以有部分服务/组件位于公共区域,公共域的服务/组件被所有set共用。常用的公共域一般是数据域。否则需要set之前做数据同步十分复杂。
严格定义Error日志,一般来说Error代表业务是有损的,需要人工排查的,不是这种级别的问题,或者某些重试机制能全部成功的错误可以用Warn代替。
可以封装日志组件,支持Error日志自动告警。
慢查询日志,帮助排查问题。
有的系统重构,底层数据存储可能完全无法兼容,需要独立的一套存储,这种情况直接发布替换的风险较高。
可以同时保留服务的新老接口,或新老服务,新服务or接口上线后,线上使用老服务or老接口,通过动态配置控制在一定的概率下,在老接口返回后(避免影响老接口的耗时),自动使用相关相关参数调用新接口,对比新老接口返回的数据,不匹配则输出日志or打点,一段时候确认新接口完全正常后,可以逐步矿大新接口的适用范围。
上线方式和上面的方法类似,首先将老版本的存量数据转换成新版本数据导入新服务的存储组件中。新服务全量上线,所有读写接口继续调用老服务,写接口老服务转发给新接口,确保新老服务的数据双写,每天/每周定时check 新老数据(逻辑上一致),确认新服务正常后,关闭老服务的写(只转发写新服务),切换老服务的读(转到读新服务),对调用方完全无感知。
云原生服务网格Istio:原理、实践、架构与源码解析
微服务架构设计模式
MySQL技术内幕 InnoDB存储引擎
Redis 深度历险:核心原理与应用实践
Kafka权威指南
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。