浅谈现公司的Spring Cloud微服务框架
说在前面
本文偏小白,大佬慎入,若有错误或者质疑,欢迎留言提问,谢谢,祝大家新年快乐。
spring cloud
Spring Cloud 是将分布式系统中一系列基础框架/工具进行整合的框架。其中包含: 服务注册与发现、服务网关、熔断器、配置中心、消息中心、服务链路追踪等等 。这也是一个服务化架构的最小组成元素,有了这些基本的组成要素,就可以实现一个最简单的服务架构。
Spring Cloud 并没有重复造轮子,Spring Cloud只是依赖于Spring Boot屏蔽掉了各个框架复杂的配置。所有的组件就相当于Spring Cloud的插件,开发人员可以根据自己的需要自由结合使用。
有Spring Boot这个利器在,所有组件都可以轻松引入、便捷开发。也一定程度上降低了各组件的学习成本、调试成本、让开发人员可以轻松上手。
服务注册与发现
从最简单、最核心的问题出发,假设服务 A 要调用服务 B,会有什么问题?
服务在哪?(服务治理问题)怎么调用?(服务调用问题)
这两个是最核心的问题,也是任何微服务框架首要解决的两个问题。
为了解决第一个问题 Spring Cloud 提供了 Eureka、Zookeeper、Cloud Foundry、Consul 等服务治理框架的集成。它们的工作模式是将所有的微服务注册到一个 Server 上,然后通过心跳进行服务健康监测。这样服务 A 调用 B 时可以从注册中心拿到可用的服务 B 的地址、端口进行调用。
我公司就是使用的是Eureka
Eureka由两个组件组成:Eureka服务器和Eureka客户端。
先找到Eureka服务器,也就是我们项目的注册中心
EurekaApplication.java
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaserverApplication.class, args);
}
}
- 注解 @EnableEurekaServer 表示该 Spring Boot 应用是一个注册中心。Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
配置文件 bootstrap.yml
server:
port: 8700
eureka:
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enableSelfPreservation: false
- eureka.client.registerWithEureka: false 和fetchRegistry: false 来表明自己是一个 eureka server。
- enableSelfPreservation: false表示在此eureka服务器中关闭自我保护模式,所谓自我保护模式是指,出现网络分区、eureka在短时间内丢失过多客户端时,会进入自我保护模式,即一个服务长时间没有发送心跳,eureka也不会将其删除。默认为true
以上是注册中心EurekaApplication.java,而所有单独开发的SB项目,就是client
而我在查看每个项目的配置文件时,发现并没有找到eureka.client.serviceUrl.defaultZone:...这样的配置
于是猜想可能在启动脚本中添加此参数,果不其然,找到服务器上的启动脚本:
-Deureka.client.serviceUrl.defaultZone=http://**.**.**.***:8700/eureka
这就是其作为eureka客户端的证据(真的很想吐槽为什么要写在启动脚本中)
第二个服务调用有人可能认为就是一个简单的 HTTP 或者 RPC 调用,不是什么问题。但是在分布式的场景下,服务调用需要考虑的因素会更多。比如一个服务有多个实例,此时请求进来了交给谁处理,请求的负载怎么平衡到各个实例,都是比较棘手的问题。Spring Cloud 提供了两种服务调用的方式:一种是 Ribbon + restTemplate,另一种是 Feign。
其中 Ribbon 是基于 HTTP 和 TCP 客户端的负载均衡器,restTemplate 是 Spring 提供的 Restful 远程调用的模板,两者结合就可以达到远程调用的负载均衡。
而 Feign 是一个更加声明式的 HTTP 客户端,开发者可以像调用本地方法一样调用它,完全感觉不到是远程调用,结合 Ribbon 也可以做负载均衡。
服务网关及熔断
微服务中的服务很多,直接暴露给用户一是不安全,二是对用户不友好。因此在微服务和面向服务的架构中,通常会有一个路由网关的角色,来负责路由转发和过滤。对应到 Spring Cloud 中有 Zuul 和 Gateway 两个组件可用。
据查看我公司只使用zuul做路由转发用
Zuul是Netflix开源的服务网关/API网关,提供动态路由、监控、弹性、安全性等功能。
ZuulApplication.java
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@RestController
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
/**
* 跨域许可设置
*/
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource= new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowCredentials(true);
corsConfig.addAllowedOrigin("*");
corsConfig.addAllowedHeader("*");
corsConfig.addAllowedMethod("OPTIONS");
corsConfig.addAllowedMethod("HEAD");
corsConfig.addAllowedMethod("GET");
corsConfig.addAllowedMethod("PUT");
corsConfig.addAllowedMethod("POST");
corsConfig.addAllowedMethod("DELETE");
corsConfig.addAllowedMethod("PATCH");
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfig);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
@RequestMapping(value = "/")
public String heartbeat() {
return "";
}
}
- 在zuul服务下添加一个corsFilter实现跨域
bootstrap.yml
server:
port: 8000
spring:
application:
name: zuul
cloud:
config:
discovery:
enabled: true
serviceId: CONFIG
eureka:
instance:
preferIpAddress: true
leaseRenewalIntervalInSeconds: 1
leaseExpirationDurationInSeconds: 2
nonSecurePort: ${server.port}
client:
serviceUrl:
defaultZone: http://${eureka.host}:${eureka.port}/eureka/
application.yml
spring:
profiles:
active: eureka
logging:
config: classpath:logback-error.xml
server:
tomcat:
max-threads: 100
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
hystrix:
command:
default:
execution:
timeout:
enabled: false
threadpool:
default:
coreSize: 50
maxQueueSize: 100
queueSizeRejectionThreshold: 100
javamelody:
login_name: viroyal
login_pwd: viroyal2017
---
spring:
profiles: eureka
zuul:
routes:
account:
path: /account/**
serviceId: USERMANAGER
stripPrefix: false
about:
path: /about/**
serviceId: ABOUT
stripPrefix: false
app:
path: /app/**
serviceId: CAMPUSCMS
stripPrefix: false
res:
path: /res/**
serviceId: CMS
stripPrefix: false
device:
path: /device/**
serviceId: CAMPUS-DEVICE
stripPrefix: false
hystrix就是熔断器
熔断器的原理很简单,如同电力过载保护器。它可以实现快速失败,如果它在一段时间内侦测到许多类似的错误,会强迫其以后的多个调用快速失败,不再访问远程服务器,从而防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。熔断器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。
hystrix默认是打开的,所以此配置中未进行过多设置,只设置了hystrix.threadpool.default.threadPool的相关属性,这里说一下queueSizeRejectionThreshold:100,是为队列设置拒绝阈值,当线程池队列到达100时则开启熔断器。
- 具体的hystrix所欲配置讲解,请参考:https://www.cnblogs.com/li3807/p/7501427.html
记住:熔断器就是保护服务高可用的最后一道防线。
关于超时,由于zuul本身就集合了hystrix和ribbon,zuul 中配置超时时间,据官方的介绍,分两种情况:
-
用 serviceId 进行路由时,使用 ribbon.ReadTimeout 和 ribbon.SocketTimeout 设置
-
用指定 url 进行路由时,使用 zuul.host.connect-timeout-millis 和 zuul.host.socket-timeout-millis 设置
此配置文件是用 serviceId 进行路由的(根据请求path,分发到对应的serviceId上),所以未设置zuul超时,且hystrix.XX.timeout.enabled设置成了false,关掉了hystrix的超时设置(实际上,如果同时配置了 Ribbon 和 Hystrix 的超时时间,则以最小的为准)。所以,此项目只开启了ribbon的超时设置。像此例中的ribbon的超时是作用全局的。
直接使用 serviceId作为前缀,可以区分不同客户端下的配置,类似如下:
USERMANAGER:
ribbon:
ConnectTimeout: 5000
ReadTimeout: 5000
- spring Cloud超时总结参考:http://www.itmuch.com/spring-cloud-sum/spring-cloud-timeout/
关于zuul.routes.XXX.stripPrefix的设置,当zuul.routes.XXX.path=/api/**时,作用如下例子:
- 当stripPrefix=true的时候 (http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/user/list)
- 当stripPrefix=false的时候(http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/api/user/list)
路由网关接收了所有的用户请求,有着很高的负载,因此它通常是一个集群。用户的请求会先经过一层负载均衡被发到路由网关。
配置中心
在微服务应用中,服务数量巨多,而每个服务不同环境都有着不同的配置,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。需要注意的是此处的配置与注册中心注册的配置信息是两个概念,此处的配置是服务本身的一些配置信息。
ConfigApplication.java
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
- 本身也是EurekaClient,使用@EnableConfigServer成为配置中心,提供配置服务
bootstraop.yml
#comment can't have chinese
#using outer net interface is convenient for local webapp start and debug
server:
port: 8888
spring:
application:
name: config
profiles:
active: native
cloud:
config:
server:
native:
searchLocations:
file:/var/configs
inetutils:
ignoredInterfaces:
- eth0
eureka:
instance:
preferIpAddress: true
leaseRenewalIntervalInSeconds: 1
leaseExpirationDurationInSeconds: 2
nonSecurePort: ${server.port}
client:
serviceUrl:
defaultZone: http://${eureka.host}:${eureka.port}/eureka/
这里 spring.appliction.name为config
我看了一下其他服务的bootstrap.yml中有两项配置
sprong.cloud.config.discovery.enabled: true
sprong.cloud.config.discovery.serviceId: CONFIG
第一个作用是:使用注册中心找寻config-server的地址
第二个作用是:配置中心在注册中心的applicationName
通过找到配置中心 发现 配置文件存储在/var/configs路径下,且读取文件名和本服务serviceId相同的文件中的配置
消息中心、服务链路追踪
据我查看,我公司并未实现消息中心和服务链路追踪,这里我就总结一下他们的作用吧
消息中心
在微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都能连接上来,由于该主题中产生的消息会被所有实例监听和消费,所以我们称它为消息总线。在总线上的各个实例都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息,例如配置信息的变更或者其他一些管理操作等。
由于消息总线在微服务架构系统的广泛使用,所以它同配置中心一样,几乎是微服务架构中的必备组件。spring cloud作为微服务架构综合性的解决方案,对此自然也有自己的实现,这就是spring cloud bus。通过spring cloud bus,可以非常容易的搭建起消息总线,同时实现了一些消息总线中的常用功能,比如配合spring cloud config实现微服务应用配置信息的动态更新等。
服务链路追踪
微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。
在微服务系统中,一个来自用户的请求,请求先达到前端A(如前端界面),然后通过远程调用,达到系统的中间件B、C(如负载均衡、网关等),最后达到后端服务D、E,后端经过一系列的业务逻辑计算最后将数据返回给用户。对于这样一个请求,经历了这么多个服务,怎么样将它的请求过程的数据记录下来呢?这就需要用到服务链路追踪。
我公司业务体系不大,所以就为完成此项集成吧。
小言
以上来看,可以说这是最精简的spring cloud分布式系统的构成了,不谈具体技术实现如何,大体的架构还是有的,也让我对spring cloud有了广义上实质的了解。