背景
微服务会把大的应用拆分成若干小的服务应用和前端应用,如何协调/治理这些应用,并解决在开发中遇到的各种问题是微服务面临的挑战。通常一个微服务系统需要关注的问题有:
- 服务的注册发现
- 服务间的远程调用
- 负载均衡/东西向流量操控
- 网关/南北向流量操控
- 弹性伸缩
- 服务的调用链跟踪
- 日志收集和告警
- 熔断和限流
- 远程配置管理
- 健康检测
- 故障恢复
- 服务自动漂移 等等
为了解决上述问题,诞生了象 Spring Cloud 这样伟大的开发框架和系统。但这些能力都分布在各种 SDK 中,需要编写特定的代码进行操控,业务开发者需要理解整个框架,并熟悉周边的辅助系统。这些,无疑对开发者造成了困扰,提高了入门门槛。这个也造成了很多企业对于微服务改造的恐惧。
服务网格恰恰屏蔽了这些内容,将服务治理完全下沉到网络层。开发者无需再编写与业务无关的代码了。
“我只要写好 restful 的服务,丢到服务网格体系中,就 run 起来了,上面的那些能力自动获得。”
一个典型的服务网格应用示意如下图:
开发框架和组织的解耦
微服务通常由多个小组协同开发。在不了解服务网格的情况下,一般的企业技术决策者对于研发部门的能力考虑是单一技术栈,单一框架,考虑的出发点是技术栈可以保持延续,主要管理层可控(采用自己或心腹较擅长的技术)。并且如果企业大规模采用传统微服务开发,深入理解这些框架的大牛变得更加重要,一旦离开,后果便是灾难性的(如人人视频的 dubbo 大牛的离开)。
但在服务网格体系中,完全摆脱了技术栈和微服务框架的约束,允许开发者使用自己擅长的技术栈。企业决策者再也不必被某个核心技术人员掣肘,最难的服务治理问题已经被解决掉了。
服务网格对微服务的技术人员的要求和分工也带来了一些变化。通常的需求有两类:
- 业务(服务)开发者:理解企业业务,并具有应用开发能力。按照上面的架构图,业务开发者写好 Service A 和 Service B 就好了。这部分角色的需求没有变化,本来在企业中就存在。
- 运维开发:只需要理解容器,容器编排,以及服务网格/service mesh/isito,写一些部署脚本,或者把当前的 CI/CD 系统连接到服务网格就好了。这部分是需要新学习的内容,好在他足够简单,并且有云厂商的“贴身服务”。
当然,只要愿意,这两种角色可以合并,每个开发者都可以成为“技术大牛”。
但协调各个应用,仍然会需要约定,要求业务开发小组共同遵循。这些约定应该是适用所有微服务开发技术。
服务响应格式统一
在服务网格中,服务端一般采用 http 的 restful 的方式。统一的响应格式会为开发带来便利,也易于封装统一的调用。
下面是一个典型的响应封装:
代码语言:txt复制{
"service": "passport",
"timeStamp": 1559360704569,
"success": true,
"data": {
"items": [
{
"id": 1,
"title": "腾讯云",
}
],
"total": 1,
"limit": 20,
"offset": 0
}
}
当然,为了通信效率,我们在服务之间调用可以使用其他的二进制 rpc 协议。在 istio 体系中,默认支持了谷歌自家的 gRPC。通过对 envoy filter 的扩展,还会支持更多的 RPC 协议,如 thift,dubbo 等。
远程访问的约定
通常对于OO类的语言,会屏蔽接口格式和通信协议,把这些约定写入开发框架或者 SDK 中,远程调用就像本地调用一样(如 java 的 feign),这样的好处很明显。但缺点也很明显:你被框架/SDK 绑定了。
服务网格中,Restful 服务间的调用采用的是 协议 (http or https) 内部服务名/域名 端口 调用,如:http://passport.xyz.svc.cluster.local:7301/{your_api_url} 。在这里的一个较好的实践是:在配置文件中使用短服务名映射,屏蔽 http 协议 和 端口,并在部署的时候将这个配置放到远程配置中心。如:
代码语言:txt复制{
"passport":"https://passport.xyz:7301/"
}
在应用中:
代码语言:txt复制//屏蔽协议,端口和域名
private String getRemote(String service, String url){
String uri = Util.getServiceUrl(service) url;
return restTemplate.getForObject(uri, String.class);
}
代码语言:txt复制//调用上述方法
String result = getRemote("passport", "/open/account/info?ticket=xxx");
这样就可以屏蔽由于部署导致的协议,端口,和域名的变更。
服务的拆分和暴露
应用拆分的颗粒度问题:
- 原子服务:基础的服务,与领域模型对应,直接与数据库打交道,包含增删改查等操作,此类服务只对内暴露。
- 业务服务:对应前端具体的业务,具有业务逻辑,有可能与数据库打交道,也可能访问其他原子服务或业务服务。有些集成了安全认证的服务可以对外暴露。
- 业务应用:直接面对最终用户的应用,可能包含UI,也可能是开放的API,必须包含安全认证。
- 网关:通常会作为内部服务对外的出口,有安全认证,编排或协议转换等功能。
服务的拆分没有一定的规则,不同的架构师/开发者会有不同的拆分方法。
服务暴露通常会带来安全的挑战。
通常建议最小暴露,按需暴露。