一、什么是微服务?
在传统的all in one单体应用架构中,所有的服务和功能都在一个应用中部署,当其中某个服务出现问题时必须停掉当前整个应用,修复后重新发布。微服务是将系统中的每个模块拆分出来(例如订单模块、用户模块、管理模块)做成一个可独立部署的服务组件,根据每个服务的吞吐量的不同,可以选择不同的部署方式。
二、什么是SpringCloud?
在介绍springcloud之前我们可以先回忆一下之前介绍过的SpringBoot,当时提到springboot本身并没有提供什么新的技术,它是通过约定大于配置,自动装配的理念来对一些现有的框架进行了封装,使我们从之前繁琐的配置中解放出来。
SpringCloud同样也是通过SpringBoot的风格,再次对当前市面上一些公司开发比较成熟、经得起考验的技术框架再次进行封装整合,留下一套简单易懂、易部署、易维护的工具包来使我们开发人员更容易的上手分布式项目的开发。
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
ps: 其实纵观这些框架的发展史我们不难发现,新的技术出现都是为了解决我们目前开发中遇到的一些亟待解决的问题,这些新的技术看似高大上的样子,但其实对于我们来说学习成本是很低的,因为人家已经对一些难懂难理解的东西进行了一次又一次的封装,留下的都是很简单的东西了。
SpringCloud的学习过程中每一个组件的使用基本都是:
- 导入依赖
- 编写配置信息
- 通过注解开启相应功能
所以说springcloud如何使用学起来很简单,重点我们要学习的是这些组件的原理思想,它们的出现是为了要解决什么问题?
三、SpringCloud Netflix
上面已经说过SpringCloud是一系列框架的有序集合,有很多类似SpringCloud XXX等的框架,其中SpringCloud Netflix 是SpringCloud的核心框架,它提供了上面所述的:Eureka服务注册和发现、Zuul网关、Ribbon负载均衡、Feign服务客户端(默认集成了Ribbon)、Hystrix断路器和监控,这几个最核心的功能。
ps: 在2018年12月份,Spring官方已宣布Spring Cloud Netflix进入维护模式,停止对Netflix的更新,但是会继续修复一些现有的bug和安全性问题。(虽然官方停止更新了,但是Netflix这套框架相对来说还是比较成熟的,可以接着用)
2019年7月份,Spring官方宣布Spring Cloud Alibaba从Spring Cloud孵化器中孵化成功(这也意味着 SpringCloud Alibaba 是国内首个进入 Spring 社区的开源项目),SpringCloud Alibaba 和 SpringCloud Netflix 类似都是Spring Cloud的核心框架,实现的功能也类似但是两者的技术组件是不相同的,目前本章就只介绍SpringCloud Netflix。(个人感觉SpringCloud Alibaba在未来会完全替代SpringCloud Netflix ~)
下面用一张springcloud官方的一张图来示意netflix中各个组件的位置:
首先先看一下项目结构有个大概的轮廓:
以下对Netflix中每个组件单独分标题来讲解:
四、Eureka
eureka是netflix开发的服务注册发现的框架,像这种类似的还有zookeeper、consul、nacos等。 注册中心的原理就是对所有服务的一个综合性管理的地方,简单点来说就是服务注册到注册中心,客户端调取服务时去注册中心找。
怎么理解dubbo、zookeeper、eureka三者之间的关系?
- dubbo是微服务整体架构的一个框架,提供了服务注册发现、远程RPC调用、服务监控等功能,和它相对的是springcloud。但是springcloud就像是一个生态,它集成了很多技术,所以它的功能比dubbo还要多。
- zookeeper本身其实是为了保证分布式一致性的一个软件,而不是为服务的发现和注册而设计的。只不过因为zookeeper的这个特性,也可以被二次开发成服务发现注册中心罢了。zookeeper和dubbo集成后(zookeeper的服务注册+dubbo的RPC远程调用),相当于eureka。
- eureka就是专门为服务的注册和发现而设计的(这也是和zookeeper不同点之一),它本身是基于Rest风格的服务型框架。 eureka分为eureka server 和 eureka client 两个组件。
怎么理解eureka server 和 eureka client 两个组件的含义呢?
- eureka server是我们配置的一个eureka的服务端(可以理解为zookeeper的注册中心),就是我们在这个服务端可以监控到服务的注册列表、eureka server的集群信息。
- eureka client 其实就是我们的各个服务了,我们的每个服务要注册到eureka服务端中,相对eureka来说 每一个微服务就是一个client客户端了。当然消费者因为也要从eureka服务端中获取服务列表来调用服务,所以消费者也是一个eureka client客户端。
谈到zookeeper和eureka则必须来说一下CAP原则,因为这两个组件所实现的CAP就不一样。
什么是CAP原则?
CAP原则又叫CAP定理,指的是在一个分布式系统中CAP只能同时满足其中的两个,即CP、AP、CA,(一般来说在分布式系统中P是一定要满足的,所以zookeeper满足的是CP,eureka满足的是AP)
P 分区容错性
分区容错性说的是在分布式系统中,本来各个节点(可以理解为不同服务器上的各个服务)之间是互相连通的,但是由于某些网络原因可能会导致有些节点之间不连通了,这样的话整个网络就被分成了多个区域,这就叫分区。虽然说分区后的某些节点之间不连通了,但是这些节点本身还是可以对外提供服务的。
因为由于网络问题导致分区在分布式系统中是无法避免的,所以P肯定是存在的(P不存在那都不算是分布式系统了,不分区的话那CA肯定能保证了),那么根据CAP定理我们可以得知只能从C和A中选其一
C 一致性
在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
在节点1中修改了某个值,需要发送一个消息到个节点2通知节点2要做同步修改,在发送这个消息的过程中为了保持数据一致性使得访问节点2的请求拿到最新的数据,那必须锁定节点2中的读写操作,等节点2收到消息修改为最新的数据后再释放那些访问节点2的请求。而这一锁定节点2读取操作的这段时间也就表明了节点2此时不可用。
A 可用性
保证每个请求不管成功或者失败都有响应。
就像上面所说的如果我们牺牲了数据的一致性,就是在数据被修改后不锁定节点2,这样虽然访问节点2的请求拿不到数据,但至少访问是成功的有响应的,给用户的体验会更好。
下面是eureka代码的具体实现步骤:
eureka server客户端:
- 导入eureka server依赖
<!--导入eureka server服务端 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 编写配置信息(eureka 可配置集群,具体配置方法看注释):
# Eureka Server配置
eureka:
instance:
hostname: eureka7001.com #Eureka 服务端的实例名称
client:
register-with-eureka: false # 是否向Eureka 注册中心注册自己 (这个本身就是服务端当然不用注册自己)
fetch-registry: false # false 表示自己是注册中心
service-url: # 简单理解就是eureka的监控页面
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 不配置默认就是源码(点进去看)里面的那个路径
#defaultZone: 只配置一个eureka服务端就写自己,配置多个做集群的话这里需要写另外几个eureka的地址
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
- 开启eureka server功能:
@SpringBootApplication
@EnableEurekaServer // 开启Eureka服务端启动类的注解,服务端可以接收别人注册进来
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
eureka client客户端的配置:
- 导入eureka client依赖
<!--导入Eureka 客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 编写eureka client配置信息
# Eureka Client配置
eureka:
client:
service-url:
# eureka客户端的defaultZone 就是配置此服务要注册到的eureka的注册中心地址,单个就配一个,集群就配置多个
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: springcloud-provider-status-8001 # 修改eureka上的描述status信息(了解即可)
#info 配置 actuator包的作用 了解即可
info:
# info下的这些配置是自定义的,点击status下面的链接后,这些配置会以json字符串相应到页面,目的就是告诉别人此eureka服务是干嘛用的
app.name: hhl-springcloud
company.name: hhl.com
- 开启eureka client 服务发现功能
@SpringBootApplication
@EnableEurekaClient // 开启eureka客户端的注解,启动后会自动将此eureka client注册到eureka server中(也叫服务发现,就是让eureka server 发现此服务)
@EnableCircuitBreaker // 开启Hystrix 断路器功能
public class CustMapProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(CustMapProvider_8001.class,args);
}
}
eureka server 启动成功后的监控页面:
每个微服务也可配置集群,后面Status描述中括号里的数字就是该服务有多少个实例。
下面来思考一个问题,我们的每一个微服务相当于是生产者provider,那消费者consumer应该如何来调用服务呢?之前我们普通的项目因为代码都是写在一起的,所以controller里直接使用@Autowired将服务(service)注入进来,通过接口的方式来调用服务即可。但是微服务项目中每一个服务都是独立部署的,我们更不可能将服务引入到消费者里来调用(这样不就又回到all in one那种架构了吗),所以为了使得消费者能调用到我们的服务,服务项目需要提供对外暴露服务的一个接口来供消费者调用服务。
以下是生产者中的controller对应的代码
// 这个controller的作用是为了提供Restful服务,它是属于服务提供者层面的一个controller
// 因为SpringCloud Netflix都是以HTTP接口的形式暴露服务的(就是说你这个服务怎么才能被消费者来调用呢,通过一个http请求的接口,而当前这个类就是服务暴露的接口类)
@RestController
public class CustMapProviderController {
@Autowired
private CustMapService custMapService;
@GetMapping("/getCustMap") // 消费者通过此路径就可以调到生产者中的custMapService.queryCustMapByID此服务了
public CustMap getCustMap(){
return custMapService.queryCustMapByID();
}
}
消费者中的controller的调用服务的代码:
// 这个controller就是消费者层的了
//注意:在消费者模块里,不应该有service层,service是属于服务提供者层面的
@RestController
public class CustMapConsumerRestController {
// springCloud就是通过restTemplate的restful风格来调用远程服务,
// dubbo是基于RPC的远程服务调用(dubbo是使用@Reference将远程的service注入进来的方式来调用服务),这就是二者的区别
@Autowired
private RestTemplate restTemplate;// 提供多种便捷访问远程http服务的方法,简单的Restful模板
// 这里的ip和端口号不应该写死,因为微服务肯定是很多个相同的服务部署在不同的机器上的,所以要根据服务名来调用,下面介绍Ribbon的时候会修改
private static final String REST_URL_PREFIX = "http://localhost:8001";
@RequestMapping("/consumer/getCustMap/{id}")
public CustMap get(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_URL_PREFIX+"/getCustMap/"+id,CustMap.class);
}
}
五、Ribbon
什么是负载均衡?
简单理解就是我们的同一个服务部署到了多个服务器上,当请求过来时我们要让这些请求均匀的分布到每一个服务器上,这样才能资源利用最大化,防止请求都跑到一个服务器上造成某个服务器挂掉…
Ribbon是一个是一个基于HTTP和TCP的客户端负载均衡工具,它的原理是通过eureka注册中心获取所有的服务列表,缓存到本地,在客户端进行调用的时候就实现本地轮询(默认的算法)的负载均衡策略。
Nginx也是一个用于负载均衡的工具,与Ribbon不同的是它是基于服务端的负载均衡,就是所有请求都到先到Nginx,由Nginx来实现负载均衡的请求转发。
Ribbon在spring cloud项目中的实现:
首先了解一点,因为Ribbon是在客户端实现的负载均衡,所以我们的配置都是在消费者模块中配置
- 消费者模块导入Ribbon依赖
<!--导入Ribbon 实现负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 开启ribbon负载均衡(只需在原来配置RestTemplate的bean上加一个注解即可!!!)
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced // 实现负载均衡,加上这一个注解即可,可以理解为RestTemplate已被Ribbon代理
public RestTemplate getRestTemplate(){
return new RestTemplate();// 将RestTemplate注入到spring容器中
}
}
- 修改原来消费者controller中调用服务的前缀
@RestController
public class CustMapConsumerRestController {
@Autowired
private RestTemplate restTemplate;
//private static final String REST_URL_PREFIX = "http://localhost:8001";
// Ribbon 这里的地址应该是一个变量,不应该是一个准确的ip,因为Ribbon要实现负载均衡肯定不能每次都访问都访问指定的一个服务器啊
// 所以,当Ribbon和Eureka整合以后,客户端就不用关心服务的IP和端口号了,直接通过应用名称变量就可以调用对应的服务了
private static final String REST_URL_PREFIX = "http://springcloud-provider-custMap";
@RequestMapping("/consumer/getCustMap/{id}")
public CustMap get(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_URL_PREFIX+"/getCustMap/"+id,CustMap.class);
}
}
以上就是Ribbon的使用,其实说到底就加一个@LoadBalanced 注解就行了。
自定义Ribbon负载均衡的规则:
Ribbon默认的是轮询的算法规则来实现负载均衡,我们也可以自定义规则。
- 编写自定义规则类
public class MyRule extends AbstractLoadBalancerRule {
private int total = 0; // 总调用次数
private int serverIndex = 0; // 调用第几个服务
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
// 这是随机的规则
/*int index = this.chooseRandomInt(serverCount); // 再次区间获取一个随机数
server = (Server)upList.get(index);*/
// 写我们自定义的规则
if (total < 5) { // total小于5表示此服务还没有调用够5次,就一直调此服务
server = upList.get(serverIndex);
total++;
} else { // 次数大于5了
serverIndex++;// 换一个服务器
total = 0;// 新的服务器调用次数置为0
if(serverIndex>upList.size()){ // serverIndex大于服务器数量了
serverIndex=0; // 那就换成第一个服务器再次重新一轮的调用
}
server = upList.get(serverIndex);// 从活着的服务中调取服务
}
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
- 编写配置类(注意此配置类不要放到主启动类的上下文中)
/* 注意:自定义Rule配置类不要放到和主启动类同级的包下,因为那样会被Spring扫描到以致所有的@RibbonClients共享
* 此配置类需要放到和主启动类不同级的包下(这是SpringCloud官网规定的,按人家的规则写就行了)
*/
@Configuration
public class RuleConfig {
@Bean
public IRule getRule(){
return new MyRule(); // 使用自定义的规则 ,只需将实现类放入spring容器即可
}
}
- 在主启动类上开启自定义配置类的功能
@SpringBootApplication
@EnableEurekaClient // 开启Eureka
// name必须指定,为多个相同服务的应用名,configuration为自己定义的规则配置类
// 也就是表明springcloud-provider-custMap这个服务在调用时,使用RuleConfig中我们配置的自定义规则
@RibbonClient(name = "springcloud-provider-custMap",configuration = RuleConfig.class)
public class CustMapConsumer_8002 {
public static void main(String[] args) {
SpringApplication.run(CustMapConsumer_8002.class,args);
}
}
Eureka和Ribbon的关系?
- Eureka需要Ribbon的负载均衡算法
- Ribbon为了实现负载均衡需要拿到服务器列表,而这个列表在Eureka中有,且从Eureka中可以实时的获取到动态的可用的服务器列表
六、Feign
Fegin的字面意思是伪装、假装。Feign是Netflix开发的一个声明式、模板化的HTTP客户端。
Spring Cloud 中通过RestFul方式调用服务的有两种方式:
- RestTemplate+Ribbon(上面所述的就是用的这种方式)
- Feign
Feign的出现其实因为有些人觉得java中就应该用面向接口调用服务的方式,而不应该像RestTemplate那种方式把路径什么的都写在代码里来调用,所以Feign就出现了。采用Feign的方式去调用服务会让代码显得更优雅。
Feign的使用步骤:
- 在api模块(就是放实体类dto等一些公共的模块,消费者和生产者都会引入该模块)中添加feign依赖
<!--导入Feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 在api模块中编写feign接口
@Component
// Feign为什么要写到api包下?就是为了让消费者层可以拿到我们定义的Feign客户端,
// 因为消费者和服务者之间是不存在什么依赖关系的,它们两个是相对独立的,但是消费者依赖了api包,所以写在这里消费者就可以拿到我们定义的这个Feign客户端了
@FeignClient(value = "springcloud-provider-custMap") // 配置Feign客户端,value为服务应用名称
public interface CustMapClientService {
// 此Mapping就是服务层中对应的某个服务对外暴露的调用路径
// 由上面的服务应用名+ 此mapping路径组成了一个访问服务的完整路径,其实就是RestTemplate调用服务的接口路径
@GetMapping("/getCustMap")
public CustMap getCustMap();
}
- 在消费者模块中同样也导入feign依赖,和api中导入的一样
- 然后就可以在消费者模块的controller中通过注入配置好的feign接口来调用服务了(这样看起来就更像我们那种通过接口调用服务的方式了,代码也看的更加优雅了- -)
@RestController
public class CustMapConsumerFeignController {
@Autowired
// 这里我们直接获取写好的Feign客户端,通过此客户端接口来调用服务(有点dubbo的感觉了 - -)
private CustMapClientService custMapClientService;
@RequestMapping("/consumer/getCustMap")
public CustMap get(){
return custMapClientService.getCustMap();
}
}
- 在消费者主启动类上开始feign注解
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.hhl"}) //启用Feign客户端(加了@FeignClient注解的接口),basePackages为要扫描哪些包下的Feign客户端
public class CustMapConsumer_8005 {
public static void main(String[] args) {
SpringApplication.run(CustMapConsumer_8005.class,args);
}
}
总结:
- Feign其实就是对RestTemplate的封装,使我们消费者代码中可以按照以前的代码习惯使用接口调取服务,而不用RestTemplate的那种编写服务路径来调取服务的方式
- Feign底层还是调用的Ribbon实现的负载均衡(也就是说Feign和负载均衡没关系,它就是为了实现java中接口调用服务的这种规范而衍生出来的一个技术)
- 由于Feign是对RestTemplate又一次封装,所以性能会比RestTemplate要低
面试题:Ribbon与Feign的区别是什么?
两种都是用于调用其他服务的,只不过方式不同
- 启动类上的注解不同,ribbon使用@RibbonClient,feign使用@EnableFeignClients
- 服务的指定位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
- 调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。 Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。
ps: 其实这个问题本身我感觉就有点毛病,ribbon是实现负载均衡的一个策略,而feign实际上是对resttemplate进一步的封装,feign底层用的还是ribbon实现的负载均衡。feign本身不具备什么功能,它只是对RestTemplate+Ribbon的又一次封装。(个人看法而已~)
七、Hystrix
在了解Hystrix之前我们先要了解一个概念:服务雪崩
什么是服务雪崩呢?在分布式系统中我们知道,各个微服务之前往往要进行相互调用来完成一个完整的服务请求,类似下图这样:
若此时serviceA请求流程突然暴增(假设A足够强悍能抗的住这些高并发的请求),那到它这里的每一个请求还要去调用B(假设B也扛得住),B再去调用C,而服务C由于一些不可控的原因(或异常或硬件问题)而扛不住这么多的请求挂掉了,不可用了。那服务B的请求就会因为C不可用而阻塞到B这里,然后B也就挂掉了,B挂掉后A的请求也会被阻塞,所以A也挂掉了,以此类推由于一个服务C挂掉,然后造成连锁反应使得与C相关的所有服务都被阻塞而挂掉,这就是服务雪崩。如下图所示:
服务雪崩造成的后果是不可预估甚至不可挽回的,所以我们必须通过某种技术来预防这种情况的发生。
Hystrix,它是一个用于处理分布式系统的延迟和容错的开源库。在分布式系统,许多依赖不可避免的会调用失败,超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,提高分布式系统的弹性。Hystrix提供了服务熔断和服务降级两种机制来应对服务雪崩的情况。
首先要说明:服务熔断(作用在服务端)和服务降级(作用在客户端)两者是有相似之处但也有区别,不能将两者混淆。
什么是服务熔断?
一般来说当某个服务(可以理解为是上述雪崩图中的服务C)出现异常或故障导致该服务响应变得过慢或不可用,此时为了保证调用该服务的其他服务(服务A、B)不被阻塞,就触发熔断机制不再调用该服务而是直接返回一个我们设置好的fallbackMethod方法,快速释放资源(这样就保证请求不会大量堆积在该服务C上而导致A、B也挂掉)。
注意:熔断器触发后,在短时间内不会调用该服务,并不是一直不去调用了,Hystrix默认是每隔5秒会尝试放行一部分请求去调用该服务,若服务恢复正常,则熔断器关闭,若服务未恢复正常,则熔断器继续保持打开的状态。
服务熔断的代码实现(熔断是服务端配置):
- 服务端(生产者)导入依赖
<!--导入hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 服务端controller配置服务熔断机制(主要就是@HystrixCommand注解的配置)
@RestController
// 服务熔断:在服务端设置
public class CustMapProviderController {
@Autowired
private CustMapService custMapService;
@GetMapping("/getCustMap/{id}")
// @HystrixCommand是hystrix的一个重要的注解:当此服务出现异常时会调用fallbackMethod指定的方法
@HystrixCommand(fallbackMethod = "hystrixGetCustMap")
// 熔断的应用场景:一般是网络故障、服务异常、访问量过多而导致的内存占用过高、服务崩溃等这些问题
public CustMap getCustMap(@PathVariable("id") Long id){
CustMap custMap = custMapService.queryCustMapByID();
if(id==100){
throw new RuntimeException("error:id不能为100");
}
return custMap;
}
public CustMap hystrixGetCustMap(@PathVariable("id") Long id){
CustMap custMap = new CustMap();
custMap.setContact("没有id为100的用户啊");
return custMap;
}
}
- 服务端主启动类开始服务熔断的支持注解
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker // 添加对熔断的支持
public class CustMapProvider_8006 {
public static void main(String[] args) {
SpringApplication.run(CustMapProvider_8006.class,args);
}
}
以上就是服务熔断的代码实现步骤,服务熔断应该还有一些具体可配置的东西,比如可以配置什么时候熔断服务,熔断后时隔多久测试一次服务是否正常等,这里就不做详细的说明了。
什么是服务降级?
服务降级的目的一般是从整体负荷考虑,人为主动去停掉一些服务来释放服务器资源,以对现有的服务做出快速响应的一种机制。它和服务熔断不同的一点是:
服务熔断更多的是因为服务故障而熔断服务
服务降级是我们为了使得资源利用最大化而主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
服务降级的具体代码实现步骤:
- 在api模块中创建一个降级服务,实现FallbackFactory接口,且加上@Component注解
//hystrix服务降级应用场景:
//一般是当服务器压力剧增时,根据我们业务需求量的判断,对一些需求量少的服务进行有策略的降级(也就是关闭某些服务),以此来缓解服务器的压力
// 注意:
// 1.服务降级一般来说是我们人为控制的
// 2.而服务熔断是下游服务故障导致的,是不可控的
@Component
public class CustMapClientServiceFallbackFactory implements FallbackFactory{
@Override
public Object create(Throwable throwable) {
return new CustMapClientService() {
@Override
public CustMap getCustMap() {
CustMap custMap = new CustMap();
custMap.setContact("该服务已被关闭,客户端设置了服务降级");
return custMap;
}
};
}
}
- 在api模块的feign接口中,配置fallbackFactory(这是feign中配置服务降级的方式)
@Component
@FeignClient(value = "springcloud-provider-custMap",fallbackFactory = CustMapClientServiceFallbackFactory.class) // 配置Feign客户端,value为服务应用名称
// fallbackFactory : 我们设置好的服务降级对应的要调用的类
public interface CustMapClientService {
@GetMapping("/getCustMap")
public CustMap getCustMap();
}
- 在客户端(消费者)模块配置文件中开启hystrix服务降级的功能:
# 开启hystrix服务降级: 服务降级在客户端(消费者)配置
feign:
hystrix:
enabled: true
此时代码完毕,当springcloud-provider-custMap服务关闭时再次调用该服务就会走我们在api模块配置好的实现FallbackFactory 接口的类中的方法,而不会直接给用户反馈404等什么的报错页面了,体验性总比服务直接挂点好吧。
服务熔断和降级是比较容易混淆的两个点,面试经常问,应注区别。
Hystrix Dashboard(了解)
hystrix dashboard是hystrix提供的一个服务监控的可视化仪表盘,用于开发人员对所有服务整体的监控,下面简单介绍下它的实现步骤吧
- 首先建立一个单独的用于dashboard监控的module
- 在此module导入dashboard依赖
<!--导入hystrix_dashboard 监控依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 主启动开启dashboard注解
@SpringBootApplication
@EnableHystrixDashboard // 开启dashboard监控(监控的前提是被监控的服务都导入了actuator依赖)
public class CustMapConsumerHystrixDashboard_8007 {
public static void main(String[] args) {
SpringApplication.run(CustMapConsumerHystrixDashboard_8007.class,args);
}
}
之前在用本机做测试的时候服务一直监控不到,找了半天原因有人说是要还要在dashboard模块的配置文件中配置个东西:
hystrix:
dashboard:
proxy-stream-allow-list: "localhost" # 监控不到服务,报的错就提示让把主机ip加到这个配置里,加了这个配置就好了
- 被监控的服务要导入必要依赖
<!--这个包可以配置一些监控的信息,了解即可-->
<!--若使用hystrix_dashboard监控,则此依赖必须导入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--导入hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 被监控的服务在主启动类中要注册一个servlet
@SpringBootApplication
@EnableEurekaClient // 开启eureka客户端的注解,启动后会自动将此eureka client注册到eureka server中
@EnableDiscoveryClient // 服务发现 了解即可
@EnableCircuitBreaker // 添加对熔断的支持
public class CustMapProvider_8006 {
public static void main(String[] args) {
SpringApplication.run(CustMapProvider_8006.class,args);
}
// 为了这个服务被hystrix-dashboard 监控,
// 我们这里需要注册一个Servlet到容器中,这段代码是固定的不用记
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream"); // 设置此servlet的访问路径
return registrationBean;
}
}
以上就基本配置完成了吧,下面就可以测试下了:
启动eureka服务端、被监控的微服务、dashboard服务端
- 先可以测试下我们服务中配置的那个Servlet页面,有以下数据表示配置成功
- 打开hystrix dashboard监控页面,输入对应信息
- 进入监控页面
比如绿色点的大小和曲线就表示服务被访问的频率高低,变成红色就表示服务有问题了等等还有其他一些信息都可以在此页面被直观的监控到。
八、Zuul
Zuul是spring cloud中的微服务网关。何为网关?就是一个网络整体系统中的前置门户入口。请求首先通过网关,进行路径的路由,定位到具体的服务节点上。
Zuul网关不是必要的。是推荐使用的。使用Zuul,一般在微服务数量较多(多于10个)的时候推荐使用,对微服务的管理有严格要求的时候推荐使用,当微服务权限要求严格要求的时候推荐使用。
可以用下面的图来理解网关在系统中的位置:
由上图网关的位置其实我们就可以大致预测网关是干嘛用的了,Zuul网关的作用如下:
- 统一入口:未全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
- 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
- 动态路由:动态的将请求路由到不同的后端集群中。
- 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。
其实简单理解Zuul网关的作用就是:路由和过滤
路由Router:就是本来我们调用服务是需要访问服务的地址的,但是通过路由我们可以访问某一个地址,由该地址转发到相应的服务地址,这就是路由,其实路由跟转发是一个道理
过滤Filter:Zuul提供了一个ZuulFilter类,我们可以配置一个继承该类的子类来配置一些对请求过滤的功能。
下面是zuul的代码实现步骤:
- 因为zuul是微服务网关,那首先它得先是一个微服务,所以我们要先建立一个zuul的模块,并注册到eureka中
- 导入依赖
<!--导入Eureka 客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--导入zuul依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 编写配置文件(端口号,应用名,eureka等这些配置就不再展示了,和上面的服务端(生产者)里面的配置都类似)
# zuul的目的就是为了对外界隐藏我们各个微服务的路径,端口号这些信息
zuul:
routes:
mycustmap: # 定义一个键值对,就是该服务被哪一个path代替了
serviceId: springcloud-provider-custMap-hystrix
path: /mycustmap/**
#ignored-services: springcloud-provider-custMap-hystrix # 不让使用此应用名访问了(也就是只能根据上面的mycustmap来访问了)
ignoredServices: "*" #就是隐藏所有的服务
prefix: /hhl # 加个访问的前缀(和tomcat里面配置的那个前缀类似)
- 主启动类开启zuul功能
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy // 开启路由网关功能
public class SpringCloudZuul_9527 {
public static void main(String[] args) {
SpringApplication.run(SpringCloudZuul_9527.class,args);
}
}
之后进行测试,浏览器输入我们zuul模块的9527的接口,可以调用到对应的服务了,表示配置成功
Zuul的过滤器代码实现:
@Component // 该类要放到spring容器中去
public class MyZuulFilter extends ZuulFilter {
public MyZuulFilter() {
super();
}
@Override
public String filterType() {
System.out.println("=====filterType方法运行=====");
return "pre";
}
@Override
public int filterOrder() {
System.out.println("=====filterOrder方法运行=====");
return 0;
}
@Override
public boolean shouldFilter() {
System.out.println("=====shouldFilter方法运行=====");
return true;
}
@Override
public Object run() throws ZuulException {
System.out.println("=====run方法运行=====");
return null;
}
}
九、SpringCloud Config
什么是SpringCloud Config?
SpringCloud Config项目是一个用于解决分布式系统的配置管理方案,它分为server和client两个部分。它的出现是为了集中管理我们所有微服务的配置。
为什么要集中管理呢?
因为在微服务架构中有很多的服务模块,每一个模块又有相应的配置,而每个模块对应的配置都是写在项目中硬编码的,为了使我们更便捷的去管理每个微服务模块的配置,出现了spring-cloud-config这个技术,它实现了微服务架构中配置与代码解耦,其实主要也方便了运维人员对系统的配置维护。
上面说到SpringCloud Config分为server端和client端,怎么去理解这两个概念呢?首先我们要思考,集中管理那总要有一个存储集中配置文件的地方吧,这个地方在哪?我们一般都放在在远程仓库例如git上,那放到git上了我们必然需要去读取远程的配置,由此我们建立了一个单独的server模块,用来去读取远程仓库中的配置信息。server模块拉取下来了远程仓库里的所有的配置信息,那我们系统中的所有模块(eureka、消费者、生成者等待所有写过的module)就作为clinet端来读取server模块上的信息。
整个流程的示意图如下:
client(我们写过的所有模块) ----》 server端 -----》 github 远程仓库配置文件
下面是config server端代码的实现步骤:
-
首先新建一个config server模块
-
导入server端依赖
<!--spring-boot-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入spring-cloud-config server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
- 编写配置文件
server:
port: 3344
spring:
application:
name: spring-cloud-config-server
# config配置,为了使我们当前的config-server项目可以连接到远程仓库的配置
cloud:
config:
server:
git:
uri: https://gitee.com/******* # 远程仓库https地址
- 主启动类开启config功能
@SpringBootApplication
@EnableConfigServer // 开启spring-cloud-config功能
public class CloudConfigServer_3344 {
public static void main(String[] args) {
SpringApplication.run(CloudConfigServer_3344.class,args);
}
}
client端的代码实现,这里以eureka的配置为例:
client端导入依赖和开启注解什么的都不用变,就是配置文件中更改就行了:
# bootstrap.yml 一般用来系统级别的配置
spring:
cloud:
config:
name: eureka-config # 读取远程哪一个文件 (不用加.yml后缀)
label: master # 读取哪个分支
profile: dev # 哪个环境
uri: http://localhost:3344/ # config服务端路径
# 由以上4个配置就可以准确定位到此项目需要读取的远程配置文件了~
ps: springboot默认读取application.yml文件,但也读取名为bootstrap.yml的配置文件,两者的区别就是application.yml一般用来用户级别的配置,bootstrap.yml 一般用来系统级别的配置。上面的这个配置就是写在bootstrap.yml中的。
以上就完成了springcloud config中的server端和client端的配置,启动改client端能正常访问则表示我们client已经读取到远程的配置并启动成功了。
下面是该client端读取的远程仓库中eureka-config配置文件的内容:
---
spring:
profiles:
active: dev # 要激活哪个环境配置
---
server:
port: 7004
spring:
profiles: dev
application:
name: eureka-config
# Eureka配置
eureka:
instance:
hostname: eureka7004.com #Eureka 服务端的实例名称
client:
register-with-eureka: false # 是否向Eureka 注册中心注册自己 (这个本身就是服务端当然不用注册自己)
fetch-registry: false # false 表示自己是注册中心
service-url:
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
---
server:
port: 7005
spring:
profiles: test
application:
name: eureka-config
# Eureka配置
eureka:
instance:
hostname: eureka7004.com #Eureka 服务端的实例名称
client:
register-with-eureka: false # 是否向Eureka 注册中心注册自己 (这个本身就是服务端当然不用注册自己)
fetch-registry: false # false 表示自己是注册中心
service-url:
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
以上就是Spring Cloud Netflix中五大组件和SpringCloud Config的介绍了。
总结:SpringCloud不单单指一个框架,它更像一个生态,它融合了很多的现有框架和技术,为开发人员留下的一套简单的使用步骤,基本每一个组件都是导入依赖,编写配置,开启注解。使用简单,但我们更要掌握的是每个组件的使用背景,它帮我们解决了什么问题。
SpringCloud中其实还有很多需要学习的内容,比如:
- SpringCloud Alibaba:和Netflix功能类似
- SpringCloud Bus:事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与SpringCloud Config联合实现热部署。
- SpringCloud Security:基于spring security的安全工具包,为你的应用程序添加安全控制。
- SpringCloud Stream:数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息。