- 一.系统架构演变
- 1.1 集中式架构
- 1.2 垂直拆分
- 1.3 分布式服务
- 1.4 服务治理(SOA)
- 1.5 微服务
- 二.远程调用方式
- 2.1 认识RPC
- 2.2 认识HTTP
- 2.3 技术选择
- 三.Spring Cloud
- 3.1 简介
- 3.2 版本
- 四.微服务场景模拟
- 五. Eureka注册中心
- 5.1 概念
- 5.2 工作原理图
- 5.3 入门案例
- 5.3.1 EurekaServer
- 5.3.2 服务注册
- 5.3.3 服务发现
- 5.4 Eureka详解
- 5.4.1 基础架构
- 5.4.2 高可用的Eureka Server
- 5.4.3 Eureka服务端配置
- 5.4.4 失效剔除和自我保护
- 六.负载均衡Ribbon
- 6.1 背景
- 6.2 Ribbon
- 七.Hystrix
- 7.1 背景
- 7.2 线程隔离、服务降级
- 7.2.1 原理
- 7.3 服务熔断
- 7.3.1 原理
- 八.Feign
- 8.1 背景
- 8.2 简介
- 8.3 案例
- 8.4 请求压缩
- 九.Gateway
- 9.1 背景
- 9.2 核心概念
- 9.3 案例入门
- 9.4 路由前缀
- 9.4.1 添加前缀
- 9.4.2 去除前缀
- 9.5 过滤器
- 9.6 跨域配置
- 9.7 Gateway和Feign区别
- 十.Spring Cloud Config配置中心
- 10.1 背景
- 10.2 配置中心搭建
- 10.3 获取配置中心配置
- 11.Spring Cloud Bus总线
- 11.1 背景
- 11.2 架构图
- 11.3 改造配置中心
- 11.4 改造服务提供方
- 11.5 测试
- 11.6 Spring Cloud完整体系结构图
一.系统架构演变本文主要阐述微服务的演变过程以及通过Spring Cloud搭建小型微服务,简单介绍网关,路由,微服务,消息监听,配置管理,服务注册,服务熔断,负载均衡,集群,等概念。
1.1 集中式架构随着互联网的发展,网站应用的规模不断扩大,需求的激增,随之而来的是技术上的压力。系统架构也因此不断的
演进、升级、迭代。从单一应用,到垂直拆分,到分布式服务,到SOA,以及现在火热的微服务架构。
概念:当网站流量很小时,只需要一个应用,将所有的功能都部署在一起,以减少部署节点和成本。
1.2 垂直拆分此时只需一台服务器即可完成,如果访问量少,一台普通的电脑也可完成,但要是遇到访问量大的话,系统就很容易面临崩溃,所以需要分担压力,因此可以通过功能拆分对系统进行优化。
- 优点:
- 系统开发速度快
- 维护成本低
- 适用于并发要求较低的系统
- 缺点:
- 代码耦合度高,后期维护困难
- 无法针对不同模块进行优化
- 无法水平扩展
- 单点容错率低,并发能力差
概念:当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:
1.3 分布式服务此时实现了压力的分担,通过配置多台服务器,每台服务器实现一个独立的功能即可,开始慢慢有点分布式的雏形了。但还不足以称为分布式,因为其中的每个功能又包含了其他非主要的业务逻辑功能,比如消息服务,缓存服务,认证服务等,所以系统还可以进一步改进。
- 优点:
- 系统拆分实现了流量分担,解决了并发问题
- 可以针对不同模块进行优化
- 方便水平扩展,负载均衡,容错率提高
- 缺点:
- 系统间相互独立,会有很多重复开发工作,影响开发效率
概念:当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
1.4 服务治理(SOA)此时把基础的服务抽取出来,视觉上比之前更精简了,但是每个业务功能对基础服务的调取都不尽相同,如果没有一个统一的标准对这些服务进行管理,那这些服务将会变得错综复杂,所以有人考虑把这些服务通过一个统一的服务接口替代,具体的细节在其内部进行实现,于是又可以进一步的对系统优化。
- 优点:将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
- 缺点:系统间耦合度变高,调用关系错综复杂,难以维护
概念:SOA(Service Oriented Architecture)面向服务的架构:它是一种设计方法,其中包含多个服务, 服务之间通过
相互依赖最终提供一系列的功能。一个服务通常以独立的形式存在于操作系统进程中。各个服务之间通过网络调用。
1.5 微服务上图中的每个应用系统可以看成一台独立的服务器,基本上每个系统都需要对基础服务进行调取,但此时的模式依旧有所欠缺,因为当多个应用系统对总线有请求时,总线就会忙不过来,因此可能造成网络阻塞,所以“微服务”开始逐渐兴起。
- 缺点:
每个供应商提供的ESB产品有偏差,自身实现较为复杂;应用服务粒度较大(即里面包含的服务种类太多,不够精分)。所有服务都通过一个通路通信,直接降低了通信速度。
概念:微服务架构是使用一套小服务来开发单个应用的方式或途径,每个服务基于单一业务能力构建,运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,并能够通过自动化部署机制来独立部署。这些服务可以使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。 微服务结构图 :
二.远程调用方式现在访问网站的方式基本都是这种,在浏览器输入一个网址就可以获取对应的服务,这是因为服务提供方提供了一个服务接口,这样处理主要业务逻辑的系统可以通过这个接口对服务进行访问,进而返回数据给 服务消费方(一般是用户) ,而不必通过统一的总线进行访问,由于调用的只是接口而不是完全的服务依赖,所以这个方式和上述的分布式服务不同,这种方式更为简洁高效。
- API Gateway网关是一个服务器,是系统的唯一入口,请求到达网关后,通过网关寻找对应的服务接口进行流量转发,有人会觉得这样网关的压力就会很大,和第一种集中式架构一样。但它们的区别在于集中式架构是通过一个url进入的系统然后再寻找对应的业务接口,而网关是针对不同的url对请求进行转发。可以将网关理解为进入系统的第一道防火墙。网关提供RESTful/HTTP的方式访问服务。而服务端通过 服务注册中心(这个概念后续会介绍) 进行服务注册和管理。
- 特点:
- 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
- 面向服务:面向服务是说每个服务都要对外暴露服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供REST的接口即可。
- 自治:自治是说服务间互相独立,互不干扰
- 团队独立:每个服务都是一个独立的开发团队。
- 技术独立:因为是面向服务,提供REST接口,使用什么技术没有别人干涉
- 前后端分离:采用前后端分离开发,提供统一REST接口,后端不用再为PC、移动段开发不同接口
- 数据库分离:每个服务都使用自己的数据源
- 微服务和SOA比较:
2.1 认识RPC无论是微服务还是SOA,都面临着服务间的远程调用。常见的调用方式有以下几种:
- RPC:Remote Procedure Call远程过程调用,基于原生TCP通信,速度快,效率高。早期的Web Service,现在热门的Dubbo,都是RPC的典型。
- Http: 基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用HTTP协议。也可以用来进行远程服务调用。现在热门的REST风格,就可以通过HTTP协议来实现。
- 概念
- RPC是一个计算机通信协议。
- 该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。说得通俗一点就是:A计算机提供一个服务,B计算机可以像调用本地服务那样调用A计算机的服务。
- 调用流程图
- 概念
- HTTP是一种网络传输协议,基于TCP,工作在应用层,规定了数据传输的格式。
- 缺点是消息封装臃肿
- 优点是对 服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念
- 调用流程图
三.Spring Cloud 3.1 简介既然两种技术各有千秋,那么该如何选择呢?
- RPC的机制是根据语言的API(language API)来定义的,如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。
- 相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么Spring Cloud搭建微服务是不二之选,因此会使用HTTP方式来实现服务间调用。
3.2 版本
- Spring Cloud是Spring旗下的一个项目
- Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。Spring Cloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:
Netflix
- Eureka:注册中心
- Zuul:服务网关
- Ribbon:负载均衡
- Feign:服务调用
- Hystrix:熔断器
这些组件通过后面的介绍,将会逐渐揭开其神秘的面纱,下面先附上一张其局部架构图,有个印象就好。
Spring Cloud的版本命名比较特殊,因为它不是一个组件,而是许多组件的集合,它的命名是以A到Z为首字母的一些单词组成(其实是伦敦地铁站的名字):
四.微服务场景模拟Spring Clound 和Spring Boot版本对应关系,比较常用的是Boot2.1及以上版本。
思路:通过一台计算机创建多个项目,每个项目通过不同的启动端口模拟多台计算机。后续将会模拟集群,大致思路是:若想某个服务通过集群形式对外提供服务,则修改对应项目的端口配置,即若无特定的端口配置,则走默认端口,进而实现相同的功能。
- 创建父工程
maven项目命名lxs-springclud。注意!!!:要加上< packing > 这个属性
org.example lxs-springclud pom 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent 2.1.5.RELEASE 1.8 Greenwich.SR1 2.1.5 5.1.46 org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import tk.mybatis mapper-spring-boot-starter ${mapper.starter.version} mysql mysql-connector-java ${mysql.version} org.projectlombok lombok org.springframework.boot spring-boot-maven-plugin
- 服务提供者
选中parent,创建子模板,命名user-server
org.springframework.boot spring-boot-starter-web tk.mybatis mapper-spring-boot-starter mysql mysql-connector-java
2.1 编写配置文件application.yml
server: port: 9091 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/springclound username: root password: root mybatis: type-aliases-package: com.lxs.user.pojo
2.2 代码编写
2.2.1 启动器
@SpringBootApplication
@MapperScan("com.lxs.user.mapper")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
2.2.2 实体类
@Data
@Table(name = "tb_user")
public class User{
// id
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;
}
此处使用@Data注解是为了简化get,set,tostring等方法的编写,前提是引入了lombok依赖,但还要开启相应配置。点击此处查看具体方法
2.2.3 dao
public interface UserMapper extends Mapper{ }
2.2.4 service
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) {
return this.userMapper.selectByPrimaryKey(id);
}
}
2.2.5 controller
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
return userService.queryById(id);
}
}
2.3 代码测试
启动项目,访问http://localhost:9091/user/7可查看结果。(sql表需要提前创建才可以返回数据,如果不想调用数据库,就编写静态文件,只要能返回数据就好。)
- 服务调用者
依旧是创建子模块,因为是调用者,所以不需要引入mybatis的依赖
org.springframework.boot spring-boot-starter-web
3.1 代码编写
3.1.1 启动器
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
Spring提供了一个RestTemplate模板工具类,对基于HTTP的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定HTTP的客户端类型,而是进行了抽象,目前常用的3种都有支持:
- HTTPClient
- OkHTTP
- JDK原生的URLConnection(默认的)
3.1.2 实体类
@Data
public class User {
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;
}
3.1.3 controller
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
String url = "HTTP://localhost:9091/user/" + id;
return restTemplate.getForObject(url, User.class);
}
}
3.2 代码测试
因为没有配置端口,所以默认8080,通过访问http://8080:consume/7可得到相同的查询结果。
- 问题反思
通过上面的步骤,一个简单的远程微服务就实现了。通过一台计算机访问另一台计算机的服务接口(localhost:9091:user/id)从而获取其服务,但真正意义上的微服务远远没有这么简单,通过查看上面的代码,我们发现有以下几个问题:
- 在consumer中,我们把url地址硬编码到了代码中,不方便后期维护
- consumer需要记忆user-service的地址,如果出现变更,可能得不到通知,地址将失效
- consumer不清楚user-service的状态,服务宕机也不知道
- user-service只有1台服务,不具备高可用性
- 即便user-service形成集群,consumer还需自己实现负载均衡
五. Eureka注册中心 5.1 概念而上述问题,正是微服务所面临的。通过对Spring Cloud的学习,上述问题都将可以找到答案。
5.2 工作原理图
- Eureka负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。
- 同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。
- 这就实现了服务的自动注册、发现、状态监控。
5.3 入门案例
- Eureka:服务注册中心(可以是一个集群),对外暴露自己的地址
- 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
- 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
- 心跳(续约):提供者定期通过HTTP方式向Eureka刷新自己的状态
5.3.1 EurekaServerEureka是服务注册中心,只做服务注册;自身并不提供服务也不消费服务。可以搭建Web工程使用Eureka,可以使用Spring Boot方式搭建。
- 创建子模板EurekaServer
org.springframework.cloud spring-cloud-starter-netflix-eureka-server
- 启动类
//声明当前应用时Eureka服务
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
- 编写配置
#连接池
spring:
application:
name: eureka-server
server:
port: 10086
eureka:
client:
service-url:
# eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址
defaultZone: HTTP://127.0.0.1:10086/eureka
# 不注册自己
register-with-eureka: false
# 不拉取服务
fetch-registry: false
- 访问测试
在服务提供工程user-service上添加Eureka客户端依赖;自动将服务注册到EurekaServer服务地址列表。
- 添加依赖
org.springframework.cloud spring-cloud-starter-netflix-eureka-client
- 在启动类开启Eureka功能,通过添加@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.lxs.user.mapper")
@EnableDiscoveryClient //开启Eureka客户端发现功能
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
- 编写配置
server: port: 9091 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/springcloud username: root password: root application: name: user-service mybatis: type-aliases-package: com.lxs.user.pojo eureka: client: service-url: defaultZone: HTTP://127.0.0.1:10086/eureka
补充:
- 这里我们添加了spring.application.name属性来指定应用名称,将来会作为应用的id使用。
- 不用指定register-with-eureka和fetch-registry,因为默认是true
- 重启项目,发现注册成功
5.4 Eureka详解 5.4.1 基础架构服务消费者通常都是外来的访问,通过Eureka提供服务接口,但到达Eureka前要经过防火墙,即网关,所以这个角色在后面的网关再继续讲解。
5.4.2 高可用的Eureka ServerEureka架构中的三个核心角色:
- 服务注册中心
Eureka的服务端应用,提供服务注册和发现功能。- 服务提供者
提供服务的应用,可以是Spring Boot应用,也可以是其它任意技术实现,只要对外提供的是REST风格服务即可。- 服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。
- 概念
- 在刚才的案例中,我们只有一EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心 。
- Eureka Server是一个web应用,可以 启动多个实例(配置不同端口) 保证Eureka Server的高可用
- 多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
- 架构图
3 客户端注册时集群
Eureka不仅搭建成功后可以形成集群,在服务注册时也可通过集群进行注册,前提是Eureka集群已经搭建成功。
eureka: client: service-url: # EurekaServer地址,多个地址以','隔开 defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka5.4.3 Eureka服务端配置
- 服务端集群注册原理
- 服务提供者在启动时,会检测配置属性中的: eureka.client.register-with-erueka=true 参数是否为true,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,EurekaServer会把这些信息保存到一个双层Map结构中 。
- 第一层Map的Key就是服务id,一般是配置中的 spring.application.name 属性,user-service
- 第二层Map的key是服务的实例id。一般host+ serviceId + port,例如: localhost:user-service:8081
值则是服务的实例对象,也就是说一个服务,这样可以同时启动多个不同实例,形成集群。- 默认注册时使用的是主机名或者localhost,如果想用ip进行注册,可以在 user-service 中添加配置如下:
eureka: instance: ip-address: 127.0.0.1 # ip地址 prefer-ip-address: true # 更倾向于使用ip,而不是host名
这里修改后在客户端获取服务时显示的是服务提供者的ip而不是主机名,但是在Eureka注册中心,显示的依旧是主机名。
- 服务续约
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew)。可以对两个参数进行修改:
eureka: instance: lease-expiration-duration-in-seconds: 90 lease-renewal-interval-in-seconds: 30
5.4.4 失效剔除和自我保护
- lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
- lease-expiration-duration-in-seconds:服务失效时间,默认值90秒
- 也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除(但不会立刻移除,后续内容会讲解),这两个值在生产环境不要修改,默认即可。
如下的配置都是在Eureka Server服务端进行:
- 服务下线
当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态
- 失效剔除
- 有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。
- 这就是上述说的为什么90s到了也没有完全移除服务,因为90s到了只是得到机会进入被移除服务的名单中,要等到计时器60s到达才可以真正进入待移除服务列表中。但此时Eureka也不会直接移除,后续还会继续讲解。
- 关于计时器的通俗理解:服务器先创建60s任务,如果在计时器开始时,服务就不能正常提供了,那么在计时器到达60s时会再进行一个60s,因为此时没有满足的条件:超时90s未续约的服务。所以在第二个计时器的30s会将该服务加入待移除列表。
- 自我保护
当关停一个服务时,就会在Eureka面板看到一个警告
这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除 (这就是为什么上述的60s计时器到达也没有移除服务,如果超过15min还无法提供服务再进行剔除)。开发模式可以通过参数对其进行关闭。
eureka: server: enable-self-preservation: false # 关闭自我保护模式(缺省为打开)六.负载均衡Ribbon 6.1 背景
6.2 Ribbon在刚才的案例中,我们启动了一个user-service,将来会有客户端通过ip地址和端口对服务进行访问,但是实际环境中,我们往往会开启多个user-service的集群。此时我们获取的服务列表中就会有多个,到底该访问哪个呢?一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。
不过Eureka中已经帮我们集成了负载均衡组件:Ribbon。
七.Hystrix 7.1 背景Ribbon是Netifx发布的负载均衡器,为Ribbon配置服务提供者地址列表后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求,常见的算法有轮询,随机等。
微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能现,会形成非常复杂的调用链路:
如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。 如果此时,某个服务出现异常:
例如: 微服务I 发生异常,请求阻塞,用户请求就不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:
7.2 线程隔离、服务降级 7.2.1 原理服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致有其它服务都不可用,形成雪崩效应。
- Hystrix解决雪崩问题的手段,主要包括:
线程隔离
服务降级
线程隔离:
- Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。
- 用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超
时,则会进行降级处理。
7.3 服务熔断 7.3.1 原理服务降级
- 用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
- 服务降级会导致请求失败,但是解决了阻塞问题,而且最多会影响这个依赖服务对应的线程池中的资源,对其他服务没有影响。
八.Feign 8.1 背景
- 熔断器:Circuit Breaker
- 位置:线程池和服务之间
- 介绍:
- 在分布式系统中应用服务熔断后;服务调用方可以自己进行判断哪些服务反应慢或存在大量超时,可以针对这些服务进行主动熔断,防止整个系统被拖垮。
- Hystrix的服务熔断机制,可以实现弹性容错;当服务请求情况好转之后,可以自动重连。通过断路的方式,将后续请求直接拒绝,一段时间(默认5秒)之后允许部分请求通过,如果调用成功则回到断路器关闭状态,否则继续打开,拒绝请求的服务。
- 模型分析图
8.2 简介前面的学习中,可以发现每次服务消费方调用服务时都需要硬编码路径
String url = "HTTP://localhost:9091/user/" + id;
为了简化开发,Feign可以帮我们解决这个问题。
8.3 案例Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。(运用在service层)
- 引入依赖
org.springframework.cloud spring-cloud-starter-openfeign
- 接口伪装
@FeignClient("user-service")
public interface UserClient {
//http://user-service/user/123
@GetMapping("/user/{id}")
User queryById(@PathVariable("id") Long id);
}
- 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟Mybatis的mapper很像
- @FeignClient ,声明这是一个Feign客户端,同时通过 value 属性指定服务名称
- @GetMapping中的/user,请不要忘记;因为Feign需要拼接可访问的地址
- controller
@RestController
@RequestMapping("/cf")
public class ConsumerFeignController {
@Autowired
private UserClient userClient;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
return userClient.queryById(id);
}
}
- 开启Feign
@SpringCloudApplication
@EnableFeignClients //开启feign功能
public class ConsumerApplication{
//...
}
Feign已经集成了Ribbon和Hystrix
但Hystrix默认是关闭的,开启步骤如下:
- 配置参数
feign: hystrix: enabled: true # 开启Feign的熔断功能
- 定义一个类,在熔断发生时返回信息
但是要实现Feign伪装后的接口,因为这个类是当熔断发生时作为响应的。
@Component
public class UserClientFallback implements UserClient {
@Override
public User queryById(Long id) {
User user = new User();
user.setId(id);
user.setName("用户异常");
return user;
}
}
- 接口中指定刚编写的类
@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
8.4 请求压缩
Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:
feign: compression: request: enabled: true # 开启请求压缩 response: enabled: true # 开启响应压缩九.Gateway 9.1 背景
9.2 核心概念不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都可经过网关,然后再由网关来实现 鉴权、动态路由等等操作。Gateway就是我们服务的统一入口。
9.3 案例入门
- 路由(route):由一个ID、一个目的URL、一组断言工厂、一组Filter组成。
- 断言(predicate):定义匹配来自于HTTP Request中的任何信息。
- 过滤器(Filter):Spring Cloud Gateway中的Filter分为两种类型:Gateway Filter和Global Filter。
- 需求分析
通过网关系统lxs-gateway将包含有 /user 的请求 路由到 http://127.0.0.1:9091/user/用户id
- 创建子模块gateway并配置依赖
org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-netflix-eureka-client
- 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
- 配置文件
server: port: 10010 spring: application: name: api-gateway cloud: gateway: routes: # 路由id,可以随意写 - id: user-service-route # 代理的服务地址 uri: lb://user-service # 路由断言,可以配置映射路径 predicates: - Path=/user/** eureka: client: service-url: defaultZone: HTTP://127.0.0.1:10086/eureka instance: prefer-ip-address: true9.4 路由前缀
9.4.1 添加前缀客户端的请求地址与微服务的服务地址如果不一致的时候,可以通过配置路径过滤器实现路径前缀的添加和去除。
对请求地址添加前缀路径之后再作为代理的服务地址;
http://127.0.0.1:10010/8 --> http://127.0.0.1:9091/user/8
routes: # 路由id,可以随意写 - id: user-service-route # 代理的服务地址 uri: lb://user-service # 路由断言,可以配置映射路径 predicates: - Path=/** filters: # 添加请求路径的前缀 - PrefixPath=/user9.4.2 去除前缀
将请求地址中路径去除一些前缀路径之后再作为代理的服务地址;
http://127.0.0.1:10010/api/user/8 --> http://127.0.0.1:9091/user/8
routes: # 路由id,可以随意写 - id: user-service-route # 代理的服务地址 uri: lb://user-service # 路由断言,可以配置映射路径 predicates: - Path=/api/user/** filters: # 表示过滤1个路径,2表示两个路径,以此类推 - StripPrefix=19.5 过滤器
- 概念
Gateway作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作往往是通过网关提供的过滤器来实现的。前面的路由前缀也是使用过滤器实现的。
- 类型
- 局部过滤器
通过spring.cloud.gateway.routes.filters 配置,只作用在当前路由上。如果配置spring.cloud.gateway.default-filters 上会对所有路由生效也算是全局的过滤器,但前提是要实现GatewayFilterFactory接口。- 全局过滤器
不需要在配置文件中配置,作用在所有的路由上;实现 GlobalFilter 接口即可。
- 执行生命周期
- Spring Cloud Gateway 的 Filter 的生命周期也类似Spring MVC的拦截器有两个:“pre” 和 “post”。“pre”和 “post”
分别会在请求被执行前调用和被执行后调用- pre 和 post 可以通过过滤器的 GatewayFilterChain 执行filter方法前后来实现
- 使用场景
9.6 跨域配置
- 请求鉴权:一般 GatewayFilterChain 执行filter方法前,如果发现没有访问权限,直接就返回空。
- 异常处理:一般 GatewayFilterChain 执行filter方法后,记录异常并返回。
- 服务调用时长统计: GatewayFilterChain 执行filter方法前后根据时间统计。
一般网关都是所有微服务的统一入口,必然在被调用的时候会出现跨域问题。
- 跨域:在js请求访问中,如果访问的地址与当前服务器的域名、ip或者端口号不一致则称为跨域请求。若不解决则不能获取到对应地址的返回结果。
- 可以在网关服务器中通过配置解决,允许哪些服务是可以跨域请求的。
spring: cloud: gateway: globalcors: corsConfigurations: # 所有访问到服务端的请求地址 '[/**]': # allowedOrigins: * 这种写法或者下面的都可以,*表示允许所有请求通过 allowedOrigins: - "http://docs.spring.io" allowedMethods: - GET
9.7 Gateway和Feign区别上述配置表示:可以允许来自 http://docs.spring.io 的get请求方式获取服务数据。
十.Spring Cloud Config配置中心 10.1 背景
- Gateway主要处理外来的请求数据,其作用可视为nginx;大部分情况下用作权限鉴定、服务端流量控制
- Feign通过将当前微服务的部分服务接口暴露出来,使各个微服务之间得以相互调用,
10.2 配置中心搭建
- 在分布式系统中,由于服务数量非常多,配置文件分散在不同的微服务项目中,管理不方便。为了方便配置文件集中管理,需要分布式配置中心组件。
- 在Spring Cloud中,提供了Spring Cloud Config,它支持配置文件放在配置服务的本地,也支持放在远程Git仓库。
- 配置中心本质上也是一个微服务,同样需要注册到Eureka服务注册中心。
- 远程仓库创建配置文件
文件命名方式:
- {application}-{profile}.yml 或 {application}-{profile}.properties
- application:应用名称
- profile:用于区分开发、测试、生产环境。
- 创建子模块config-server并导入依赖
org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-config-server
- 启动类
@SpringBootApplication
@EnableConfigServer //开启配置服务
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
- 配置文件
server: port: 12000 spring: application: name: config-server cloud: config: server: git: uri: https://gitee.com/lxsong77/lxs-config.git eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka
- 启动测试,访问http://localhost:12000/user-dev.yml,得到相应信息。
配置中心已经搭建完毕,以后配置文件信息不再由微服务项目提供,而是从配置中心获取。接下来对服务提供方进行改造。
- 添加依赖
org.springframework.cloud spring-cloud-starter-config
- 修改配置
- 删除原有配置application.yml
- 创建新文件bootstrap.yml
- 远程仓库文件名:user-dev,分支:master
spring: cloud: config: # 要与仓库中的配置文件的application保持一致 name: user # 要与仓库中的配置文件的profile保持一致 profile: dev # 要与仓库中的配置文件所属的版本(分支)一样 label: master discovery: # 使用配置中心 enabled: true # 配置中心服务名 service-id: config-server eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka
- bootstrap.yml和application.yml区别
11.Spring Cloud Bus总线 11.1 背景
- bootstrap.yml文件也是Spring Boot的默认配置文件,而且其加载的时间相比于application.yml更早。
- bootstrap.yml文件相当于项目启动时的引导文件,内容相对固定。
- application.yml文件是微服务的一些常规配置参数,变化比较频繁
11.2 架构图 11.3 改造配置中心当配置中心中的文件内容发生改变时,服务提供方需要重启服务才可以获得最新数据,因此我们可以添加一个监听方,当内容一改变,然后服务提供方就可以获取最新数据。
比较常用的有RabbitMQ,kafka。
- 添加依赖
org.springframework.cloud spring-cloud-bus org.springframework.cloud spring-cloud-stream-binder-rabbit
- 修改配置文件
server: port: 12000 spring: application: name: config-server cloud: config: server: git: uri: https://gitee.com/lxsong77/lxs-config.git # 配置rabbitmq信息;如果是都与默认值一致则不需要配置 rabbitmq: host: localhost port: 5672 username: guest password: guest eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka management: endpoints: web: exposure: # 暴露触发消息总线的地址 include: bus-refresh11.4 改造服务提供方
- 添加依赖
org.springframework.cloud spring-cloud-bus org.springframework.cloud spring-cloud-stream-binder-rabbit org.springframework.boot spring-boot-starter-actuator
- 修改配置文件
格式参照配置中心
# 配置rabbitmq信息;如果是都与默认值一致则不需要配置 rabbitmq: host: localhost port: 5672 username: guest password: guest
- 修改controller,添加@RefreshScope
@RestController
@RefreshScope
public class UserController{
//xxx
}
11.5 测试
- 依次启动注册中心 eureka-server 、配置中心 config-server 、用户服务 user-service
- 访问用户微服务http://localhost:9091/user/7;查看IDEA控制台输出结果
- 修改Git仓库中配置文件 user-dev.yml内容
- 使用Postman工具发送POST方式请求访问地址
http://127.0.0.1:12000/actuator/bus-refresh- 查看结果
- 说明
- 请求地址http://127.0.0.1:12000/actuator/bus-refresh中 /actuator是固定的
- 请求http://127.0.0.1:12000/actuator/bus-refresh地址的作用:
- 访问配置中心的消息总线服务,消息总线服务接收到请求后会向消息队列中发送消息,各个微服务会监听消息队列。当微服务接收到队列中的消息后,会重新从配置中心获取最新的配置信息。
- 补充
11.6 Spring Cloud完整体系结构图
- 安装RabbitMQ前应安装otp平台。
- 如果RabbitMQ未能启动成功,可能是电脑名字为中文,可以改成试试。



