学习笔记-springCloud
网站整体架构
SpringCloud与springBoot
-
SpringBoot专注于快速方便的开发单个个体微服务。
-
SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供∶配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等集成服务。
-
.SpringBoot可以离开SpringClooud独立使用,开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系
-
总结:SpringBoot专注于快速、方便的开发单个个体微服务,SpringCloud关注全局的服务治理框架
面试问题
1.1、什么是微服务?
1.2、微服务之间是如何独立通讯的?
1.3、SpringCloud和 Dubbo有哪些区别?
1.4、SpringBoot和SpringCloud,请你谈谈对他们的理解
1.5、什么是服务熔断?什么是服务降级
1.6、微服务的优缺点是分别是什么?说下你在项目开发中遇到的坑
1.7、你所知道的微服务技术栈有哪些?请列举一二
1.8、eureka和zookeeper都可以提供服务注册与发现的功能,请说说两个的区别?
参考文档
基础环境搭建
父项目依赖
空的maven项目
<modules>
<module>springCloudapi</module>
<module>springCloud-provider-dept-8001</module>
<module>springcloud-consumer-dept-80</module>
</modules>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<!--打包方式-->
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<!--springCLoud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--sprinyBoot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!--Springboot 启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</dependencyManagement>
API
实体类提供者
不用定义版本号,会从父项目中获取!!!
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
@Data
@NoArgsConstructor
@Accessors(chain = true)//链式写法 @Builder
//支持 dept.setName().setDeptno();
public class Dept implements Serializable {
private long deptno;
private String dname;
private String db_source;
public Dept(String dname){
this.dname=dname;
}
}
Provider-8001
springboot项目
依赖
注意以依赖的形式导入另一个项目实体:springCloudapi
<dependencies>
<!--需要拿到实体类,所以配置api module-->
<dependency>
<groupId>org.example</groupId>
<artifactId>springCloudapi</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--jetty-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
springboot配置
server:
port: 8001
mybatis:
type-aliases-package: com.yang.pojo
config-location: classpath:mybatis/mybatis-config.xml #没啥用
mapper-locations: classpath:mybatis/mapper/*.xml #重要,注册mapper
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?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
password: 123456
username: root
Dao
@Repository
@Mapper
public interface DeptDao {
boolean addDept(Dept dept);
Dept queryDeptById(long deptno);
List<Dept> queryAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace = 绑定一个对应的Dao/Mapper 接口 -->
<mapper namespace="com.yang.dao.DeptDao">
<select id="queryAll" resultType="dept" >
select * from db01.dept
</select>
<insert id="addDept" parameterType="dept">
insert into dept (dname,db_source) values (#{dname},DATABASE())
</insert>
<select id="queryDeptById" resultType="dept">
select * from dept where deptno=#{deptno}
</select>
</mapper>
Service
@Service
public class DeptServiceImpl implements DeptService{
@Autowired
private DeptDao deptDao;
@Override
public boolean addDept(Dept dept) {
return deptDao.addDept(dept);
}
@Override
public Dept queryDeptById(long deptno) {
return deptDao.queryDeptById(deptno);
}
@Override
public List<Dept> queryAll() {
return deptDao.queryAll();
}
}
Controller
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@PostMapping("/dept/add")
public boolean addDept(@RequestBody Dept dept){
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{deptno}")
public Dept getDept(@PathVariable("deptno") long deptno){
return deptService.queryDeptById(deptno);
}
@GetMapping("/dept/list")
public List<Dept> list(){
return deptService.queryAll();
}
}
Consumer-80
消费端,没有service层,只有Controller,通过远程请求Provider 的地址从而实现对客户的服务
)
依赖: 同样要引入 springCloudapi
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>springCloudapi</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: 80
Config中注册一个工具:RestTemplate
@Configuration
public class MyConfig {
@Bean
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}
Controller实现远程调用:
restTemplate.getForObject/postForObject等方法选择,根据要调用的请求方式来判断,如调用provider的”/dept/add“请求时,provider中是PostMapping处理请求,则这里就用 postForObject 发送请求.
@RestController
public class DeptConsumerController {
//消费者没有service层
@Autowired
//需要手动在config中注册Bean
private RestTemplate restTemplate;//提供多种便捷访问远程http服务的方法,简单的Restful服务模板
private static final String REST_URL_PREFIX="http://localhost:8001";
@GetMapping("/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 addDept(Dept dept){
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
}
@GetMapping("/consumer/dept/list")
public List<Dept> list(){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
}
}
Eureka
自我保护机制
某时刻某一个微服务不可以用了,eureka不会立刻清理,依旧会对该微服务的信息进行保存!
- 默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka之间无法正常通行,以上行为可能变得非常危险了--因为微服务本身其实是健康的,此时本不应该注销这个服务。Eureka通过自我保护机制来解决这个问题--当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该EurekaServer节点会自动退出自我保护模
- 在自我保护模式中,EurekaServer会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该EurekaServer节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话:好死不如赖活着
- 综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮和稳定
- 在SpringCloud中,可以使用
eureka.server.enable-se1f-preservation = false
禁用自我保护模式
【不推荐关闭自我保护机制】
注册中心
由于无法正常启动,将父工程的springboot版本讲了一些:
<!--导入eureka报错 降低了版本:2.1.6.RELEASE、 2.3.7.RELEASE-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
依赖:
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
springBoot配置:
server:
port: 7001
eureka:
instance:
hostname: localhost # Eureka 服务端的实例化名称
client:
register-with-eureka: false #是否向eureka注册中心注册自己
fetch-registry: false # false 表示自己的注册中心
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主启动类开启Eureka: @EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer //EurekaServer 服务端的启动类,可以接受别人注册进来
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
测试:
运行主启动类,访问:http://localhost:7001/
Provider
在之前搭建好的环境上添加:
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
eureka:
instance:
hostname: localhost # Eureka 服务端的实例化名称
instance-id: springCloud-provider-dept-hystrix-8001 #修改eureka上的默认描述信息
prefer-ip-address: true # 显示真实的ip地址 而不是localhost
client:
#register-with-eureka: true #是否向eureka注册中心注册自己
#fetch-registry: true # false 表示自己的注册中心
service-url:
defaultZone: http://${eureka.instance.hostname}:7001/eureka/
在主启动类上启动Eureka注册 : @EnableEurekaClient
//手动注册启动类
@SpringBootApplication
@EnableEurekaClient //在服务启动后自动注册到Eureka中
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
信息配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
info:
app.name: yang-springcloud
company.name: haimeiyou
服务发现
下面以provider为例:
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
开启发现服务: @EnableDiscoveryClient
@EnableDiscoveryClient//服务发现
@SpringBootApplication//手动注册启动类
@EnableEurekaClient //在服务启动后自动注册到Eureka中
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
使用: DiscoveryClient
@RestController
public class DeptController {
//能够获取一些配置信息,得到具体的微服务
@Autowired
private DiscoveryClient client;
@RequestMapping("/dept/discovery")
public Object discovery(){
List<String> services = client.getServices();
System.out.println("discovery==》services"+services);
List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println(instance.getHost()+"\t"
+instance.getPort()+"\t"
+instance.getUri()+"\t"
+instance.getServiceId()
);
}
return this.client;
}
}
集群
)
配置3个注册中心:eureka7001、eureka7002、eureka7003
service-url绑定除自己以外的其他地址
- eureka7001
server:
port: 7001
eureka:
instance:
hostname: Eureka7001 # Eureka 服务端的实例化名称
client:
register-with-eureka: false #是否向eureka注册中心注册自己
fetch-registry: false # false 表示自己的注册中心
service-url:
defaultZone: http://Eureka7002:7002/eureka/,http://Eureka7003:7003/eureka/
- eureka7002
server:
port: 7002
eureka:
instance:
hostname: Eureka7001 # Eureka 服务端的实例化名称
client:
register-with-eureka: false #是否向eureka注册中心注册自己
fetch-registry: false # false 表示自己的注册中心
service-url:
defaultZone: http://Eureka7001:7001/eureka/,http://Eureka7003:7003/eureka/
- eureka7003
server:
port: 7003
eureka:
instance:
hostname: Eureka7001 # Eureka 服务端的实例化名称
client:
register-with-eureka: false #是否向eureka注册中心注册自己
fetch-registry: false # false 表示自己的注册中心
service-url:
defaultZone: http://Eureka7001:7001/eureka/,http://Eureka7002:7002/eureka/
provider注册:对三个都注册
eureka:
instance:
hostname: localhost # Eureka 服务端的实例化名称
instance-id: springCloud-provider-dept8001 #修改eureka上的默认描述信息
client:
service-url:
defaultZone: http://Eureka7001:7001/eureka/,http://Eureka7002:7002/eureka/,http://Eureka7003:7003/eureka/
显示:
Eureka vs zookeeper
CAP原则
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
-
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
-
可用性(A):保证每个请求不管成功或者失败都有响应。
-
分区容忍性(P):系统中任意信息的丢失或失败不会影响系统的继续运作。 [1]
CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP。如果在某个分布式系统中数据无副本, 那么系统必然满足强一致性条件, 因为只有独一数据,不会出现数据不一致的情况,此时C和P两要素具备,但是如果系统发生了网络分区状况或者宕机,必然导致某些数据不可以访问,此时可用性条件就不能被满足,即在此情况下获得了CP系统,但是CAP不可同时满足
zookeeper CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况:当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30~120s,且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络问题使得水集群失去master节点是较大概率会发生的事件,虽然服务最终能够恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka AP?
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保住注册服务的可用性,
只不过查到的信息可能不是最新的,除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其他节点中
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪
ribbon
是什么
-
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
-
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将NetFlix的中间层服务连接在一起。Ribbon的客户端组件提供一系列完整的配置项如:连接超时、重试等等。简单的说,就是在配置文件中列出LoadBalancer(简称LB:负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法!
-
负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。
-
负载均衡分类
-
集中式:
即在服务的消费方和提供方之间使用独立的LB设施,如Nginx:反向代理服务器!,由该设施负责把访问请求通过某种策略转发至服务的提供方!
-
进程式
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址!
-
配置consumer
(受限于电脑配置从集群开始没有实操,下面只是理论)
依赖:
下面的ribbon二选一,哪个能用选哪个,spring-cloud-starter-netflix-ribbon 适合新版本
(**新版的erueka是包含ribbon,不需要加依赖,不然会启动不了 ????? **)
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
配置并启动eureka
server:
port: 80
eureka:
client:
register-with-eureka: false # 不向eureka注册自己
service-url:
defaultZone: http://localhost:7001/eureka/,...,... # 这里写集群的三个地址
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class,args);
}
}
开启负载均衡 @LoadBalanced
@Configuration
public class MyConfig {
@Bean
@LoadBalanced //配置负载均衡实现RestTemplate
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}
在controller中使用:
(服务名不支持下划线 ???)
//Ribbon的地址应该是变量,通过服务名来访问
//private static final String REST_URL_PREFIX="http://localhost:8001";
private static final String REST_URL_PREFIX="http://SPRINGCLOUDE-PROVIDER-DEPT";
//后面跟的是provider的spring配置文件中配置的spring-app的名字
配置多个provider
创建3个数据库表,3个provider项目,端口号分别为8001,8002,8003,保证数据库内容一致(数据库字段除外) 3个项目spring-app-name (服务名)完全一样,都链接进入集群,启动后可以在eureka中看见一个服务有三个服务实例:
测试
使用consumer连接数据库查询,在负载均衡作用下,会依次进入三个不同的服务实例,从三个不同的数据库中查询
负载均衡策略
接口:IRule:
-
RoundRobinRule 轮询 默认
-
AvailabilityFilteringRule
会先过滤掉跳闸、出现访问故障的服务,对剩下的服务进行轮询
-
RandomRule 随机
配置其他策略:
@Configuration
public class MyRule {
@Bean
public IRule iRule(){
return new RandomRule();
}
}
主启动类上配置:
@RibbonClient name=服务名 configuration=配置策略类
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "SPRINGCLOUDE-PROVIDER-DEPT",configuration = MyRule.class)
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class,args);
}
}
自定义策略
- 也在myrule包里,不和主启动类同级
- 继承 AbstractLoadBalancerRule 类
- 如下实现 每个服务5次后轮询
package com.myrule;
public class MyRoundRule extends AbstractLoadBalancerRule {
private int currentIndex=0;
private int count=0;
public MyRoundRule() {
}
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();//获取 ‘活着的’ 服务
List<Server> allList = lb.getAllServers();//获取所有服务
int serverCount = upList.size();
if (serverCount == 0) {
return null;
}
//=======================================
if (count<5){
server=upList.get(currentIndex);
count++;
}else {
count=0;
currentIndex++;
if (currentIndex>serverCount-1){
currentIndex=0;
}
server=upList.get(currentIndex);
}
//======================================
int index = this.chooseRandomInt(serverCount);//获取一个区间的随机数
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
Feign
feign不是做负载均衡的,feign只是集成了ribbon,负载均衡还是feign内置的ribbon做。feign的作用的替代RestTemplate,性能比较低,但是可以使代码可读性很强。
依赖:
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--新版用下面的-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
配置服务service:
@Component
@FeignClient(value = "SPRINGCLOUDE-PROVIDER-DEPT") //服务名
public interface DeptClientService {
@GetMapping("/dept/get/{id}")
Dept queryById(@PathVariable("id")long id);
Dept queryAll();
boolean addDept(Dept dept);
}
在feign-consumer中使用:
@Autowired
private DeptClientService service;
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept){
return this.service.addDept(dept);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id")long id){
return this.service.queryById(id);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list(){
return this.service.queryAll();
}
放弃了下面的服务访问方式,转而面向接口编程:
@Autowired
private RestTemplate restTemplate;
private static final String REST_URL_PREFIX="http://SPRINGCLOUDE-PROVIDER-DEPT";
consumer主启动类下开启feign
@SpringBootApplication
@EnableEurekaClient
//@RibbonClient(name = "SPRINGCLOUDE-PROVIDER-DEPT",configuration = MyRule.class)
@EnableFeignClients(basePackages = {"com.yang"})
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class,args);
}
}
服务熔断
介绍
什么是Hystrix
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个服务预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
服务熔断
熔断机制是对应雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand。
使用服务熔断
新建项目,复制provider-8001
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
载入备选方法:@HystrixCommand(fallbackMethod = "hystrixGetDept")//使用熔断机制
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/dept/get/{deptno}")
@HystrixCommand(fallbackMethod = "hystrixGetDept")//使用熔断机制
public Dept getDept(@PathVariable("deptno") long deptno){
Dept dept = deptService.queryDeptById(deptno);
if (dept==null){
throw new RuntimeException("id->"+deptno+"没有对应信息 @Exception");
}
return dept;
}
//熔断的备选方案
public Dept hystrixGetDept(@PathVariable("deptno") long deptno){
return new Dept()
.setDeptno(deptno)
.setDname("id->"+deptno+"没有对应信息 @Hystrix")
.setDb_source("no this database");
}
}
开启支持: @EnableCircuitBreaker
@EnableDiscoveryClient//服务发现
@SpringBootApplication//手动注册启动类
@EnableEurekaClient //在服务启动后自动注册到Eureka中
@EnableCircuitBreaker //添加熔断支持、断路器
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class,args);
}
}
测试:查询不存的对象,通过熔断机制返回的信息:
使用服务降级
结合feign使用
api 服务接口中调用服务降级: fallbackFactory= xxx.class
@Component
@FeignClient(value = "SPRINGCLOUDE-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class) //服务名
public interface DeptClientService {
@GetMapping("/dept/get/{id}")
Dept queryById(@PathVariable("id")long id);
@GetMapping("/dept/list")
List<Dept> queryAll();
@GetMapping("/dept/add")
boolean addDept(Dept dept);
}
提供降级的服务:
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
@Override
public DeptClientService create(Throwable throwable) {
return new DeptClientService() {
@Override
public Dept queryById(long id) {
return new Dept()
.setDeptno(id)
.setDname("id->"+id+"没有对应信息,这个服务已关闭,请稍后再试")
.setDb_source("no database");
}
@Override
public List<Dept> queryAll() {
return null;
}
@Override
public boolean addDept(Dept dept) {
return false;
}
};
}
}
在使用feign的客户端配置开启服务降级:
feign:
hystrix:
enabled: true
个人理解:
服务降级是客户端consumer的设置,使用后,即使服务端provider 崩溃(或者为了资源合理分布而暂时关闭)还能使用备用的降级服务。
服务熔断是服务端provider 的设置,当服务出现超时、异常时进行捕捉并作出对应处理
Dashboard流量监控
监控工具
新建客户端springcloud-consumer-hystrix-dashboard,端口9001
server:
port: 9001
<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-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>springCloudapi</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>
主启动类上开启流量监控: @EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class ConsumerDashboard_9001 {
public static void main(String[] args) {
SpringApplication.run(ConsumerDashboard_9001.class,args);
}
}
测试:访问Hystrix Dashboard
监控对象
监控对象是provider,
在导入了hystrix和actuator 依赖的provider中的主启动类上添加servlet:
@EnableDiscoveryClient//服务发现
@SpringBootApplication//手动注册启动类
@EnableEurekaClient //在服务启动后自动注册到Eureka中
@EnableCircuitBreaker //添加熔断支持、断路器
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class,args);
}
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
测试:访问:localhost:8001/actuator/hystrix.stream
监控
在监控页面上输入 https://localhost:8001/actuator/hystrix.stream delay=2000ms Title= name
进入下面的页面:
-
实心圆:
共有两种含义,他通过颜色的变化代表了实例的健康程度它的健康程度从绿色<黄色<橙色<红色递减。该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大,该实心圆就越大,所以通过该实心圆的展示,就可以在大量的实例中快速发现故障实例和高压力实例。
-
线:
用来记录2分钟内流量的相对变化,可以通过它观察到流量的上升和下降趋势
-
整图:
Zuul路由网关
概述:
zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
构建
新建项目:springcloud-zuul-9527
依赖:新加 zuul
<dependencies>
<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-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>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>springCloudapi</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:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ # 这里写集群(3个地址)
instance:
instance-id: zuul9527
prefer-ip-address: true
info:
app.name: yang-springcloud
开启网关: @EnableZuulProxy
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication_9527 {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication_9527.class,args);
}
}
测试
运行eureka 7001 、provider 8001 、 zuul 9527
可以看到注册中心有两个服务:
直接访问provider可以拿到数据:
通过网关也能拿到数据:
隐藏路径中的服务名
在springcloud-zuul-9527 中添加:
zuul:
routes:
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
ignored-services: springcloud-provider-dept # 代服务名的路径不再支持访问
#ignored-services: "*" 隐藏所有的服务名路径
测试:
设置前缀
zuul:
prefix: /yang
完整路径: http://localhost:9527/yang/mydept/dept/get/2
Git
下载Git:https://git-scm.com/download/win
上传
git add .
git commit -m "注释语句"
git push
远程访问项目-Server
新建项目: springcloud-config-server-3344
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server -->
<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-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
server:
port: 3344
spring:
application:
name: springcloud-config-server
#连接远程仓库
cloud:
config:
server:
git:
uri: https://gitee.com/yang-KUKU/springcloud-config.git
开启服务: @EnableConfigServer
@SpringBootApplication
@EnableConfigServer
public class Config_Server_3344 {
public static void main(String[] args) {
SpringApplication.run(Config_Server_3344.class,args);
}
}
测试:
Client
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
配置:
- bootstrap.yml
#系统级的配置 application 是用户级别的配置
spring:
cloud:
config:
name: config-client # 需要远程读取的资源名称,不用后缀
uri: http://localhost:3344
profile: dev
label: master
- application.yml
spring:
application:
name: springcloud-config3355
controller:
@RestController
public class ConifgClientController {
@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 getConifg(){
return "applicationName"+applicationName+
"eurekaServer"+eurekaServer+
"port"+port;
}
}
注意:controller的编写只是为了更形象的显示从远程获取到了配置,此配置已经发生作用,不用编写另外的代码
编写主启动类测试: 可见客户端从远程拿到了配置信息
总结:
实现远程统一配置管理,源代码干净简洁、安全
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!