最近学习了spring cloud的一些主要组件,大概的看了下这些组件的源码。想着可以写一篇文章来总结一下对于spring cloud的理解。技术细节就不写了,主要是从宏观上来谈谈对spring cloud的理解。

一、什么是spring cloud?

spring cloud 可以认为是一种分布式服务的框架,它为开发人员提供了快速构建分布式系统的常用模式的一些工具,比如说配置管理、服务的注册与发现、服务调用的负载均衡、资源隔离、熔断降级等等,spring cloud为这些提供了一阵套完整的解决方案。

二、什么是分布式系统?

上面说spring cloud是一种分布式服务的框架,那么什么是分布式服务呢?

在谈什么是分布式系统之前,可以先回顾一下以前的那种所有的功能模块都放在一个服务里的那种系统,一个系统化几十万行代码,部署在单台机器上。一个比较大的系统,可能有十几个人协作开发,但是使用的都是同一套代码,大家功能开发完成过后,使用的都是同一套代码来发布生产上线。这样做有些什么问题呢?

  1. 代码耦合严重,维护很困难。大家使用的同一套代码,一旦一个人有需求需要修改公用代码,由于对其他人的业务逻辑不熟悉,很可能改了上线过后,会影响其他人的已经存在于线上的功能。另外数据也有可能是耦合在一起的,一个人修改了数据,然后并不知道别人对这个数据是有依赖的,最后一上线,就发现出问题了。系统不复杂还好,随着业务发展,功能增多,在原有的一个工程里面不断地增加代码,这对于后期的维护简直是一个灾难。
  2. 代码复用性问题。多个人协作开发,一个人之前写过这个功能的代码,但对另外一个人没有感知且需要依赖这个功能的代码,那另外那个人有可能就会写出来相同功能的不同代码出来。
  3. 开发效率问题。一个大系统多个人开发,你的功能开发好了,并不能立马就能上线,可能要等到别人的也开发好了过后才能一起测试上线。
  4. 可用性问题。别人模块的代码产生了bug,比如死循环导致CPU资源过载,或者内存溢出的一些故障,也会导致自己模块收到影响。另外程序部署在单个节点,假如说机器断电、宕机等,就会导致服务的不可用。
  5. 请求量很大,单台机器瓶颈了。一台机器,1s只能处理那么多请求了,再多就处理不了了,这时候就必须要增加机器部署代码进行请求的负载均衡了。

说了这么多的缺点,那么这种系统有优点吗?优点就是系统简单,运维部署都很简单,很容易上手和维护。

由于上面的那些缺点,分布式的系统就诞生了。分布式的系统根据模块功能拆分微服务化能很好的解决上面的一些问题,但是分布式系统也有着它的缺点:就是会让整体的系统变得更加复杂,运维成本也会大幅度增大,功能拆分后,单个请求的响应时间由于会经过多个网络IO而增大,但是整体的系统吞吐量会增大。虽然分布式系统也有着一些缺点,但对着系统的不断变复杂,以及系统需要对高并发的支持,总的来说分布式系统肯定是利大于弊的。这也是为什么现在分布式系统得到广泛应用的原因。

三、设计一个分布式的系统需要一些什么功能模块?

通过上面的介绍,我们知道,分布式系统其实就是一个大系统根据功能模块进行拆分,然后提取出来进行服务化,对外提供服务。因此对于一个大型的分布式系统,内部肯定存在大量的服务之间的依赖调用。那么这些服务之间的依赖调用,可能会存在一些什么问题呢?如果让你去设计一个分布式系统框架,你会怎么去设计?spring cloud又是怎么解决这些问题的呢?

存在的问题:

  1. 配置管理,服务的注册与发现。服务之间调用,在发送网络请求时怎么找到目标服务。这就需要服务调用端对要调用的服务服务配置管理。服务端增加了机器节点,以及下线节点或者服务端节点故障,调用端怎么能够感知到。这就需要设计一个统一的配置管理中心来对服务的配置进行管理。
  2. 负载均衡。在服务调用的时候,掉用端需要进行负载均衡处理,总不能把请求流量都打到一台服务节点上去,导致单个节点过载压力过大吧。因此客户端在调用的时候,需要把请求流量均匀的发送到服务端。
  3. 服务之间故障的蔓延。这个是什么意思呢?就是一个服务出现了故障,会把故障蔓延到调用它的系统,最后导致整个的分布式系统不可用。比如说,一个服务调用超时,导致其上游调用端的超时。再比如说一个服务请求响应过慢,导致其上游调用系统在某个时间点把全部的线程资源都耗在了这个慢的调用上,导致其他请求没有线程资源去处理而等待最后导致超时等等问题。

当然还有其他的问题。上面列举的问题是分布式系统再设计的时候需要考虑的常见的问题。
那么spring cloud是怎么解决上面的那些问题的呢?下面来介绍下,spring cloud的一些常用的模块。

四、spring cloud的常用功能模块

1、eureka——注册中心

在spring cloud中,eureka就是服务的注册中心,用于服务的配置管理。例如服务的注册与发现、服务的下线。这里可以对eureka的原理做一个简单的介绍。

在整个spring cloud体系中,网络通信基本使用的都是http协议。eureka分为eureka server(注册中心)和eureka client(分布式系统中的服务)。eureka server和eureka client之间的通信就是采用的http协议。

  1. eureka server的内部会维护一个注册表,注册表其实是一个map结构,key是服务的名称,key对应的value又是一个map,这个map的key是当前节点编号,value是这个节点对应的ip地址和端口号等信息。eureka client启动的时候会发送http请求到注册中心进行注册,其实就是把当前服务的节点信息和地址端口号存进注册表map中。一下是注册表数据结构。
    {
    “ServiceA”: {
    “001”: Lease<InstanceInfo>,
    “002”: Lease<InstanceInfo>,
    “003”: Lease<InstanceInfo>
    },
    “ServiceB”: {
    “001”: Lease<InstanceInfo>
    }
    }
  2. eureka client在启动的时候也会发送http请求到eureka server拉取全量的注册表到本地内存,来供服务的调用。之后会有一个定时任务,定时去eureka server去拉取增量注册表(eureka server会有一个queue来保存新的节点注册,新的节点下线等信息),跟本地注册表合并。
  3. eureka client会发送心跳到eureka server,来表示自己当前这个节点是处于存货状态。发送心跳其实就是更改eureka server注册表map中对应的实例对象中的一个时间戳。
  4. 服务下线的时候分为主动下线和服务挂掉。主动下线会发送一个下线http请求给eureka server,告诉eureka server 当前这个节点需要下线了。还有一个是被动下线,被动下线是eureka server有一个定时任务,会对注册表的每个实例的心跳时间戳做统计,根据心跳时间戳来判断当前这个实例是否还存活。另外在故障服务节点剔除时,如果故障服务实例过多,并不是将所有故障服务实例全部剔除,而是会分批摘除。
  5. eureka server也是一个集群,eureka server节点之间也会存在注册表的同步。注册、下线的同步其实是一个eureka server会找集群里其他server发送http请求进行同步,其他server收到请求后只会在本地执行,不会再转发同步了。

以上就是eureka 大概的一个原理了。看了eureka server的一个源码,说实话,eureka源码编码写的并不好,逻辑层次不清晰,存在代码冗余,存在大量的硬编码,自我保护机制那一块由于直接写死配置值在代码里而导致修改默认配置值时就会产生bug。不过eureka设计思想中也好多的值得学习的地方的。另外eureka 采用的是http协议,http就限制了数据的传输不是全双工的,所以里面大量使用了定时任务来进行处理。这样也存在的一个问题就是实时性会差一些,比如说服务故障下线对于其调用端来说不是立马能感知到的,eureka server心跳故障统计就有延迟,然后eureka client拉取增量注册表又会有延迟。

2、ribbon——负载均衡

ribbon组件解决了spring cloud在服务调用的时候的负载均衡问题。ribbon提供了一些常用的负载均衡算法的实现,当然用户也可以自定义负载均衡算法。默认使用的是RoundRobinRule轮训负载均衡算法。

3、feign——声明式接口编程

feign的作用主要是简化我们的编程。feign通过对外暴露接口+springmvc注解,调用端只要依赖这个api接口,spring生成动态代理根据springmvc注解和eureka注册表以及ribbon负载均衡,就能够发送http请求到目标服务并拿到响应结果。

4、hystrix——资源隔离、限流、熔断、降级

hystrix主要用于解决分布式系统故障蔓延的问题。一个服务可能会调用多个下游服务,如果有一个下游服务堵塞导致调用端将所有的线程资源都耗在了这个上面,那么对于其他下游服务的调用请求只能等待了,导致超时造成服务整体不可用,因此要进行线程的资源隔离,其实底层使用的是信号量Semaphore和线程池线池来进行线程资源隔离的。或者是一个服务挂掉了,调用这个服务的其他服务都调用报错,导致这个系统不可用。这时候就要采用熔断降级机制了。降级的意思是说,如果调用服务报错或者超时或者异常,该调用就采用一个默认值(数据)或者默认逻辑或者自定义降级逻辑进行返回。熔断的意思是说,如果一段时间内存在过多次数的失败之后,直接开启短路器,即直接走降级逻辑。然后一段时间过后尝试正常调用,看服务是否恢复,恢复了就关闭短路器。

5、zuul——网关,请求路由和统一处理

在大型分布式系统,有很多很多的服务,前端系统不可能保存需要调用的所有的服务的地址,然后调用对应服务。因此一般都会提供一个网关系统统一接受前端请求,然后进行路由转发。还有一点就是网关系统可以处理一些请求的通用逻辑,比如说,统一认证、统一降级、还有一些跟业务相关的通用逻辑等等。

spring cloud优势与不足

优势:
spring cloud 是基于spring boot,大大简化开发。spring cloud提供了一套较为完整的分布式系统化解决方案,这也是为什么很多的中小型公司现在比较倾向于使用spring cloud来作为分布式开发框架。

不足:
服务的注册发现的通信采用的也是http,这就导致了服务故障下线的延迟,从而导致请求调用发送到了已经下线的节点,导致调用失败。
服务之间的调用采用的也是http协议,因此这对网络调用的性能有一定的影响,http相对于那些基于NIO多路复用的网络通信以及服务端reactor模式线程模型而言,性能肯定是要差些的。但影响一般不是很大,因为很多时候性能瓶颈不是在http通信协议上,而是在你自己写的程序逻辑上或者是什么其他的方面。为什么这么说呢,比如说一个请求响应时间是100ms,换成基于NIO多路复用的网络通信以及服务端reactor模式线程模型来进行请求处理可能性能提升个几毫秒,相对于整体的响应时间来说并没有提高多少。当然,在那种并发量非常高,对性能要求很高的系统,采用http肯定还是要差不少的,但对于大多数系统,我认为差异并没有那么明显。