前言
让时光积累真正的价值
上一篇笔记《理解什么是分布式系统》,我们大概理解了分布式是什么玩意。Spring Cloud是广泛使用的微服务架构。
学习Spring Cloud就免不了要学习Spring Cloud的各种组件,在这一篇笔记中,我们来了解一下Spring Cloud各个组件扮演的角色。
参考博客:外行人都能看懂的SpringCloud,错过了血亏!
导读
Spring Cloud提供一系列组件来解决分布式环境遇到的问题。我们来看看这些组件针对什么场景的
问题一:多节点下,节点之间需要相互通信怎么解决?
Spring Cloud提供Eureka组件,服务实例管理无压力。
问题二:服务A调用服务B,但是服务B有多个节点该怎么选择?
使用Ribbon组件,提供负载均衡功能,实例决策不用愁。
问题三:服务A调用服务B,服务B凉了,怎么办?
使用Hystrix组件,提供服务熔断和错误回调服务,保证不影响其他服务。
问题四:远程调用要写Http,代码看到自己都烦,怎么办?
使用Feign组件,你定义接口,Feign帮你实现,助力优雅远程调用。
实例管理员———Eureka
Eureka是用来解决什么问题的?
原来的系统使用Spring Cloud拆分了以后,节点之间通信就成了大问题。
在这里你可能会问了,不就是通信嘛,算什么大问题?直接写一份配置文件,然后通过HTTP调用不就可以通信了,这算是什么大问题呀?

这样做也可以解决问题,但是有很多缺点的,一旦节点的IP改变就要手动修改配置信息。服务节点一多起来,维护工作简直就是噩梦。
其实这个问题说白了就是实例维护的问题:
为了解决微服务中服务实例维护的问题(IP问题),诞生了很多服务治理的框架和产品,它们都是通过在服务注册与发现的机制,来实现服务实例的自动化管理。
Spring Cloud的组件中,Eureka是最常使用的实例维护组件。
Eureka怎样解决实例维护问题?
服务节点启动的时,将自己注册到Eureka注册中心,并且从注册中心中获取其他服务实例节点信息。

服务A获取到服务B的实例节点信息,就可以远程调用服务B了。
在项目中应该要如何使用Eureka?
在这里先了解一下Eureka相关的内容,Eureka分为Eureka客户端和Eureka服务端。
Eureka服务端(注册中心)
每个服务单元都会向服务中心注册自己提供的服务,包括IP域名、端口号、服务版本号、通信协议等一些附加信息。同时Eureka服务端还会通过检测心跳信息来判断服务实例的状态。
Eureka客户端
运行在服务实例节点上,在服务实例启动时,会将自己注册到注册中心。注册完成以后,eureka会获取到其他服务节点的信息。并且Eureka客户端会定期向服务端发送心跳消息。
Eureka Client又可以分为服务提供者和服务消费者,不过在大多数的情况下,服务节点是服务消费者同时又是服务提供者。
很显然,使用Eureka最简单的方式,就是创建一个Eureka服务端和将服务实例注册为Eureka 客户端。
创建Eureka服务端:
首先要创建一个Spring Cloud的项目,添加eureka服务端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
在启动类添加注解
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
配置application.yml
server:
port: 8080
eureka:
instance:
hostname: localhost # eureka 实例名称
client:
register-with-eureka: false # 不向注册中心注册自己
fetch-registry: false # 是否检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 注册中心访问地址
spring:
application:
name: eureka-server
使用Eureka 客户端
添加Eureka客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
添加启动类:
@EnableEurekaClient
public class ServerAApplication{
public static void main(String[] args){
SpringApplication.run(ServerAApplication.class, args);
}
}
Eureka内容的说明
这里仅仅对Eureka做一个入门的介绍,Eureka其他的内容由于篇幅的关系改天再做介绍(我不会告诉你们我还没完全整明白……..)
负载均衡器——Ribbon
引入Eureka,做到了实例自动管理。再也不用担心实例维护问题了。
现在服务A调用服务B来完成请求,但是服务B的运行速度太慢了,每次服务A都要等这服务B很久才能完成,这样的话就拉低了请求整体的响应时间。分布式系统的下,这个问题好解决!增加一个服务B的实例就可以了,如一个不行那就两个……
可是问题就来了,有多个服务B,服务A该选哪一个?
服务实例太多,怎么选?

这简直就是皇帝的烦恼,妃子太多,宠幸哪个都要想半天….
其实皇帝还是不少方案的,妃子太多了,那就翻牌决定吧,翻到哪一个妃子就区宠幸哪个。
不喜欢这样?
那就轮流,初一选一号妃子,初二选二号妃子…..一个月天天不重样…..
spring cloud提供一个组件——Ribbon。就跟皇帝选妃子一样,Ribbon就是决策具体调用哪个服务实例的。说白了就是复杂均衡。
负载均衡,其含义就是将负载(工作任务)进行平衡,分摊到多个操作单元(例如wed服务器)上运行,从而协同完成工作任务。
Ribbon怎么做决策的?
Ribbon是随机选择实例,还是轮流调用实例?
其实两种方式Ribbon都支持,怎样决策实例调用,在Ribbon抽象为策略。除了随机和轮询,Ribbon还支持很多种策略,也支持用户自定义策略。
Ribbon提供了一个ILoadBalance的接口,用户只要实现这个接口,并设置到Ribbon,就可以定义自己的负载策略了。当然全部自己实现就很费劲了,Ribbon还提供了一个模板类AbstractLoadBalancerRule,它实现了ILoadBalance接口,用户只需要实现少量的操作就可以实现自己的负载策略了。
Ribbon常见的策略有:
- 随机策略(RandomRule):随机选择server
- 轮询策略(RoundRibbonRule):按照顺序选择server
- 重试策略(RetryRule):在配置的一段时间server不成功,则一直尝试其他server,知道成功
- 响应时间加权(ResponseTimeWeightedRule):根据响应是时间快慢进行加权,有限选择响应时间块的server
- 可用过滤(AvailabilityFilteringRule):server一直失败会被标记和过滤,负载重的server也会被过滤。
……………
在开发中应该怎样使用Ribbon?
Ribbon有很多的使用方式,这里介绍一下最简单的一种——eureka搭配Ribbon和RestTemplate使用。
这种用法的原理是这样的:Eureka客户端注册完成,实例获取到了其它服务实例的列表,Ribbon利用这个实例列表的数据来做调用决策。在容器中配置一个RestTemplate实例,Ribbon会使用动态代理的方式代理RestTemplate实例,将访问的实例动态修改为Ribbon决策出来的实例。
Eureka和Ribbon相关的包:
<dependency>
<groupId>com.netflix.Ribbon</groupId>
<artifactId>Ribbon-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-Ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
</dependency>
服务A的启动类,添加RestTemplate实例到容器:
@SpringBootApplication
@EnableEurekaClient
public class ServerAApplication{
public static void main(String[] args){
SpringApplication.run(ServerAApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
使用Ribbon之前:
@RestController
public class ControllerA{
@Autowired
private RestTemplate restTemplate;
@GetMapping
public String getServerBData(){
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://localhost:8081/Bdata", String.class);
String body = forEntity.getBody();
return body;
}
}
使用Ribbon之后:
@RestController
public class ControllerA{
@Autowired
private RestTemplate restTemplate;
@GetMapping
public String getServerBData(){
/**
* 注意SERVER_B是所有服务B在Application里面配置的name
* 这里SERVER_B代表所有服务B的节点
*/
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://SERVER_B/Bdata", String.class);
String body = forEntity.getBody();
return body;
}
}
关于Ribbon内容的说明:
这里仅仅简单的介绍了Ribbon的作用,想要深入了解Ribbon的相关内容可以在网上查找详细的资料,或者期待一下我之后的笔记
关于Ribbon的内容还参考了这篇博客:
服务的保护者———Hystrix
系统不可用了,为什么?
现在需求改了,服务A需要依赖服务B,服务B需要依赖服务C,服务C需要依赖服务D(A-->B-->C-->D)。
完成需求以后。一上线的就出现了很大的问题。系统老是被运行不良的实例拖垮:
原来在高并发的情况下,一旦这个某服务出现了延迟的情况,可能导致大量的请求都处于延迟在状态。请求积累越来越多,资源被耗尽,最终整个分布式系统不可用。这就是“雪崩”。
在这里举个例子:

月生在一家物流仓库做兼职,他被分配到一条流水线上工作,这条流水线的任务是将客户在某宝下单的商品封箱,并贴上快递单。
在流水线上有几组岗位,A组工人负责将商品放上流水线,B组工人负责将填充好泡沫的包装箱放上流水线,C组工人负责将商品放进箱子里面并封上胶带,D组工人负责给包装好的箱子贴上快递单。我们的包裹 就是通过这一条流水线生产出来的。
很幸运,月生被分配到A组,A组的工作很简单,只有月生一个人。相比起来B组和C组的人就会多很多。下游里面有一些职责月生工作慢的同事,面对这种情况月生也不敢说什么。
后来,C组工人突然突然有好几个去了上厕所,还有一个人突然要接电话。月生当让不会放过这个机会,心想“哈哈让你们说我慢”,扬起嘴角的月生加快了工作的速度。剩余的C组工人处理不完包装箱和商品,很快商品和包装箱就积满了整条流水线。为了应对这堆积如山的商品和货物,工人只能把流水线先关停了。
这跟微服务的情况很像,商品越积越多,导致这条流水线不能使用。
怎么解决这样的问题?
就拿上面的例子来说,当C组工人处理不完工作时,让月生休息一下再往流水线上加商品就好了。
在Spring Cloud中提供了Hystrix组件,Hystrix有断路器的功能。
当一个服务出现大量异常,再次调用这个服务,Hystrix会直接返回,而不是继续等待。这样就阻断了故障的蔓延。
此外,Hystrix实现了资源隔离,依赖的服务都独立使用一个线程池,防止一个服务的线程资源耗尽会影响到其他的正常服务的调用。
下面是开源中国上Hystrix的介绍:
Hystrix供分布式系统使用,提供容错和延迟功能,隔离远程系统、远程访问、第三方程序的访问点,防止级联失败。保证分布式系统面临不可避免失败时仍然有弹性。
关于Hystrix内容的说明:
Hystrix一般结合Feign使用(在下面会介绍),这里就不介绍Hystrix单独使用的方式了(我是为了压缩篇幅,才不是我没用过…..)
优雅调用助手———Feign
为什么要使用Feign?
在接入了Eureka、Ribbon和Hystrix之后,我们遇到的大部分的问题都解决了,但是这样开发会觉得繁琐。为什么呢?
远程调用需要RestTemplate,应用层的代码里需要指定远程服务地址。这种感觉,就像直接写JDBC的代码一样。
简化开发步骤,可以在项目中使用Feign。
Feign是一种声明式,模板化的HTTP客户端。在Spring Cloud项目中使用Feign,我们可以做到像调用本地方法一样调用HTTP接口。开发者完全感觉不到这个是远程的方法。
怎么使用Feign实现远程调用?
在项目中使用Feign,需要引入Spring Cloud Feign的依赖。值得注意的是,Spring Cloud Feign整合了Ribbon和Hystrix,只需要引入下面的依赖,不需要额外引入Ribbon和Hystrix。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openFeign</artifactId>
</dependency>
在启动类添加注解,激活Feign:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class ServerAApplication {
public static void main(String[] args) {
SpringApplication.run(ServerAApplication.class, args);
}
}
声明远程接口:
/**
* 指定使用服务B,整合Hystrix指定错误回调类
*/
@FeignClient(name = "SEVER_B",fallbackFactory = ServerBRemote.ServerBRemoteFallbackFactory.class)
public interface ServerBRemote {
//声明远程调用的接口
@GetMapping("/Bdata")
String getBdata();
class ServerBRemoteFallbackFactory implements FallbackFactory<FirstRemote> {
@Override
public ServerBRemote create(Throwable throwable) {
throwable.printStackTrace();
return new ServerBRemote() {
@Override
public String getBdata() {
return "error";
}
};
}
}
}
调用远程服务:
@RestController
public class ControllerA{
@Autowired
private ServerBRemote serverBRemote;
@GetMapping
public String getServerBData(){
return serverBRemote.getBdata();
}
}
最后
各位小哥哥小姐姐,都看到这里了,给个赞,点下关注呗。前几天第一个陌生人给我点了关注,我整个人都高兴到跳起来,还顺带做了20多个俯卧撑…..

刚刚开始写博客,难免有很多不足之处,期待各位大佬的指点