点击围观!腾讯 TAPD 助力金融行业研发提效、敏捷转型最佳实践! 了解详情
写点什么

微服务落地,我们在考虑什么?

  • 2019-04-05
  • 本文字数:0 字

    阅读完需:约 1 分钟

微服务落地,我们在考虑什么?

微服务已经成为过去几年软件架构设计的“事实标准”,大多数企业在推动内部数字化转型的过程中,服务软件系统开始由单一或者 SOA 服务向微服务转型,那么转型过程应该如何落实,需要遵循什么原则呢?本文结合过往微服务落地实践经验,分享微服务落地实践的过程中思考。


目前当技术人员提及微服务的时候,首先想到的是 spring cloud,Dubbo 等实现服务的技术框架。这在我们采用微服务的初期阶段,这也是最先考虑的因素。可是随着服务化的进行,我们并没有享受到由框架的便利性与快捷性带来业务的突飞猛进的成就感,恰恰相反,过多的服务化以及服务间冗余且多元化通信机制反而加重了业务处理的负担。这必然不是我们想要的微服务,却是大多数企业在执行的微服务。


因此我们开始重新审视整个行业,审视微服务的发展历程。与过往不同的是:前期阶段,我们把更多的精力投入到业务上,而一定程度上“忽略”技术,因为此时我们建立的信念是无论何种形式的“服务形态”一定是为业务服务的。


当我们站在全局的角度,观看整理后的服务,发现了一个极其优美的图形化结构,各个节点的边界清晰,职责分明;节点间的链路畅通,协议规整。这时我们知道我们终于走在了正确的道路上。

我们遵循的原则

当经过一定时间的挣扎以后,我们觉得微服务的关注点不在于技术本身,但并不意味着不关注技术。在反思过程中,我们认为微服务实践中有两个原则不能变:服务一定是围绕业务的,服务的交互是标准的。我们把原则分为两个阶段:初期阶段和实践阶段。

初期阶段

初期阶段,遵循第一条原则,服务一定是围绕业务的。微服务初期阶段,重要的是业务梳理,而不是花费大量时间在 RPC、Service Discovery、 Circuit Breaker 这些概念或者 Eureka,Docker,Gateway,Dubbo 等技术框架的调研上。此时我们重心关注服务的边界与职责划分。


这是遵循的两条原则:


(1)保证单一业务服务高效聚合;


(2)降低服务间的相互调用(此举是避免陷入大量分布式业务的处理)。这样的原则下,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,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

Content 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

  • 202+success message with status uri:异步处理,返回进度查询资源 uri(/api/vendor/v1/status/{id})

  • 400+success+errors(含出错项 index 的错误列表):批量创建时部分成功,返回成功信息和错误信息

  • 401+exception{error_code+message}:缺乏认证信息

  • 403+exception{error_code+message}:未授权访问,访问被拒绝

  • 406+exception{ error_code+message}:不支持 client 要求的格式或语言时返回该信息(Not Acceptable)

  • 415+exception{error_code+message}:请求中的文档格式不支持

  • 422+exception{error_code+message}:不能处理的数据,比如 json 格式错误、文件内容项错误或会破坏业务规则

  • 429+exception{ error_code+message}:太多请求,流控时使用                    

  • 500+exception{error_code+message}:服务器内部错误。

统一日志拦截

基于 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 要写不同的数据解析方式,从而造成大量重复的工作。

遗留问题

当然我们落地过程的选择也不一定尽善尽美,也有很多随着业务处理能力的加强,之前没有考虑到的问题,例如:


  • 各个服务自身并发数据支撑能力;

  • 服务交互的内部代码瓶颈,包括调用链路冗余,响应偏慢等;

  • 数据库的并发支撑与性能优化;

  • 与容器服务集成的参数配置,开发与部署环境的转变;

  • 调用链路可能出现的回环问题,交叉的业务单元调用,导致调用链路混乱;

  • 数据的缓存设计,加快业务响应速率;

  • 这些问题在后续不断深入的理解和探索中,都会找到相应的解决方案。


2019-04-05 09:006026

评论 3 条评论

发布
用户头像
既然使用了HttpCode码的语义,为什么还要在返回的消息体中加入code呢,你能举出HttpCode不能满足的场景吗?
2019-04-10 20:21
回复
应该是考虑不同业务,不同错误类型对应不同的错误码。
2019-04-11 21:30
回复
HttpCode的语义更多是通用协议层面的解读,并不能包含真正的业务语义,在返回消息体重加入的code,是用来标明当前HttpCode所携带的具体业务信息,方便操作人员能够快速定位业务故障。
2019-04-23 16:04
回复
没有更多了
发现更多内容

第 4 周作业

老元宵

ElasticSearch.04 - 基础操作

insight

elasticsearch 2月春节不断更

心理声学基础

行者AI

心理 音乐

14. Python 与数据库那点事儿,滚雪球学 Python

梦想橡皮擦

python 爬虫 2月春节不断更

算法从有序数组中移除重复的数据,AI学习资源2020 John 易筋 ARTS 打卡 Week 38

John(易筋)

ARTS 打卡计划 ai youbute学习资源

一维数组的动态和

小马哥

算法

阿里云大佬爆裂推荐“redis全新手册”,内容即精华

比伯

Java redis 程序员 架构 程序人生

Java中多线程启动,为什么调用的是start方法,而不是run方法?

Java 编程 架构

门诊数字化:患者信息识别方式

boshi

医疗 数字化基础 七日更

面试的季节到了,老哥确定不来复习下数据结构吗

Silently9527

面试 数据结构与算法

山东党建系统!组织部智慧管理平台搭建

源中瑞-龙先生

智慧党建 组织部 山东

区块链挖矿系统APP开发|区块链挖矿软件开发(现成)

v16629866266

厉害了!这群95后正在用三维成像技术让科幻变成现实

华为云开发者联盟

视频 华为云 三维 裸眼 光学

字幕组时代落幕,翻译的未来可能是?

字节跳动技术团队

哲少荐书:鞋狗

Jackey

书籍推荐

日记 2021年2月18日(周四)

Changing Lin

2月春节不断更

【STM32】EXTI---外部中断/事件控制器

AXYZdong

硬件 stm32 2月春节不断更

华为云FusionInsight MRS在金融行业存算分离的实践

华为云开发者联盟

大数据 金融 华为云 存算分离 FusionInsight MRS

技术实践 | 新思路!解决线上系统异常问题

百度开发者中心

新闻|2021 FOSDEM为期两天的活动成功举办,一大波学习资源来袭!

PostgreSQLChina

数据库 postgresql 软件 开源社区

4.从legacy或concurrent开始(从入口开始,然后让我们奔向未来)

全栈潇晨

React React Hooks react源码

LeetCode题解:1091. 二进制矩阵中的最短路径,BFS,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

端口隔离和VLAN的区别

什么是阻抗?

不脱发的程序猿

阻抗 电路设计 电子元器件

2021新年最新分享:阿里Java岗5轮技术面经整理

比伯

Java 编程 架构 面试 程序人生

用例文档

三生赤水

话题讨论 | 如何使用“网站SEO”,让网站排在最前面?

我是哪吒

大前端 后端 话题讨论 SEO 2月春节不断更

Elasticsearch mapping 复杂数据类型

escray

elastic 七日更 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

【函数计算实践】nodejs初探示例——本地mac环境

程序员架构进阶

架构 nodejs 函数计算 七日更 2月春节不断更

如何 1 天快速集成自己的“Clubhouse”?

融云 RongCloud

音视频 clubhouse 语音社交 融云

微服务落地,我们在考虑什么?
_架构_李宁_InfoQ精选文章