2017 年 10 月 22 日,又拍云 Open Talk 联合 Spring Cloud 中国社区成功举办了“进击的微服务实战派上海站”。冰鉴科技信息技术部总监朱清作了《一年了,我们用 sc 干了啥》,以下是分享整理

朱清,毕业于电子科技大学,现在是冰鉴科技的信息技术副总监,Spring Cloud 中国社区联合创始人之一,曾就职于腾讯视频。冰鉴科技是一个做个人和小微企业第三方征信平台,主要是给 P2P、小额贷款公司,以及像消费金融公司、银行等等借贷机构提供风控服务。

一、微服务构建框架 Spring Boot

1、配置

Spring Boot 是一个在 Spring 的基础上面做了很多简化的框架。首先得益于它“习惯用于配置”的设计理念,所以启动的容易程度要简单非常多。它有一个 conifg 的 Jar 包,里面有非常多第三方依赖的基础配置,可以直接在 applicationb.yml 对应的数据库 uil,包括 DB 的名字、用户密码、用户名等做设置,可以快速的启动应用。

2、部署

它的部署非常简单,使用 Spring Boot 可以一键产生 Jar 包,这里面包括 Code 部分、第三方的组件以及内嵌的 tomcat / jetty 容器。我们在使用的过程中也不需要在外部融一些网络应用的中间件。

3、监控

它也使监控变得简单,actuator 是 spring boot 提供的对应用系统的自省和监控的集成功能,可以对应用系统进行配置查看、相关功能统计等。比如做监控的时候,给服务注册与发现组件提供应用状态检查的接口,会帮助我们在启动时候,去访问当前应用 /health 的接口,我们可以获得返回的应用健康信息,包括服务注册与发现中心,是 down 还是 up ,最后会返回一个 states,如果所有东西都没问题,会有一个 states ok,并且 HTTP Code 是 200;但是如果你的 DB 根本就是连不通,或者你的 MQR 等等集成如果连接给错了等等,它自己会检测这个机制,会给你一个无开头的 Code,它会被测成一个不可用的实例。

1.png

主要暴露的功能

(一)组件脚手架

在 Spring Boot 的基础上集成了很多自己常用的第三方的依赖,比如 Mongodb、Redis、Fastdfs、Rocketmq 等,除了 Rocketmq,Fastdfs,都有现成 starter 可以直接引入。

我们定制了一个自己的 starter ,在已有的那些 starter 基础上,创建配置文件,并把脚手架一键打包上去,开发只要在 top 文件引入 starter 就可以开发业务,不用关心到底使用了哪些组件。

(二)场景调用设计

我们的业务主要有两个场景的设计:第一种是实时的标准,A 服务调用 B 服务,需要实时返回结果,这是一个常见的业务流程,调用方实时依赖执行结果的业务场景,我们的基础架构组也在尝试把内部调用集成 rpc 协议。

2.png

场景设计—http 调用

另一种场景是异步消息,比如做逻辑的解耦,上游不关心下游执行结果,可以直接通过 MQ(消息中间件),做物理上的解耦。生产者产生消息,由 consume 订阅相应的 Q ,就可以了解上游发起了怎样的异步调用,执行对应的业务。当然在 MQ 方面主要还是发一些简单的消息,并不会在里面放任何数据。因为之前我们有在 MQ 里面放数据,而因为监控不到位,最后把 MQ 搞挂了。这是踩的一个坑,不建议在里面放数据,只是放消息。

3.png

场景设计异步消息

讲到 Spring Boot,它离微服务还差些什么?它其实就是一个构建微服务的框架,在这个基础上面没有注册发现,也没有授权等,也不能通过这个把所有的应用监控起来,配置还是跟应用关联在一起。它只是一个微框架,是构架微服务应用的基础框架。

二、微服务外围脚手架 Spring Cloud

(一)网关 zuul

首先,我们要讲下网关,我们目前使用的是原生的 zuul。

在微服务架构模式下后端服务的实例数一般是动态的,比如我可能加入一个新的服务,这个可能就会有一个影射关系,对于客户端而言很难发现动态改变的服务实例的访问地址信息。这个时候我们会基于 zuul 做一下跟 Nginx 类似的,一个反向代理,对应某一个 path,把它转给某一个 server ID 。同时我们在 zuul 上面也用了 zuulset 对授权服务、健全服务的集成。

zuul 常见的有这两种配置的方式,一个是这种直接对 path 的转发,比如某一个网站直接转过去;另外一个是对 server ID,比如所有注册中心的服务都可以通过它的 server ID 做一个对应的请求的转发。

通过 filter,我们可以实现安全的监控,比如健全、白名单、黑名单的过滤等,都可以在网关的filter 中加入一些对应实现的组件,通过 filter 对应的类型我们可以完成很多自己想实现的功能。比如你想看某一次调用花了多少时间,在前端调用 zuul,上面损失很少的情况下,可以直接统计通过 Nginx 到你的服务转发之间的时间。另外比如你在 prefilter 和 postfilter 去定义一下对应的时间,也是可以做这个统计的,但是我们并没有这样做,因为损失太小了,基本上可以忽略不计。此外在 prefilter 上面,我们会做很多过滤,包括黑白名单相关的检验以及健全。

(二)服务注册与发现 consul

服务注册与发现我们使用了 consul,用 consul 做服务注册与发现,对应用本身是完全零侵入的。我们实现的服务注册与发现,主要是基于 Docker ,用了一个叫 Registrator 的组件,主要的作用是通过 Docker events 发现哪一个服务启动了以及哪一个服务的实例启动起来了,对应的 IP 是多少,然后把这个信息发给 consul agent,再从 consul agent 发送到 consul server,这样就可以知道对应的哪一个 service。比如在定义了 service name,它就会直接把这个信息发给 consul,包括前面提到的用 /health ,去做 server 的健康检查。同时可以去通过它的标签或环境变量设置检查的间隔,比如 5 秒或者 15 秒。

(三)配置中心 config

配置中心我们使用 config,因为我们是做 2B 业务,很多配置都不怎么动,有很多业务的配置在数据库里面。比如说给某一个用户分配一个渠道,这个渠道对应到后面有哪一些模型或者数据,这些东西都不会写在像 Spring Boot config - server 里面;而更多的是用来做一些不同环境上面的,比如说你的 DB 的历史是不一样的,要做一个隔离,直接通过资源信息的命名的规则就可以了。此外如果要动态去改变一些东西,无非就是恢复上面的一些控制。

(四)熔断器 Hystrix

熔断器大家都很熟悉,我们直接使用 Hystrix ,这个其实是你对开发规范的一些强制的要求。在我们刚开始做这个应用的时候,我们没有太多考虑对第三方的依赖,而j常见的是去加载一个超时,以免这个服务挂掉的时候请求一直 Hold 在那里,大家都很清楚他们开默认的有限大小的线城池是 200 。如果我们现在季军来几个第三方的数据源,其中一个服务挂掉了,而同时前端可能有大量的请求需要这个数据源的支持,我们会发很多请求到这个数据源,那么很多现成的资源就会 hold 在那里,其他的服务也会因为这个服务挂了导致不可用。所以我们用到了Hystrix 一个关键的技术,它通过一个 group 信息对线城池的一个隔离。同时我们可以在它失败的时候,给前端的客户返回一些比较友好的信息。比如“现在服务暂时不可用”。

(五)服务调用 feign

在服务调用方面我们内部还是用的 feign ,你只需要去定义一下 feign,在里面指定你内部的 serviceID ;而对外部的一些请求,可能涉及到调用链最终的问题。feign 也可以跟 hystrix 结合的比较好,你只要创建专用于调用服务的Component,然后把 component 引入进来就可以了。

(六)服务调用链追踪 sleuth

假设你原来在一个单体里面,客户发一个请求进来,你把数据的请求、对数据的处理、这个模型的计算、反其他规则的计算、最后角色引擎相关的计算,都在一个单体应用里面完成,所有的日志都能看到他是在同一个线上 ID 下面(假设非多线程情况下),会有很多日志,很容易关联起来。但是当你做了服务拆分以后,显然这个难度就变大了。比如 logstash 收集一个主机采集上来以后,你可以直接通过现成的 ID 看到上下文所有打印的日志的情况,去做故障、客户反馈上面的查询、分析到哪个环节出了问题等。

而当拆成很多之后,第一个可能面临的问题是日志收集的问题,另一个则是整个调用链的问题。假设我们日志收集的问题已经解决了,然后对elasticsearch 收集进去的数据,其实是有时序的,如果我两台主机网络不太好,本来这个请求可能先到了A主机,A 主机调了 B 主机提供的一个服务,理论上来讲我们希望看到的日志是先看到 A,再看到 B。但是有可能由于你的 logstash,或者我们后面采用的 fleentd 到 elasticsearch 网络时延的问题,会导致 B 主机打出来的日志比 A 先到,这时候你的日志看上去一定是非常乱的。我们如果想去对一个服务里面出现的那些问题定位的话,相比也是比较困难的。

针对服务化应用全链路追踪的问题,Google 发表了 Dapper 论文,介绍了他们如何进行服务追踪分析。其基本思路是在服务调用的请求和响应中加入ID,标明上下游请求的关系。利用这些信息,可以可视化地分析服务调用链路和服务间的依赖关系。如果使用 sleuth ,开两个服务,A 服务到 B 服务,如果抓一个包,肯定会发现在 A 调 B 的时候,如果 A 引入了 sleuth ,B 也引入了 sleuth ,它会在 A 这边,进到你的体系里面,假设先产生一个 Trace ID,是一个 http 请求,会在调用 B 的时候,把这个Trace ID 带到 handles 里面 ,当 B 收到 A 调用请求的时候,会在 handles 确认,已经产生的一个Trace ID,就接着往下面用就可以了,其实就是这样一个思路。此外我们还有通过消息中间件的方式做了一个 A 与 B 服务调用之间的异步的解耦,就不是一个http 调用的方式,你就需要你集成一下 MQ 对 sleuth 的支持。

zipkin 我们没有用,由于使用elk检索日志,sleuth 帮助记录了 traceid,故觉得有点鸡肋,直接采用了检索traceid。

4.png

服务访问结构

在讲完整个采用的服务之后,大家脑袋里面应该会浮现这样一张图,对我们来说,在一个外部请求进来的时候,会先经过我们的服务网关,我们把我们的授权,以及认证的服务单独做了一个 service ,这样一旦有一些更新,它可以独立地去发布。服务网关,现在主要是一个 token base 的认证方式,会拿到这个 token 到 server 上面做一个验证,验证通过之后外部请求就会通过我们的 zuul 做一个反向代理,会查是向哪一个 service 转,会在服务注册与发现的中心根据它的 service name 拿到一堆实例,在这个实例里面,会向对应的服务发生一个调用,可能编排层的服务会调用能力层的服务,这时候就涉及到内部服务之间的调用,我们内部服务之间的调用时没有健全的说话的。

(七)其他-运维与测试能力是积淀

1、运维有测试能力是积淀

在整个做微服务的过程中,运维与测试相关的能力是积淀。因为如果你的运维和测试能力跟不上,在你服务做拆分以后,部署、测试都会面临特别大的压力。我们整个“运维+测试”两个团队才6个人,如果服务从原来比如单体应用只有4个,到现在40多个,而没有一个很好的,比如持续集成与部署相关设施的,那绝对是灾难性的。刚才说到的那些日志搜集都要搜集起来,有一个服务之间的调用关系的调用链的管理,能帮我快速的定位一些问题。

2、持续集成与部署

在持续集成与部署方面,我们主要还是在用 git 和 jenkins 。通过 jenkins 我们自己做了一个自动化接口测试平台。目前只是做接口测试,而性能测试在大版本发布之前会集中来做。通过我们的测试,构建之后会发到 harbor,一个企业级的镜像仓库,而实际的部署,是用到 rancher,不可否认 rancher 是在容器管理编排方面的小白入门的利器,我们开始用它也是因为它的界面比较清爽,有起来比较简单。在 rancher 上主要实现两个方案,一个是部署,一个是升级。

3、日志收集

此外还要注意日志收集的部分。像传统的方法存在一个问题,在 Docker 环境下,一个宿主机上面有很多不同服务的业务实例,那这些日志怎么办?难道要按个做脚本采集文件信息吗?更大的问题是你是有 file 的,如果是云上买的,你可能需要扩大磁盘的容量,因为你有可能需要做监控脚本,清理日志。在做到微服务、容器化之后,第一个要做的就是让你的日志减少,这样就没有文件,直接通过 SDD Out 的方式,对 logstash、sleuth 采集起来之后,可以直接送日志数据,中间不会产生一个日志,不会清理。因为我们之前一开始还没有做这套运维基础的时候,经常都需要手工的清日志,在业务量突增的时候会产生很多日志。我们做征信的时候中间要打很多日志,可能一次请求里面要打十几兆的日志,因为涉及到的第三方数据源实在太多了,中间有很多计算的过程也要打印下来,否则的话没法知道中间到底出了什么问题。

4、监控与告警

我觉得监控与告警就是一个先行的东西,在我们没有做微服务之前也有。对我们做 IT 系统来讲,如果出现问题,要确保在客户没有投诉我们之前,快速的发现并且去处理这些问题。这个时候我们就需要对我们收集上来的日志做一个日志级别的规范和定义,比如我定义了 error、info 等,当一个第三方的数据源挂了,打了一个 error 级别的日志,这个日志是我一定要通知你的,这就需要一个规范。我们从 ES 上每分钟会传上来这些日志,会看里面 error 级别的日志对应的 service 是哪一台,并把它发到对应负责的开发那。这个我们也是通过微信发的,但是要注意不要乱发告警信息,多了之后一定会疲惫,一天如果收到 200 多条,真正有重要的信息过来的时候,你可能看都没看,直接就消除掉了。

OT官网二维码.png