实战备忘:使用spring cloud构建微服务架构

背景需求

公司游戏运营平台由以下模块组成:游戏中心、会员专区、储值中心、客服中心、以及各个游戏官网等多个模块,每个模块都是一个独立的网站。平台已经上线运营有七八年,底层架构是我们自己根据一开源项目定制改写而成,期间由于业务复杂、需求不断增加和变动,代码修修补补,已经臃肿不堪,耦合比较重,经常是牵一发而动全身。

前段时间重新接手这个项目的开发维护,身心颇为疲惫,于是决定想办法逐步解耦、重构、改造平台。以便在有限的人力之内能轻松的完成日常开发维护工作。

我的思路是根据功能区分进行解耦,把各个功能独立出来成为一个服务中心,然后服务中心对外提供api,有需要访问该功能服务的,就请求访问该api即可。比如获取游戏列表资料、会员资料维护、会员头像维护、会员储值资料维护、游戏接口对接等等,都可以独立成为服务中心并对外提供接口访问。基于这样的思路,发现近两年很火的微服务很适合这样的架构,而spring cloud则是比较容易入手且相对应用也比较广泛的微服务解决方案,于是决定根据实际需要采用spring cloud里的部分模块来解决问题。

Spring Cloud介绍

Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。

Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等项目。

微服务架构

微服务架构”在这几年非常的火热,以至于关于微服务架构相关的产品社区也变得越来越活跃(比如:netflix、dubbo),Spring Cloud也因Spring社区的强大知名度和影响力也被广大架构师与开发者备受关注。

那么什么是“微服务架构”呢?简单的说,微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署、独立维护、独立扩展,服务与服务间通过诸如RESTful API的方式互相调用。

对于“微服务架构”,大家在互联网可以搜索到很多相关的介绍和研究文章来进行学习和了解。也可以阅读始祖Martin Fowler的《Microservices》,这里不做更多的介绍和描述。

以下是根据我们的需求画的主要模块服务架构草图:

微服务架构草图

这个草图只是根据实际需求初步所做的服务所画出来微服务基本架构,实际上和spring cloud所包含的东西相差甚远,个人觉得不管什么技术,都不能生搬硬套于自己的项目里,而是要结合实际去用。

微服务架构模块介绍:

一、服务注册中心(eureka-server)

Spring Cloud通过Netflix OSS的Eureka来实现服务发现,无法发现的主要目的是为了让每个服务之间可以相互通信。Eureka Server为微服务中心,所有服务模块都需要在注册中心注册,所有订阅也都需要通过注册中心进行。

spring cloud使用注解的方式提供Eureka服务端(@EnableEurekaServer)服务。

具体可以通过官方指引《Service Registration and Discovery》进行搭建。

二、配置管理中心(config-server)

Spring Cloud提供了Config Server,它有在分布式系统开发中外部配置的功能。通过Config Server,我们可以集中存储所有应用的配置文件。各个服务在启动时,会按照优先级先读去bootstrap文件,然后再根据bootstrap配置文件里的配置信息去读取config-server里所保存的application配置文件信息。

Config Server支持在git、svn或者在文件系统中放置配置文件。可以使用以下格式来区分不同应用的不同配置文件:

{application}/{profile}[/{label}]
{application}-{profile}.yml
{label}/{application}-{profile}.yml
{application}-{profile}.properties
{label}/{application}-{profile}.properties

spring cloud 提供了注解@EnableConfigServer来启用配置服务

具体可以通过官方指引《Centralized Configuration》进行搭建。

注意:在各服务中配置连接“配置管理中心”时,需要在bootstrap.properties先配置eureka服务注册中心(bootstrap.properties的优先级比application.properties高),否则spring.cloud.config.discovery.serverId这个配置参数则不可用,这样就得在每个服务里都配置spring.cloud.config.uri参数,如果uri变了,所有服务的spring.cloud.config.uri参数就得重新配一次,很麻烦。

如下是服务提供者“service-game-api”配置连接配置管理中心的bootstrap.properties配置信息:

spring.application.name=service-game-api  
#eureka配置必須放在bootstrap配置文件的spring.cloud.config相關配置之前,否則spring.cloud.config.discovery.serviceId不起作用
eureka.port=8761  
#eureka.client.serviceUrl.defaultZone=http://localhost:${eureka.port}/eureka/
eureka.client.serviceUrl.defaultZone=http://eurekanode1:${eureka.port}/eureka/,http://eurekanode2:${eureka.port}/eureka/,http://eurekanode3:${eureka.port}/eureka/  
spring.profiles.active=prod

#spring.cloud.config.enable=true
#允許從服務註冊中心發現config service
spring.cloud.config.discovery.enabled=true  
spring.cloud.config.discovery.serviceId=service-config  
spring.cloud.config.profile=prod  
#spring.cloud.config.failFast=false

# Spring Boot Actuator
management.security.enabled=false

三、服务提供者(service-provider)

服务提供者也就是服务的客户端,是微服务的内容提供者,需要向服务注册中心(eureka-server)注册自己,我们自己所开发的服务功能模块,就属于服务提供者。

spring cloud使用注解的方式@EnableDiscoveryClient向服务端进行注册服务。

具体可以通过官方指引《Service Registration and Discovery》进行搭建。

四、负载均衡(consumer-server)——使用ribbon方案

spring cloud提供了Ribbon和Feign作为客户端的负载均衡方案,我们这里只是用了Ribbon。在spring cloud下,使用Ribbon直接注入一个RestTemplate对象即可,此RestTemplate已做好负载均衡的配置。

具体可以参考官方指引《Client Side Load Balancing with Ribbon and Spring Cloud》进行构建。

负载均衡里有一个地方需要注意的,就是断路器的应用。

断路器

在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元间通过服务注册与订阅的方式相互依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障依赖方的响应而形成任务积压,最终导致自身服务的瘫痪。

举个栗子,在一个电商网站中,我们可能会将系统拆分成:用户、订单、库存、积分、评论等一系列的服务单元。用户创建一个订单的时候,在调用订单服务创建订单的时候,会向库存服务请求出货(判断是否有足够库存来出货)。此时若库存服务因网络原因无法被访问到,导致创建订单服务的线程进入等待库存服务的响应,在漫长的等待之后用户会因为请求库存失败而得到创建订单失败的结果。如果在高并发情况之下,因这些等待线程在等待库存服务的响应而未能释放,使得后续到来的创建订单请求被阻塞,最终导致订单服务也不可用。

在微服务架构中,存在着许多服务单元,若一个单元出现过账,就会因依赖关系行程故障蔓延,最终导致整个系统的瘫痪,这样的架构相较传统架构就更加不稳定。为了解决这样的问题,因此产生了断路器模式。

断路器模式源于Martin Fowler的Circuit Breaker一文。“断路器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器”能够及时的切断故障电路,防止发生过载、发热、甚至起火等严重后果。

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

在Spring Cloud中使用了Hystrix 来实现断路器的功能。具体可以参考官方指引《Circuit Breaker》里的介绍来使用。

注意:spring cloud中的hystrix对于服务延时timeout的检测时间默认设得比较短,在测试时导致正常的服务也会返回fallbackMethod,所以可以在配置文件里设置以下参数:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 10000  

适当增加命令执行的timeout时间。

五、外部调用

建好并成功启动consumer-server之后,即可通过consu-server访问服务提供者所提供的服务,比如consumer-server的访问网址是http://api.example.com:3333,在consumer-server里接通了访问服务提供者game-api的getLoginUrl服务,则外部调用时通过访问http://api.example.com:3333/game-api/getLoginUrl 即可。


到这里,使用spring cloud构建基本的微服务就已经介绍完了,估计会有人会疑问为什么没有搭建路由网关模块,因为我们公司已经有使用citrix提供的路由网关访问,所以不再另外搭建。spring cloud的路由网关是通过Zuul来实现,有需要了解的可以参考官方指引《Routing and Filtering》搭建。

至于spring cloud的里的监控模块、消息、过滤器等其他服务模块,我们暂时都还没有用到,就先不做介绍,有需要的都可以到spring cloud官方教程了解,里面都有比较详细的介绍。

Wilson

张弛有度、简约不简单