从命名我们就可以知道,它是Spring Source的产物,Spring 社区的强大背书可以说是Java企业界最有影响力的组织了,除了 Spring Source之外,还有Pivotal和Netfix是其强大的后盾与技术输出。
其中Netflix开源的整套微服务架构套件是Spring Cloud的核心。
或许很多人会说 Spring Cloud 和 Dubbo的对比有点不公平,Dubbo只是实现了服务治理,而 Spring Cloud下面有17个子项目(可能还会新增)分别覆盖了微服务架构下的方方面面,服务治理只是其中的一个方面,
一定程度来说,Dubbo只是 Spring Cloud Netflix中的一个子集。但是在选择框架上,方案完整度恰恰是一个需要重点关注的内容。
Spring Cloud 是一个大的工具包,它为开发者提供了配置管理、微服务发现、断路器、智能路由、微代理、控制总线、一次性Token、全局锁、决策竞选、
分布式会话和集群状态等一系列的分布式系统开发和管理工具。
无论是通过浏览器还是其它渠道过来的请求首先会通过代理服务器也就是网关经过鉴权等操作进入,并根据请求路径进行路由,然后通过负载均衡进行分发,负载均衡从注册中心读取服务列表,根据服务列表进行选择最终请求到达目标服务。
这其中涉及注册中心Eureka、智能路由Zuul、负载均衡Ribbon、配置中心Spring Cloud Config、服务间调用Feign。
Spring Boot
Spring Boot 不是微服务框架,它只是用于快速开发普通的 Spring 应用的模板。只有加入了其它服务管理方面的组件之后才是真正的微服务。
Spring boot 是一个Spring的工具包,其特点是极大的简化了Spring应用的搭建、开发、部署过程。取消了XML配置,只需要通过注解方式进行配置即可。
Spring boot 将系统必要的依赖包打包到一起,以模块的形式存在,在maven中只要引入spirng-boot-starter-parent,就相当于把依赖同时引入了,不用再手动的去创建和管理这些依赖。
注册中心 Eureka(尤里卡)
微服务架构中最重要的部分是服务管理,服务管理是通过注册中心实现的,注册中心是最核心的组件。
注册中心负责把所有的服务进行统一管理,所有的微服务不再是独立的个体,而是一个有机整体,通过组织对外提供集中式服务。
由于微服务实例有成百上千个,而且网络地址是动态分配的,由于经常扩展、注销、变更实例的地址也是经常变化的。
以前的方式根本玩不转,通过注册发现后相当于都找到了组织,所有的服务组成了一个聚合体,再调用其它服务时可以通过服务名直接调用,不用再关心IP和端口。
如果没有注册中心,有的服务可能会被人遗忘或压根就没人知道,有了注册中心所有的服务就便于被外界发现和使用。
Eureka 是Netflix 开发的服务发现框架,无论是服务端还是客户端都注册到服务中心上,然后客户端通过Rest方式请求应用服务。
Eureka 承载了所有的服务注册和发现功能,所有注册到它上面的服务都是Eureka的客户端,客户端之间调用的时候就可以直接通过注册的服务名进行调用了。
Eureka Server 是服务注册中心,采用客户端发现模式,每一个服务启动时根据其配置,它会注册到 Eureka Server 上,同时它的网络地址也会写到注册表上,这样 Eureka Server 就有了所有服务实例的信息。
Eureka Server 提供了仪表盘功能,通过 Eureka 监控页面可以直接观察到所有服务的状况。Eureka 通过心跳机制保证服务的可用性。当某个节点在规定的时间内没有发送心跳信号时,Eureka 会从服务注册表中将这个节点移除。
Eureka Server 还提供客户端缓存机制,即使所有的 Eureka Server 都挂掉 客户端仍然可以利用缓存中的信息调用其它服务节点。
网关 Zuul
Zuul是一个反向代理工具,类似的工具还有 Nginx,它们的作用是为前台提供后台服务的聚合,提供一个统一的服务出口,同时负责鉴权、认证、安全和跳转。
服务网关可以有很多实现方法,如 Nginx,甚至是一个Node.js 的服务端。
客户端负载均衡 Ribbon([ˈrɪbən]丝带)
Ribbon 的功能是提供客户端负载均衡,Ribbon利用从Eureka中读取到的服务信息列表,在调用服务实例时,合理的进行负载。
Ribbon 采用不同的策略,如轮询、随机、响应时间加权。当客户端调用某个服务时先向Ribbon发起请求,由Ribbon以某种策略做负载均衡后将请求转发到目标服务。
在多个服务实例的情况下,一个实例挂掉Ribbon不会再找这个实例,当挂掉的实例又恢复后Ribbon会再找这个实例,但是有时间间隔,大概是发送心跳的时间间隔。
当注册中心 Eureka 挂掉后 Ribbon 不受影响可以继续利用缓存的服务列表提供负载均衡服务,但是如果同时有一个服务实例也挂掉了Ribbon还会请求这个实例,因为无法获得更新列表。
因此注册中心可以暂时挂掉但不能长时间挂掉,否则Ribbon不能实现对宕机节点的剔除。
断路器 Hystrix [hɪst'rɪks]
通过隔离、控制服务从而对延迟和故障提供更强大的容错能力,避免将整个系统拖垮。
可配置超时阈值,超过阈值时直接执行 fallback 逻辑。
通过断路的方式,可以将后续请求直接拒绝掉,一段时间后只允许部分请求通过。如果调用成功则回到闭路状态否则继续断开。
熔断器可以实现快速失败,如果一段时间内侦测到类似的错误,会强迫其以后的多个调用快速失败。
交通疏导的原理
大城市堵车是家常便饭的事,有时堵了很久,最终发现只是一起小的刮蹭引起的。
解决拥堵不仅要快速处理事故,而且还要把正在向事故路段汇聚的车辆导流到其它道路,不能让车辆再往这条路行驶。
传统应用,一个小事故就会短时间拖垮整个系统。
分布式配置中心 Config
第一次抽取配置:将代码中的配置统一抽取到配置文件中,第二次抽取配置:将多个分布式应用的配置文件统一抽取到配置中心中。
非必要的spring框架级的配置逐渐被 annotation 所取代。
Spring Cloud Config 目前支持三种存储方式,本地资源、SVN和GIT
服务间调用 Feign([fen]伪装者)
调用方首先引入 Feign 依赖,然后创建一个 Feign Client 接口用于调用其它服务。
接口方法只是普通的 Rest 服务,不需要任何额外的配置。
调用方只需要在启动类上增加一条注解,在接口上引用被调用方的别名即可。
对被调用方来说没有任何代码侵入,全程透明。
Feign 内部集成 Ribbon , 所以自带负载均衡功能。
调用跟踪 Sleuth([sluθ]侦探)
在系统里通过唯一ID记录请求从一个服务调用了哪些其它服务,能清楚的知道调用关系及每个调用环节的时长,出现问题便于定位。。
再通过Zipkin进行日志聚合,将所有记录信息收集整理统一展示。
Sleuth 为微服务引入了一套记录体系,一个是 trace ID , 另一个是 span ID.
就像是快递单,一个快递单一个唯一的 trace ID , 每到一个中转站对应一个唯一的 span ID。
使用 Sleuth 只需要在 pom.xml 增加 Sleuth 依赖,然后在调用方法上加 log 即可,系统会自动将跟踪信息加到 log 里面。
代码示例
启用spring-boot 和 feign 的 pom.xml 配置
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.nono
demo
0.0.1-SNAPSHOT
jar
demo
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.10.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-feign
1.4.3.RELEASE
org.springframework.boot
spring-boot-maven-plugin
客户端调用接口定义
packagecom.nono.demo;
importorg.springframework.cloud.netflix.feign.FeignClient;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value="exp-client",url="http://localhost:8080/exp")
publicinterfaceExpFeignClient{
@RequestMapping(method=RequestMethod.GET,value="/example-ws/hello")
Stringhello();
}
通过客户端接口调用服务的代码
packagecom.nono.demo;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.cloud.client.discovery.EnableDiscoveryClient;
importorg.springframework.cloud.netflix.feign.EnableFeignClients;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
importorg.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@RestController
publicclassDemoApplication{
@Autowired
ExpFeignClient expFeignClient;
publicstaticvoidmain(String[]args){
SpringApplication.run(DemoApplication.class,args);
}
@RequestMapping(value="/hello",method=RequestMethod.GET)
Stringindex(){
returnexpFeignClient.hello();
}
}
领取专属 10元无门槛券
私享最新 技术干货