从单体程序到微服务,再到当下流行的服务网格概念,Spring 连接起了这两个时代。它曾是单体程序的代名词,但是却在微服务时代浴火重生,给我们带来了 Spring Cloud。
借助于 Spring Cloud,苏宁大数据中心完成了微服务架构转型,在实践中并不是一帆风顺,有思索、有迷茫,更有解决问题的乐趣。
为什么要微服务化?
为什么是 Spring Cloud?
苏宁数据中台后端是传统的开发架构, VIP 负载均衡 + Nginx + SpringMVC,代码以单体程序为主。
正常情况下一个项目使用统一域名,在苏宁现有开发架构下,统一域名导致后端只能有一个 war 包,程序变成单体程序变成必然。
如下图所示是典型的旧式项目代码目录:
整个项目管理、开发思路,围绕单体程序开发模型设计,带来的弊端很明显:
首先微服务化思路,并不高大上,我们为什么选择微服务化,首要原因是管理问题。
结合苏宁现有开发架构,整个微服务架构如图:域名解析 + VIP 负载均衡 + Nginx + 服务网关 + 各个服务。
下图是某个项目采用微服务化后的工程代码目录:
整个代码目录更清晰,利于模块拆分、人员职责安排。
数据中台项目背景介绍
苏宁数据中台是一个大项目群:
目前商业报告分析工具:Cognos、阿里 QuickBI 等,是将数据建模、可视化设计能力放到一起,这是天工与它们的最大区别。
所有这一切靠人工维护,既容易出错又不利于数据安全,也不能及时响应用户需求,这些都是慧眼系统要解决的问题。
微服务框架选型
Dubbo 架构介绍
Dubbo 主要有四个模块:
Provider 注册服务到 Regsitry,Consumer 向 Regsitry 订阅服务信息,Monitor服务监控服务调用情况。
整个服务调用流程如下:
Spring Cloud 架构介绍
Spring Cloud 整个架构与 Dubbo 非常类似:
不同的有如下几点:
关于 Zookeeper 是否适合做注册中心,请参考文章:《Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery》、《阿里巴巴为什么不用 ZooKeeper 做服务发现》
综合以上几点,考虑到架构统一,未来发展趋势,我们选择了 Spring Cloud。
Spring Cloud 主要帮助我们做系统内部服务化,REST 接口形式,不会破坏现有服务,前端服务调用无需做任何调整。
选择 Spring Cloud 还有一个重要原因是 Dubbo 与苏宁 RSF 服务框架高度重合,在对外服务接口上,我们还是以 RSF 接口为主。
基于 Spring Cloud 的服务化实践
整体架构介绍
整体有几个组件:注册中心、服务网关、服务监控、负载均衡器。注册中心使用 Spring Cloud 提供的 Eureka,服务网关使用 Spring Cloud 提供的 Zuul 组件,负载均衡器使用 Ribbon 组件。
服务网关的负载均衡策略选择的是:WeightedResponseTimeRule,根据服务器响应时间来决定路由到哪个节点。
服务监控组件,用来监控服务性能、调用情况,最重要的一点,是将整个服务链路能串联起来。
服务监控设计
监控是一个系统的眼睛,是断然不可缺少的一部分,Zipkin 提供了很好的服务链路监控,结合我们自身的使用场景,最终我们没有选择 Zipkin,为什么?
首先了解下 Zipkin 整体架构:
整体架构如下图所示,Zipkin 监控数据格式如下:
Zipkin 有如下缺点:
基于以上几点,我们决定自研服务链路监控系统,整个系统架构如下,我们利用 Kafka、Druid,原则上提供了无限扩展性。
Druid 对应 Zipkin 中的角色:Collector(数据收集) + Storage(存储:ES)。
我们结合业务的需要,设计了监控日志格式,如下图所示:
一条调用链路,有相同的根 ID,服务名由三部分组成:
一级名称有 7 种值:
你可能会问,没有存储父 ID,如何判断一条链路中的父子关系?这里我们设计一个特殊的事务 ID 生成规则,通过事务 ID 本身即能判断父子关系,如下图所示:
下图监控系统的链路展示页面:
基于 Hystrix 的熔断设计
Hystrix 对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与 Hystrix 本身的功能不谋而合。
因此 Netflix 团队将该框架命名为 Hystrix,并使用了对应的卡通形象作为 Logo。
在一个分布式系统里,许多依赖会不可避免的调用失败,比如超时、异常等。
如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是 Hystrix 需要做的事情。
Hystrix 提供了熔断、隔离、Fallback、Cache、监控等功能,它能够在一个、或多个依赖同时出现问题时保证系统依然可用。
使用 Hystrix 很简单,只需要添加相应依赖即可,最方便的方式是使用注解 HystrixCommand:
- @RestController
- public class HystrixTest {
- @RequestMapping(value = "/query/user/name", method = RequestMethod.GET )
- @HystrixCommand(fallbackMethod = "getDefaultUserName", threadPoolKey = "query_user",
- threadPoolProperties = {
- @HystrixProperty(name = CORE_SIZE, value = "10"),
- @HystrixProperty(name = MAX_QUEUE_SIZE, value = "10")
- },
- commandProperties = {
- @HystrixProperty(name = CIRCUIT_BREAKER_ENABLED, value = "true"),
- @HystrixProperty(name = CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "1000"),
- @HystrixProperty(name = CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "25")
- }
- )
- static String getUserName(String userID) throws InterruptedException {
- Thread.sleep(-1);
- return userID;
- }
- public String getDefaultUserName(String userID) {
- return "";
- }
- }
基于服务网关 Zuul 实现的广播功能
有些时候我们希望 url 请求被所有服务实例执行,这里我们对 Zuul 做了一个改造,增加了一个 BroadCastFilter,在 url 请求 header 设置 gate_broadcast 为 true,那么这个请求,将被转发给所有服务实例。
逻辑流程如下:
微服务带来的问题
服务拆分粒度不好把握
Spring Cloud 的微服务有一个 ServiceId 的概念,通常一个 war 包对应一个 ServiceId,这个 ServiceId 下可以有多个服务。粒度拆分方式主要有:横向、纵向。
纵向切分主要有如下几个方式:
横向切分,一般用来提取公共的基础服务,比如:用户名密码校验服务、用户基本信息查询。
运维、开发复杂度增加
单体程序时代只有一个 war 包,微服务鼓励服务拆分,war 数量、部署节点大大增加。
此外,一个流程处理往往会由多个分布式服务协同完成,带来了不少棘手的问题:
这些都给开发者提出了更高的要求。
调试难度增加
微服务方式鼓励服务拆分,通过服务间依赖完成功能,给开发、测试带来了挑战,合理选择微服务、代码复用两种方案。
后续架构演进
服务版本控制
没有版本控制,意味着我们无法做灰度发布,毁灭性版本发布后,无法做到对老版本兼容,下图为服务 A、B、C、D 间的版本依赖关系:
我们实现思路是对 Zuul 进行改造:
基于 Gateway 的服务熔断、限流机制
目前有一些开源的框架如 ratelimit,通过在 Ruul 增加 filter 来实现限流熔断。
但是有几个问题:
综上所述,我们已经着手一些自研工作,能与我们业务场景贴合得更紧密。
来源:51CTO原创