当前位置: 首页 > 科技观察

当微服务落地时,我们在想什么?

时间:2023-03-16 17:07:45 科技观察

介绍:微服务在过去几年已经成为软件架构设计的“事实标准”。在推进内部数字化转型的过程中,大部分企业已经开始将服务软件系统从单体或SOA服务向微服务转型。那么在改造过程中应该遵循哪些原则呢?本文结合以往博云微服务实现的实践经验,分享微服务实现过程中的思考。目前,技术人员一提到微服务,首先想到的就是SpringCloud、Dubbo等实现服务的技术框架。这是我们早期采用微服务的第一个考虑。然而,随着服务化的推进,我们还没有享受到框架便捷、快捷带来的成就感。相反,服务之间过度的服务化和冗余、多样化的通信机制增加了业务处理的负担。这当然不是我们想要的微服务,但却是大多数企业正在实施的微服务。于是,我们开始重新审视整个行业,审视微服务的发展历程。与以往不同的是,前期我们把更多的精力放在了业务上,一定程度上“忽略”了技术,因为这时候我们树立了这样的信念,无论是什么形式的“服务形态”都必须是为了业务服务。当我们从全局的角度来看组织起来的服务时,我们发现了一个极其漂亮的图形化结构,每个节点边界清晰,职责明确;节点之间的平滑链接和常规协议。那时候我们知道我们终于走上了正确的轨道。我们遵循的原则经过一定时间的奋斗,我们觉得微服务的重点不在技术本身,但不代表我们不关注技术。在反思的过程中,我们认为在微服务的实践中有两个原则是不能改变的:服务必须面向业务,服务交互是标准的。我们将原则分为两个阶段:初始阶段和实践阶段。在起步阶段,遵循第一原则,服务一定要围绕业务。微服务起步阶段,重要的是梳理业务,而不是花大量时间在RPC、ServiceDiscovery、CircuitBreaker等概念上,或者研究Eureka、Docker、Gateway、Dubbo等技术框架.此时,我们关注职责的边界和分离。这是要遵循的两个原则:保证单体业务服务的高效聚合;减少服务之间的相互调用(这是为了避免陷入大量分布式业务的处理)。在这个原则下,DDD为我们提供了帮助,同时也实现了根据业务本身的特点来安排服务的初始阶段。同时我们发现,即使在DDD的指导下,在不同的业务应用中,每个服务的聚合形式和调用方式也不同。所以我们觉得微服务本身并没有静态的模型,一切都是围绕着业务动态变化的。理性只在一定时期内体现。在实战阶段,当业务建模完成后,我们就可以清楚地知道各个业务的职责以及与其他业务的关系。从理论层面,我们完成了业务微服务建模。这时候,我们开始进行服务实现。在实施阶段,我们更多关注的不是技术框架,而是技术框架的内涵——服务交互标准。此时我们遵循第二个原则:服务的交互是标准的。所谓服务交互标准,是从协议标准、框架标准、接口标准三个层次来解读的。协议标准目前,网络应用的协议比较复杂。我们希望选择能够满足业务场景的协议作为通信标准。因此,我们考虑了统一的认证协议、加解密协议、内部接口交互协议、外围接口服务协议等,各司其职,支持服务通信的标准化。协议标准不仅服务于平台本身,在与其他业务单元通信时也需要遵循协议标准,实现业务单元之间的快速联动。框架标准为了支撑业务,我们不依赖任何自动代码生成框架,而是根据我们的协议支持选择最小的服务运行框架来构建统一的业务单元支撑框架。这里需要说明的是,框架标准需要考虑业务特性和协议特性,不能一概而论,因此其有效性可能只在当前业务平台本身。构建标准框架的好处是可以为应用中的所有业务单元快速复制,简化了不同开发框架带来的联调阶段的问题。接口标准接口有两种类型:业务内部接口和业务服务接口。不管什么样的接口也遵循标准化的原则。业务内部接口的核心是压缩协议,加快业务流程,因此可以使用RPC等高效协议支持的接口方式;业务服务接口的核心是表示业务承载的信息,所以使用restful接口规范比较合适。接口设计需要涵盖但不限于标准化的请求方式、统一的参数处理、统一的结果返回、统一的异常处理、统一的日志处理等。服务拆分与聚合前提:服务拆分与聚合本文中微服务设计web的暂时不考虑,只说明后端服务拆分和聚合的做法。服务拆分和聚合要遵循的原则:服务必须围绕业务。但事实是,在当下追求“开源集成”的背景下,纯业务单元在不借助第三方工具的情况下,需要消耗巨大的成本来实现业务需求。依赖性强。因此,我们在拆分和聚合服务时,考虑了两种实现方式:业务支持和工具支持。业务支持业务支持需要考虑业务服务对象和业务的内部逻辑。业务服务对象作为整个业务单元的外在形式,可以通过命名明确表达其涵盖的业务范围;业务内部逻辑需要对业务单元进行细粒度的拆分,类似于一个实体类可以包含多个其他关联的实体对象(当然,如果业务拆分的足够多,内部逻辑也可以看成是一个独立的业务单元,但这会增加业务的直接通信负载)。基于内部业务逻辑构建业务服务对象的真实场景。具体的拆分细节可以依靠DDD的实用方法来进行(当然需要根据业务做相应的调整,没有通用的方法)。工具支持工具支持需要结合业务考虑,分为通用工具和专用工具两种。通用工具旨在为所有业务单元的运营提供统一的支撑平台,从而减少工具维护所花费的精力,使业务开发人员能够专注于业务实施。通用工具包包括统一的日志处理、统一的拦截处理、返回数据的统一打包、异常的统一处理等;专门的工具专注于特定的业务部门,由业务部门自己维护(例如迭代升级)。工具支持层面不会对外提供restful或者rpc接口,对外表现形式是一个编译好的依赖工具包(比如对Github的管理接口的封装)。根据实现原则完成服务架构选择后,我们需要考虑合适的实现选择。选型方案需要考虑的因素很多:技术背景(尤其是团队中编程语言的设置)、服务支撑工具(注册中心、网关、服务调用、负载均衡数据库等)、服务运行工具(tomcat、jetty、jboss等),服务部署工具(物理部署、虚拟化、容器等),工具协议支持集合(http、rpc、mtqq、idoc等)。但无论如何,选择最终还是要根据团队开发者当前的技能支持。这也是我们选择的核心因素,因为白盒总是比黑盒相对安全,相对可控。这里是我们的技术栈选择框架(只是我们熟悉的内容),暂时不涉及技术框架的对比说明。服务开发框架:springboot、dubbo、grpc、ServiceMesh(基于ServiceMesh的开发服务框架)分布式存储/注册中心:Zookeeper、Consul、Eureka、Etcd服务网关:Kong、Openresty、SpringcloudZuul、Springcloudgateway负载均衡:nginx、springcloudRibbon、haproxy、Kubernetesservice服务远程调用:springcloudfeign缓存服务:memchace、redis数据库:mariadb、mysql消息服务:RabbitMQ、NATS、Kafka配置中心:springcloudconfig、Apollo、Consul事件机制:CloudEvent服务编排:Conductor、Kubernetes服务治理:springcloud、Dubbo、ServiceMesh基于消息机制的分布式事务处理(遵循CAP或BASE理论模型实现)业务运行工具:jvm、nginx或其他可执行环境支持开发编译工具:Jenkins、maven、gitlab接口文档:Swagger部署工具:物理部署(jar包或可执行编译二进制文件)虚拟化部署(虚拟镜像模板)容器化部署(Docker)我们在落地的过程中,根据团队的技术特点,开发阶段重点选择SpringCloud涵盖的技术栈。方便易用,可快速上手。在运行阶段,选择具有服务编排能力的Kubernetes容器化运行环境,结合DevOps工具链,可以快速迭代部署。服务接口设计服务接口是对外展示业务逻辑的唯一入口。接口定义是否规范也是微服务实现的关键指标之一。在实践过程中,我们参考了多个开源项目的界面设计。对于任何资源对象,分为几类场景:资源集合操作、资源实体操作、异常处理、参数处理、统一数据返回、审计日志等特定场景。统一的接口请求和响应标准。业务单元的端口大多围绕资源集合类和资源实体类进行操作。因此,我们从restful接口规范入手,结合具体场景,对请求方法、请求url、请求参数以及请求头、响应头、响应值等信息进行了规范。请求参数涵盖默认语义,包括:Get(获取信息)、Post(创建)、Put(全部修改)、Patch(部分修改)、Delete(删除)。以创建Students实体对象为例,给出请求和响应标准。URLURL请求包括请求方法、统一前缀和具体url三部分,统一前缀具有一定的含义命名规则,包括api声明、供应商标识、版本说明等必要信息,例如:post/api/cloud/v1/students?exist={skip,replace}requestheadertypeapplication/json:用于single和bulk,用于表示请求数据为json格式application/vnd.ms-excel:从excel格式的文件导入创建Acceptapplication/json:acceptResponsedatainjsonformatAuthorizationOauth2.0'saccesstoken(bearertoken)Accept-Language(optional)可接受语言,国际化,en-US表示美式英语请求数据格式+typejsonformat:{items:[]}请求创建学生对象json(expression):request(batch)创建学生对象列表json(expression)request(batch)创建学生信息excel文本响应头Content-Typeaplication/jsonContent-Language(optional)contentlanguageLast-The时间戳信息最后一次修改的响应值ModifieddataSuccessmessage:多种类型Errormessage:多种类型Exception:多种类型的统一异常处理统一的异常处理包括状态码和exc状态代码涵盖的选项信息。具体部分定义如下:200/201+successmessage(包含资源数量信息+uri信息):创建成功,适用于数量较少(如小于500)的创建操作,数量多时异步处理大于设置值,返回值202202+successmessagewithstatusuri:异步处理,返回进度查询资源uri(/api/vendor/v1/status/{id})400+success+errors(错误列表包括erroritemindex):批量创建部分成功,返回成功信息和错误信息401+exception{error_code+message}:缺少认证信息403+exception{error_code+message}:未授权访问,访问被拒绝406+exception{error_code+message}:当不支持客户端要求的格式或语言时返回此消息(NotAcceptable)415+exception{error_code+message}:Thedocumentformatintherequestdoesnotsupport422+exception{error_code+message}:无法处理的数据,如json格式错误,文件内容项错误或将被破坏业务规则429+exception{error_code+message}:请求过多,使用500+exception{error_code+message}进行流控:服务器内部错误统一日志拦截基于AOP方式拦截所有请求,统一日志记录,需要时请求入站和出站其他非业务处理(比如鉴权)的统一数据返回标准,我们参考Restful数据返回标准封装了自己的数据返回格式:code,message,body,error,统一的数据返回格式,可以在接口层统一拦截处理,实现返回数据的规范化。code:返回状态码message:返回响应的语义解释resultbody:响应的具体数据信息,包括元数据信息、具体响应数据和请求连接error:表示返回的错误信息具体响应格式如下:{"code":200,"message":"获取学生列表成功","body":{"links":[{"rel":"self","href":"http://localhost:8080/api/clou...t%3D0{&fields}","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}],"metadata":[]"content":[{"id":1,"name":"test3","status":"running","props":"test","re??mark":"test","ownerId":1,"createrId":1,"menderId":1,"gmtCreate":"2019-03-1110:42:15","gmtModify":null,"startDate":null,"endDate":null,"链接":[{"rel":"self","href":"http://localhost:8080/api/clou...ot%3B,"hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}]}"errors":{}}服务接口的设计一定要围绕标准化的规则进行,减少后期因接口变更带来的前后端联调问题。因为在实践中,我们经常会遇到格式不一致导致网络上数据分析方法不同的情况。这导致大量的重复工作。遗留问题当然,我们的落地流程选择不一定是完美的,随着业务处理能力的加强,还有很多以前没有考虑到的问题,比如:各服务自身并发数据支持能力的内部代码瓶颈与服务交互,包括调用链路冗余、响应慢等。数据库并发支持和性能优化及容器服务集成参数配置,开发部署环境变化,调用链路可能出现环回问题,跨业务单元调用,导致调用环节出现数据混乱我们会在后续深入理解和探索缓存设计加快业务响应速度的过程中找到相应的解决方案,大家可以持续关注我们的微服务后续解决方案。