导读mysql
微服务已经成为过去几年软件架构设计的“事实标准”,大多数企业在推进内部数字化转型的过程当中,服务软件系统开始由单一或者SOA服务向微服务转型。那么转型过程须要遵循哪些原则呢?本文结合过往博云微服务落地实践经验,分享微服务落地实践的过程当中思考。nginx
目前当技术人员说起微服务的时候,首先想到的是Spring Cloud、Dubbo等实现服务的技术框架。这在咱们采用微服务的初期阶段是最早考虑的因素。但是随着服务化的进行,咱们并无享受到由框架的便利性与快捷性所带来的业务日新月异的成就感。偏偏相反,过多的服务化以及服务间冗余且多元化通讯机制反而加剧了业务处理的负担。这必然不是咱们想要的微服务,倒是大多数企业在执行的微服务。git
所以咱们开始从新审视整个行业,审视微服务的发展历程。与过往不一样的是:前期阶段,咱们把更多的精力投入到业务上,而必定程度上“忽略”技术,由于此时咱们创建的信念是不管何种形式的“服务形态”必定是为业务服务的。web
当咱们站在全局的角度,观看整理后的服务,发现了一个及其优美的图形化结构,各个节点的边界清晰,职责分明;节点间的链路畅通,协议规整。这时咱们知道咱们终于走在了正确的道路上。redis
咱们遵循的原则spring
当通过必定时间的挣扎之后,咱们以为微服务的关注点不在于技术自己,但并不意味着不关注技术。在反思过程当中,咱们认为微服务实践中有两个原则不能变:服务必定是围绕业务的,服务的交互是标准的。咱们把原则分为两个阶段:初期阶段,实践阶段。sql
初期阶段数据库
初期阶段,遵循第一条原则,服务必定是围绕业务的。微服务初期阶段,重要的是业务梳理,而不是花费大量时间在RPC、Service Discovery、 Circuit Breaker这些概念或者Eureka,Docker,Gateway,Dubbo等技术框架的调研上,此时咱们重心关注服务的边界与职责划分。编程
这是遵循的两条原则:一、保证单一业务服务高效聚合;二、下降服务间的相互调用(此举是避免陷入大量分布式业务的处理)。这样的原则下,DDD为咱们提供了帮助,也依据业务自己的特性实现了服务初期阶段的整理。同时咱们发现就算借助DDD的指导,在不一样的业务应用中,各个服务也有不一样的聚合形态和调用方式。所以咱们以为微服务自己没有一成不变的模式,一切都是围绕业务动态变化的。合理性也仅仅体如今必定阶段的时间范围以内。json
实践阶段
当业务建模完成,咱们可以清晰的知道各个业务的职责以及与其余业务的关联关系,从理论层面咱们完成了业务微服务建模。此时咱们开始着手服务的落地实践,在落地实践阶段咱们更多关注点一样不在于技术框架,而是在于技术框架的内涵-即服务交互标准。
此时咱们遵循了第二条原则:服务的交互是标准的。所谓服务交互标准从三个层面解读:协议标准,框架标准,接口标准。
协议标准
目前网络应用的协议比较复杂,咱们但愿选取可以符合业务场景的协议做为通讯标准。所以咱们考虑了统一的认证鉴权协议、加密解密协议、内部接口交互协议,外围接口服务协议等,各个协议各司其职,用来支撑服务通讯的标准化。协议标准不只仅为平台自身服务,同时与其余业务单元进行通讯时,只须要遵循协议标准,就能够实现业务单元之间的快速联动。
框架标准
为了支撑业务,咱们没有依赖任何的自动化代码生成框架,而是根据咱们的协议支持状况,选择最小的服务运行框架,来构建统一的业务单元支撑框架。这里须要说明的一点,框架标准须要考虑业务特性,协议特性,不能一律而论,所以它的有效性也许只在当前构建的业务平台自己。构建标准框架的好处是针对应用内的全部业务单元能够快速复制,简化由于各自开发框架不一样致使联调阶段出现问题。
接口标准
接口分两种:业务内部接口和业务服务接口。不管哪一种接口一样遵循标准化原则。
业务内部接口的核心在于压缩协议,加快业务的处理流程,所以能够采用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,Spring cloud Zuul,Spring cloud gateway
负载均衡:nginx,spring cloud Ribbon,haproxy,Kubernetes service
服务远程调用:Spring cloud feign
缓存服务:memchace,redis
数据库:mariadb,mysql
消息服务:RabbitMQ,NATS,Kafka
配置中心:spring cloud config,Apollo,Consul
事件机制:Cloud Event
服务编排:Conductor ,Kubernetes
服务治理:spring cloud,Dubbo,ServiceMesh
基于消息机制的分布式事务处理(遵循CAP或者BASE理论模型的实现)
业务运行工具:jvm,nginx或者其余可运行环境支撑
开发编译工具:Jenkins,maven,gitlab
接口文档:Swagger
部署工具:物理部署(jar包或者可运行的编译的二进制文件)虚拟化部署(虚拟镜像模板)容器化部署(Docker)
咱们在落地的过程当中,根据团队技术特色开发阶段重点选择了Spring Cloud中涵盖的技术栈。方便易用,可以快速入手。运行阶段选择具有服务编排能力的Kubernetes容器化运行环境,而且结合Devops工具链可以快速迭代部署。
服务接口设计
服务接口是对外展示业务逻辑的惟一入口,接口定义的规范与否也是微服务落地的关键指标之一,咱们在实践的过程当中参考了多个开源项目的接口设计,针对任何一个资源对象,总体分为几类场景:资源集合类操做,资源实体操做,异常处理,参数处理,统一数据返回,审计日志以及其余具体场景。
统一的接口请求与响应标准
其中业务单元绝大多数端口围绕着资源集合类、资源实体类进行操做,所以咱们从restful接口规范出发,结合具体场景,规范了请求方式,请求url,请求参数,请求header,响应header,响应值等信息。
请求参数涵盖默认语义,包括:Get(获取信息),Post(建立),Put(全量修改),Patch(部分修改),Delete(删除)
以Students实体对象的新建为例,给出请求与响应标准。
URL
URL请求包括三部分:请求方式,统一前缀以及具体url,统一前缀具有必定含义的命名规则,包括api申明,供应商标识,版本说明等必要信息,例如:
Post /api/cloud/v1/students?exist={skip,replace}
请求header
type
aplication/json:用于single和bulk时,用来表示请求数据为json格式 application/vnd.ms-excel:从excel格式的文件导入建立
Accept
aplication/json:接受json格式的响应数据
Authorization
Oauth2.0的access token(bearer token)
Accept-Language(可选)
可接受的语言,国际化,en-US表示美国英语
请求数据格式+类型
json格式:{items:[]}
请求建立students对象json(表达):
请求(批量)建立student对象列表json(表达)
请求(批量)建立student信息excel文
响应header
Content-Type
aplication/json
Content-Language(可选)
内容语言
Last-Modified
数据最近一次修改的时间戳信息
响应值
Success message:多种类型
Error message:多种类型
Exception:多种类型
统一异常处理
统一异常处理包括状态码以及状态码涵盖的异常信息,具体部分定义以下:
200/201+success message(含资源数量信息+uri信息):建立成功,适用于数量很少(好比小于500)的建立操做,大于设定的值时进行异步处理,参加返回值202
统一日志拦截
基于AOP模式拦截全部请求,在请求入站与出站的时候,作统一日志记录以及须要的其余非业务处理(例如鉴权)
统一的数据返回标准
咱们参考Restful数据返回标准,封装咱们本身的数据返回格式:code,message,body,error,统一的数据返回格式能够在接口层作统一的拦截处理。实现返回数据的标准化。
code:返回状态码
message:返回响应结果的语义解释
body:响应的具体数据信息,包括metada信息,具体响应数据以及请求链接
error:表明返回的错误信息
具体的响应格式以下所示:
{ "code": 200, "message": "获取学生列表成功", "body": { "links": [ { "rel": "self", "href": "http://localhost:8080/api/cloud/v1/students?name=test&startDate=2019-01-01&endDate=2019-09-01&style=normal&sort=asc&limit=10&offset=0{&fields}", "hreflang": null, "media": null, "title": null, "type": null, "deprecation": null } ], "metadata": [] "content": [ { "id": 1, "name": "test3", "status": "running", "props": "test", "remark": "test", "ownerId": 1, "createrId": 1, "menderId": 1, "gmtCreate": "2019-03-11 10:42:15", "gmtModify": null, "startDate": null, "endDate": null, "links": [ { "rel": "self", "href": "http://localhost:8080/api/cloud/v1/students/1?style=normal&fields=", "hreflang": null, "media": null, "title": null, "type": null, "deprecation": null } ] } ] } "errors": {} }
服务接口的设计必定是围绕标准化的规则进行的,这样才能在后期减小由于接口变更致使不断出现的先后端联调问题。由于在实践中咱们常常遇到格式不统一致使web要写不一样的数据解析方式,从而形成大量重复的工做。
遗留问题
固然咱们落地过程的选择也不必定尽善尽美,也有不少随着业务处理能力的增强而在以前没有考虑到的问题,例如:
这些问题咱们在后续不断深刻地理解和探索中会找到相应的解决方案,你们能够在后续继续关注咱们的微服务解决方案。