SpringCloud-Netflix 核心组件与微服务架构实践
微服务
随着互联网技术的飞速发展,目前全球超过半的人口在使用互联网,人们的生活随着互联网的发展,发生了翻天覆地的变化。各行各业都在应用互联网 国家政策也在大力支持互联网的发展。随着越来越多的用户参与,业务场景越来越复杂,传统的单体架构己经很难满足互联网技术的发展要求。这主要体现在两方面, 是随着业务复杂度的提高,代码的可维护性、扩展性和可读性在降低; 使维护系统的成本、修改系统的成本在提高。所以,改变单体应用架构己经势在必行。另外,随着云计算、大数据、人工智能的飞速发展,对系统架构也提出了越来越高的要求。
微服务(不是一个框架 而是一种架构思想),是著名的 OO(面向对象, Object Oriented )专家 Martin Fowler 提出来的,它是用来描述将软件应用程序设计为独立部署的服务的种特殊方式。最近两年,微服务在各大技术会议、文章、书籍上出现的频率已经让人意识到它对于软件领域所带来的影响力。微服务架构的系统是个分布式系统,按业务领域划分为独立的服务单元,有自动化运维、容错、快速演进的特点,它能够解决传统单体架构系统的痛点,同时也能满足越来越复杂的业务需求。
单体架构不足
在应用的初始阶段,单体架构无论是在开发速度、运维难度上,还是服务器的成本上都有着显著的优势。在一个产品的前景不明确的初始阶段,用单体架构是非常明智的选择。随着应用业务的发展和业务复杂度的提高,这种架构明显存在很多的不足,主要体现在以下3个方面:
1. 业务越来越复杂,单体应用的代码量越来越大,代码的可读性、可维护性和可扩展性下降,新人接手代码所需的时间成倍增加,业务扩展带来的代价越来越大。
2. 随着用户越来越多,程序承受的并发越来越高,单体应用的并发能力有限。
3. 测试的难度越来越大,单体应用的业务都在同一个程序中,随着业务的扩张、复杂度的增加,单体应用修改业务或者增加业务或许会给其他业务带来一定的影响,导致测试难度增加。
到底什么是微服务
什么是微服务呢?
就是将一个大的应用,拆分成多个小的模块,每个模块都有自己的功能和职责,每个模块可以进行交互,这就是微服务。
对于微服务,业界没有严格统一的定义,但是作为“微服务”这名词的发明人,Martin Fowler 对微服务的定义似乎更具有权威性和指导意义,他的理解如下:
简而言之,微服务架构的风格,就是将单一程序开发成多个微服务,每个微服务运行在自己的进程中,并使用轻量级机制通信,通常是 HTTP RESTFUL API 。这些服务围绕业务能力来划分构建的,并通过完全自动化部署机制来独立部署这些服务可以使用不同的编程语言,以及不同数据存储技术,以保证最低限度的集中式管理。
微服务的特点
1.按业务(功能)划分为一个独立运行的程序,即服务单元。
2.服务之间可以通过 HTTP 协议相互通信。 http是一个万能的协议 (web应用都支持的模式)
3.自动化部署。
4.可以用不同的编程语言。
5.可以用不同的存储技术。
6.服务集中化管理。
7.微服务是一个分布式系统。
微服务特点的具体阐述
微服务单元按业务来划分(不是绝对的)
微服务的“微”到底需要定义到什么样的程度,这是个非常难以界定的概念,可以从以下几个个方面来界定:
是根据代码量来定义,根据代码的多少来判断程序的大小;
是根据开发时间的长短来判断;
是根据业务的大小来划分。
根据 Martin Fowler 的定义,微服务的“微”是按照业务来划分的 。一个大的业务可以拆分成若干小的业务, 一个小的业务又可以拆分成若干更小的业务,业务到底怎么拆分才算合适,这需要开发人员自己去决定。例如微博最常见的功能是微博内容、关注和粉丝,而其中微博内容又有点赞、评论等,如何将微博这个复杂的程序划分为单个的服务,需要由开发团队去决定。按业务划分的微服务单元独立部署,运行在独立的进程中 这些微服务单元是高度组件化的模块,并提供了稳定的模块边界,服务与服务之间没有任何的组合,有非常好的扩展性和复用性。传统的软件开发模式通常由 UI 团队、服务端团队、数据库和运维团队构成,相应地将软件按照职能划分为 、服务端、数据库和运维等模块。通常这些开发人员各司其职 很少有人跨职能去工作。 如果按照业务来划分服务,每个服务都需要独立的 UI 、服务端、数据库和运维。也就是说, 一个小的业务的微服务需要动用几个团队的人去协作,这显然增加了团队与团队之间交流协作的成本。所以产生了跨职能团队,这个团队负责一个服务的所有工作,包括 UI 、服务端和数据库。当这个团队只有几个人的时候,就对开发人员提出了更高的要求。
微服务可以通过 HTTP 来互相通信
按照业务划分的微服务单元独立部署 并运行在各自的进程中。微服务单元之间的通信一般倾向于使用 HTTP 这种简单的通信机制,更多的时候是使用 RESTfulAPI 。这种接收请求、处理业务逻辑、返回数据的 HTTP 模式非常高效,并且这种通机制与平台和语言无关。例如用 Java 写的服务可以消费用 Go 语言写的服务。
微服务的数据库独立
在单体架构中,所有的业务都共用一个数据库。随着业务量的增加,数据库的表的数量越来越多,难以管理和维护,并且数据量的增加会导致查询速度越来越慢。 例如一个应用有这样几个业务:用户的信息、用户的账户、用户的购物 、数据查询服务等。这是典型的单体架构。
微服务的特点就是按业务划分服务,服务与服务之间无稠合,就连数据库也是独立的,典型的微服务的架构就是每个微服务都有自己独立的数据库,数据库之间没有联系这样做的好处在于,随着业务的不断扩张,服务与服务不需要提供数据库集成,而是提供 API接口相互调用:还有个好处是数据库独立,单业务的数据量少,易于维护,数据库性能有着明显的优势,数据库的迁移也很方便。另外,随着存储技术的发展,数据库的存储方式不再仅仅是关系型数据库,非关系数据库的应用也非常广泛,例如 MongoDB ,它们有着良好的读 性能,因此越来越受欢迎,典型的微服务的系统,可能每个服务的数据库都不相同,每个服务所使用的数据存储技术也不相同。
微服务的自动化部署(CI /CD)(持续集成 持续交付)
在微服务架构中,系统会被拆分为若干个微服务,每个微服务又是一个独立的应用程序。单体架构的应用程序只需要部署一次,而微服务架构有多少个服务就需要部署多少次。随着服务数量的增加,如果微服务按照单体架构的部署方式,部署的难度会呈指数增加。业务的粒度划分得越细,微服务的数量就越多,这时需要更稳定的部署机制。随着技术的发展,尤其是 Docker容器技术的推进,以及自动化部署工具(例如开源组件 Jenkins)的出现,自动化部署变得越来越简单。
自动化部署可以提高部署的效率,减少人为的控制,部署过程中出现错误的概率降低,部署过程的每一步自动化,提高软件的质量。构建一个自动化部署的系统,虽然在前期需要开发人员或者运维人员的学习,但是对于整个软件系统来说是一个全新的概念。在软件系统的整个生命周期之中,每一步是由程序控制的,而不是人为控制,软件的质量提高到了一个新的高度。随着 DevOps这种全新概念的推进,自动化部署必然会成为微服务部署的一种方式。
微服务的优势
相对于单体服务来说,微服务具有很多的优势,主要体现在以下方面:
1. 将一个复杂的业务分解成若干小的业务,每个业务拆分成一个服务,服务的边界明确,将复杂的问题简单化。服务按照业务拆分,编码也是按照业务来拆分,代码的可读性和可扩展性增加。新人加入团队,不需要了解所有的业务代码,只需要了解他所接管的服务的代码,新人学习时间成本减少。
2. 由于微服务系统是分布式系统,服务与服务之间没有任何的耦合。随着业务的增加,可以根据业务再拆分服务,具有极强的横向扩展能力。随着应用的用户量的增加,并发量增加,可以将微服务集群化部署,从而增加系统的负载能力。简而言之,微服务系统的微服务单元具有很强的横向扩展能力。
3. 服务与服务之间通过 HTTP网络通信协议来通信,单个微服务内部高度耦合,服务与服务之间完全独立,无耦合。这使得微服务可以采用任何的开发语言和技术来实现。开发人员不再被强迫使用公司以前的技术或者已经过时的技术,而是可以自由选择最适合业务场景的或者最适合自己的开发语言和技术,提高开发效率、降低开发成本。
4. 如果是一个单体的应用,由于业务的复杂性、代码的耦合性,以及可能存在的历史问题。在重写一个单体应用时,要求重写的应用的人员了解所有的业务,所以重写单体应用是非常困难的,并且重写风险也较高。如果是微服务系统,由于微服务系统是按照业务的进行拆分的,并且有坚实的服务边界,所以重写某个服务就相当于重写某一个业务的代码,非常简单。
5. 微服务的每个服务单元都是独立部署的,即独立运行在某个进程里。微服务的修改和部署对其他服务没有影响。试想,假设一个应用只有一个简单的修改,如果是单体架构,需要测试和部署整个应用;而如果采用微服务架构,只需要测试并部署被修改的那个服务,这就大大减少了测试和部署的时间。
6. 微服务在 CAP理论中采用的是 AP架构,即具有高可用和分区容错的特点。高可用主要体现在系统 7 x 24小时不间断的服务,它要求系统有大量的服务器集群,从而提高了系统的负载能力。另外,分区容错也使得系统更加健壮。
微服务的不足(正视它的不足)
凡事都有两面性,微服务也不例外,微服务相对于单体应用来说具有很多的优势,当然也有它的不足,主要体现在如下方面:
1.微服务的复杂度
2.分布式事务问题
3.分布式锁的问题
4.分布式ID的问题
5.服务的划分(按照功能划分 还是按照组件来划分呢) 分工
6.服务的部署(不用自动化部署 自动化部署)
服务调用
单体应用与微服务之间的区别:
使用单体应用
我们要实现的功能是下订单
需求:下订单前要查下商品的信息,商品的库存要大于0,才能下单
直接实现下单和查询商品的方法。
数据库:数据库不用拆分,直接创建两个表分别存储订单以及商品信息。
使用微服务构建分布式服务
使用微服务构建的是分布式服务,微服务之间通过网络进行通信。
我们使用服务提供者与服务消费者来描述微服务之间的调用关系。
| 名词 | 定义 |
|---|---|
| 服务提供者(provider) | 服务的被调用方(即:为其他服务提供服务的服务) |
| 服务消费者(consumer) | 服务的调用方(即:依赖其他服务的服务) |
以电商系统中常见的用户下单为例,用户向订单微服务发起一个购买的请求。在进行保存订单之前,需要调用商品微服务查询当前商品库存,单价等信息。
在这种场景下,订单微服务就是一个服务消费者,商品微服务就是一个服务提供者
拆分数据库
Ø 商品数据库 mymall_goods
Ø 订单数据库 mymall_order
创建两个工程
服务提供者(provider):实现商品相关方法
服务消费者(consumer):实现下订单方法,调用商品相关方法
小结:
Ø provider:一个提供根据id查询商品的微服务
Ø consumer:一个服务调用者,通过RestTemplate远程调用provider中的GoodsController类中的getById(@PathVariable(“id”) Integer id)方法。
存在什么问题?
在consumer中,我们把url地址硬编码(写死)到了代码中,不方便后期维护
consumer需要记忆provider的地址,如果出现变更,可能得不到通知地址将失效
consumer不清楚provider的状态,服务宕机也不知道。
provider只有1台服务,不具备高可用性(HA =hign available)
其实上面说的问题,概括一下就是分布式服务必然要面临的问题:
服务管理
Ø 如何自动注册和发现
Ø 服务如何实现负载均衡
Ø 服务如何解决容灾问题
Ø 服务如何实现统一配置
Ø 等等…
以上的问题,都将在SpringCloud中得到答案。
SpringCloud
微服务是一种架构方式,最终肯定需要技术架构去实施。
微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?
- 后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。
- 技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑。
- 群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring?SpringCloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。
- 使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架搭建。
简介
SpringCloud是Spring旗下的项目之一
https://spring.io/projects/spring-cloud
https://www.springcloud.cc/spring-cloud-dalston.html#_features
Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。
SpringCloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。
目前开发中常用的落地实现有两种:
-
SpringCloud Netflix 一站式微服务架构
-
SpringCloud Alibaba 新的一站式微服务架构
下面是Netflix系列学习,后面还会再学习Alibaba系列
Netflix:微服务大规模的应用,Netflix公司在技术上毫无保留的把一整套微服务架构核心技术栈开源了出来,叫做Netflix OSS (open source software)
-
Eureka:注册中心
-
Ribbon:负载均衡
-
Feign:服务调用
-
Hystix:熔断器
-
Config:配置中心
以上只是其中一部分
版本
SpringCloud的版本命名比较特殊,因为它不是一个组件,而是许多组件的集合,它的命名是以A到Z的为首字母的一些单词组成,也叫Train Version(火车头命名方式),每个版本是伦敦地铁站的名字,我们采用 Hoxton.SR12(目前公司使用比较多的版本)
我们在项目中,目前使用的版本
https://docs.spring.io/spring-cloud/docs/Hoxton.SR12/reference/html/
查看版本的对应关系
https://spring.io/projects/spring-cloud
注意上面支持的springboot版本,我们在项目中SpringBoot使用2.3.2.RELEASE版本
版本对应关系
"spring-cloud": {
"Hoxton.SR12": "Spring Boot >=2.2.0.RELEASE and <2.4.0.M1",
"2020.0.6": "Spring Boot >=2.4.0.M1 and <2.6.0-M1",
"2021.0.0-M1": "Spring Boot >=2.6.0-M1 and <2.6.0-M3",
"2021.0.0-M3": "Spring Boot >=2.6.0-M3 and <2.6.0-RC1",
"2021.0.0-RC1": "Spring Boot >=2.6.0-RC1 and <2.6.1",
"2021.0.5": "Spring Boot >=2.6.1 and <3.0.0-M1",
"2022.0.0-M1": "Spring Boot >=3.0.0-M1 and <3.0.0-M2",
"2022.0.0-M2": "Spring Boot >=3.0.0-M2 and <3.0.0-M3",
"2022.0.0-M3": "Spring Boot >=3.0.0-M3 and <3.0.0-M4",
"2022.0.0-M4": "Spring Boot >=3.0.0-M4 and <3.0.0-M5",
"2022.0.0-M5": "Spring Boot >=3.0.0-M5 and <3.0.0-RC1",
"2022.0.0-RC1": "Spring Boot >=3.0.0-RC1 and <3.1.0-M1"
},
"spring-cloud-azure": {
"4.3.0": "Spring Boot >=2.5.0.M1 and <2.7.0-M1",
"4.4.1": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
"6.0.0-beta.3": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
},
"spring-cloud-gcp": {
"2.0.11": "Spring Boot >=2.4.0-M1 and <2.6.0-M1",
"3.4.0": "Spring Boot >=2.6.0-M1 and <3.0.0-M1"
},
"spring-cloud-services": {
"2.3.0.RELEASE": "Spring Boot >=2.3.0.RELEASE and <2.4.0-M1",
"2.4.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1",
"3.3.0": "Spring Boot >=2.5.0-M1 and <2.6.0-M1",
"3.4.0": "Spring Boot >=2.6.0-M1 and <2.7.0-M1",
"3.5.0": "Spring Boot >=2.7.0-M1 and <3.0.0-M1"
},
Eureka(注册中心)
Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新。
心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。
网约车相当于服务提供者
打车人相当于服务消费者
服务消费(调用)者(打车人)无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过“心跳”机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。
这就实现了服务的自动注册、发现、状态监控。
EurekaServer
1、添加pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ldustu</groupId>
<artifactId>eureka-server</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<springcloud.version>2021.0.4</springcloud.version>
<!--Hoxton.SR12-->
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!--使用dedependencyManagement相当于继承了springcloud-dependencies,相当于规定了spring cloud 版本-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${springcloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、配置application.yml对注册中心进行配置
server:
port: 8761 # 指定该Eureka实例的端口
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false # 不要将自己注册到注册中心
fetch-registry: false #不需要从注册中心上获取自己,检索自己
server:
enable-self-preservation: false # 禁用自我保护
3、在启动类上添加eurekaServer注解
package com.ldustu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //启动eurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
4、启动eureka server测试
访问http://localhost:8761/
Eureka Client
将goods-provider-eureka-client注册到eureka中心
1、添加pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ldustu</groupId>
<artifactId>goods-provider-eureka-client</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.13</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>8</java.version>
<springcloud.version>2021.0.4</springcloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- mybatis 依赖jar -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- mysql 依赖jar -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<!-- swagger 依赖jar -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${springcloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、配置application.yml将客户端注册到eureka
spring:
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER # springboot 版本较高,使用swagger2 2.9版本有问题,所以加了此配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.247.129:3306/myMall_goods?serverTimezone=UTC&useSSL=false&characterEncoding=UTF-8
username: root
password: ldustu2022
application:
name: goods-provider
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
type-aliases-package: com.ldustu.entity
server:
port: 9090
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka # 注册到eureka-server上,配置注册中心地址
instance:
lease-renewal-interval-in-seconds: 10 # 每10s发送一次心跳
lease-expiration-duration-in-seconds: 60 #间隔60s就算过期,连续6次,eureka-server都收不到心跳
3、启动application
package com.ldustu;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@EnableSwagger2
@SpringBootApplication
@MapperScan("com.ldustu.dao")
//@EnableEurekaClient //启用Eureka客户端,可以不写
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4、**访问注册中心:**http://localhost:8761/
order-consumer-eureka-client 也注册到注册中心,并调用服务提供者
1、添加pom.xml依赖。
2、配置application.yml将客户端注册到eureka
3、配置restTemplate配置类,添加轮询策略
package com.ldustu.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
@Slf4j
public class RestTemplateConfig {
@Bean
@LoadBalanced //restTemplate支持负载均衡,使用服务名就必须加上这个注解
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
4、消费者(consumer)通过注册中心调用服务提供者(provider)
package com.ldustu.service.impl;
import com.ldustu.dao.OrdersDao;
import com.ldustu.entity.Orders;
import com.ldustu.service.OrdersService;
import com.ldustu.vo.MyGoods;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
@Service
@Slf4j
public class OrdersServiceImpl implements OrdersService {
// @Resource
// private GoodsDao goodsDao;
@Resource
private OrdersDao ordersDao;
@Resource
private RestTemplate restTemplate;
/**
* @param uid 用户id
* @param goodsId 商品id
* @param amount 购买数量
* @return
*/
@Override
public int doOrder(Integer uid, Integer goodsId, Integer amount) {
//对象名.方法名,调用商品dao查询商品详情信息
// Goods goods = goodsDao.getById(goodsId);
// String goodsServiceUrl="http://localhost:9090/goods/";
//使用注册中心后要改成服务名,服务名就是它${spring.application.name}
//服务名代表服务提供者的多台机器
String goodsServiceUrl="http://goods-provider/goods/";
//使用restTemplate发出get请求,参数1:请求的url 参数2:反序列化的类型
//还做了反序列化工作
MyGoods goods = restTemplate.getForObject(goodsServiceUrl + goodsId, MyGoods.class);
//该商品存在并且商品库存大于购买数量,允许下单
if(goods!=null && goods.getStore()>amount){
Date currentTime=new Date();
//创建订单对象
Orders orders=Orders.builder().uid(uid)
.goodsId(goodsId)
.status(0)
.createTime(currentTime)
.updateTime(currentTime)
.buyNum(amount)
.buyPrice(goods.getPrice())
//订单总金额=商品单价乘以购买数量
.orderMoney(goods.getPrice().multiply(new BigDecimal(amount)))
.build();
//调用dao下单
int result = ordersDao.add(orders);
//返回下单结果
return result;
}
//下单不成功
return 0;
}
}
5、通过postman测试接口
HA(高可用)
高可用:high availability
我们如果只配置一台注册中心(Eureka Server),就会出现SPOF(单点故障),如果Eureka Server发生宕机,就会影响注册到Eureka Server上的微服务,导致微服务不可用。
所以在生产环境中,通常会部署一个高可用的Eureka Server 集群。
编写高可用
Eureka Server 可以通过运行多个实例并相互注册的方式实现高可用部署,Eureka Server 实例会彼此增量的同步信息,从而确保所有节点数据一致。
事实上,节点之间的相互注册是Eureka Server的默认行为。
配置系统的hosts,实现本地高可用
host学习参考网址:https://baike.baidu.com/item/hosts/10474546
Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开,其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址,一旦找到,系统会立即打开对应网页,如果没有找到,则系统会再将网址提交DNS域名解析服务器进行IP地址的解析。需要注意的是,Hosts文件配置的映射是静态的,如果网络上的计算机更改了请及时更新IP地址,否则将不能访问。浏览器访问网站,要首先通过DNS服务器把要访问的网站域名解析成其指定的IP地址,之后,浏览器才能对此网站进行定位并且访问其数据。操作系统规定,在进行DNS请求以前,先检查自己的Hosts文件中是否有这个域名和IP的映射关系。如果有,则直接访问这个IP地址指定的网络位置,如果没有,再向已知的DNS服务器提出域名解析请求。也就是说Hosts的IP解析优先级比DNS要高。
Ø Windows系统下的hosts文件路径是:C:\Windows\System32\drivers\etc\hosts
Ø Linux及Mac OS等系统的文件路径是:/etc/hosts
具体配置如下:
127.0.0.1 peer1 peer2 peer3
配置时如果不起作用,把空格改成tab键的方式。
resources/config 下新建三个配置文件
#application-server1.yml
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false # 不要将自己注册到注册中心
fetch-registry: false #不需要从注册中心上获取自己,检索自己
service-url:
defaultZone: http://peer2:8762/eureka,http://peer3:8763/eureka # server1注册到server2和server3
#application-server2.yml
server:
port: 8762
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false # 不要将自己注册到注册中心
fetch-registry: false #不需要从注册中心上获取自己,检索自己
service-url:
defaultZone: http://peer1:8761/eureka,http://peer3:8763/eureka
application-server3
server:
port: 8763
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false # 不要将自己注册到注册中心
fetch-registry: false #不需要从注册中心上获取自己,检索自己
service-url:
defaultZone: http://peer1:8761/eureka,http://peer2:8762/eureka
修改application.yml, 让两个节点互相注册
spring:
profiles:
active: server1 # 找到这个文件application-${spring.profiles.active}.yml
打包
mvn clean package -Dmaven.test.skip=true
cd到jar包存放位置 启动测试
java -jar *.jar --spring.profiles.active=server1
java -jar *.jar --spring.profiles.active=server2
java -jar *.jar --spring.profiles.active=server3
启动第一台eureka-server时,console上会报错,因为它注册不到peer2
浏览器分别访问8761端口和8762端口
将应用注册到Eureka Server 集群上
修改application.yml配置,让客户端同时注册到多个eureka上
eureka:
client:
service-url:
defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/http://peer3:8763/eureka/ # 注意用逗号分开
# defaultZone: http://localhost:8761/eureka/
# instance:
# prefer-ip-address: true
分别查看注册中心。
CAP 原则(面试)
Eureka 与 Zookeeper 的比较
著名的 CAP(Consistency(一致性) Availability(可用性) Partiton tolerance(分区容错性)) 理论指出:一个分布式系统不可能同时满足 C(一致性)、A(可用性) 和 P(分区容错性);
由于分区容错性在是分布式系统中必须要保证的,因此我们只能在 A 和 C 之间进行权衡,在此 Zookeeper 保证的是 CP, 而 Eureka 则是 AP。
p 全称:Partition tolerance (分区容忍)
主要是指网络问题, 比如:A 、B、C 三台机器之间相互ping不通、网络不通,这种情况在分布式系统里面是允许的,也是很有可能发生的,我们要容忍这种情况的出现,在这种情况出现的时候,我们是选择 “一致性的C” 还是选择 “可用性的A”,就看应用场景。
mysql(单机版CA)–就是一致性和可用性。
Zookeeper 保证 CP
在 ZooKeeper 中,当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举,但是问题在于,选举 leader 需要一定时间, 且选举期间整个 ZooKeeper 集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得 ZooKeeper 集群失去 master 节点是大概率事件,虽然服务最终能够恢复,但是在选举时间内导致服务注册长期不可用是难以容忍的。
Eureka 保证 AP
Eureka 优先保证可用性,Eureka 各个节点是平等的,某几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka 的客户端在向某个 Eureka 注册时如果发现连接失败,则会自动切换至其它节点,只要有一台 Eureka 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。
所以 Eureka 在网络故障导致部分节点失去联系的情况下,只要有一个节点可用, 那么注册和查询服务就可以正常使用,而不会像 zookeeper 那样使整个注册服务瘫痪,Eureka 优先保证了可用性;
Eureka概念的理解
服务的注册
当项目(eureka-client)启动时(eureka-server的客户端),就会向eureka-server发送自己的元数据(原始数据)(运行的ip,端口port,健康的状态监控等,因为使用的是http/Restful请求风格),eureka-server会在自己内部保留这些元数据(内存中)。(有一个服务列表)(restful风格,以http动词的请求方式,完成对url资源的操作)
服务的续约
项目启动成功了,除了向eureka-server注册自己成功,还会定时的向eureka-server汇报自己,心跳,表示自己还活着。(修改一个时间)
服务的下线(主动下线)
当项目关闭时,会向eureka-server报告,说明自己要下机了,eureka会删除该客户端。
服务的剔除(被动下线,主动剔除)
当项目超过了指定时间没有向eureka-server汇报自己,那么eureka-server就会认为此节点死掉了,会把它剔除掉,也不会请求到此节点了。
自我保护机制
自我保护机制是 Eureka 注册中心的重要特性,当 Eureka 注册中心进入自我保护模式时,在 Eureka Server 首页会输出如下警告信息:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT.
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED
JUST TO BE SAFE.
小写是:
emergency! eureka may be incorrectly claiming instances are up when they’re not. renewals are
lesser than threshold and hence the instances are not being expired just to be safe.
翻译为:突发事件!注册中心 可能错误地声称实例已启动,而实例未启动。续订量小于阈值,因此实例不会为了安全而过期。
在没有 Eureka 自我保护的情况下,如果 Eureka Server 在一定时间内没有接收到某个微服务实例的心跳,Eureka Server 将会注销该实例,但是当发生网络分区故障时,那么微服务与 Eureka Server 之间将无法正常通信,以上行为可能变得非常危险了,因为微服务本身其实是正常的,此时不应该注销这个微服务,如果没有自我保护机制,那么 Eureka Server 就会将此服务注销掉。
Eureka 通过“自我保护模式”来解决这个问题——当 Eureka Server 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么就会把这个微服务节点进行保护。一旦进入自我保护模式,Eureka Server 就会保护服务注册表中的信息,不删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该 Eureka Server 节点会再自动退出自我保护模式。
所以,自我保护模式是一种应对网络异常的安全保护措施,**它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务,**使用自我保护模式,可以让 Eureka 集群更加的健壮、稳定。
当然也可以使用配置项:eureka.server.enable-self-preservation = false 禁用自我保护模式。
关闭自我保护模式后会出现红色:
THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
翻译为:自我保护模式已关闭。如果出现网络/其他问题,这可能无法保护实例过期。
但是 Eureka Server 自我保护模式也会给我们带来一些困扰,如果在保护期内某个服务提供者刚好非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端具有一些容错机制,如重试,断路器等。
Eureka 的自我保护模式是有意义的,该模式被激活后,它不会从注册列表中剔除因长时间没收到心跳导致注册过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式旨在避免因网络分区故障导致服务不可用的问题。
例如,两个微服务客户端实例 A 和 B 之间有调用的关系,A 是消费者,B 是提供者,但是由于网络故障,B 未能及时向 Eureka 发送心跳续约,这时候 Eureka 不能简单的将 B 从注册表中剔除,因为如果剔除了,A 就无法从 Eureka 服务器中获取 B 注册的服务,但是这时候 B 服务是可用的;
所以,Eureka 的自我保护模式最好还是开启它。
关于自我保护常用几个配置如下:
服务器端配置:
#测试时关闭自我保护机制,保证不可用服务及时踢出
eureka.server.enable-self-preservation=false
客户配置:
#每间隔 2s,向服务端发送一次心跳,证明自己依然"存活"
eureka.instance.lease-renewal-interval-in-seconds=2
#告诉服务端,如果我 10s 之内没有给你发心跳,就代表我故障了,将我踢出掉
eureka.instance.lease-expiration-duration-in-seconds=10
Ribbon(负载均衡)
什么是负载均衡(loadbalancer)?
负载均衡,英文名称为Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务。
负载均衡主要分为:软件负载(比如Nginx)和硬件负载(比如F5)
在微服务盛行的现在,软件负载在微服务里成为主流,Netflix的Ribbon就是其中之一
我们这里只关注软件负载,硬件负载略过。
软件负载也分为:服端务负载(服务端发现模式SLB)、客户端负载(客户端发现模式)
Nginx本身就是服务,Nginx做负载,就是服务端做负载均衡。Dubbo也是服务端的负载均衡。
简介
一般来说,在生产环境中,各个微服务都会部署多个实例。那么服务消费者要如何将请求分摊到多个服务提供者实例上呢?
可以使用Ribbon实现客户端侧负载均衡。
Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为。为Ribbon配置服务提供者地址列表后,Ribbon就可以基于某种均衡算法,自动帮助服务消费者去请求。Ribbon默认提供了很多负载均衡算法,如:轮询、随机、hash等,也可以为Ribbon实现自定义的负载均衡算法。
在Spring Cloud中,当Ribbon与Eureka配合使用是,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例
代码实现
为服务消费者整合Ribbon
注意:使用springboot 2.3.12.RELEASE版本,和spring cloud Hoxton.SR12版本。
为项目引入Ribbon的依赖,Ribbon的依赖如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
由于之前我们的微服务已经添加了spring-cloud-starter-netflix-eureka-client,该依赖已包含spring-cloud-starter-netflix-ribbon,因此无需再次引入。
为RestTemplate添加@LoadBalanced注解,就可以为 RestTemplate整合Ribbon,使其具备负载均衡的能力。
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate(); //默认使用URLConnection
}
Ribbon默认的负载方式是轮询,查看服务提供者信息,验证负载策略。
@Resource
private LoadBalancerClient loadBalancerClient;
@GetMapping("/getServerAddress")
public String getServerAddress(){
ServiceInstance serviceInstance = loadBalancerClient.choose("goods-provider");
log.info("获取服务提供者的URI和port:{},{}",serviceInstance.getUri(),serviceInstance.getPort());
return serviceInstance.toString();
}
Ribbon配置自定义
有些场景下,我们可能需要自定义Ribbon的配置,例如修改Ribbon的负载均衡规则等。
Spring Cloud 允许使用属性自定义Ribbon的配置,两种方式是等价的。
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced //添加负载均衡支持,负载均衡使用的是ribbon
public RestTemplate restTemplate(){
return new RestTemplate();
}
//一定要加上它,再次注册bean,否则负载均衡不起作用
@Bean
public IRule ribbonRule(){
return new RandomRule(); //这里配置策略,和配置文件对应。随机规则
}
}
Loadbalancer(负载均衡)
SpringCloud官方推出的用于负载均衡的组件,用法和ribbon一样,在新版的springCloud中Ribbon已经被官方弃用了
如果使用eureka作为注册中心,那么可以使用ribbon来做负载均衡,如果不使用eureka
来做注册中心,也可以选择loadBalancer来做负载均衡
Spring cloud如果用新版本,就用下面的
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
loadbalancer的策略有两种,都实现了ReactorServiceInstanceLoadBalancer接口
RandomLoadBalancer和RoundRobinLoadBalancer两种
使用spring boot 2.6.13 和 spring cloud使用2021.0.4 实验
OpenFeign(服务调用)
OpenFeign 是 一个声明式的REST调用客户端;(调用远程的restful风格的http接口的一个组件)
调用组件其实很多,比如:
1、Httpclient(apache)
2、Httpurlconnection (jdk)
3、restTemplate(spring)
4、OkHttp(android)
5、Feign (Netflix) --> 实现非常优雅
Spring Cloud OpenFeign 对 restTemplate 负载均衡进行了简化,在其基础上进行了进一步的封装,在配置上大大简化了开发工作,它是一种声明式的调用方式,它的使用方法是定义一个接口,然后在接口上添加注解,然后可以采用Spring MVC的标准注解进行开发,OpenFeign可以与Eureka和loadbalancer组合使用以支持负载均衡;
OpenFeign能干什么?
OpenFeign旨在简化微服务消费方(调用者,客户端)代码的开发;
前面在使用loadbalancer+RestTemplate进行服务调用时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方式;
但是在实际开发中,由于服务提供者提供的接口非常多,一个接口也可能会被多处调用,OpenFeign在loadbalancer+RestTemplate的基础上做了进一步封装,在OpenFeign封装之后,我们只需创建一个接口并使用注解的方式来配置,即可完成对服务提供方的接口绑定,简化了使用loadbalancer + RestTemplate的调用,自动封装服务调用客户端,减少代码开发量;
代码参考
1、添加依赖
<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>
2、创建feign接口
@FeignClient(name = "goods-provider")
public interface GoodsFeign {
@GetMapping("/goods/{id}")
Goods getById(@PathVariable("id") Integer id);
}
@FeignClient注解中的name属性是一个任意的客户端名称,用于创建Ribbon(loadbalancer)负载均衡器。
在本例中,由于使用了Eureka,所以Ribbon(loadbalancer)会把goods-provider解析成Eureka Server服务注册表中的服务。当然,如果不使用Eureka,也可以使用url属性指定请求的URL
3、修改service
@Resource
private GoodsFeign goodsfeign;
public int doOrder(Integer uid,Integer goodsId, Integer amount) {
//使用openfeign来调用
MyGoods goods = goodsFeign.getById(goodsId);
}
4、修改启动类
@SpringBootApplication
@MapperScan("com.powernode.dao")
@EnableFeignClients // 启用feign客户端
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5、属性文件配置部分代码
spring:
application:
name: order-consumer
# eureka client 配置
eureka:
client:
#指定服务注册中心的位置
serviceUrl:
defaultZone: http://localhost:8761/eureka/
feign:
client:
config:
default:
# 连接超时时间
connectTimeout: 3000
# 读取超时时间
readTimeout: 5000
其他配置参考
#不注册自己,我是一个消费者,别人如果不调用我的话,我就不用注册
eureka.client.register-with-eureka=false
#每间隔2s,向服务端发送一次心跳,证明自己依然"存活"
eureka.instance.lease-renewal-interval-in-seconds=2
#告诉服务端,如果我10s之内没有给你发心跳,就代表我故障了,将我踢出掉
eureka.instance.lease-expiration-duration-in-seconds=10
#告诉服务端,服务实例以IP作为链接,而不是取机器名
eureka.instance.prefer-ip-address=true
#告诉服务端,服务实例的唯一ID
eureka.instance.instance-id=03-springcloud-service-feign
调用超时设置
#配置OpenFeign超时时间
feign.client.config.default.connectTimeout=3000
feign.client.config.default.readTimeout=5000
调用参数处理
1、传一个基本参数 (int string long boolean)
2、传2个或多个基本参数(int string long boolean)
3、传一个自定义对象参数(user、order)
4、传多个自定义对象参数;(不行)
5、传一个集合(List,Map…)
6、传多个集合对象:不行
6、传一个对象,一个或多个基本参数
7、uri路径传参数 /param/{id}
8、传一个日期参数:@RequestBody
@RestController
@Slf4j
public class ParameterController {
//传2个或多个基本参数(int string long boolean ....)
@GetMapping("/service1")
public String service1(int id, String phone) {
log.info("service1 --> id= " + id + ", phone = " + phone);
return "success";
}
//传一个自定义对象参数(user、order)
// 加@RequestBody只能传json {"id":10,"name":"zhangsan","phone":"12345678"}
//不加@RequesteBody id=10&name=zhangsan&phone=12345678
//如果使用@RequestBody ,不能使用Get请求,应为get请求没有请求体
@PostMapping("/service2")
public String service2(@RequestBody Users users) {
log.info("users= " + users);
return "success";
}
/**
* 这种情况不允许,controller 不允许这样写,请求时会报错
* @param users
* @param member
* @return
*/
@PostMapping("/service3")
public String service3(@RequestBody Users users, @RequestBody Member member) {
log.info("users= {}", users);
log.info("member-->{}", member);
return "success";
}
/**
* {
* "member": {
* "address": "beijing",
* "id": 100
* },
* "users": {
* "id": 6,
* "name": "thomas",
* "phone": "13909090908"
* }
* }
* @param userAndMember
* @return
*/
@PostMapping("/service4")
public String service4(@RequestBody UserAndMember userAndMember) {
log.info("userAndMember= {}", userAndMember);
return "success";
}
//传一个集合(List,Map...)
@PostMapping("/service5")
public String service5(@RequestBody List<Users> usersList) {
log.info("usersList= " + usersList);
return "success";
}
//传两个或多个集合(List,Map...) -- (不行,会报错)
@PostMapping("/service6")
public Object service6(List<Users> usersList, Map<String, Users> usersMap) {
System.out.println("service6 --> usersList= " + usersList + ", usersMap = " + usersMap);
return null;
}
//传一个对象,一个或多个基本参数
@PostMapping("/service7")
public String service7(@RequestBody Users users, int id, String phone) {
log.info("users= " + users + ", phone = " + phone +",id="+id);
return "success";
}
//uri路径传参数 /param/{id}
//http://localhost:8080/service8/500?goodsId=900
@GetMapping("/service8/{goodsId}")
public String service8(@PathVariable("goodsId") Integer goodsId) {
log.info("goodsId= " + goodsId);
return "success";
}
//传一个日期参数
@PostMapping("/service9")
public String service9(@RequestBody Date date) {
log.info("date= " + date);
return "success";
}
}
定义接口工程
这种写法的好处
1 消费者不需要定义feign接口,而是直接依赖feign接口工程,然后使用依赖注入,直接调用相应方法即可。
2 服务提供者的依赖feign接口工程,controller可以直接实现feign接口
优点:知道写哪些api接口,controller不需要写请求方法和请求url,方法名和参数还有返回值都不需要定义了。
不通过注册中心
//先去注册中心找名为hello的微服务,没有的话就调用url的地址
@FeignClient(value="hello",url="http://www.baidu.com")
public interface BaiduFeign {
@GetMapping("/")
String queryBaidu();
}
Hystrix(熔断降级)
Hystrix被称为熔断器;
熔断器也有叫断路器,他们表示同一个意思,最早来源于微服务之父 Martin
Fowler 的论文 CircuitBreaker 一文。“熔断器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,能够及时切断故障电路,防止发生过载、发热甚至起火等严重后果。
在分布式系统里,许多服务之间通过远程调用实现信息交互,调用时不可避免会出现调用失败,比如超时、异常等原因导致调用失败,Hystrix能够保证在一个服务出问题的情况下,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Spring Cloud Hystrix 实现了熔断器、线程隔离等一系列服务保护功能。该功能也是基于 Netflix 的开源框架 Hystrix 实现的,该框架的目标在于当程序BUG,数据不匹配,响应时间过长,服务不可用、异常等等情况时,提供一种系统容错能力。 (Spring Cloud Hystrix 对 Netflix Hystrix 做了一个starter)
提供功能
(1)熔断降级
(2)请求限流
服务降级是指当某个微服务响应时间过长,发生异常,或者服务不可用了,我们不能把错误信息返回回来,或者让它一直卡在那里,所以要准备一个对应的策略(一个方法),当发生这种问题时,我们直接调用这个备用的方法来快速返回一个默认的结果,让请求得到快速响应,而不是一直卡在那里;
请求限流即对请求数量的限制;
熔断降级
feign整合hystrix的熔断降级
使用版本选择Spring boot 使用2.3.12.RELEASE,Spring cloud 使用Hoxton.SR12
注意:高版本的springcloud不支持hystrix
1、添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、启动类上添加注解
启动上添加注解@EnableCircuitBreaker或@EnableHystrix,从而为项目启用断路器支持。
查看源码可知,@EnableHystrix注解的作用和@EnableCircuitBreaker注解的作用一样,@EnableHystrix注解对@EnableCircuitBreaker注解进行了封装。
@SpringBootApplication
@EnableFeignClients // 启用feign客户端
@EnableHystrix // 启用hystrix
//@EnableCircuitBreaker // 启用熔断器
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3、配置文件打开feign支持
feign 默认是支持hystrix的, 但是在Spring cloud 默认关闭了, 因为业务需求不一定要使用;所以现在要使用首先得打开它,在yml文件加上如下配置
feign:
#打开feign支持
hystrix:
enabled: true
降级
降级写法1:fallback属性
在feign客户端的注解上有个属性叫fallback 然后指向一个类,比如 GoodsFeignFackBack.class
@FeignClient(name = "goods-provider",fallback = GoodsFeignFackBack.class) //声明我们的服务提供者
public interface GoodsFeign {
//openfeign 的方式
//路径参数一定要加上@PathVariable,要不然会出错
@GetMapping("/goods/{id}")
Goods getByGoodsId(@PathVariable("id") Integer goodsId);
}
GoodsFeignFackBack类:
@Component
@Slf4j
public class GoodsFeignFackBack implements GoodsFeign {
@Override
public Goods getByGoodsId(Integer goodsId) {
log.error("服务提供者挂了~~~");
Goods goods=Goods.builder().id(-1).name("不存在的商品").store(-1).build();
return goods;
}
}
如此方法降级便可以了;
当然如果需要拿到具体的服务错误信息,那么可以这样:
降级写法2:fallbackFactory属性
客户端指定一个fallbackFactory即可;
//回调工厂
@FeignClient(name = "goods-provider",fallbackFactory = GoodsFallBackFactory.class) //声明我们的服务提供者
public interface GoodsFeign {
//openfeign 的方式
//路径参数一定要加上@PathVariable,要不然会出错
@GetMapping("/goods/{id}")
Goods getByGoodsId(@PathVariable("id") Integer goodsId);
}
fallbackFactory
这个throwable就是熔断后抛出的错误,至此就完成了feign与hystrix的整合;
import feign.hystrix.FallbackFactory; //注意导入这个接口依赖
@Component
@Slf4j
public class GoodsFallBackFactory implements FallbackFactory<GoodsFeign> {
@Override
public GoodsFeign create(Throwable throwable) {
// return new GoodsFeignFackBack();
//匿名内部类
return new GoodsFeign() {
@Override
public Goods getByGoodsId(Integer goodsId) {
log.error("使用fallback工程,服务提供者挂了~~~,我们要降级");
Goods goods=Goods.builder().id(-1).name("不存在的商品").store(-1).build();
log.error("熔断降级的原因为:{}",throwable.toString());
return goods;
}
};
}
}
Hystrix限流
限流有很多方案:
Nginx
Redis + Lua
Sentinel
hystrix通过线程池的方式来管理微服务的调用,它默认是一个线程池(大小10个) 管理你的所有微服务,你可以给某个微服务开辟新的线程池:
threadPoolKey是线程池唯一标识, hystrix会使用该标识来计数,看线程占用是否超过了, 超过了就会直接降级该次调用;
这里coreSize的值为2,那么假设该方法调用时间是1s执行完, 那么在1s内如果有超过2个请求进来的话,剩下的请求则全部降级;
其中maxQueueSize是一个线程队列,里面只能放一个请求线程,本来线程数有2个,队列里面允许放1个,那么总共只能有3个请求线程执行,如果超过了就会限流;
限流代码参考:
@Service
@Slf4j
public class HystrixLimitService {
@HystrixCommand(fallbackMethod = "fallback",
threadPoolKey = "goods",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "2"),
@HystrixProperty(name = "maxQueueSize", value = "1")})
public String limitTest() throws InterruptedException {
Thread.sleep(900);
return "hello limit";
}
/**
* 达到限流值了,就调用该方法来返回结果
*
* @return
*/
public String fallback() {
return "限流了......";
}
}
@RestController
@Slf4j
public class HystrixLimitController {
@Resource
private HystrixLimitService hystrixLimitService;
@GetMapping("/hystrix/limit")
public String limitTest() throws InterruptedException {
return hystrixLimitService.limitTest();
}
}
使用浏览器访问:http://localhost:8080/hystrix/limit
使用F5 快速刷新浏览器,请求,就会限流
config(配置中心)
我们知道,每个项目都会有配置文件,比如 连接数据库的配置,连接Redis的配置,短信接口地址的配置,支付的配置,连接MQ的配置,一些秘钥的配置等等;
对于传统的单体应用来说,只有一个项目,根据不同环境大概有4个配置文件(开发,测试,UAT,生产);
但是采用微服务架构的项目,由于服务的不断拆分,需要管理的配置文件的数量迅速增加,那么这个时候我们就需要解决如何管理配置文件的问题,于是就提出了一个叫配置中心的概念;
配置中心即一套基础组件,用于统一管理各种应用的配置;
常用分布式配置中心框架
Apollo(阿波罗):携程开源的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景;
**Spring Cloud Config **:Spring Cloud官方提供的微服务开发的配置中心,主要用于Spring Cloud微服务项目开发中;
Nacos(纳口斯):阿里巴巴2018年7月推出来的一个开源项目,是一个动态服务注册与发现、配置管理和服务管理平台;
Qconf:奇虎360内部分布式配置管理工具,用来替代传统的配置文件,使得配置信息和程序代码分离,同时配置变化能够实时同步到客户端,而且保证用户高效读取配置,这使的工程师从琐碎的配置修改、代码提交、配置上线流程中解放出来,极大地简化了配置管理工作;
Disconf:百度的分布式配置管理平台,专注于各种分布式系统配置管理, 提供统一的配置管理服务;
大型互联网公司自己内部都有自己独立分布式配置中心,用于解决服务配置管理问题;
Spring Cloud Config
Spring Cloud Config 是一个解决分布式系统的配置管理解决方案;
它由 config-repository、config-server和config-client(各个微服务) 三个部分构成,
1、config-repository:是实际存放配置文件的地方;(gitlab(Ruby),github,gitee)
2、config-server:配置中心的服务端,用来管理config-repository,并接受每个config-client获取配置的请求,以接口的形式将配置文件的内容提供出去;
3、config-client:它通常是我们的微服务应用,它会访问config-server接口,从config-server请求配置文件数据从而初始化自己的应用;
如此一来,配置文件可以集中得到管理,便捷且安全,应用在启动的过程中只要访问配置中心就可以拿到当前环境下需要的配置文件;
Spring Cloud Config默认使用Git来存放配置信息 、也可以使用SVN、本地存放配置文件、数据库、Redis等一些方式来存放配置信息;
工作原理
Spring cloud Config 的工作过程如下图所示:
1、首先需要一个远程Git仓库,平时测试可以使用GitHub/Gitee,在实际生产环境中,需要自己搭建一个Git服务器(gitlab(Ruby语言开发的)),远程Git仓库的主要作用是用来保存我们的配置文件;
2、除了远程Git仓库,还有一个本地Git仓库,每当Config Server访问远程Git仓库时,都会克隆一份到本地,这样当远程仓库无法连接时,就直接使用本地存储的配置信息;
3、微服务 A、微服务 B 则是我们的具体应用,这些应用在启动的时会从Config Server中获取相应的配置信息;A和B就是 config client
4、当微服务A、微服务B尝试从Config Server中加载配置信息的时候,Config Server会先通过git clone命令克隆一份配置文件保存到本地;
配置中心仓库
spring cloud config配置中心默认采用git存储配置,而且实际开发中也是采用Git存储;
我们在这里采用国内的码云gitee,也可以使用国外的GitHub,由于使用Github网络比较慢,可能连接失败;
1、 gitee上创建项目spring-cloud-config-repo仓库
2、项目下创建本地目录
在项目下创建一个文件夹config-repo(mkdir config-repo)
3、创建配置文件
在config-repo中创建我们的配置文件,如下:
application.properties
application-dev.properties
application-test.properties
application-prod.properties
可以用touch 命令创建文件
在四个文件里面分别写上要添加的内容。
4、推送到gitee仓库
使用命令操作
git add .
git commit -m ‘first commit’
git push
也可以通过idea将本地文件同步到 Gitee仓库中;
配置中心服务端
1、创建一个 Spring Boot 项目,添加依赖
<!--添加spring-boot-starter-parent 作为parent-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.13</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--springcloud 相当继承了spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
springcloud新版本需要依赖bootstrap
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
2、 在application.yml中配置一下git仓库信息
此处我们使用gitee (也可以使用GitHub,由于网络比较慢,可能连接失败) ,
首先我的gitee上有一个名为 spring-cloud-config-repo的仓库,我们的配置文件如下:
server:
port: 8030 # 内嵌tomcat服务器的端口号
spring:
application:
name: config-server # 应用名称
cloud:
config:
server:
git:
uri: https://gitee.com/woshiyu/spring-cloud-config-repo.git # 配置Git仓库的地址
username: ****** # Git仓库的账号
password: ****** # Git仓库的密码
default-label: master #git仓库默认的分支(你克隆代码默认从哪个分支克隆)
basedir: ${user.home}/config-repository # 克隆的远程文件存放在本地磁盘哪个位置
search-paths: config-repo # git仓库要搜索的路径,可以写多个路径,多个路径之间用逗号分割
其中:
-
uri 表示配置中心所在仓库的位置;
-
search-paths 表示仓库下的子目录;
-
username 表示你的Gitee用户名;
-
password 表示你的Gitee密码;
-
default-label 默认获取git上的代码分支
3、开启配置中心支持
在启动类,也就是 main 方法的类上添加注解 @EnableConfigServer
@SpringBootApplication
@EnableConfigServer // 开启spring cloud config(配置中心)支持
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class,args);
}
}
至此我们的配置中心服务端就创建好了;
此时启动我们的配置中心服务,通过/{application}/{profile}/{label}就能访问到我们
的配置文件了;
映射规则
文件:master分支下的application-dev.properties
/{application}/{profile}[/{label}]
http://localhost:8030/application/dev/master
文件:master分支下的application.properties
http://localhost:8030/application/master
/{application}-{profile}.properties
http://localhost:8030/application-dev.properties 只能获取文件中的内容,别的不返回
/{label}/{application}-{profile}.properties
http://localhost:8030/master/application-dev.properties 只能获取文件中的内容,别的不返回
/{application}-{profile}.yml
http://localhost:8030/application-dev.yml 只能获取文件中的内容,别的不返回
/{label}/{application}-{profile}.yml
http://localhost:8030/master/application-dev.yml 只能获取文件中的内容,别的不返回
其中:
{application} 表示配置文件的名字,对应的配置文件即application;
{profile} 表示环境,有dev、test、uat、product等;
{label} 表示分支,默认是master分支上;
通过浏览器上访问,返回JSON格式的数据:
name 表示配置文件名application部分,
profiles 表示环境部分,
label 表示分支,
version 表示GitHub上提交时产生的版本号;
同时当我们访问成功后,在控制台会打印了相关的日志信息;
当访问成功后配置中心会通过git clone命令将远程配置文件在本地也保存一份,以确保在远程git仓库故障时我们的应用还可以继续正常使用(第一次启动的时候,是需要能连接Gitee的);
默认是克隆到本地磁盘的:
C:/Users/ADMINI~1/AppData/Local/Temp/config-repo-6794090491048398367/config-repository
可以修改,
#克隆的远程文件存放在本地磁盘哪个位置
spring.cloud.config.server.git.basedir=${user.home}/config-repository
配置中心客户端
前面已经搭建好了配置中心的服务端,并且通过访问接口从config server服务端读取配置信息,不过实际开发中,更多的不是我们人为去获取配置信息,而是由微服务自己从config server服务端上加载配置信息,那么怎么来加载呢?
我们的各个微服务都是config客户端,比如服务提供者、服务消费者等各个微服务都可以从配置中心获取配置信息
1、新建项目,添加依赖
<!--添加spring-boot-starter-parent 作为parent-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.13</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.4</spring-cloud.version>
</properties>
<dependencies>
<!--支持全栈web开发,里面包括了Tomcat和Spring-webmvc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot 测试 依赖jar -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--springcloud 相当继承了spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
高版本需要添加bootstrap依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
2、获取配置信息
创建 bootstrap.yml文件,用户获取配置信息,文件内容如下:
spring:
application:
name: my-application # 对应config server所获取的配置文件的{application}
cloud:
config:
uri: http://localhost:8030/
profile: dev # profile对应config server所获取的配置文件中的{profile}
label: master # 指定Git仓库的分支,对应config server所获取的配置文件的{label}
name: application # 名字 最后一个横线前面
(注意这些信息一定要放在bootstrap.yml 或者 bootstrap.properties 文件中才有效)
其中:
name 对应配置文件中的application部分;
profile 对应了profile部分;
label 对应了label部分;
uri 表示配置中心的地址;
我们解释一下bootstrap.properties文件;
Spring Cloud有一个“引导上下文"的概念,这是主应用程序的父上下文,引导上下文负责从配置服务器加载配置属性,以及解密外部配置文件中的属性,和主应用程序加载application.(yml或 properties)中的属性不同,引导上下文加载(bootstrap)中的属性有更高的优先级,因此默认情况下它们不能被本地配置覆盖;
注意:当使用最新版本的 Spring Could Config Client 时,请注意去看看文档,这个版本的配置方式发生了大改动,如需使用 bootstrap.yml ,则需要在 pom.xml 下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
3、创建一个 Controller 进行测试
我们可以直接使用@Value注解注入配置的属性值,也可以通过Environment对象来获取配置的属性值;
@RestController
@RefreshScope
public class ConfigController {
@Value("${url}")//读取远程配置中心配置的信息
private String url;
@Autowired
private Environment env;
@RequestMapping("/cloud/url")
public String url() {
return this.url;
}
@RequestMapping("/cloud/url2")
public String url2() {
return env.getProperty("url");
}
}
配置信息安全机制
前面是在Git仓库中明文存储配置信息值,很多场景下,对于某些敏感的配置内容(例如数据库账号、密码等),应该加密存储,config server为我们考虑到了这一点,对配置内容提供了加密与解密支持;
1、安装JCE
config server的加解密功能依赖Java Cryptography Extension(JCE)
Java 8 JCE下载地址:
http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
下载得到JCE的zip压缩包并解压,将其中的jar包覆盖到JDK/jre/lib/security目录下;
或者这个路径 JDK\jre\lib\security\policy\unlimited目录
2、配置密钥
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.0.6</version>
</dependency>
注意:bootstrap不要使用太高的版本,否则密钥这要配置很多信息
在配置中心服务端项目中配置一个加解密的秘钥
在bootstrap.yml 中配置
设置对称密钥,用这个密钥进行加密和解密,必须要配置,否则会报错
encrypt:
key: thomas # 加密密钥
3、查看加解密环境
重启配置服务器
http://localhost:端口/encrypt/status
查看config server服务加解密环境;
也可以使用curl 发出请求
curl -X GET http://localhost:8030/encrypt/status
4、对称加密和解密
config server提供了加密与解密的接口,分别是
加密接口:http://localhost:端口/encrypt
解密接口:http://localhost:端口/decrypt (这个接口我们不需要用)
解密是config-server自动完成的;
启动项目,验证加解密:
加密:用postman工具post请求加密你的明文;
得到:962d36c7ada7faced3e3e95c0bac34733920b0174c4109cf30eead7140b1d36b
解密:curl http://localhost:端口/decrypt -d
962d36c7ada7faced3e3e95c0bac34733920b0174c4109cf30eead7140b1d36b
得到:123456
或者使用postman测试解密
在git仓库增加修改配置文件application-dev.properties,增加如下内容:
spring.datasource.username={cipher}9455e7d7333e1cc9d1a8680244f6db3aae6de93825d4bc6f9f4258993f7a8cad
spring.datasource.password={cipher}5a6e3e417d416736d638cd4db7a6380a71a8709c3ec335a28e01cff05705eeaa
git add .
git commit -m ‘添加了数据源信息’
git push
访问地址:http://localhost:8030/application-dev.properties 得到明文;
说明config server能自动解密配置内容;
配置动态刷新
很多场景下,需要在运行期间动态调整配置,如果配置发生了修改,微服务要如何实现配置的动态刷新呢?
Config客户端中配置
配置信息手动刷新
Spring Boot 的actuator提供了一个刷新端点/refresh,添加依赖spring-boot-starter-actuator,可用于配置的刷新;
1、添加依赖
<!--springboot的一个监控actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、添加注解@RefreshScope
在Controller上添加注解@RefreshScope,添加这个注解的类会在配置更新时得到特殊的处理;
3、开放web访问端点
management:
endpoints:
web:
exposure:
include: '*' # 暴露spring-boot-starter-actuator下的端点
4、手动刷新配置
访问 http://localhost:端口/actuator/refresh 进行手动刷新配置(Post访问)
使用postman实验或者使用curl
curl -X POST http://localhost:端口/actuator/refresh
刷新后:在程序不重启的情况下,能自动读取到最新修改的配置值;
不足:这种方式的刷新,就是你对每个微服务分别进行刷新,一个一个操作,如果你有800个微服务,那么就需要手动刷新这800个微服务;
配置信息自动刷新
前面使用**/actuator/refresh**端点手动刷新配置虽然可以实现刷新,但所有微服务节点的配置都需要手动去刷新,如果微服务非常多,其工作量非常庞大。因此,实现配置的自动刷新是势在必行,Spring Cloud Bus就可以用来实现配置的自动刷新;
Spring Cloud Bus使用轻量级的消息代理/总线(例如RabbitMQ、Kafka等)广播传播状态的更改(例如配置的更新)或者其他的管理指令,可以将Spring Cloud Bus想象成一个分布式的Spring Boot Actuator;
1、项目添加依赖(服务端)
<!-- spring-cloud-starter-bus-amqp -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!-- spring-boot-starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、配置文件增加rabbitmq的配置(服务端)
spring:
application:
name: config-server
cloud:
bus:
enabled: true
rabbitmq:
host: 192.168.150.143
port: 5672
username: admin
password: 123456
virtual-host: powernode
management:
endpoints:
web:
exposure:
include: '*'
3、各个微服务(客户端)
其他各个微服务用于接收消息,那么也需要有spring cloud bus的依赖和RabbitMQ的连接信息
添加依赖
<!-- spring-cloud-starter-bus-amqp -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
配置信息
spring:
rabbitmq:
host: 192.168.188.135
port: 5672
username: admin
password: 123456
virtual-host: powernode
4、动态刷新配置(全局广播)
使用post请求方式刷新配置服务器,使用curl 或者postman均可
curl -X POST http://localhost:8030/actuator/bus-refresh
存在问题:老版本使用以上请求没问题,新版本就会报"status":405,“error”:"Method Not Allowed"错误
解决:访问http://localhost:8030/actuator查看监控的端点路径,发现已经变为http://localhost:8030/actuator/busrefresh
# springcloud 新版本 使用下面请求刷新配置服务器
curl -X POST http://localhost:8030/actuator/busrefresh
则RabbitMQ将收到消息,然后微服务会消费消息,config的所有客户端的微服务配置都会动态刷新;
动态刷新配置(定点通知)
定点通知:不再通知所有的 Config 客户端,而是根据需求只通知其中某一个 Config 客户端。
使用 Spring Cloud Bus 实现定点通知的方法简单,只要我们在发送 POST 请求时使用以下格式即可。
http://{host}:{port}/actuator/busrefresh/{destination}
参数说明如下:
Ø {host}: 表示 Config 服务端的主机地址,既可以是域名,也可以是 IP 地址。
Ø {port}:表示 Config 服务端的端口号.
Ø {destination}:表示需要定点通知的 Config 客户端(微服务),由 Config 客户端的服务名(spring.application.name)+端口号(server.port)组成,例如只通知 microservice-cloud-config-client-8040 刷新配置,则取值为 microServiceCloudConfigClient:8040。
curl -X POST http://localhost:8030/actuator/busrefresh/microServiceCloudConfigClient:8040
高可用
99.99% 一年有53分钟服务不可用
有了配置中心之后,其他的微服务都是从配置中心上获取配置信息,此时配置中心就至关重要了,在真实的项目环境中,Spring Cloud Config配置中心难免会出现各种问题,此时就需要考虑Spring Cloud Config的高可用(HA)机制了;
Spring Cloud Config的高可用机制解决方式非常简单,把Spring Cloud Config注册到Eureka就搞定了,此时用户访问的时候不是直接从配置中心获取配置信息,而是先通过eureka中获取配置中心的地址,然后再从配置中心获取具体服务的配置信息;
服务端
我们准备三个配置文件:
application-config8030.properties
application-config8040.properties
application-config8050.properties
启动指定配置文件,启动三个配置中心:
java -jar ***.jar 后指定profile,如下:
–spring.profiles.active=config8030
–spring.profiles.active=config8040
–spring.profiles.active=config8050
config-server-ha 添加eureka-client依赖
<!--spring-cloud-starter-netflix-eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
指定注册中心地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
客户端
添加eureka-client依赖:
<!--spring-cloud-starter-netflix-eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
指定eureka注册中心的地址:
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
bootstrap.properties文件:
spring.cloud.config.profile=dev8080
spring.cloud.config.label=master
#配置中心的url可以通过注册中心发现
spring.cloud.config.discovery.enabled=true
eureka中注册的配置服务器的服务名,这是重点,很容易写错,不要写错了
spring.cloud.config.discovery.service-id=config-server
启动客户端
当某些配置中心宕机后,客户端的微服务会基于注册中心去尝试下一个配置中心的url;
使用nignx
Spring cloud config 高可用第二种方式:使用Nginx;
spring.cloud.config.uri=http://nginx的ip:nginx端口/ 这个url写成访问nginx,然后通过nginx负载均衡转发到3个config服务;
https://blog.csdn.net/yhl15736773842/article/details/131476804
安全认证
配置中心服务端
1、添加依赖
<!-- spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、配置账号和密码
在application.properties文件中配置账号和密码
#配置访问账号
spring.security.user.name=thomas
#配置访问密码
spring.security.user.password=123456
客户端修改及配置
1、配置访问账号和密码
在bootstrap.properties文件中配置访问账号和密码:
#配置访问config配置中心的访问账号和密码
spring.cloud.config.username=thomas
spring.cloud.config.password=123456
更多推荐


所有评论(0)