SpringCloud
SpringCloud
回顾之前的知识~
- JavaSE
- 数据库
- 前端
- Servlet
- SpringBoot
- Dubbo,Zookeeper、分布式基础
- Maven、Git
- Ajax,Json
- ……/
四个核心问题
- 服务很多,客户端怎么访问
- 服务之间如何通信
- 如何治理服务
- 服务挂了怎么办
1.微服务
什么是微服务
微服务是一种架构风格,他要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合;可以通过http的方式进行互通。要说微服务架构,先得说说过去我们的单体应用架构
单体应用架构
所谓单体应用架构(all in one)是指,我们将一个应用中的所有应用服务都封装在一个应用中。
无论ERP,CRM或是其他什么系统,你都把数据库访问,web访问,等等各个都放到一个war中
- 这样做的好处是,易于开发和测试;也十分方便部署;当需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了
- 单体应用架构的缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用的war包。特别是对于一个大型应用,我们不可能把所有内柔都放在一个应用里面,我们如何维护、如何分工合作都是问题
微服务架构
all in one的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。
所谓微服务架构,就是打破之前的all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时间可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制
这样做的好处是
- 节省了条用资源
- 每个功能元素的服务都是一个可替换的、可独立升级的软件代码
微服务的技术栈有哪些
微服务条目 | 落地技术 |
---|---|
服务开发 | SpringBoot,Spirng,SpringMVC |
服务配置与管理 | Netflix公司的Archaius,阿里的Diamond等 |
服务注册与发现 | Eureka、Consul、Zookeeper |
服务调用 | Rest、PRC、gRPC |
服务熔断 | Hystrix、Envoy等 |
负载均衡 | Ribbon、Nginx等 |
服务接口调用(客户端调用服务的简化工具) | Feign等 |
消息队列 | Kafka、RabbitMQ、ActiveMQ等 |
服务配置中心管理 | SpringCloudConfig、Chef等 |
服务路由(API网关) | Zuul等 |
服务监控 | Zabbix、Nagios、Metrics、Specatator |
全链路追踪 | Zipkin、Brave、Dapper等 |
服务部署 | Docker、OpenStack、Kubernetes等 |
数据流操作开发包 | SpringCloud Stream(封装Redis,Rabbit,Kafka等发送接收消息) |
事件消息总线 | SpringCloud Bus |
为什么选择SpringCloud作为微服务架构
1、选型依据
- 整体解决方案和框架成熟度
- 社区热度
- 可维护性
- 学习曲线
2、当前各大IT公司用的微服务架构有哪些
- 阿里:Dubbo+HFS
- 京东:JSF
- 新浪:Motan
- 当当网 DubboX
- ……
2.SpringCloud入门概述
2.1、SpringCloud是什么
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
官网https://spring.io/cloud
2.2、SpringCloud和SpringBoot的关系
Spring boot 是 Spring 的一套快速配置脚手架,可以基于spring boot 快速开发单个微服务,Spring Boot,看名字就知道是Spring的引导,就是用于启动Spring的,使得Spring的学习和使用变得快速无痛。不仅适合替换原有的工程结构,更适合微服务开发。
Spring Cloud基于Spring Boot,为微服务体系开发中的架构问题,提供了一整套的解决方案——服务注册与发现,服务消费,服务保护与熔断,网关,分布式调用追踪,分布式配置管理等。
Spring Cloud是一个基于Spring Boot实现的云应用开发工具;Spring boot专注于快速、方便集成的单个个体,Spring Cloud是关注全局的服务治理框架;spring boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring boot来实现。
SpringBoot专注于快速开发、方便的开发单个个体微服务,SpringCloud关注全面的服务治理框架
2.3、Dubbo和SpringCloud技术选型
1、分布式+服务治理Dubbo
目前成熟的互联网架构:应用服务话拆分+消息中间件
2、Dubbo和SpirngCloud对比
Dubbo | SpringCloud | |
---|---|---|
服务注册中心 | Zookeeper | SpringCloud Netfilx Eureka |
服务调用方式 | RPC | REST API |
服务监控 | Dubbo+monitor | SpringBoot Admin |
断路器 | 不完善 | SpringCloud Netfilx Hystrix |
服务网关 | 无 | SpirngCloud Netfilx Zuul |
分布式配置 | 无 | SpringCloud Config |
服务跟踪 | 无 | SpringCloud Sleuth |
消息总线 | 无 | SpringCloud Bus |
数据流 | 无 | SpringCloud Stream |
批量任务 | 无 | SpringCloud Task |
最大区别:SpringCloud抛弃了Dubbo的RPC通信,采用了基于HTTP的REST的方式
结论:相当于组装机和品牌机一下的区别,一个自由度高,另一个整体性高一些
3.测试
1.准备工作
创建父项目
-
新建一个空的maven项目,取名SpringCloud
-
修改打包方式
<!-- 打包方式--> <packaging>pom</packaging>
-
管理依赖版本
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <junit.version>4.12</junit.version> <lombox.version>1.16.18</lombox.version> <log4j.version>1.2.17</log4j.version> </properties> <!--依赖管理--> <dependencyManagement> <dependencies> <!-- SpringCloud的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR8</version> <type>pom</type> <scope>import</scope> </dependency> <!-- SpringBoot的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.10.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 数据库依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.11</version> </dependency> <!-- SpringBoot启动器--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!-- Junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <!-- Lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombox.version}</version> </dependency> <!-- log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.11</version> </dependency> </dependencies> </dependencyManagement>
创建数据库Dept
-- ----------------------------
-- Table structure for dept
-- ----------------------------
DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept` (
`deptno` bigint NOT NULL AUTO_INCREMENT,
`dname` varchar(60) DEFAULT NULL,
`db_source` varchar(50) DEFAULT NULL,
PRIMARY KEY (`deptno`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='部门表';
-- ----------------------------
-- Records of dept
-- ----------------------------
BEGIN;
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (1, '开发部', 'db01');
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (2, '人事部', 'db01');
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (3, '财务部', 'db01');
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (4, '市场部', 'db01');
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (5, '运维部', 'db01');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
2.创建API工程
-
在父项目下新建一个空的maven的module,取名为SpringCloud-API
-
导入Maven依赖
<!-- 当前的Module自己需要的依赖,如果父依赖依据配置了版本,这里就不用写了--> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
-
在com.springcloud.pojo中写入实体类Dept
@Data @NoArgsConstructor @Accessors(chain = true) //链式写法 /* * 链式写法: * Dept dept = new Dept(); * dept.setDeptNo(11).setDname('XXX') * */ public class Dept implements Serializable { //Dept实体类,orm类表关系映射 private int deptno;//主键 private String dname; //这个数据库存在哪个数据库的字段~微服务,一个服务对应数据库,同一个信息可能存在与不同的数据库 private String db_source; public Dept(String dname){ this.dname=dname; } }
3.创建Provider工程
-
在父项目下新建一个空的maven的module,取名为SpringCloud-Provider-dept-8001
-
导入依赖
<dependencies> <!-- 我们需要拿到实体类,所以要配置api module--> <dependency> <groupId>org.example</groupId> <artifactId>SpringCloud-API</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- Junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!-- test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </dependency> <!-- springBoot web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 热部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies>
-
配置application.yaml【修改web端口号为8001】
server: port: 8001 servlet: encoding: force: true charset: utf-8 enabled: true #mybatis配置 mybatis: type-aliases-package: com.springcloud.pojo mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: application: name: springcloud-provider-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC username: root password: root1@12
-
在dao层写一个DeptDao的接口
package com.springcloud.dao; import com.springcloud.pojo.Dept; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; import java.util.List; @Mapper @Repository public interface DeptDao { boolean addDept(Dept dept); Dept queryById(Long id); List<Dept> queryAll(); }
-
在resources目录下创建mybatis/mapper/DeptMapper.xml,实现DeptDao接口
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.springcloud.dao.DeptDao"> <insert id="addDept" parameterType="Dept"> insert into dept (dname, db_source) values (#{dname},DATABASE()); </insert> <select id="queryById" resultType="Dept" parameterType="Long"> select * from dept where deptno =#{id}; </select> <select id="queryAll" resultType="Dept" > select * from dept; </select> </mapper>
-
在Service层写一个DeptService接口
public interface DeptService { boolean addDept(Dept dept); Dept queryById(Long id); List<Dept> queryAll(); }
-
实现DeptService接口
@Service public class DeptServiceImpl implements DeptService{ @Autowired private DeptDao deptDao; @Override public boolean addDept(Dept dept) { return deptDao.addDept(dept); } @Override public Dept queryById(Long id) { return deptDao.queryById(id); } @Override public List<Dept> queryAll(){ return deptDao.queryAll(); } }
-
在controller层写一个DeptController类
//提供RestFul服务 @RestController public class DeptControl { @Autowired private DeptService deptService; @PostMapping("/dept/add") public boolean addDept(Dept dept){ return deptService.addDept(dept); } @GetMapping("/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return deptService.queryById(id); } @GetMapping("/dept/list") public List<Dept> queryAll(){ return deptService.queryAll(); } }
-
最后写一个主启动类DeptProvider_8001
@SpringBootApplication public class DeptProvider_8001 { public static void main(String[] args) { SpringApplication.run(DeptProvider_8001.class,args); } }
测试
4.创建Consumer工程
-
在父项目下新建一个空的maven的module,取名为SpringCloud-Consumer-dept-80
-
导入Maven依赖【API工程,WEB,热部署】
<!-- 实体类Web--> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>SpringCloud-API</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies>
-
配置application.yaml把端口号改为80端口
server: port: 80
-
在config目录中写一个ConfigBean把RestTemplate注册到Bean中
@Configuration public class ConfigBean { //@Configuration---spring application.xml @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
-
在controller层写一个DeptController类
@RestController public class DeptConsumerController { //理解一个问题:消费者,不应该有service层 //RestTemplate ……供我们直接调用就可以了!注册到spring中 //{url,实体,Map,Class<T> responseType} @Autowired private RestTemplate restTemplate; private static final String REST_URL_PREFIX = "http://localhost:8001/"; @RequestMapping("/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return restTemplate.getForObject(REST_URL_PREFIX+"dept/get/"+id,Dept.class); } @RequestMapping("consumer/dept/add") public boolean add(Dept dept){ return restTemplate.postForObject(REST_URL_PREFIX+"dept/add",dept,boolean.class); } @RequestMapping("/consumer/dept/list") public List<Dept> list(){ return restTemplate.getForObject(REST_URL_PREFIX+"dept/list",List.class); } }
-
最后写一个主启动类DeptConsumer_80
@SpringBootApplication public class DeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(DeptConsumer_80.class,args); } }
测试
4.Eureka
4.1、什么是Eureka
- Eureka怎么读:【英/juˈriːkə】
- Netfilx在设计Eureka的时候,遵循的就是AP原则【满足可用性,分区容错性,通过对数据一致性要求低一些】
- Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
4.2、原理讲解
Eureka的基本架构
- SpringCloud封装了Netflix公司开发的Eureka模块来实现服务注册和发现(对比zookeeper)
- Eureka采用CS架构的设计,EureksServer作为服务注册功能的服务器,他是服务注册中心
- 而系统中的其他微服务。使用了Eureka的客户端连接到EurekaServer并维持心跳连接。这样系统的维护人员就可以通过EurekaServer来监控各个微服务是否正常运行,SpringCloud的一些其他模块(比如Zuul)就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑

三大角色
-
Eureka Server:提供服务注册和发现,多个Eureka Server之间会同步数据,做到状态一致(最终一致性)
- Service Provider:服务提供方,将自身服务注册到Eureka,从而使服务消费方能够找到
- Service Consumer:服务消费方,从Eureka获取注册服务列表,从而能够消费服务
4.3、Eureka服务搭建
在父项目下新建一个空的maven的module,取名为SpringCloud-Eureka-7001
-
导入maven依赖
<!-- 导包--> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>2.2.10.RELEASE</version> </dependency> </dependencies>
-
配置application.yaml
server: port: 7001 #Eureka配置 eureka: instance: hostname: localhost #Eureks服务端的实例名称 client: register-with-eureka: false #是否向eureka注册中心注册自己 ,他是服务中心所以为false fetch-registry: false #fetch-registry: false表示自己为注册中心 service-url: #监控页面 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
-
写一个主启动类EurekaServer_7001打开@EnableEurekaServer
package com.SpringCloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer //Eureka的服务端的启动类 public class EurekaServer_7001 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7001.class,args); } }
测试
4.4、Eureka服务注册
- 在SpringCloud-Provider-dept-8001中导入maven依赖
<!-- 加入Eureka-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
- 在yaml中添加新的配置
#Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: SpringCloud-Provider-dept-8001 #修改 Eureka 的默认描述
- 在主启动类中开启注解
@EnableEurekaClient //在服务启动后自动注册到Eureka中
4.5、信息配置
在SpringCloud-Provider-dept-8001中导入maven依赖
<!-- 完善监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.10.RELEASE</version>
</dependency>
添加yaml配置
#info配置
info:
app;: TEST
company.name: TEST PROJECT
@EnableDiscoveryClient :获取服务清单
在Controller里面
//注册进来的微服务 获取一些信息
@GetMapping("/dept/discovery")
public Object discovery(){
//获取服务清单的列表
List<String> services = client.getServices();
//获取具体的微服务信息
List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
return this.client;
}
4.6、自我保护机制
官方对于自我保护机制的定义:
自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。
自我保护机制的工作机制是:如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:
- Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
- Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
- 当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。
默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
自我保护机制
Eureka自我保护机制,通过配置 eureka.server.enable-self-preservation
来true
打开/false
禁用自我保护机制,默认打开状态,建议生产环境打开此配置。
4.7 Eureka集群配置
建立三个Eureka的module名称分别为
- SpringCloud-Eureka-7001
- SpringCloud-Eureka-7002
- SpringCloud-Eureka-7003
配置各自的application.yaml
server:
port: 7001
#Eureka配置
eureka:
instance:
hostname: 127.0.0.1 #Eureks服务端的实例名称
client:
register-with-eureka: false #是否向eureka注册中心注册自己 ,他是服务中心所以为false
fetch-registry: false #fetch-registry: false表示自己为注册中心
service-url: #监控页面
#这里分别天其他两个得到hostname
defaultZone: http://localhost:7002/eureka/,http://192.168.1.100:7003/eureka/
创建各自的启动类
@SpringBootApplication
@EnableEurekaServer //Eureka的服务端的启动类
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
最后在提供者module的application中修改或添加配置
#Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka/,http://localhost:7002/eureka/,http://192.168.1.100:7003/eureka/
instance:
instance-id: SpringCloud-Provider-dept-8001 #修改 Eureka 的默认描述
4.8、对比ureak与Zookeeper
1、回顾CAP
CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A):保证每个请求不管成功或者失败都有响应。
分区容忍性(P):系统中任意信息的丢失或失败不会影响系统的继续运作。
CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP。如果在某个分布式系统中数据无副本, 那么系统必然满足强一致性条件, 因为只有独一数据,不会出现数据不一致的情况,此时C和P两要素具备,但是如果系统发生了网络分区状况或者宕机,必然导致某些数据不可以访问,此时可用性条件就不能被满足,即在此情况下获得了CP系统,但是CAP不可同时满足[2] 。
因此在进行分布式架构设计时,必须做出取舍。当前一般是通过分布式缓存中各节点的最终一致性来提高系统的性能,通过使用多节点之间的数据异步复制技术来实现集群化的数据一致性。通常使用类似 memcached 之类的 NOSQL 作为实现手段。虽然 memcached 也可以是分布式集群环境的,但是对于一份数据来说,它总是存储在某一台 memcached 服务器上。如果发生网络故障或是服务器死机,则存储在这台服务器上的所有数据都将不可访问。由于数据是存储在内存中的,重启服务器,将导致数据全部丢失。当然也可以自己实现一套机制,用来在分布式 memcached 之间进行数据的同步和持久化,但是实现难度是非常大的 [3] 。
- CA:单点集群,满足一致性,可用性的系统,通常可扩展性较差
- CP:满足一致性,分区容错性的系统,通常性能不是特别高
- AP:满足可用性,分区容错性的系统,通常可能对一致性要求低一些
2、作为服务注册中心,Eureka比Zookeeper好在哪里?
著名的CAP理论提出,一个分布式系统不可能同时满足C(一致性),A(可用性),P(容错性)
- Zookeeper保证的是CP
- Eureka保证的是AP
因此,Eureka可以很好的应对网络故障导致的部分节点丢失的情况,而不会像zookeeper那样使整个注册中心瘫痪
5.Ribbon
5.1、什么是Ribbon
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。
负载均衡的分类
- 集中式负载均衡,客户端和服务端使用独立的负载均衡措施(硬件的F5,软件的Nginx)
- 进程内负载均衡,将负载均衡逻辑集成在客户端组件中,客户端从注册中心获取可用服务,然后再从服务地址中选择合适的服务端发起请求(Ribbon)
5.2、测试Ribbon
在Consumer中导入Eureka和Ribbon的依赖
<!-- ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!--Eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
在配置文件中增加Eureka的配置
#Eureka配置
eureka:
client:
register-with-eureka: false #消费者不需要向Eureka注册自己
service-url: #随机访问一个实现负载均衡
defaultZone: http://127.0.0.1:7001/eureka/,http://localhost:7002/eureka/,http://192.168.1.100:7003/eureka/
ConfigBean中新增一个注解
@Configuration
public class ConfigBean { //@Configuration---spring application.xml
//配置负载均衡实现RestTemplate
@Bean
@LoadBalanced //Ribbon
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
修改DeptController中REST_URL_PREFIX的值为注册中心里服务的ID
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT/";
测试
5.3、实现均衡负载
新建两个数据库db02,db03,内容与db01数据库一样 除了db_source字段改为数据库名称
-- ----------------------------
-- Table structure for dept
-- ----------------------------
DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept` (
`deptno` bigint NOT NULL AUTO_INCREMENT,
`dname` varchar(60) DEFAULT NULL,
`db_source` varchar(50) DEFAULT NULL,
PRIMARY KEY (`deptno`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='部门表';
-- ----------------------------
-- Records of dept
-- ----------------------------
BEGIN;
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (1, '开发部', 'db01');
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (2, '人事部', 'db01');
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (3, '财务部', 'db01');
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (4, '市场部', 'db01');
INSERT INTO `dept` (`deptno`, `dname`, `db_source`) VALUES (5, '运维部', 'db01');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
新建两个Provider的module 与SpringCloud-Provider-dept-8001模块一样分别名为
- SpringCloud-Provider-dept-8002
- SpringCloud-Provider-dept-8003
修改配置里面的Eureka的默认描述与数据库的URL
#spring的配置
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
username: root
password: root1@12
#Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka/,http://localhost:7002/eureka/,http://192.168.1.100:7003/eureka/
instance:
instance-id: SpringCloud-Provider-dept-8002 #修改 Eureka 的默认描述
修改主启动类的类名分别为
- DeptProvider_8002
- DeptProvider_8003
最后打开全部服务发现,消费者项目刷新后会进入不同的提供者项目
6.Feign负载均衡
6.1、简介
Feign是声明式的web service客户端,它让微服务的调用变得更简单了,类似Controller调用service。SpringCloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
只需要创建一个接口,然后添加注解即可!
feign,主要是社区、大家都习惯面向接口编程,这个是很多开发人员的规范。调用微服务访问的两种方法
1.微服务名字【ribbon】
2.接口和注解【feign】
Feign能做什么
- Feign意在使编写Java Http客户端变得更容易
- 在实际的开发当中,由于对服务依赖的调用不止一处,这就导致一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。
- Feign就在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它,(类似于以前Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可)即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。
6.2、示例
复制consumer项目,改名为SpringCloud-Consumer-dept-feign-80
在API项目和新建的项目中添加Feign的maven依赖
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
在API中创建service层写入DeptClientService接口
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {
@PostMapping("/dept/add")
public boolean addDept(Dept dept);
@GetMapping("/dept/get/{id}")
public Dept get(@PathVariable("id") Long id);
@GetMapping("/dept/list")
public List<Dept> queryAll();
}
修改SpringCloud-Consumer-dept-feign-80中Controller层的DeptConsumerController
@RestController
public class DeptConsumerController {
@Autowired
private DeptClientService deptClientService ;
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id){
return this.deptClientService.get(id);
}
@RequestMapping("consumer/dept/add")
public boolean add(Dept dept){
return this.deptClientService.addDept(dept);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list(){
return this.deptClientService.queryAll();
}
}
给主启动类添加新的注解【@EnableFeignClients(basePackages = {"com.springcloud"})】
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.springcloud"})
public class DeptConsumer_feign_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_feign_80.class,args);
}
}
7.Hystrix
在微服务架构中,一个应用往往由多个服务组成,这些服务之间相互依赖,依赖关系错综复杂。
例如一个微服务系统中存在 A、B、C、D、E、F 等多个服务,它们的依赖关系如下图。
图1:服务依赖关系
通常情况下,一个用户请求往往需要多个服务配合才能完成。如图 1 所示,在所有服务都处于可用状态时,请求 1 需要调用 A、D、E、F 四个服务才能完成,请求 2 需要调用 B、E、D 三个服务才能完成,请求 3 需要调用服务 C、F、E、D 四个服务才能完成。
当服务 E 发生故障或网络延迟时,会出现以下情况:
- 即使其他所有服务都可用,由于服务 E 的不可用,那么用户请求 1、2、3 都会处于阻塞状态,等待服务 E 的响应。在高并发的场景下,会导致整个服务器的线程资源在短时间内迅速消耗殆尽。
- 所有依赖于服务 E 的其他服务,例如服务 B、D 以及 F 也都会处于线程阻塞状态,等待服务 E 的响应,导致这些服务的不可用。
- 所有依赖服务B、D 和 F 的服务,例如服务 A 和服务 C 也会处于线程阻塞状态,以等待服务 D 和服务 F 的响应,导致服务 A 和服务 C 也不可用。
从以上过程可以看出,当微服务系统的一个服务出现故障时,故障会沿着服务的调用链路在系统中疯狂蔓延,最终导致整个微服务系统的瘫痪,这就是“雪崩效应”。为了防止此类事件的发生,微服务架构引入了“熔断器”的一系列服务容错和保护机制。
7.1、熔断器
熔断器(Circuit Breaker)一词来源物理学中的电路知识,它的作用是当线路出现故障时,迅速切断电源以保护电路的安全。
在微服务领域,熔断器最早是由 Martin Fowler 在他发表的 《Circuit Breaker》一文中提出。与物理学中的熔断器作用相似,微服务架构中的熔断器能够在某个服务发生故障后,向服务调用方返回一个符合预期的、可处理的降级响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常。这样就保证了服务调用方的线程不会被长时间、不必要地占用,避免故障在微服务系统中的蔓延,防止系统雪崩效应的发生。
7.2、Hystrix
Spring Cloud Hystrix 是一款优秀的服务容错与保护组件,也是 Spring Cloud 中最重要的组件之一。
Spring Cloud Hystrix 是基于 Netflix 公司的开源组件 Hystrix 实现的,它提供了熔断器功能,能够有效地阻止分布式微服务系统中出现联动故障,以提高微服务系统的弹性。Spring Cloud Hystrix 具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。
Hystrix [hɪst'rɪks],中文含义是豪猪,豪猪的背上长满了棘刺,使它拥有了强大的自我保护能力。而 Spring Cloud Hystrix 作为一个服务容错与保护组件,也可以让服务拥有自我保护的能力,因此也有人将其戏称为“豪猪哥”。
在微服务系统中,Hystrix 能够帮助我们实现以下目标:
- 保护线程资源:防止单个服务的故障耗尽系统中的所有线程资源。
- 快速失败机制:当某个服务发生了故障,不让服务调用方一直等待,而是直接返回请求失败。
- 提供降级(FallBack)方案:在请求失败后,提供一个设计好的降级方案,通常是一个兜底方法,当请求失败后即调用该方法。
- 防止故障扩散:使用熔断机制,防止故障扩散到其他服务。
- 监控功能:提供熔断器故障监控组件 Hystrix Dashboard,随时监控熔断器的状态。
服务降级:释放服务器资源的资源以保证核心业务的正常高效运行的措施
服务熔断:服务熔断是服务降级的一种特殊情况,它是防止服务雪崩而采取的措施
7.3、测试Hystrix熔断
@HystrixCommand(fallbackMethod = "XXX")
复制SpringCloud-Provider-dept-8001项目,改名为SpringCloud-Provider-dept-hystrix-8001
添加Hystrix的mvaen依赖
<!-- 导入Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
更改一下项目的标识符,在配置文件中
#Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka/,http://localhost:7002/eureka/,http://192.168.1.100:7003/eureka/
instance:
instance-id: SpringCloud-Provider-dept-hystrix-8001 #修改 Eureka 的默认描述
prefer-ip-address: true #状态栏ip引用, true为不使用主机名来定义注册中心的地址,而使用IP地址的形式
在Controller的get方法上增加一个注释@HystrixCommand(fallbackMethod = "hystrixGet"),当有异常时执行该方法
@GetMapping("/dept/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept get(@PathVariable("id") Long id){
Dept dept = deptService.queryById(id);
if(dept==null){
throw new RuntimeException("id=>"+id+",不存在用户,或者信息无法找到");
}
return dept;
}
写一个熔断执行方法hystrixGet()
,
/**
* 备选方案
*/
public Dept hystrixGet(@PathVariable("id") Long id){
return new Dept()
.setDeptno(id.intValue())
.setDname("id=>"+id+"没有对应信息,null--@Hystrix")
.setDb_source("no this database in MySQL");
}
7.4、Hystrix 服务降级
Hystrix 提供了服务降级功能,能够保证当前服务不受其他服务故障的影响,提高服务的健壮性。
服务降级的使用场景有以下 2 种:
- 在服务器压力剧增时,根据实际业务情况及流量,对一些不重要、不紧急的服务进行有策略地不处理或简单处理,从而释放服务器资源以保证核心服务正常运作。
- 当某些服务不可用时,为了避免长时间等待造成服务卡顿或雪崩效应,而主动执行备用的降级逻辑立刻返回一个友好的提示,以保障主体业务不受影响。
我们可以通过重写 HystrixCommand 的 getFallBack() 方法或 HystrixObservableCommand 的 resumeWithFallback() 方法,使服务支持服务降级。
Hystrix 服务降级 FallBack 既可以放在服务端进行,也可以放在客户端进行。
Hystrix 会在以下场景下进行服务降级处理:
- 程序运行异常
- 服务超时
- 熔断器处于打开状态
- 线程池资源耗尽
案例
在API项目的Service层中编写DeptClientServiceFallbackFactory
类实现FallbackFactory
接口
- 返回一个实现DeptClientService的类,这个类里面的方法就是降级后的方法
//降级
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
@Override
public DeptClientService create(Throwable cause) {
return new DeptClientService() {
@Override
public boolean addDept(Dept dept) {
return false;
}
@Override
public Dept get(Long id) {
return new Dept().setDeptno(id.intValue()).setDname("服务降级").setDb_source("NO DATA");
}
@Override
public List<Dept> queryAll() {
return null;
}
};
}
}
在DeptClientService
类的@FeignClient
注解中追加fallbackFactory
以引用刚刚创建的类
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {
@PostMapping("/dept/add")
public boolean addDept(Dept dept);
@GetMapping("/dept/get/{id}")
public Dept get(@PathVariable("id") Long id);
@GetMapping("/dept/list")
public List<Dept> queryAll();
}
在SpringCloud-Consumer-dept-feign-80项目的配置中追加开启feign.hystrix
#开启feign.hystrix
feign:
hystrix:
enabled: true
这个时候我们关闭服务器,就会发现服务降级了
7.5、Hystrix:DashBoard
Hystrix提供了对于微服务调用状态的监控信息,但是需要结合spring-boot-actuator模块一起使用。
Hystrix Dashboard主要用来实时监控Hystrix的各项指标信息。通过Hystrix Dashboard反馈的实时信息,可以帮助我们快速发现系统中存在的问题。
新建一个SpringCloud-Consumer-hystrix-dashboard-9001项目
- 导入Maven依赖
<dependencies>
<!-- 导入Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- 导入Hystrix-Dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
- 添加配置
server:
port: 9001
hystrix:
dashboard:
proxy-stream-allow-list: localhost,192.168.1.100,127.0.0.1 #增加可以监控的地址
- 主启动类添加
@EnableHystrixDashboard
注解
@SpringBootApplication
@EnableHystrixDashboard
public class DeptConsumerDashboard_9001 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumerDashboard_9001.class,args);
}
}
- 在服务器项目SpringCloud-Provider-dept-hystr-8001项目中添加
hystrix:dashboard
的maven依赖
<!-- 导入Hystrix-Dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
- 在主启动中增加一个Servlet
//启动类
@SpringBootApplication
@EnableEurekaClient //在服务启动后自动注册到Eureka中
@EnableDiscoveryClient //服务发现
@EnableCircuitBreaker //添加对熔断对支持 断路器
public class DeptProvider_hystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_hystrix_8001.class,args);
}
//增加一个Servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
在DashBoard中输入监控地址http://localhost:8001/actuator/hystrix.stream
8.Zuul路由网关
8.1、概述
什么是Zuul?
zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责将外部请求转发到具体的服务实例上,是实现外部访问统一入口的基础,而过滤功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得
注意:Zuul服务最终还是会注册Eureka
提供:代理+路由+过滤 三大功能
8.2、示例
创建一个项目 SpringCloud-Zuul-9527
导入maven依赖
<!-- zuul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- 加入Eureka-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- 完善监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.10.RELEASE</version>
</dependency>
<!-- 导入Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- 导入Hystrix-Dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>SpringCloud-API</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
添加配置文件
server:
port: 9527
spring:
application:
name: springcloud-zuul
#Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka/,http://localhost:7002/eureka/,http://192.168.1.100:7003/eureka/
instance:
instance-id: SpringCloud-Zuul-9257 #修改 Eureka 的默认描述
prefer-ip-address: true #状态栏ip引用, true为不使用主机名来定义注册中心的地址,而使用IP地址的形式
#info配置
info:
app: TEST
company.name: TEST PROJECT
zuul:
routes:
springcloud-provider-dept.serverId: springcloud-provider-dept
springcloud-provider-dept.path: /mydept/**
ignored-services: "*" #不能以这个路径访问,ignored :忽略,隐藏全部的 ,隐藏全部的 使用服务不可访问
#prefix #设置路径前缀
在主启动类添加注释@EnableZuulProxy
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy //开启Zuul代理
public class ZuulApplication_9527 {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication_9527.class,args);
}
}
通过访问Zuul的服务就可以访问服务器了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2021-09-13 证券投资基金-证券投资基金的监管与信息披露