这里写目录标题

1.系统架构的演变1.1 集中式架构1.2 垂直拆分1.3 分布式服务1.4 流动计算架构(SOA)1.5 微服务

2.服务调用方式2.1 RPC和HTTP2.2 Http客户端工具2.3 Spring的RestTemplate(封装Http客户端)

3.SpringCloud3.1 微服务场景模拟3.2服务拆分3.3 提供者和消费者3.4 微服务案例实现3.5 存在的问题

4.Eureka注册中心4.1 Eureka的作用4.2 搭建eureka-server4.3 服务注册(provider端实现)4.4 服务发现(consumer端实现)4.5 服务注册和服务发现的旧版本实现(了解)

5.Eureka进阶5.1 Eureka基础架构5.2 高可用的Eureka Server5.2.1 动手搭建高可用的Eureka Server5.2.2 注册服务到集群5.2.2.1 测试服务同步

5.3 服务提供者5.4 服务消费者5.5 失效剔除和自我保护

6.Ribbon负载均衡6.1 开启负载均衡6.2 源码跟踪6.2.1 测试

6.3 负载均衡策略IRule6.4 Ribbon流程6.5 负载均衡策略6.5.1 自定义负载均衡策略

6.6 饥饿加载

7.Nacos注册中心7.1 服务注册到nacos7.2 服务、集群、实例关系(服务分级存储模型)7.3 给user-service配置集群7.4 同集群优先的负载均衡7.5 权重配置7.6 环境隔离7.6.1 创建namespace7.6.2 给微服务配置namespace

7.7 Nacos与Eureka的区别

8.Nacos配置管理8.1 统一配置管理8.1.1 在nacos中添加配置文件8.1.2 从微服务拉取配置

8.2 配置热更新8.3 配置共享8.4 配置共享的优先级

9.Hystrix(保护机制)9.1 分布式系统面临问题9.2 Hystix的作用9.3 Hystix解决雪崩问题的手段9.4 线程隔离(服务降级)9.4.1 动手实现服务降级9.4.2 默认FallBack9.4.3 设置超时

9.5 服务熔断9.5.1 动手实现服务熔断9.5.2 熔断的三种状态

10.Feign10.1 使用Feign替代RestTemplate10.2 自定义配置10.3 Feign使用优化(底层使用连接池)10.4 抽取接口Api10.4.1 继承方式

10.5 负载均衡10.6 Hystrix支持

11.Gateway服务网关11.1 为什么需要网关11.2 动手部署一个gateway服务11.3 网关路由的流程图11.4 断言工厂11.5 过滤器工厂

1.系统架构的演变

集中式架构 > 垂直拆分 > 分布式架构 > SOA > 微服务

1.1 集中式架构

  将业务的所有功能集中在一个项目中开发,打成一个包部署。

优点

架构简单部署成本低 缺点

耦合度高(维护困难、升级困难)

1.2 垂直拆分

  当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:

优点

系统拆分实现了流量分担,解决了并发问题可以针对不同模块进行优化方便水平扩展,负载均衡,容错率提高 缺点

系统间相互独立,会有很多重复开发工作,影响开发效率

1.3 分布式服务

  当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。

优点

将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率 缺点

系统间耦合度变高,调用关系错综复杂,难以维护

1.4 流动计算架构(SOA)

  当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键 服务治理要做什么? 1.服务注册中心,实现服务自动注册和发现,无需人为记录服务地址 2.服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系 3.动态监控服务状态监控报告,人为控制服务状态

缺点

服务间会有依赖关系,一旦某个环节出错会影响较大服务关系复杂,运维、测试部署困难,不符合DevOps思想

1.5 微服务

微服务的架构特征:

单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责自治:自治是说服务间互相独立,互不干扰

团队独立:每个服务都是一个独立的开发团队,人数不能过多。技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口数据库分离:每个服务都使用自己的数据源部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护 面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

  微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。

因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 。

2.服务调用方式

2.1 RPC和HTTP

  无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?常见的远程调用方式有以下2种:

RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。

  如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么SpringCloud搭建微服务是不二之选。在我们的项目中,我们会选择SpringCloud套件,因此我们会使用Http方式来实现服务间调用。

2.2 Http客户端工具

已经有很多的http客户端工具,能够帮助我们做这些事情,例如:

HttpClientOKHttpURLConnection

接下来,不过这些不同的客户端,API各不相同

2.3 Spring的RestTemplate(封装Http客户端)

  Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:

HttpClientOkHttpJDK原生的URLConnection(默认的)

使用步骤: 一、在Spring容器中注册RestTemplate 可以在启动类位置注册:

@SpringBootApplication

public class HttpDemoApplication {

public static void main(String[] args) {

SpringApplication.run(HttpDemoApplication.class, args);

}

@Bean

public RestTemplate restTemplate() {

return new RestTemplate();

}

}

二、在测试类中注入,并调用其他服务的接口

@RunWith(SpringRunner.class)

@SpringBootTest(classes = HttpDemoApplication.class)

public class HttpDemoApplicationTests {

@Autowired

private RestTemplate restTemplate;

@Test

public void httpGet() {

// 调用springboot案例中的rest接口

User user = this.restTemplate.getForObject("http://localhost/user/1", User.class);

System.out.println(user);

}

}

通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。

3.SpringCloud

  SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。

3.1 微服务场景模拟

案例:从订单模块查询订单的同时,根据订单中包含的用户编号查询出用户信息 在进行模拟之前,我们先了解两个概念:服务拆分、远程调用

3.2服务拆分

  在集中式架构中,我们会将订单服务和用户服务的功能写在同一个项目中,缺点也很明显:功能模块耦合度高,不利于开发和维护   但是在分布式架构中,我们会将服务进行拆分,微服务也同样如此

一、服务拆分原则:

不同微服务,不要重复开发相同业务微服务数据独立,不要访问其它微服务的数据库微服务可以将自己的业务暴露为接口,供其它微服务调用

二、服务拆分示例: cloud-demo:父工程,管理依赖

order-service:订单微服务,负责订单相关业务user-service:用户微服务,负责用户相关业务

3.3 提供者和消费者

在调用关系中,我们要明确服务提供者和服务消费者

服务提供者:

提供接口给其他微服务 服务消费者:

调用其他微服务的接口

3.4 微服务案例实现

一、服务提供者对外提供接口 服务提供者只需要对外提供接口即可,不需要其他操作

@Slf4j

@RestController

@RequestMapping("/user")

public class UserController {

@Autowired

private UserService userService;

/**

* 根据用户编号查询用户信息

*/

@GetMapping("/{id}")

public User queryById(@PathVariable("id") Long id) {

return userService.queryById(id);

}

}

二、服务消费者注册RestTemplate 服务站消费者想要调用远程服务(Http方式)的前提是注册RestTemplate(此处是JDK原生的URLConnection), 我们可以在启动类中,注册RestTemplate实例

@SpringBootApplication

public class OrderApplication {

public static void main(String[] args) {

SpringApplication.run(OrderApplication.class, args);

}

@Bean

public RestTemplate restTemplate() {

return new RestTemplate();

}

}

三、服务消费者远程调用服务 使用RestTemplate的getForObject()方法进行服务调用

@RestController

@RequestMapping("order")

public class OrderController {

@Autowired

private OrderService orderService;

@Autowired

private RestTemplate restTemplate;

@GetMapping("{orderId}")

public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {

Order order = orderService.queryOrderById(orderId);

// 远程调用(Http方式)

String url = "http://localhost:8081/user/" + order.getUserId();

User user = restTemplate.getForObject(url, User.class);

order.setUser(user);

return order;

}

}

四、流程图

3.5 存在的问题

问题引入

order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?有多个user-service实例地址,order-service调用时该如何选择?order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?

问题总结

在consumer中,我们把url地址硬编码到了代码中,不方便后期维护consumer需要记忆provider的地址,如果出现变更,可能得不到通知,地址将失效consumer不清楚provider的状态,服务宕机也不知道provider只有1台服务,不具备高可用性即便provider形成集群,consumer还需自己实现负载均衡

其实上面说的问题,概括一下就是分布式服务必然要面临的问题

服务管理

如何自动注册和发现如何实现状态监管如何实现动态路由 服务如何实现负载均衡服务如何解决容灾问题服务如何实现统一配置

4.Eureka注册中心

问题分析   在刚才的案例中,user服务对外提供服务,需要对外暴露自己的地址。而order服务(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。

4.1 Eureka的作用

一、order-service怎么知道user-service的实例地址 获取地址信息流程:

user-service实例启动后,将自己的信息注册到eureka-server(Eureka服务端),这个叫注册服务eureka-server保存服务名称到服务实例地址列表的映射关系

集群时, 一个服务名称可能对应多个服务实例地址 provider根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取

二、order-service怎么从多个user-service实例中选择具体的实例

order-service从实例列表中利用负载均衡算法选中一个实例地址向该实例地址发起远程调用

三、order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?

user-service服务会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳当超过一定时间没有发送心跳时,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除order-service拉取服务列表时,就能将故障实例排除了

4.2 搭建eureka-server

一、引入springCloud的核心依赖   spring-cloud-dependencies包含了所有Spring项目中所需的基础依赖,如SpringBoot, SpringCloud Netfix,通过引入这个依赖,我们就可以使用SpringCloud的各自依赖和框架

  当下我们暂时只需要使用eureka-server的依赖,所以我们把spring-cloud-dependencies设为可选依赖,我们可以将spring-cloud-dependencies在父工程中定义

org.springframework.cloud

spring-cloud-dependencies

Hoxton.SR10

pom

import

Springcloud的版本依赖问题(最全,包含springCloud所有的版本)

注意点:SpringCloud和Springboot的版本是存在对应关系, 例如使用SpringCloud Finchley.SR2能支持Springboot 2.0.x版本.但是不能支持Springboot版本2.2.x,版本不一致时就会报错:Unable to start embedded Tomcat

二、引入eureka依赖   在我们的子工程中,想要使用SpringCloud的组件,只需要在子工程的pom.xml中引入对应的依赖即可例如:引入SpringCloud为eureka提供的starter依赖,无需添加版本号

org.springframework.cloud

spring-cloud-starter-netflix-eureka-server

三、编写启动类(@EnableEurekaServer) 启动类一定要添加@EnableEurekaServer注解,开启eureka的注册中心功能

package cn.itcast.eureka;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication

@EnableEurekaServer

public class EurekaApplication {

public static void main(String[] args) {

SpringApplication.run(EurekaApplication.class, args);

}

}

四、编写application.yml

server:

port: 10086 # 端口

spring:

application:

name: eureka-server # 应用名称, 会在Eureka中显示

eureka:

client:

service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。

defaultZone: http://127.0.0.1:${server.port}/eureka

五、启动服务 启动微服务,然后在浏览器访问:http://127.0.0.1:10086

4.3 服务注册(provider端实现)

下面,我们将user-service注册到eureka-server中去。

前提:引入springCloud的核心依赖 已在父工程的pom.xml中引入

org.springframework.cloud

spring-cloud-dependencies

Hoxton.SR10

pom

import

一、引入依赖 引入下面的eureka-client依赖. 注意:此处引入的是eureka-client,而非eureka-server

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

二、编写application.yml

spring:

application:

name: user-service # 应用名称,注册到eureka后的服务名称

eureka:

client:

service-url: # EurekaServer地址

defaultZone: http://127.0.0.1:10086/eureka

三、启动多个user-service实例 为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service。将第二个实例的端口改为8082

查看eureka-server管理页面:

4.4 服务发现(consumer端实现)

前提:引入springCloud的核心依赖 已在父工程的pom.xml中引入

org.springframework.cloud

spring-cloud-dependencies

Hoxton.SR10

pom

import

一、引入依赖(依赖与服务注册一直) 之前说过,服务发现、服务注册统一都封装在eureka-client依赖,因此这一步与服务注册时一致。 注意:此处引入的是eureka-client,而非eureka-server

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

二、编写application.yml(配置与服务注册一致) 服务发现也需要知道eureka地址,因此第二步与服务注册一致,都是配置eureka信息:

spring:

application:

name: order-service

eureka:

client:

service-url:

defaultZone: http://127.0.0.1:10086/eureka

启动项目后,查看eureka-server管理页面:

三、服务拉取和负载均衡   最后,我们要去eureka-server中拉取user-service服务的实例列表,并且实现负载均衡。不过这些动作不用我们去做,只需要添加一些注解即可。

在注册RestTemplate时,添加@LoadBalanced注解,实现负载均衡

四、修改远程接口路径 修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径,用服务名代替ip、端口: spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡。

4.5 服务注册和服务发现的旧版本实现(了解)

  当前我们使用的SpringBoot版本为2.3.9.RELEASE,使用的SpringCloud为Hoxton.SR10,这个版本不需要我们手动开启Eureka客户端功能,那我们需要了解一下旧版是如何实现服务注册和服务发现

模拟旧版本:将SpringBoot版本改为2.0.6.RELEASE,将SpringCloud版本改为Finchley.SR2,

一、服务注册 需要在引导类上添加@EnableDiscoveryClient来开启Eureka客户端功能

@SpringBootApplication

@EnableDiscoveryClient

public class ProviderApplication {

public static void main(String[] args) {

SpringApplication.run(ProviderApplication .class, args);

}

}

二、服务发现 同样需要在引导类上添加@EnableDiscoveryClient来开启Eureka客户端功能

```java

@SpringBootApplication

@EnableDiscoveryClient

public class ConsumerApplication{

public static void main(String[] args) {

SpringApplication.run(ConsumerApplication.class, args);

}

}

老版本需要使用DiscoveryClient类的方法,根据服务名称,获取服务实例

@RestController

@RequestMapping("order")

public class OrderController{

@Autowired

private RestTemplate restTemplate;

@Autowired

private DiscoveryClient discoveryClient; // eureka客户端,可以获取到eureka中服务的信息

@GetMapping("{orderId}")

public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {

// 根据服务名称,获取服务实例列表。有可能是集群,所以是service实例集合

List instances = discoveryClient.getInstances("user-service");

// 获取第一个实例

ServiceInstance instance = instances.get(0);

// 获取ip和端口信息,拼接成服务地址

String baseUrl = "http://" + instance.getHost() + ":" + instance.getPort() + "/user/" + id;

User user = this.restTemplate.getForObject(baseUrl, User.class);

return user;

}

}

5.Eureka进阶

5.1 Eureka基础架构

服务注册中心 Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server。服务提供者 提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。服务消费者 消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。

5.2 高可用的Eureka Server

  Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。

服务同步   多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

5.2.1 动手搭建高可用的Eureka Server

我们假设要运行两个eureka-server的集群,端口分别为:10086和10087。只需要把eureka-server启动两次即可。

一、启动第一个eureka-Server 需要在defaultZone中配置其他Eureka服务的地址,例如10087

server:

port: 10086 # 端口

spring:

application:

name: eureka-server # 应用名称,会在Eureka中显示

eureka:

client:

service-url: # 配置其他Eureka服务的地址,而不是自己,比如10087

defaultZone: http://127.0.0.1:10087/eureka

启动服务后,会报异常,这个异常是正常情况,等把10087也启动后,异常就会消失

二、启动第二个eureka-Server 在Idea中一个应用不能重复启动,所以我们需要重新配置一个启动器

这些信息复制上一个eureka-Server

修改application.yml,然后启动EurekaApplication(10087)

server:

port: 10087

spring:

application:

name: eureka-server

eureka:

client:

service-url:

defaultZone: http://127.0.0.1:10086/eureka

三、访问集群 访问http://127.0.0.1:10086/

访问http://127.0.0.1:10087/

5.2.2 注册服务到集群

注册服务到集群   此时user服务,只要配置10086节点和10087节点中任何一个即可,因为集群有服务同步,但是既然已经使用到了集群,为了防止其中一个节点宕机,注册服务时要填上集群中所有节点

eureka:

client:

service-url: # EurekaServer地址,多个地址以','隔开

defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

5.2.2.1 测试服务同步

一、user服务在10086节点注册后, 会将服务信息同步到10087节点 此时consumer在10086这个节点注册服务,该节点也会将服务信息同步到10087,我们可以测试一下

10087节点的显示

二、order服务请求10087节点的user服务 我们调用order服务的接口,debug发现能调通user服务的接口,虽然这两个服务配置的eureka-server节点不同

总结: 即使两个服务(user服务、order服务)配置的eureka-server的节点不同,但只要这两个eureka-server在一个集群中, user服务和order服务之间也能互相远程调用

5.3 服务提供者

一、服务注册   服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-eureka=true参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。

第一层Map的Key就是服务id,一般是配置中的spring.application.name属性第二层Map的key是服务的实例id。一般host+ serviceId + port,例如:locahost:service-provider:8081值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。

二、服务续约   在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);

有两个重要参数可以修改服务续约的行为:

eureka:

instance:

lease-expiration-duration-in-seconds: 90

lease-renewal-interval-in-seconds: 30

lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒 lease-expiration-duration-in-seconds:服务失效时间,默认值90秒

  也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。

  但是在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。

eureka:

instance:

lease-expiration-duration-in-seconds: 10 # 10秒即过期

lease-renewal-interval-in-seconds: 5 # 5秒一次心跳

5.4 服务消费者

获取服务列表   当服务消费者启动时,会检测eureka.client.fetch-registry=true参数的值,如果为true,则会拉取Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒会重新获取并更新数据。我们可以通过下面的参数来修改:   生产环境中,我们不需要修改这个值。但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。

eureka:

client:

registry-fetch-interval-seconds: 5

5.5 失效剔除和自我保护

一、服务下线   当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。

二、失效剔除   有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。

  可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒,生产环境不要修改。

  这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如:10秒

三、自我保护 我们关停一个服务,就会在Eureka面板看到一条警告:

  这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:(eureka-service)

eureka:

server:

enable-self-preservation: false # 关闭自我保护模式(缺省为打开)

eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)

6.Ribbon负载均衡

  在实际开发环境中, 往往会开启很多provider的集群, 此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?现在我们不需要自己编写负载均衡算法, Eureka中已经帮我们集成了负载均衡组件:Ribbon

负载均衡原理:

6.1 开启负载均衡

@Bean

@LoadBalanced

public RestTemplate restTemplate() {

return new RestTemplate();

}

修改调用方式,不再手动设置ip和端口号,而是直接通过服务名称调用:

6.2 源码跟踪

为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。

显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。

一、LoadBalancerIntercepor RestTemplate.getForObject --> RestTemplate.execute --> RestTemplate.doExecute --> AbstractClientHttpRequest.execute --> AbstractBufferingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.execute --> LoadBalancerInterceptor.intercept方法 可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:

request.getURI():获取请求uri,本例中就是 http://user-service/user/1originalUri.getHost():获取uri路径的主机名,其实就是服务id,user-servicethis.loadBalancer.execute():处理服务id,和用户请求。

这里的this.loadBalancer是LoadBalancerClient类型,我们继续跟入。

二、LoadBalancerClient

getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8082端口的服务

放行后,再次访问并跟踪,发现获取的是8081:

6.2.1 测试

使用LoadBalancerClient获取服务列表

@RunWith(SpringRunner.class)

@SpringBootTest(classes = ItcastServiceConsumerApplication.class)

public class LoadBalanceTest {

@Autowired

private RibbonLoadBalancerClient client;

@Test

public void testLoadBalance(){

for (int i = 0; i < 100; i++) {

ServiceInstance instance = this.client.choose("service-provider");

System.out.println(instance.getHost() + ":" +instance.getPort());

}

}

}

结果: 符合了我们的预期推测,确实是轮询方式。

6.3 负载均衡策略IRule

在刚才的代码中,可以看到获取服务使通过一个getServer方法来做负载均衡:

我们继续跟入:

继续跟踪源码chooseServer方法,发现这么一段代码:

我们看看这个rule是谁:

这里的rule默认值是一个RoundRobinRule,看类的介绍:

6.4 Ribbon流程

SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改。用一幅图来总结一下:

拦截我们的RestTemplate请求http://userservice/user/1RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-serviceDynamicServerListLoadBalancer根据user-service到eureka拉取服务列表eureka返回列表,localhost:8081、localhost:8082IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求

6.5 负载均衡策略

负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类: 不同规则的含义如下:

6.5.1 自定义负载均衡策略

通过定义IRule实现可以修改负载均衡规则,有两种方式:

一、代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:

@Bean

public IRule randomRule(){

return new RandomRule();

}

二、配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:

userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务

ribbon:

NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则

6.6 饥饿加载

  Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

ribbon:

eager-load:

enabled: true

clients: userservice

7.Nacos注册中心

  国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心。

一、下载安装 本课程采用1.4.1.版本的Nacos,课前资料已经准备了安装包: windows版本使用nacos-server-1.4.1.zip包即可。

二、解压 将这个包解压到任意非中文目录下,如图:

三、端口配置 Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口: 修改其中的内容:

四、启动 启动非常简单,进入bin目录,结构如下: 然后执行命令即可:

// windows命令:

startup.cmd -m standalone

执行后的效果如图:

五、访问 我们启动了Nacos服务后,在浏览器输入地址:http://127.0.0.1:8848/nacos即可:

默认的账号和密码都是nacos,进入后:

7.1 服务注册到nacos

  Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。

一、引入依赖 在cloud-demo父工程的pom文件中的中引入SpringCloudAlibaba的依赖:

com.alibaba.cloud

spring-cloud-alibaba-dependencies

2.2.6.RELEASE

pom

import

然后在user-service和order-service中的pom文件中引入nacos-discovery依赖:

com.alibaba.cloud

spring-cloud-starter-alibaba-nacos-discovery

注意:不要忘了注释掉eureka的依赖。

二、配置nacos地址 在user-service和order-service的application.yml中添加nacos地址:

spring:

cloud:

nacos:

server-addr: localhost:8848

注意:不要忘了注释掉eureka的地址

三、重启 重启微服务后,登录nacos管理页面,可以看到微服务信息:

四、调用服务 和之前使用eureka时一模一样,注册RestTemplate,然后使用RestTemplate进行远程服务调用

@RestController

@RequestMapping("order")

public class OrderController {

@Autowired

private OrderService orderService;

@Autowired

private RestTemplate restTemplate;

@GetMapping("{orderId}")

public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {

// 根据id查询订单并返回

Order order = orderService.queryOrderById(orderId);

String url = "http://user-service/user/" + order.getUserId();

User user = restTemplate.getForObject(url, User.class);

order.setUser(user);

return order;

}

}

7.2 服务、集群、实例关系(服务分级存储模型)

一个服务可以有多个实例,例如我们的user-service,可以有:

127.0.0.1:8081127.0.0.1:8082127.0.0.1:8083

假如这些实例分布于全国各地的不同机房,例如:

127.0.0.1:8081,在上海机房127.0.0.1:8082,在上海机房127.0.0.1:8083,在杭州机房

Nacos就将同一机房内的实例 划分为一个集群。

也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型,如图: 微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。例如: 杭州机房内的order-service应该优先访问同机房的user-service。

7.3 给user-service配置集群

修改user-service的application.yml文件,添加集群配置:

spring:

cloud:

nacos:

server-addr: localhost:8848

discovery:

cluster-name: HZ # 集群名称

重启两个user-service实例后,我们可以在nacos控制台看到下面结果:在HZ集群下有两个实例

我们再次复制一个user-service启动配置,添加属性:

-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH

启动UserApplication3后再次查看nacos控制台:

7.4 同集群优先的负载均衡

nacos的负载均衡实现方式与之前使用erurka时是一致的, 只需要在注册RestTemplate时添加@LoadBalanced注解即可

@MapperScan("cn.itcast.order.mapper")

@SpringBootApplication

public class OrderApplication {

public static void main(String[] args) {

SpringApplication.run(OrderApplication.class, args);

}

@Bean

@LoadBalanced

public RestTemplate restTemplate() {

return new RestTemplate();

}

}

默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。

一、给order-service配置集群信息 修改order-service的application.yml文件,添加集群配置, 告诉LoadBalancerClient优先获取哪个集群的服务列表

spring:

cloud:

nacos:

server-addr: localhost:8848

discovery:

cluster-name: HZ # 集群名称

二、修改负载均衡规则 修改order-service的application.yml文件,修改负载均衡规则:

userservice:

ribbon:

NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则

7.5 权重配置

实际部署中会出现这样的场景:   服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。

修改权重   因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重:

在弹出的编辑窗口,修改权重: 注意:如果权重修改为0,则该实例永远不会被访问

7.6 环境隔离

Nacos提供了namespace来实现环境隔离功能。

nacos中可以有多个namespacenamespace下可以有group、service等不同namespace之间相互隔离,例如不同namespace的服务互相不可见

7.6.1 创建namespace

默认情况下,所有service、data、group都在同一个namespace,名为public:

我们可以点击页面新增按钮,添加一个namespace:

然后,填写表单:

就能在页面看到一个新的namespace:

7.6.2 给微服务配置namespace

给微服务配置namespace只能通过修改配置来实现。 例如,修改order-service的application.yml文件:

spring:

cloud:

nacos:

server-addr: localhost:8848

discovery:

cluster-name: HZ

namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID

重启order-service后,访问控制台,可以看到下面的结果:

user-service都在pulic这个命令空间中, 当我们切换到dev命名空间后,发现了order-server

此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错:

7.7 Nacos与Eureka的区别

Nacos的服务实例分为两种类型:

临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。

配置一个服务实例为永久实例:

spring:

cloud:

nacos:

discovery:

ephemeral: false # 设置为非临时实例

Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异:

Nacos与eureka的共同点

都支持服务注册和服务拉取都支持服务提供者心跳方式做健康检测

Nacos与Eureka的区别

Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式临时实例心跳不正常会被剔除,非临时实例则不会被剔除Nacos支持服务列表变更的消息推送模式,服务列表更新更及时Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

8.Nacos配置管理

Nacos除了可以做注册中心,同样可以做配置管理来使用。

8.1 统一配置管理

  当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。   Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。

8.1.1 在nacos中添加配置文件

如何在nacos中管理配置呢?

然后在弹出的表单中,填写配置信息: 注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。

8.1.2 从微服务拉取配置

微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。 但如果尚未读取application.yml,又如何得知nacos地址呢? 因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:

一、引入nacos-config依赖 首先,在user-service服务中,引入nacos-config的客户端依赖:

com.alibaba.cloud

spring-cloud-starter-alibaba-nacos-config

二、添加bootstrap.yaml

spring:

application:

name: user-service # 服务名称

profiles:

active: dev #开发环境,这里是dev

cloud:

nacos:

server-addr: localhost:8848 # Nacos地址

config:

file-extension: yaml # 文件后缀名

这里会根据spring.cloud.nacos.server-addr获取nacos地址,再根据${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。本例中,就是去读取user-service-dev.yaml:

三、读取nacos配置 在user-service中的UserController中添加业务逻辑,读取pattern.dateformat配置:

在页面访问,可以看到效果:

8.2 配置热更新

我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新。 要实现配置热更新,可以使用两种方式:

方式一 在@Value注入的变量所在类上添加注解@RefreshScope:

方式二 使用@ConfigurationProperties注解代替@Value注解。 在user-service服务中,添加一个类,读取patterrn.dateformat属性:

package cn.itcast.user.config;

import lombok.Data;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.stereotype.Component;

@Component

@Data

@ConfigurationProperties(prefix = "pattern")

public class PatternProperties {

private String dateformat;

}

在UserController中使用这个类代替@Value:

8.3 配置共享

其实微服务启动时,会去nacos读取多个配置文件,例如:

包含环境的配置 [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml不包含环境的配置 [spring.application.name].yaml,例如:user-service.yaml

而[spring.application.name].yaml不包含环境,因此可以被多个环境共享。

一、添加一个环境共享配置 我们在nacos中添加一个userservice.yaml文件:

二、在user-service中读取共享配置 在user-service服务中,修改PatternProperties类,读取新添加的属性:

在user-service服务中,修改UserController,添加一个方法:

三、运行两个UserApplication,使用不同的profile 改UserApplication2这个启动项,改变其profile值: 这样,UserApplication(8081)使用的profile是dev,UserApplication2(8082)使用的profile是test。

四、启动UserApplication和UserApplication2 访问http://localhost:8081/user/prop,结果:

访问http://localhost:8082/user/prop,结果: 可以看出来,不管是dev,还是test环境,都读取到了envSharedValue这个属性的值。

8.4 配置共享的优先级

当nacos、服务本地同时出现相同属性时,优先级有高低之分:

9.Hystrix(保护机制)

9.1 分布式系统面临问题

  多个微服务之间调用的时候,假如微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的"扇出"。   如果扇出的链路上某个微服务的调用响应的时间过长或者不可用,对微服A的调用就会占用越来越多的系统资源,进而引起系统崩溃,即"雪崩效应"。

情景1: 微服务之间相互调用,关系复杂,正常情况如下图所示:

情景2:某个时刻,服务A挂了,服务B和服务C依然在调用服务A

情景3:由于服务A挂了,导致服务C和服务B无法得到服务A的响应,这时候服务C和服务B由于大量线程积压,最终导致服务C和服务B挂掉.

情景4: 相同道理,由于服务之间有关联,所以会导致整个调用链上的所有服务都挂掉.

9.2 Hystix的作用

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,可以保证一个服务出现故障时,不会导致整个系统出现雪崩效应,以提高分布式系统弹性;作为“断路器”,在一个服务出现故障时,可以通过短路器监控,返回一个可以处理的响应结果,保证服务调用线程不会长时间被占用,避免故障蔓延。

9.3 Hystix解决雪崩问题的手段

线程隔离(服务降级)

当方法执行遇到故障时, 就使用降级方法来替代执行 服务熔断

9.4 线程隔离(服务降级)

  服务出现故障时,给故障服务降级到事先准备好的故障处理结果,将此结果返回给服务消费者,如:客户端访问服务1,服务1调用服务2,服务2出现故障,Hystrix服务降级,返回一个可以处理的结果给服务1,服务1再以友好的错误界面返回给客户端。

9.4.1 动手实现服务降级

一、引入依赖(消费者) 在order-service中引入Hystrix依赖

org.springframework.cloud

spring-cloud-starter-netflix-hystrix

二、在启动类上添加@EnableCircuitBreaker注解,开启熔断

Spring提供了一个组合注解@SpringCloudApplication, 它包含了我们之前学习的几个组件(SpringBoot、Eureka客户端、开启Hystrix)

三、编写降级逻辑 在方法上添加@HystrixCommand(fallbackMethod = "降级方法名称")注解, 声明降级方法, 当前方法执行过程中, 发生了故障, 就执行降级方法进行返回

我们可以把降级方法认为是当前方法的替代品, 所以编写降级方法时,需要注意以下两点

相同的参数列表相同的返回值类型

如果没有满足相同的参数列表和返回值声明, 在方法启动时,就会抛出异常

四、测试 我们停止user-service的服务, 当调用order-service的方法时, 遇到故障(这个方法自己的异常也算), 此时就会执行降级方法, 并进行返回

9.4.2 默认FallBack

我们刚刚是将fallback写在业务方法上, 如果这样的方法很多, 且需要的降级方法都一样, 此时我们可以将fallback配置在类上, 实现默认fallback(对这个类中所有方法有效)

@DefaultProperties(defaultFallback = “defaultFallBack”):在类上指明统一的失败降级方法@HystrixCommand:在方法上直接使用该注解,使用默认的剪辑方法。defaultFallback:默认降级方法,不用任何参数,以匹配更多方法,但是返回值一定一致

9.4.3 设置超时

在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystix的默认超时时长为1,我们可以通过配置修改这个值:

我们可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置Hystrix超时时间。该配置没有提示。

hystrix:

command:

default:

execution:

isolation:

thread:

timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms

改造服务提供者 改造服务提供者的UserController接口,随机休眠一段时间,以触发熔断:

@GetMapping("{id}")

public User queryUserById(@PathVariable("id") Long id) {

try {

Thread.sleep(6000);

} catch (InterruptedException e) {

e.printStackTrace();

}

return this.userService.queryUserById(id);

}

9.5 服务熔断

  熔断机制是应对服务雪崩的一种链路保护机制,当服务出现故障时,服务会进行降级,熔断该服务节点,迅速返回错误响应信息。当检测到服务访问正常时,恢复其链路节点。

简单理解:   例如调用order-service的queryOrder方法,当我们添加了@HystrixCommand注解, 此时如果近20次(默认值)调用失败率超过50%(默认值), 服务器就将此方法进行了熔断, 此时所有对queryOrder的调用都会走降级方法, 等5秒后(默认值), 此方法进入半开状态之后,将会再次尝试失败的原始方法。

9.5.1 动手实现服务熔断

为了能够精确控制请求的成功或失败,我们在order-service的调用业务中加入一段逻辑:

@GetMapping("{orderId}")

@HystrixCommand // 标记该方法需要降级服务

public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {

if (orderId == 1) {

throw new RuntimeException("太忙了");

}

// 根据id查询订单并返回

Order order = orderService.queryOrderById(orderId);

String url = "http://user-service/user/" + order.getUserId();

User user = restTemplate.getForObject(url, User.class);

order.setUser(user);

return order;

}

这样如果参数是id为1,一定失败,其它情况都成功。(不要忘了清空service-provider中的休眠逻辑)

我们准备两个请求窗口:

一个请求:http://localhost/order/1,注定失败一个请求:http://localhost/order/2,肯定成功

当我们疯狂访问id为1的请求时(超过20次), 就会触发熔断。断路器会断开,一切请求都会被降级处理. 此时我们访问id为2的请求, 也会失败, 而且失败时间很短, 只有几毫秒作用   简单来说,就是当一个被标注了@HystrixCommand的方法, 连续20次访问(这个数值可以修改)都出现了故障后, 服务器会将这个方法熔断, 之后即使是正常参数的请求访问, 也只会执行降级方法, 不过这个熔断的恢复时间很快

不过,默认的熔断触发要求较高,休眠时间窗较短,为了测试方便,我们可以通过配置修改熔断策略:

circuitBreaker.requestVolumeThreshold=10

circuitBreaker.sleepWindowInMilliseconds=10000

circuitBreaker.errorThresholdPercentage=50

requestVolumeThreshold:触发熔断的最小请求次数,默认20

如果20次请求都出来故障, 此时服务器就会将这个方法熔断 errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%sleepWindowInMilliseconds:休眠时长,默认是5000毫秒

处于打开状态的断路器要经过多长时间才会进入半开状态,进入半开状态之后,将会再次尝试失败的原始方法。

9.5.2 熔断的三种状态

Closed:关闭状态,所有请求都正常访问。Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时

10.Feign

先来看我们以前利用RestTemplate发起远程调用的代码:

String url = "http://user-service/user/" + order.getUserId();

User user = restTemplate.getForObject(url, User.class);

存在问题:

代码可读性差,编程体验不统一参数复杂URL难以维护

10.1 使用Feign替代RestTemplate

步骤:

1.引入依赖2.添加@EnableFeignClients注解3.编写FeignClient接口4.使用FeignClient中定义的方法代替RestTemplate

一、引入依赖 我们在order-service服务的pom文件中引入feign的依赖:

org.springframework.cloud

spring-cloud-starter-openfeign

二、添加注解 在order-service的启动类添加注解@EnableFeignClients开启Feign的功能:

三、编写Feign客户端 注意UserClient是接口类型

package cn.itcast.order.client;

import cn.itcast.order.pojo.User;

import org.springframework.cloud.openfeign.FeignClient;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("user-service")

public interface UserClient {

// 基于远程调用服务的信息

@GetMapping("/user/{id}")

User findById(@PathVariable("id") Long id);

}

服务名称:user-service请求方式:GET请求路径:/user/{id}请求参数:@PathVariable(“id”) Long id返回值类型:User

四、在需要远程调用的地方,注入Feign客户端

@Autowired

private UserClient userClient;

@GetMapping("{orderId}")

@HystrixCommand(fallbackMethod = "queryUserByIdFallBack") // 标记该方法需要降级服务

public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {

// 根据id查询订单并返回

Order order = orderService.queryOrderById(orderId);

// String url = "http://user-service/user/" + order.getUserId();

// User user = restTemplate.getForObject(url, User.class);

User user = userClient.findById(order.getUserId());

order.setUser(user);

return order;

}

10.2 自定义配置

Feign可以支持很多的自定义配置,如下表所示: 一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。

如何自定义配置:

一、配置文件方式 基于配置文件修改feign的日志级别可以针对单个服务:

feign:

client:

config:

userservice: # 针对某个微服务的配置

loggerLevel: FULL # 日志级别

而日志的级别分为四种:

NONE:不记录任何日志信息,这是默认值。BASIC:仅记录请求的方法,URL以及响应状态码和执行时间HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

二、Java代码方式 也可以基于Java代码来修改日志级别,先声明一个类,然后声明一个Logger.Level的对象:

public class DefaultFeignConfiguration {

@Bean

public Logger.Level feignLogLevel(){

return Logger.Level.BASIC; // 日志级别为BASIC

}

}

如果要全局生效,将其放到启动类的@EnableFeignClients这个注解中:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)

如果是局部生效,则把它放到对应的@FeignClient这个注解中:

@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)

10.3 Feign使用优化(底层使用连接池)

Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

URLConnection:默认实现,不支持连接池Apache HttpClient :支持连接池OKHttp:支持连接池

我们提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。这里我们使用HttpClient来演示

使用Http连接池流程

引入feign-httpClient依赖配置文件开启httpClient功能,设置连接池参数

注意点:

日志级别尽量用basic使用HttpClient或OKHttp代替URLConnection

一、引入HttpClient的依赖

io.github.openfeign

feign-httpclient

二、配置连接池 在order-service的application.yml中添加配置:

feign:

httpclient:

enabled: true # 开启feign对HttpClient的支持

max-connections: 200 # 最大的连接数

max-connections-per-route: 50 # 每个路径的最大连接数

接下来,在FeignClientFactoryBean(实例化对象)中的loadBalance方法中打断点:

Debug方式启动order-service服务,可以看到这里的client,底层就是Apache HttpClient:

10.4 抽取接口Api

我们可以观察到Feign的客户端与服务提供者的controller代码非常相似: 我们是不是可以简化这种重复的代码编写

10.4.1 继承方式

一、,抽取一个独立的module, 命名为feign-api

二、引入Feign的starter依赖

org.springframework.cloud

spring-cloud-starter-openfeign

三、在feign-api中编写UserAPI

四、在user-service和order-service中使用feign-api 在order-service、user-service的pom文件中中引入feign-api的依赖:

cn.itcast.demo

feign-api

1.0

五、测试 发现服务报错了: 这是因为UserClient现在在cn.itcast.feign.clients包下,而order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一个包,无法扫描到UserClient。

解决扫描包问题 方式一: 指定Feign应该扫描的包:

@EnableFeignClients(basePackages = "cn.itcast.feign.clients")

方式二: 指定需要加载的Client接口:

@EnableFeignClients(clients = {UserClient.class})

10.5 负载均衡

Feign中本身已经集成了Ribbon依赖和自动配置:

因此我们不需要额外引入依赖,也不需要再注册RestTemplate对象。 Feign底层和RestTemplate底层都使用了URLConnection

10.6 Hystrix支持

一、开启Feign的熔断功能 Feign默认也有对Hystrix的集成:

只不过,默认情况下是关闭的。我们需要通过下面的参数来开启:(在itcast-service-consumer工程添加配置内容)

feign:

hystrix:

enabled: true # 开启Feign的熔断功能

注意: 虽然Feign已经继承了Hystrix, 但是如果你要使用@HystrixCommand注解,那么还是得依赖spring-cloud-starter-netflix-hystrix, 这两个依赖并不冲突, Feign中对Hystrix的集成, 主要是对远程服务降级

-->

org.springframework.cloud-->

spring-cloud-starter-netflix-hystrix-->

-->

org.springframework.cloud

spring-cloud-starter-openfeign

二、编写降级服务类UserClientFallback,实现UserClient   UserClientFallback实现了UserClient , 需要重写queryById方法, 而重写的这个方法即降级方法(替代方法).   注意UserClientFallback 要添加@Component注解,让Spring管理

@Component

public class UserClientFallback implements UserClient {

@Override

public User queryById(Long id) {

User user = new User();

user.setUserName("服务器繁忙,请稍后再试!");

return user;

}

}

三、在UserClient中,指定刚才编写的实现类 通过fallback来指定降级处理类

@FeignClient(value = "service-provider", fallback = UserClientFallback.class) // 标注该类是一个feign接口

public interface UserClient {

@GetMapping("user/{id}")

User queryUserById(@PathVariable("id") Long id);

}

11.Gateway服务网关

11.1 为什么需要网关

Gateway网关是我们服务的守门神,所有微服务的统一入口。

一、架构图

二、网关的核心功能特性:

权限控制 网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。路由和负载均衡 一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。限流 当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

在SpringCloud中网关的实现包括两种:

gatewayzuul

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

11.2 动手部署一个gateway服务

一、创建服务 在SpringCloud下创建一个gateway-server模块

二、引入依赖 当下我们使用Nacos注册中心,让gateway从nacos中拉取服务,

org.springframework.cloud

spring-cloud-starter-gateway

com.alibaba.cloud

spring-cloud-starter-alibaba-nacos-discovery

三、编写启动类

package cn.itcast.gateway;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class GatewayApplication {

public static void main(String[] args) {

SpringApplication.run(GatewayApplication.class, args);

}

}

四、编写基础配置和路由规则

server:

port: 10010 # 网关端口

spring:

application:

name: gateway # 服务名称

cloud:

nacos:

server-addr: localhost:8848 # nacos地址

gateway:

routes: # 网关路由配置

- id: user # 路由id,自定义,只要唯一即可

# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址

uri: lb://user-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称

predicates: # 路由断言,也就是判断请求是否符合路由规则的条件

- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求

- id: order

uri: lb://order-service

predicates:

- Path=/order/**

id:路由的ID 没有固定规则但要求唯一,建议配合服务名,需要保持唯一uri:路由的目标地址 lb就是负载均衡,后面跟服务名称predicates:路由断言 也就是判断请求是否符合路由规则的条件Path:按照路径匹配(这个可以改)

五、启动gateway项目 我们发现在nacos中也注册了这个服务

访问order服务: 访问http://localhost:10010/order/101时,路径/order/101符合- Path=/order/**规则,请求转发到uri:http://order-service/order/101,得到了结果:

访问user服务: 访问http://localhost:10010/user/1时,路径/user/1符合- Path=/user/**规则,请求转发到uri:http://user-service/user/1,得到了结果:

11.3 网关路由的流程图

11.4 断言工厂

  我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件

  例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个:

11.5 过滤器工厂

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理: 路由过滤器的种类 Spring提供了31种不同的路由过滤器工厂。例如:

精彩链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: