SpringCloud-Netflix笔记
文章目录
前言
本文原创博主(本人),csdn博客地址https://blog.csdn.net/Jay_Chou345,转载请注明出处
微服务架构的几个核心问题
- 服务很多,客户端该怎么访问?
- 这么多服务之间怎么进行通信
- 这么多服务怎么治理
- 如果其中一个服务挂了怎么办
解决方案:
springcloud并不是一个技术框架,而是一个生态
技术选型
-
springcloud Netflix 一站式解决方案,现已停止维护
api网关,zuul组件
Feign——HttpClinet——Http通信方式,同步,阻塞
服务注册发现:Euraka
熔断机制:Hystrix
。。。
. Apache dubbo zookeeper 半自动,专注于通信,其余需要整合别人的
api:没有,找第三方组件
Dubbo:基于java开源的RPC的通信框架
zookeeper:分布式应用程序协调服务
没有熔断机制,借助Hystrix
这个方案并不完善。。。
springcloud alibaba 一站式解决方案,更简单
万变不离其宗:
- API网关——路由
- HTTP,RPC——通信
- 注册和发现——高可用
- 熔断机制——服务降级
微服务概述
什么是微服务?
目前而言,业界并没有一个统一的标准定义
微服务提倡将单一的一个程序分成一组小的服务,每个服务运行在自己的进程内,服务之间相互协调,互相配置,为用户提供价值
但通常而言,微服务架构是一种架构模式, 或者说是一种架构风格,它提倡将单一 的应用程序划分成一 组小的服务,每个服务运行在其独立的自己的进程内,服务之间互相协调,互相配置,为用户提供最终价值。
服务之间采用轻量级的通信机制互相沟通,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中
另外,应尽量避免统一的, 集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言,工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储
微服务化的核心就是将传统的一站式应用,根据业务拆分成一个个的服务彻底地去耦合,每个微服务提
供单个业务功能的服务,一个服务做-件事情,从技术角度看就是一种小而独立的处理过程, 类似进程的概
念,能够自行单独启动或销毁,拥有自己独立的数据库。
springcloud概述
SpringCloud,基于SpringBoot提供了一套微服务解决方案, 包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于NetFlix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。
SpringCloud利用SpringBoot的开发便利性,巧妙地简化了分布式系统基础设施的开发,SpringCloud为开发人员 .提供了快速构建分布式系统的一些工具,包括配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等,他们都可以用SpringBoot的开发风格做到一键启动和部署。
SpringBoot并没有重复造轮子,它只是将目前各家公司开发的比较成熟,经得起实际考研的服务框架组合起来,
通过SpringBoot风格进行再封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂,易部署和易维护的分布式系统开发工具包
SpringCloud是分布式微服务架构下的一站式解决方案, 是各个微服务架构落地技术的集合体,俗称微服务全家
桶。
应用服务拆分+消息中间件
dubbo和springcloud区别
最大区别:springcloud抛弃了dubbo的RPC通信,采取了基于HTTP的REST方式
一纸契约,只利用api调用,不存在代码级别的强依赖,后者虽然牺牲了服务调用的性能,但是更适合如今的微服务环境
dubbo相当于品牌NUC,而springcloud相当于一台组装机
因为Dubbo定义是一款RPC框架,而springcloud是微服务架构下的一站式解决方案
版本对比
依赖版本
Spring Cloud Version | Spring Cloud Alibaba Version | Spring Boot Version |
---|---|---|
Spring Cloud 2020.0.1 | 2021.1 | 2.4.2 |
Spring Cloud Hoxton.SR9 | 2.2.6.RELEASE | 2.3.2.RELEASE |
Spring Cloud Greenwich.SR6 | 2.1.4.RELEASE | 2.1.13.RELEASE |
Spring Cloud Hoxton.SR3 | 2.2.1.RELEASE | 2.2.5.RELEASE |
Spring Cloud Hoxton.RELEASE | 2.2.0.RELEASE | 2.2.X.RELEASE |
Spring Cloud Greenwich | 2.1.2.RELEASE | 2.1.X.RELEASE |
Spring Cloud Finchley | 2.0.4.RELEASE(停止维护,建议升级) | 2.0.X.RELEASE |
Spring Cloud Edgware | 1.5.1.RELEASE(停止维护,建议升级) | 1.5.X.RELEASE |
组件版本
Spring Cloud Alibaba Version | Sentinel Version | Nacos Version | RocketMQ Version | Dubbo Version | Seata Version |
---|---|---|---|---|---|
2.2.6.RELEASE | 1.8.1 | 1.4.2 | 4.4.0 | 2.7.8 | 1.3.0 |
2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE | 1.8.0 | 1.4.1 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE | 1.8.0 | 1.3.3 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE | 1.7.1 | 1.2.1 | 4.4.0 | 2.7.6 | 1.2.0 |
2.2.0.RELEASE | 1.7.1 | 1.1.4 | 4.4.0 | 2.7.4.1 | 1.0.0 |
2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE | 1.7.0 | 1.1.4 | 4.4.0 | 2.7.3 | 0.9.0 |
2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE | 1.6.3 | 1.1.1 | 4.4.0 | 2.7.3 | 0.7.1 |
快速开始
建立一个maven项目,删除src文件夹,配置依赖
此时这个项目相当于父项目,在此项目中创建子模块(module),子模块虽然pom文件是空的,但是其所导入的maven依赖如果存在于父模块中,则与父项目的pom依赖版本保持一致
<?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.ifyyf</groupId>
<artifactId>springcloud-study</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>study-api</module>
<module>study-provider-dept-8001</module>
<module>study-consumer-detp-80</module>
</modules>
<!-- 打包方式pom -->
<packaging>pom</packaging>
<!-- 版本号 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<druid.version>1.1.10</druid.version>
<logback-core.version>1.2.3</logback-core.version>
</properties>
<!-- Management管理依赖 -->
<dependencyManagement>
<dependencies>
<!-- springcloud依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- springboot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- springboot启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback-core.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
可以看见子模块添加的lombok与父模块定义的版本一致
创建子模块并配置pom依赖(直接从父类引入,版本与父类一致)
消费者模块:study-consumer-detp-80
生产者模块:study-provider-dept-8001
因为分模块部署到不同的服务器上,所以需要接口调用接口,在本demo中是以消费者调用生产者的接口
所以生产者需要有完整一套接口服务mybatis-dao-service-serviceImpl-controller
而消费者不应该有service,所以需要将RestTemplate,注册到spring中调用
RestTemplate
:提供多种便捷访问远程http服务的方法类,简单的restFul服务模板
生产者的controller代码
package com.ifyyf.controller;
import com.ifyyf.pojo.Dept;
import com.ifyyf.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Author if
* @Description: What is it
* @Date 2021-08-24 下午 12:13
*/
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/get/{deptno}")
public Dept get(@PathVariable("deptno") Long deptno){
return deptService.getById(deptno);
}
@GetMapping("/getAll")
public List<Dept> getAll(){
return deptService.getAll();
}
}
消费者的ConfigBean和Controller代码如下:
package com.ifyyf.config;
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;
/**
* @Author if
* @Description: @Configuration的类相当于spring的applicationContext.xml
* @Date 2021-08-24 下午 01:05
*/
@Configuration
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
package com.ifyyf.controller;
import com.ifyyf.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @Author if
* @Description: What is it
* @Date 2021-08-24 下午 01:03
*/
@RestController
@CrossOrigin
@RequestMapping("/consumer/dept")
public class DeptConsumerController {
/**
* 消费者不应该有service
* 因为分模块部署到不同的服务器上,所以需要接口调用接口
* RestTemplate,注册到spring中直接调用
* getForObject(URI url, Class<T> responseType)、
* url是网络接口地址,responseType是本类中返回值的类型的class
*
* postForObject(URI url, @Nullable Object request, Class<T> responseType)
* url是网络接口地址,request是参数,responseType是本类中返回值的类型的class
*/
//提供多种便捷访问远程http服务的方法类,简单的restFul服务模板
@Autowired
private RestTemplate restTemplate;
//远程接口主机地址
private static String HOST="http://localhost:8001";
/**
* RestFul风格
* @param deptno
* @return
*/
@GetMapping("/get/{deptno}")
public Dept get(@PathVariable("deptno") Long deptno){
return restTemplate.getForObject(HOST+"/dept/get/"+deptno,Dept.class);
}
/**
* list返回值就给List.class
* @return
*/
@GetMapping("/getAll")
public List<Dept> getAll(){
return restTemplate.getForObject(HOST+"/dept/getAll",List.class);
}
}
小结:
不管是访问了http://localhost:8001/dept/getAll,还是http://localhost/consumer/dept/getAll
都能正常调用到数据库拿到结果
Eureka服务注册与发现
什么是Eureka
Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于REST的服务,用于定位服务,以实现
云端中间层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务发现与注册,只需
要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于Dubbo的注册
中心,比如Zookeeper
Eureka的基本结构
-
SpringCloud封装了NetFlix公司开发的Eureka模块来实现服务注册和发现(对比Zookeeper)
-
Eureka采用了C-S的架构设计,EurekaServer 作为服务注册功能的服务器,他是服务注册中心
-
而系统中的其他微服务。使用Eureka的客户端连接到EurekaServer并维持心跳连接。 这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行,SpringCloud的一 些其他模块(比如Zuul)就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑;
-
心跳连接:搁一小段时间发送一下请求,如果没有响应则认为对方已经死亡
-
与Dubbo的区别
- Eureka包含两个组件: Eureka Server和Eureka Client。
- Eureka Server提供服务注册服务,各个节点启动后,会在EurekaServer中进行注册,这样Eureka Server
中的服务注册表中将会村粗所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。 - 心跳机制:Eureka Client是一个Java客户端, 用于简化EurekaServer的交互,客户端同时也具备-个内置的,使用轮询负载算法的负载均衡器。在应用启动后,将会向EurekaServer发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将 会从服务注册表中把这个服务节点移除掉(默认周期为90秒)
-
三大角色
- 客户端Eureka Server: 提供服务的注册于发现。
- 提供者Service Provider: 将自身服务注册到Eureka中,从而使消费方能够找到
- 消费者Service Consumer: 服务消费方从Eureka中获取注册服务列表,从而找到消费服务
起步
springcloud项目创建子模块study-eureka-7001
pom依赖导入新版的netflix的
<?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">
<parent>
<artifactId>springcloud-study</artifactId>
<groupId>com.ifyyf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>study-eureka-7001</artifactId>
<!-- 导入eureka的依赖,因为父项目指定了springcloud的版本,所以组件版本也固定了 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
配置yml文件
server:
port: 7001
# Eureka配置
eureka:
instance:
hostname: localhost # 服务端的实例名称
client:
register-with-eureka: false # 是否想eureka注册中心注册自己本身
fetch-registry: false # fetch-registry为false表示自己是注册中心
service-url: # 监控页面
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
配置注册中心的启动类
package com.ifyyf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @Author if
* @Description: Eureka注册中心
* @Date 2021-08-24 下午 06:48
*/
/**
* Eureka服务端的启动类,可以接收别的服务注册进来
* 访问直接访问localhost:7001根目录即可
*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
访问http://localhost:7001/
得到下图页面
自我保护机制
一句话总结:某时刻某一个微服务不可以用了,eureka不会立刻清理,依旧会对该微服务的信息进行保存!
- 默认情况下,如果EurekaServer在一 定时间内没有接收到某个微服务实例的心跳,EurekaServer将 会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka之间无法正常通行,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个服务。Eureka通过自我保护机制来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息 ,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该EurekaServer节 点会自动退出自我保护模式。
- 在自我保护模式中,EurekaServer会保护服务注册表中的信息, 不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该EurekaServer 节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。-句话:好死不如赖活着
- 综上,自我保护模式是一-种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮和稳定
- 在SpringCloud中,可以使用
eureka.server.enable-self-preservation = false
禁用自我保护模式- [不推荐关闭自我保护机制]
注册服务
study-provider-dept-8001
添加Eureka依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
启动类添加注解
package com.ifyyf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @Author if
* @Description:
* 疑问@EnableEurekaClient和@EnableDiscoveryClient共同点与区别是什么?
* 共同点:都是能够让注册中心能够发现,扫描到改服务
* 区别:@EnableEurekaClient只适用于Eureka作为注册中心,@EnableDiscoveryClient可以是其他注册中心
* @Date 2021-08-24 下午 12:42
*/
@SpringBootApplication
/**
* 开启Eureka的客户端,会自动根据配置类的地址自动注册到Eureka中
*/
@EnableEurekaClient
//使之能够被注册中心发现,不仅仅是eureka
@EnableDiscoveryClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
yml配置
# Eureka配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: springcloud-provider-dept8001 # 修改Eureka上的默认描述信息
# info配置
info:
app.name: study-springcloud
company.name: www.ifyyf.com
然后先重启注册中心,再启动微服务,访问注册中心
图示左边是微服务的id名称,右边链接是服务的相关info(在yml的info配置)
访问所有/单个微服务的信息
在服务中可以访问到其他微服务的信息
例如在study-provider-dept-8001
服务的controller层导入DiscoveryClient类
/**
* 导入这个包org.springframework.cloud.client.discovery.DiscoveryClient
*/
@Autowired
private DiscoveryClient client;
/**
* 根据DiscoveryClient类可以获取注册中心注册的微服务的一些信息
* @return
*/
@GetMapping("/discovery")
public Object discovery(){
//获取微服务列表清单
List<String> services = client.getServices();
System.out.println("discovery =>services: "+services);
//根据具体的微服务id(ApplicationName),获取实例对象
List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println("--------------------");
System.out.println("instance.getHost() = "+instance.getHost());
System.out.println("instance.getPort() = "+instance.getPort());
System.out.println("instance.getUri() = "+instance.getUri());
System.out.println("instance.getServiceId() = "+instance.getServiceId());
}
return this.client;
}
输出结果
discovery =>services: [springcloud-provider-dept]
--------------------
instance.getHost() = 192.168.80.1
instance.getPort() = 8001
instance.getUri() = http://192.168.80.1:8001
instance.getServiceId() = SPRINGCLOUD-PROVIDER-DEPT
集群环境搭建
为什么要用eureka集群?
之所以进行eureka集群的搭建,在于我们平时的生产环境中,很难保证单节点的eureka服务能提供百分百不间断的服务,如果eureka无响应了,整个项目应用都会出现问题
因此要保证eureka随时都能提供服务的情况下,最好的方式就是采用eureka的集群模式,也就是搭建eureka的高可用,在eureka的集群模式下,多个eureka server之间可以同步注册服务
因此,在一个eureka宕掉的情况下,仍然可以提供服务注册和服务发现的能力,从而达到注册中心的高可用
如何搭建eureka集群
假设我们需要3台eureka服务配置集群,则需要在每个服务的yml配置文件中挂载到另外两个的url上
以study-eureka-7001
为例,study-eureka-7002
与study-eureka-7003
同理
server:
port: 7001
# Eureka配置
eureka:
instance:
hostname: local1.ifyyf.com # 服务端的实例名称
client:
register-with-eureka: false # 是否想eureka注册中心注册自己本身
fetch-registry: false # fetch-registry为false表示自己是注册中心
service-url: # 监控页面
# 单机配置
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群配置(关联挂载别的集群地址)
defaultZone: http://local2.ifyyf.com:7002/eureka/,http://local3.ifyyf.com:7003/eureka/
注:此处的主机地址是通过修改C:\Windows\System32\drivers\etc
目录下的hosts文件实现的,实际上都是指向了本机的192.168.80.1
,为了区分以及集群配置
此时eureka集群搭建完成,study-provider-dept-8001
服务端需要同时注册到所有注册中心中去
也就是将原来yml配置的注册中心的单个url修改成多个url,以逗号分割
eureka:
client:
service-url:
defaultZone: http://local2.ifyyf.com:7002/eureka/,http://local1.ifyyf.com:7001/eureka/,http://local3.ifyyf.com:7003/eureka/
结果
此时启动注册中心,再启动服务,可以看见3台注册中心都成功注册了服务,且如果其中有某一两台eureka注册中心断线宕机,剩下的注册中心依旧存在,达到了注册中心的高可用性
CAP原则及对比Zookeeper
CAP原则又称CAP定理,指的是在一个分布式系统中
一致性(Consistency)
在分布式系统中的所有数据备份,在同一时刻是否同样的值,即写操作之后的读操作,必须返回该值。(分为弱一致性、强一致性和最终一致性)
可用性(Availability)
在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
分区容错性(Partition tolerance)
以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择
CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP ,因为无法同时满足3个要求
取舍策略
CAP三个特性只能满足其中两个,那么取舍的策略就共有三种 :
**CA:**单点集群,满足-致性,可用性的系统,通常可扩展性较差
如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。传统的关系型数据库RDBMS:Oracle、MySQL就是CA。
**CP:**满足一致性,分区容错性的系统,通常性能不是特别高
如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。
**AP:**满足可用性,分区容错性的系统,通常可能对一致性要求低一些
要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。
Eureka比Zookeeper好在哪
一个分布式系统不能同时满足CAP
由于分布式系统必须要满足分区容错性P
,所以只能在可用性A
与一致性C
之间权衡
-
Zookeeper保证的是CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对一致性的要求要高于可用性。
但是zk会出现这样一种情况, 当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。
问题在于,选举leader的时间太长,30~120s,且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。
在云部署的环境下,因为网络问题使得zk集群失去master节点是较大概率会发生的事件,虽然服务最终能够恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
-
Eureka保证的是AP
Eureka看明白了这一-点, 因此在设计时就优先保证可用性。Eureka各个节 点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。
而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一-台Eureka还在,就能保住注册服务的可用性,只不过查到的信息可能不是最新的,
除此之外,Eureka还有一 -种自我保护机制, 如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其他节点中
因此,Eureka可以很好地应对因为网络故障等原因导致的部分节点失去联系的情况,而不会像zookeeper那样整个服务瘫痪
json被解析成了xml?
如果发现本该返回json数据的接口,返回时变成了xml格式,那可能是eureka中自带的jackson-dataformat-xml
将json数据解析成了xml,只需要在客户端的xml文件中的eureka依赖下排除这个依赖即可
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<!-- 问题:json被解析成xml返回浏览器, 解决:客户端加入此排除jackson依赖 -->
<exclusions>
<exclusion>
<artifactId>jackson-dataformat-xml</artifactId>
<groupId>com.fasterxml.jackson.dataformat</groupId>
</exclusion>
</exclusions>
</dependency>
Ribbon:负载均衡及Ribbon
什么是Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目 ,主要功能是提供客户端的软件负载均衡算法,将NetFlix的中间层服务连接在一起。
Ribbon的客户端组件提供-系列完整的配置项如:连接超时、重试等等。
简单的说,就是在配置文件中列出LoadBalancer (简称LB:负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等等)去连接这些机器。
我们也很容易使用Ribbon实现自定义的负载均衡算法!
Ribbon能干嘛
- LB,负载均衡,在微服务或分布式集群中经常用的一种应用
- 负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)
- 常见的负载均衡的软件有Ngnix,Lvs等
- dubbo、SpringCloud中均给我们提供了负载均衡, SpringCloud的负载均衡算法可以自定义
- 负载均衡简单分类
- 集中式LB
- 即在服务的消费方和提供方之间使用独立的L B设施,如Nginx: 反向代理服务器 ,由该设施负责把
访问请求通过某种策略转发至服务的提供方
- 即在服务的消费方和提供方之间使用独立的L B设施,如Nginx: 反向代理服务器 ,由该设施负责把
- 进程式LB
- 将LB逻辑集成到消费方(客户端),消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器(从Eureka中拿出所有服务,根据算法选出合适的)
- Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方
的地址
- 集中式LB
使用Ribbon实现轮询负载均衡
首先创建3个内容相同的数据库
以study-db01
、study-db02
、study-db03
为例,均含有dept表,但是表中有一个字段不同
source字段代表当前数据库名称(等会负载均衡时方便区分)
创建另外两个服务study-provider-dept-8002
与study-provider-dept-8003
,并配置好不同的数据库
在客户端使用@LoadBalanced注解
开启Ribbon负载均衡实现RestTemplate,仅仅只是多了一个注解而已
/**
* @LoadBalanced注解配置负载均衡实现RestTemplate
* @return
*/
@LoadBalanced //Ribbon
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
启动eureka集群,再启动3个服务,最后开启客户端进行访问
访问80端口的用户端,多刷新几次,可以看到返回的数据中source字段分别为study-db01
、study-db02
、
study-db03
证明客户端访问到注册中心的服务时采用了Ribbon的负载均衡,轮询均摊到了各个服务中去,才会使得每次访问的结果不同
自定义Ribbon负载均衡算法
在客户端启动类中使用Ribbon自定义负载均衡策略配置注解
@RibbonClient(name="SPRINGCLOUD-PROVIDER-DEPT",configuration = MyRule.class)
- name是针对的某个服务的名称进行自定义负载均衡
- configuration是负载均衡算法类的class
注:这个自定义的类不能放在@ComponentScan所扫描的当前包以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是我们达不到特殊化指定的目的了。
Feign负载均衡
什么是Feign
feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。 SpringCloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
只需要创建一个接口, 然后添加注解即可!
ribbon是面向微服务名字进行访问,而feign是面向接口和注解进行访问
不需要像ribbon一样,根据服务名称进行配置,而是在微服务接口上添加一个feign注解,即可(与Dao接口上添加@Mapper注解差不多的感觉)
Feign集成了Ribbon,比ribbon更方便,但是加了一层之后性能会降低一点
起步
在api模块中创建interface接口类DeptClintService
,用于Feign的配置
package com.ifyyf.service;
import com.ifyyf.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
/**
* @Author if
* @Description: mapping注解的url是指向对应服务的接口url
* @Date 2021-08-25 下午 10:30
*/
//指定负载均衡的服务名称
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
@Component
public interface DeptClintService {
@GetMapping("/dept/getAll")
List<Dept> queryAll();
@GetMapping("/dept/get/{deptno}")
Dept queryById(@PathVariable("deptno")Long deptno);
}
因为之前的study-consumer-dept-80
是配置了Ribbon,所以再创建一个新的客户端模块study-consumer-dept-feign
用于配置Feign
这俩选其一即可,都是80端口的客户端,所以yml等文件都是完全相同的
DeptConsumerController
客户端接口中注册刚刚写的DeptClintService
feign配置接口
具体的访问流程在注释里
总结:用户=>客户端Controller=>Feign负载均衡=>注册中心集群=>服务端Controller=>mysql数据库集群
package com.ifyyf.controller;
import com.ifyyf.pojo.Dept;
import com.ifyyf.service.DeptClintService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @Author if
* @Description: 消费者客户端
* 用户应该访问的是本接口ip:80/consumer/dept/getAll
* 然后代理到了Feign负载均衡接口类,由Feign的接口类根据算法(轮询)打向配置好了的并注册到了注册中心的服务接口去
* 打向的主机地址由@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")注解接管
* 具体的接口地址由方法上的@GetMapping("/dept/getAll")接管
* 总结:用户=>客户端Controller=>Feign负载均衡=>注册中心集群=>服务端Controller=>mysql数据库集群
* @Date 2021-08-24 下午 01:03
*/
@RestController
@CrossOrigin
@RequestMapping("/consumer/dept")
public class DeptConsumerController {
@Autowired
private DeptClintService deptClintService=null;
/**
* RestFul风格
* @param deptno
* @return
*/
@GetMapping("/get/{deptno}")
public Dept get(@PathVariable("deptno") Long deptno){
return deptClintService.queryById(deptno);
}
/**
* list返回值就给List.class
* @return
*/
@GetMapping("/getAll")
public List<Dept> getAll(){
return deptClintService.queryAll();
}
}
最后开启注册中心,服务端,最后开启客户端并访问接口,可以看到成功访问,刷新后拿到不同结果,负载均衡成功实现
Hystrix:服务熔断、服务降级与服务监控
分布式面临的问题
复杂分布式体系结构中的应用程序有数-十个依赖关系,每个依赖关系在某些时候将不可避免的失败!
Hystrix是什么
在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性
“断路器”本身是一种开关装置, 当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向
调用方返回一个服务预期的,可处理的备选响应(FalBack) ,而不是长时间的等待或者抛出调用方法无法处理
的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
Hystrix被设计的目标是:
- 对通过第三方客户端库访问的依赖项(通常是通过网络)的延迟和故障进行保护和控制。
- 在复杂的分布式系统中阻止级联故障。
- 快速失败,快速恢复。
- 回退,尽可能优雅地降级。
- 启用近实时监控、警报和操作控制。
什么是服务熔断
熔断机制是对应雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后恢复调用链路。
在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。
熔断机制的注解是@HystrixCommand.
起步
新建study-provider-dept-hystrix-8001
作为hystrix的的服务端
controller层的接口添加@HystrixCommand注解,指定当前方法熔断的解决方法名称
这样在服务出现异常问题时,会自动执行到注解标识的名称的方法(下列代码中的异常为手动模拟)
package com.ifyyf.controller;
import com.ifyyf.pojo.Dept;
import com.ifyyf.service.DeptService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Author if
* @Description: What is it
* @Date 2021-08-24 下午 12:13
*/
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/get/{deptno}")
//指定当前方法熔断的解决方法名称
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept get(@PathVariable("deptno") Long deptno){
Dept dept = deptService.getById(deptno);
if(dept==null){
//手动模拟异常情况
throw new NullPointerException("不存在id为 "+deptno+" 的数据");
}
return dept;
}
public Dept hystrixGet(Long deptno){
return new Dept()
.setDeptno(deptno)
.setDname("不存在id为 "+deptno+" 的数据")
.setSource("no this database in mysql");
}
}
启动类使用==@EnableHystrix==注解开启hystrix服务熔断机制
注:旧版本是@EnableCircuitBreaker,新版本是@EnableHystrix
疑问:那我为什么不try-catch或者统一异常处理呢,这不是更方便嘛?
什么是服务降级
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作
“临时性地拆东墙补西墙”
服务降级这个问题,如果从整体来操作,
- 一定是先降级优先级低的接口,两权相害取其轻
- 如果服务链路整体没有性能特别差的点,比如就是外部流量突然激增,那么就从外到内开始降级
- 如果某个服务能检测到自身负载上升,那么可以从这个服务自身做降级
- 服务降级用在客户端,从整体网站负荷考虑,拆东墙补西墙,访问降级的服务时,切换到配置类中重写的方法
- 服务熔断用在服务端,单个服务异常时,切换到备用方法
起步
步骤
-
创建
DeptClientServiceFallbackFactory
服务降级配置类继承FallbackFactory
接口package com.ifyyf.service; import com.ifyyf.pojo.Dept; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; import java.util.List; /** * @Author if * @Description: 服务降级 * @Date 2021-08-26 下午 03:18 */ @Component public class DeptClientServiceFallbackFactory implements FallbackFactory { @Override public DeptClintService create(Throwable throwable) { return new DeptClintService() { @Override public List<Dept> queryAll() { return null; } @Override public Dept queryById(Long deptno) { return new Dept() .setDeptno(deptno) .setDname("无法查询id为"+deptno+"的数据,为了分担其他服务的压力,该服务现已被关闭") .setSource("服务关闭,无法查询"); } }; } }
-
重写
DeptClintService
api接口中的方法 -
@FeignClient
注解配置fallbackFactory
属性,指定服务降级配置类的classpackage com.ifyyf.service; import com.ifyyf.pojo.Dept; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import java.util.List; /** * @Author if * @Description: mapping注解的url是指向对应服务的接口url * @Date 2021-08-25 下午 10:30 */ //指定负载均衡的服务名称,并添加服务降级配置类的class @FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class) @Component public interface DeptClintService { @GetMapping("/dept/getAll") List<Dept> queryAll(); @GetMapping("/dept/get/{deptno}") Dept queryById(@PathVariable("deptno")Long deptno); }
-
客户端开启feign.hystrix服务降级
# 客户端80端口模块study-consumer-dept-feign中的yml配置文件 # 开启feign.hystrix服务降级 feign: hystrix: enabled: true
-
正常访问时,数据正常,当服务断开时,返回配置类中配置的信息
结果
正常访问
服务断开后
服务监控
hystrix dashboard仪表盘可以帮助开发者可视化的看见服务被请求的情况
首先创建模块study-consumer-hystrix-dashboard
xml导入依赖
<!-- hystrix依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- hystrix监控 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
配置yml
server:
port: 9001
启动类
package com.ifyyf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
/**
* @Author if
* @Description: What is it
* @Date 2021-08-26 下午 04:19
*/
@SpringBootApplication
//开启hystrix监控
@EnableHystrixDashboard
public class DeptConsumerDashboard_9001 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumerDashboard_9001.class,args);
}
}
被监控的服务端study-provider-dept-hystrix-8001
导入依赖
<!-- actuator完善hystrix的监控信息(必须) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
启动类添加
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
访问http://localhost:9001/hystrix
并输入http://192.168.80.1:8001/actuator/hystrix.stream
等信息进入监控仪表盘
Zuul路由网关
什么是zuul
Zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础, 而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。ZuuI和Eureka进行整合, 将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
注意: Zuul服务最终还是会注册进Eureka
提供:代理+路由+过滤三大功能
一般网关也有另外一个名字gateway
(直译)
起步
创建study-zuul-9527
模块
配置pom依赖
<?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">
<parent>
<artifactId>springcloud-study</artifactId>
<groupId>com.ifyyf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>study-zuul-9527</artifactId>
<dependencies>
<!-- zuul依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<!-- 引入该模块的依赖,使得能够访问到该模块的包和类文件等 -->
<dependency>
<groupId>com.ifyyf</groupId>
<artifactId>study-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.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<!-- 问题:json被解析成xml返回浏览器, 解决:客户端加入此排除jackson依赖 -->
<exclusions>
<exclusion>
<artifactId>jackson-dataformat-xml</artifactId>
<groupId>com.fasterxml.jackson.dataformat</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- hystrix依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- hystrix监控 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
</project>
yml配置文件,将网关注册到注册中心
server:
port: 9527
spring:
application:
name: springcloud-zuul
# Eureka配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://local2.ifyyf.com:7002/eureka/,http://local1.ifyyf.com:7001/eureka/,http://local3.ifyyf.com:7003/eureka/
instance:
instance-id: springcloud-zuul-9527 # 修改Eureka上的默认描述信息
# info配置
info:
app.name: study-springcloud
company.name: www.ifyyf.com
# 配置路由重定向,如果不加这个重定向路由的话需要根据服务名称调用服务
zuul:
routes:
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
mydept2.serviceId: springcloud-consumer-dept-feign-80
mydept2.path: /mydept2/**
# 忽略服务名称调用服务,只能根据↑配置的path访问
# ignored-services: springcloud-provider-dept
ignored-services: "*" # 有多个服务时,"*"可以隐藏所有的
# prefix: /if # 设置公共的前缀
启动类ZuulApplication_9527
package com.ifyyf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @Author if
* @Description: What is it
* @Date 2021-08-30 下午 11:23
*/
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication_9527 {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication_9527.class,args);
}
}
然后可以访问localhost:9527/springcloud-provider-dept/dept/get/1
来访问这个服务
但是如果将服务名称暴露在url上是很不安全的,所以需要配置路由
# 配置路由重定向,如果不加这个重定向路由的话需要根据服务名称调用服务
zuul:
routes:
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
mydept2.serviceId: springcloud-consumer-dept-feign-80
mydept2.path: /mydept2/**
# 忽略服务名称调用服务,只能根据↑配置的path访问
# ignored-services: springcloud-provider-dept
ignored-services: "*" # 有多个服务时,"*"可以隐藏所有的
# prefix: /if # 设置公共的前缀
routes下可以随意配置多个map,根据path替换服务名称调用
这里我将80端口的feign负载均衡客户端注册到了注册中心,所以zuul能访问并配置路由
通过http://localhost:9527/mydept/dept/get/2
可以访问到配置好的服务端
也可以通过http://localhost:9527/mydept2/consumer/dept/get/3
访问到客户端
Config配置中心
分布式面临的问题
分布式微服务中,将应用拆分成了多个服务模块,而这每一个模块都有一个application.yml配置文件,成千上百的配置文件配置起来非常麻烦,所以我们需要一个统一配置文件的配置中心
什么是Config分布式配置中心
Spring Cloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用
的所有环节提供了一一个中心化的外部配置
Spring Cloud Config 分为服务端和客户端两部分
- 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密,解密信息等访问接口。
- 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。
- 配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理。并且可以通过git客户端工具来方便的管理和访问配置内容。
SpringCloud config分布式配置中心能干嘛
实现了代码和配置解耦:运维只需要修改远程仓库的配置文件,而无需动开发人员的代码文件
集中管理配置文件
- 不同环境,不同配置,动态化的配置更新,分环境部署,比如/dev /test/ /prod /beta /release -
- 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一 拉取配置自己的信息。
- 当配置发生变动时,服务不需要重启,即可感知到配置的变化,并应用新的配置
- 将配置信息以REST接口的形式暴露
SpringCloud config分布式配置中心与github整合
由于Spring Cloud Config默认使用Git来存储配置文件(也有其他方式, 比如支持SVN和本地文件), 但是最推
荐的还是Git,而且使用的是http / https访问的形式
服务器连接git获取配置
首先gitee创建仓库springcloud-config
,这里我测试的仓库地址
clone到本地,创建application.yml文件
注:需要配置多环境的话---
千万别省略,不然会报错
spring:
profiles:
active: dev # 当前选择激活dev
---
spring:
profiles: dev
application:
name: springcloud-config-dev
---
spring:
profiles: test
application:
name: springcloud-config-test
add,commit ,push上去
环境搭建完成
接下来创建配蜘中心模块study-config-server-3344
导入依赖
<?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">
<parent>
<artifactId>springcloud-study</artifactId>
<groupId>com.ifyyf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>study-config-server-3344</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
配置yml文件
server:
port: 3344
spring:
application:
name: springcloud-config-server-3344
# 连接远程仓库(下面那个是uri,不是url !!!)
cloud:
config:
server:
git:
uri: https://gitee.com/ifyyf/springcloud-config.git # git链接地址
# username: xxxxxx # 如果git仓库是私有,且出现身份验证错误,需要填写用户名和密码
# password: xxxxxx # 如果git仓库是私有,且出现身份验证错误,需要填写用户名和密码
# label: master # 读取的分支
# Eureka配置
#eureka:
# client:
# service-url: # 监控页面
# defaultZone: http://local1.ifyyf.com:7001/eureka/,http://local2.ifyyf.com:7002/eureka/,http://local3.ifyyf.com:7003/eureka/
# instance:
# instance-id: springcloud-config-server-3344 # 修改Eureka上的默认描述信息
启动类
package com.ifyyf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* @Author if
* @Description: What is it
* @Date 2021-08-31 下午 12:11
*/
@SpringBootApplication
//开启服务器连接git的配置中心
@EnableConfigServer
public class Config_server_3344 {
public static void main(String[] args) {
SpringApplication.run(Config_server_3344.class,args);
}
}
启动项目,访问http://localhost:3344/master/application-dev.yml
或者http://localhost:3344/master/application-test.yml
都可以访问到
注:此处master可以省略,只是表面label分支而已
客户端连接服务端访问远程
bootstrap.yml
是系统级别的配置文件,application.yml
是用户级别的配置文件,所以前者级别更高
我们客户端模块要从云端拿数据,所以需要用到bootstrap.yml
创建bootstrap.yml
# 系统级别的配置,从云端拿数据需要用到本配置
# 3355为客户端,去连接3344配置中心服务端,然后服务器去gitee上获取配置文件
spring:
cloud:
config:
uri: http://localhost:3344
label: master # 仓库的分支
name: config-client # 需要从git上读取的资源名称,不需要yml后缀
profile: dev # 拿的版本
# 以上3个配置相当于访问到了http://localhost:3344/config-client/test/master
创建application.yml
spring:
application:
name: springcloud-config-server-3355
创建controller,为了直观看到配置信息等
package com.ifyyf.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author if
* @Description: What is it
* @Date 2021-08-31 下午 01:03
*/
@RestController
public class ConfigClientController {
@Value("${spring.application.name}")
private String applicationName;
@Value("${eureka.client.service-url.defaultZone}")
private String eurekaServer;
@Value("${server.port}")
private String port;
@RequestMapping("/config")
public String config(){
return "applicationName = "+applicationName
+ " eurekaServer = "+eurekaServer
+" port = "+port;
}
}
启动类
package com.ifyyf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Author if
* @Description: What is it
* @Date 2021-08-31 下午 01:01
*/
@SpringBootApplication
public class ConfigClient_3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClient_3355.class,args);
}
}
启动客户端模块(配置中心服务端不能关闭)
git仓库里写的端口,dev是8081,test是8082,根据bootstrap.yml
编写的版本访问对应的端口
这里以dev、8081端口为例,访问http://localhost:8202/config
出现以下文字证明获取成功
applicationName = springcloud-provider-dept eurekaServer = http://local2.ifyyf.com:7002/eureka/,http://local1.ifyyf.com:7001/eureka/,http://local3.ifyyf.com:7003/eureka/ port = 8201
完结
所有工程原代码放在个人gitee上了,地址→gitee仓库地址
在学习的过程中,父工程的pom依赖总是有改变的,最后呈上一份父工程的pom文件
<?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.ifyyf</groupId>
<artifactId>springcloud-study</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>study-api</module>
<module>study-provider-dept-8001</module>
<module>study-consumer-dept-80</module>
<module>study-eureka-7001</module>
<module>study-eureka-7002</module>
<module>study-eureka-7003</module>
<module>study-provider-dept-8003</module>
<module>study-provider-dept-8002</module>
<module>study-consumer-dept-feign</module>
<module>study-provider-dept-hystrix-8001</module>
<module>study-consumer-hystrix-dashboard</module>
<module>study-zuul-9527</module>
<module>study-config-server-3344</module>
<module>study-config-client-3355</module>
</modules>
<!-- 打包方式pom -->
<packaging>pom</packaging>
<!-- 版本号 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<druid.version>1.1.10</druid.version>
<logback-core.version>1.2.3</logback-core.version>
<fastjson.version>1.2.31</fastjson.version>
</properties>
<!-- Management管理依赖 -->
<dependencyManagement>
<dependencies>
<!-- springcloud依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- springboot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- springboot启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback-core.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
完结撒花❀