的⽤户量、数据量规模都⽐较⼩,项目所有的功能模块都放在一个工程中编码、 编译、打包并且部署在一个Tomcat容器中的架构模式就是单体应用架构,这样的架构既简单实 ⽤、便于维护,成本⼜低,成为了那个时代的主流架构⽅式。
缺点
业务量上涨之后,单体应用架构进一步丰富变化,比如应用集群部署、使用Nginx进行负载均衡、增加 缓存服务器、增加文件服务器、数据库集群并做读写分离等,通过以上措施增强应对高并发的能力、应 对一定的复杂业务场景,但依然属于单体应用架构
为了避免上⾯提到的那些问题,开始做模块的垂直划分,做垂直划分的原则是基于拉勾现有的业务 特性来做,核心目标标第⼀个是为了业务之间互不影响,第⼆个是在研发团队的壮⼤后为了提⾼效率, 减少组件之间的依赖
缺点
在做了垂直划分以后,模块随之增多,维护的成本在也变⾼,⼀些通⽤的业务和模块重复的越来越 多,为了解决上⾯提到的接⼝协议不统⼀、服务⽆法监控、服务的负载均衡,引⼊了阿⾥巴巴开源的 Dubbo ,⼀款⾼性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成。它提供了三⼤核⼼能 ⼒:⾯向接⼝的远程⽅法调⽤,智能容错和负载均衡,以及服务⾃动注册和发现
SOA (Service-Oriented Architecture),即面向服务的架构。根据实际业务,把系统拆分成合适 的、独立部署的模块,模块之间相互独立(通过Webservice/Dubbo等技术进行通信)
优点
缺点
微服务架构可以说是SOA架构的一种拓展,这种架构模式下它拆分粒度更小、服务更独立。把应用 拆分成为一个个微小的服务,不同的服务可以使用不同的开发语言和存储,服务之间往往通过Restful等 轻量级通信。微服务架构关键在于微小、独立、轻量级通信
微服务是在 SOA 上做的升华粒度更加细致,微服务架构强调的⼀个重点是业务需要彻底的组件化和服务化
微服务架构和SOA架构很明显的一个区别就是服务拆分粒度的不同,但是对于拉勾的架构发展来 说,我们所看到的SOA阶段其实服务拆分粒度相对来说已经比较细了(超前哦!),所以上述拉勾SOA 到拉勾微服务,从服务拆分上来说变化并不大,只是引入了相对完整的新一代Spring Cloud微服务技 术。自然,上述我们看到的都是拉勾架构演变的阶段结果,每一个阶段其实都经历了很多变化,服务拆分其实也是走过了从粗到细,并非绝对的一步到位
API请求调用统一接入API网关层,由网关转发请求,API网关更专注在安全、路由、流量等问题的处理上(微服务团队专注于处理业务逻辑即可)
服务提供者将所提供服务的信息(服务器IP和端口、服务访问协议等)注册/登记到注册中心
服务消费者能够从注册中心获取到较为实时的服务列表,然后根究一定的策略选择一个服务访问
服务间通信采用的方式,例如:http,rpc
熔断即断路保护,微服务架构中,如果下游服务因访问压力过大而响应变慢或失败,上游服务为了 保护系统整体可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断
微服务架构越发流行,一个项目往往拆分成很多个服务,那么一次请求就需要涉及到很多个服务。不同 的微服务可能是由不同的团队开发、可能使用不同的编程语言实现、整个项目也有可能部署在了很多服 务器上(甚至百台、千台)横跨多个不同的数据中心。所谓链路追踪,就是对一次请求涉及的很多个服 务链路进行日志记录、性能监控
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分 布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等, 都可以用 Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将 目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封 装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统 开发工具包
数据库准备
CREATE DATABASE springcloud;
USE springcloud;
CREATE TABLE commodity(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50), #商品名称
price DOUBLE, #商品价格
DESC VARCHAR(100), #商品描述
images VARCHAR(400), #商品图片
stock INT, #商品库存
TYPE VARCHAR(20), #商品类型
deteled TINYINT(1) #逻辑删除
);
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<modules>
<module>productmodule>
<module>consumemodule>
modules>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.3version>
<relativePath/>
parent>
<groupId>cn.winktogroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springcloudname>
<description>Demo project for Spring Clouddescription>
<properties>
<java.version>1.8java.version>
properties>
<packaging>pompackaging>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>8source>
<target>8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<executions>
<execution>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
project>
pom文件
<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>springcloudartifactId>
<groupId>cn.winktogroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>productartifactId>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
dependencies>
project>
application.yaml
mybatis:
type-aliases-package: cn.winkto.bean
mapper-locations: classpath:mapper/*.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: blingbling123.
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
application:
name: eureka-product
server:
port: 9000
实体类
public class Commodity {
private int id;
private String name;
private double price;
private String desc;
private String images;
private int stock;
private String type;
private boolean deteled;
}
mapper
@Repository
public interface CommodityMapper {
public Commodity selectCommodityById(int id);
}
service
public interface CommodityService {
public Commodity selectCommodityById(int id);
}
@Service
public class CommodityServiceImpl implements CommodityService {
@Autowired
CommodityMapper commodityMapper;
@Override
public Commodity selectCommodityById(int id) {
return commodityMapper.selectCommodityById(id);
}
}
controller
@RestController
public class CommodityController {
@Autowired
CommodityService commodityService;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println("生产者被调用");
return commodityService.selectCommodityById(id);
}
}
启动类
@SpringBootApplication
@MapperScan("cn.winkto.mapper")
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
pom文件
<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>springcloudartifactId>
<groupId>cn.winktogroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>consumeartifactId>
project>
application.yaml
server:
port: 9100
spring:
application:
name: consume
bean
public class Commodity {
private int id;
private String name;
private double price;
private String desc;
private String images;
private int stock;
private String type;
private boolean deteled;
}
配置类
@Configuration
public class WinktoConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
controller
@RestController
public class CommodityController {
@Autowired
RestTemplate restTemplate;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println("消费者被调用");
return restTemplate.getForObject("http://localhost:9000/selectCommodityById/1", Commodity.class);
}
}
启动类
@SpringBootApplication
public class ConsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumeApplication.class, args);
}
}
http://localhost:9000/selectCommodityById/1http://localhost:9100/selectCommodityById/1
解决方案
版本号问题
springcloud使用的版本号是英文方式,而不是传统的数字版本,因为springcloud是微服务的解决方案,他会有很多子项目,每个子项目都维护这自己的版本号,为了避免冲突,就使用了伦敦地铁站的名字作为版本号。以首字母作为顺序,a,b,c,d…排列
现有版本号:Angel、Brixton、Camden、Daston、Edgware、Finchley、GreenWich、Hoxton
后缀名
一定要注意版本对应!!!
Eureka 工作模式
Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性
目录结构
![]](https://img-blog.csdnimg.cn/f9f6bc4de18d47ee843e72d457885cda.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2d5ZmdoaA==,size_16,color_FFFFFF,t_70)
父依赖中导入springcloud
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR12version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
模块依赖
<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>springcloudartifactId>
<groupId>cn.winktogroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>eurekaartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
project>
application.yaml
server:
port: 8099
spring:
application:
name: eureka-server
eureka:
client:
service-url:
# 客户端与EurekaServer交互的地址,是一个map
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 是否向eureka注册自己,自己就是服务端,所以不用注册
register-with-eureka: false
# 是否需要从Eureka Server获取服务信息,己就是服务端,所以不用拉取
fetch-registry: false
instance:
hostname: localhost
启动器
@SpringBootApplication
//开启euraka服务
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
最终效果
http://localhost:8099/
server:
port: 8099
spring:
application:
name: eureka-server
eureka:
client:
service-url:
# 客户端与EurekaServer交互的地址,是一个map
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 是否向eureka注册自己,自己就是服务端,所以不用注册
register-with-eureka: true
# 是否需要从Eureka Server获取服务信息,己就是服务端,所以不用拉取
fetch-registry: true
instance:
hostname: localhost
#使用ip注册,否则会使用主机名注册
prefer-ip-address: true
#自定义实例显示格式
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
生产者依赖修改
<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>springcloudartifactId>
<groupId>cn.winktogroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>productartifactId>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
project>
生产者application.yaml
mybatis:
type-aliases-package: cn.winkto.bean
mapper-locations: classpath:mapper/*.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: blingbling123.
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
application:
name: product
server:
port: 9000
eureka:
client:
service-url:
# 客户端与EurekaServer交互的地址,是一个map
defaultZone: http://localhost:8099/eureka/
instance:
#使用ip注册,否则会使用主机名注册
prefer-ip-address: true
#自定义实例显示格式
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
消费者依赖修改
<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>springcloudartifactId>
<groupId>cn.winktogroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>consumeartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
project>
消费者application.yaml
server:
port: 9100
spring:
application:
name: consume
eureka:
client:
service-url:
# 客户端与EurekaServer交互的地址,是一个map
defaultZone: http://localhost:8099/eureka/
instance:
#使用ip注册,否则会使用主机名注册
prefer-ip-address: true
#自定义实例显示格式
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
生产者和消费者启动器都添加/@EnableDiscoveryClient注解
消费者修改启动类
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumeApplication.class, args);
}
}
消费者消费过程修改
@RestController
public class CommodityController {
@Autowired
RestTemplate restTemplate;
@Autowired
DiscoveryClient discoveryClient;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println("消费者被调用");
// return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
// 通过服务id(服务名)获取服务列表
List<ServiceInstance> product = discoveryClient.getInstances("product");
// http://192.168.1.16:9000
System.out.println(product.get(0).getUri());
return restTemplate.getForObject(product.get(0).getUri()+"/selectCommodityById/"+id, Commodity.class);
}
}
添加元数据
mybatis:
type-aliases-package: cn.winkto.bean
mapper-locations: classpath:mapper/*.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: blingbling123.
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
application:
name: pruduct
server:
port: 9000
eureka:
client:
service-url:
# 客户端与EurekaServer交互的地址,是一个map
defaultZone: http://127.0.0.1:8099/eureka/,http://127.0.0.1:8199/eureka/
instance:
#使用ip注册,否则会使用主机名注册
prefer-ip-address: true
#自定义实例显示格式
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
metadata-map:
name: winkto
password: blingbling
消费者获取元数据
@RestController
public class CommodityController {
@Autowired
RestTemplate restTemplate;
@Autowired
DiscoveryClient discoveryClient;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println("消费者被调用");
// return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
// 通过服务id(服务名)获取服务列表
List product = discoveryClient.getInstances("pruduct");
// http://192.168.1.16:9000
System.out.println(product.get(0).getMetadata());
return restTemplate.getForObject(product.get(0).getUri()+"/selectCommodityById/"+id, Commodity.class);
}
}
服务每隔30秒会向注册中心续约(心跳)一次(也称为报活),如果没有续约,租约在90秒后到期, 然后服务会被失效。每隔30秒的续约操作我们称之为心跳检测
当需要修改时,需要修改下配置即可
server:
port: 8099
spring:
application:
name: eureka-server
eureka:
client:
service-url:
# 客户端与EurekaServer交互的地址,是一个map(互相注册)
defaultZone: http://127.0.0.1:8199/eureka/
# 是否向eureka注册自己,自己就是服务端,所以不用注册
register-with-eureka: false
# 是否需要从Eureka Server获取服务信息,己就是服务端,所以不用拉取
fetch-registry: false
instance:
#使用ip注册,否则会使用主机名注册
prefer-ip-address: true
#自定义实例显示格式
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
# 续约间隔时间,默认30秒
lease-renewal-interval-in-seconds: 30
# 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除
lease-expiration-duration-in-seconds: 90
自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的 健壮、稳定的运行
如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么 Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制
Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用
当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中
Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪
负载均衡一般分为服务器端负载均衡和客户端负载均衡
服务器端负载均衡,比如Nginx、F5这些,请求到达服务器之后由这些负载均衡器根据一定的 算法将请求路由到目标服务器处理
客户端负载均衡,比如我们要说的Ribbon,服务消费者客户端会有一个服务器地址列表,调用 方在请求前通过一定的负载均衡算法选择一个服务器进行访问,负载均衡算法的执行是在请求客户端进行
无需额外依赖
复制一份product为product-duplicatet(端口号为9001),启动eureka-server
修改生产者controller(打印一下端口号)
@RestController
public class CommodityController {
@Value("${server.port}")
int port;
@Autowired
CommodityService commodityService;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println(port+"生产者被调用");
return commodityService.selectCommodityById(id);
}
}
修改消费者(添加@LoadBalanced表负载均衡)
@Configuration
public class WinktoConfig {
// 旧版注册
// @Bean
// @LoadBalanced
// public RestTemplate restTemplate(){
// return new RestTemplate();
// }
// 新版注册
@Bean
@LoadBalanced
public RestOperations restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
@RestController
public class CommodityController {
@Autowired
RestOperations restTemplate;
@Autowired
DiscoveryClient discoveryClient;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println("消费者被调用");
// return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
// 通过服务id(服务名)获取服务列表
// List product = discoveryClient.getInstances("product");
// http://192.168.1.16:9000
// System.out.println(product.get(0).getMetadata());
// 无需ip与端口号,直接填写服务名称即可
return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
}
}
http://localhost:9100/selectCommodityById/1
Ribbon内置了多种负载均衡策略,负载均衡的顶级接口为 com.netflix.loadbalancer.IRule,其结构图为
负载均衡策略 | 描述 |
---|---|
RoundRobinRule | 轮询策略,默认超过10次获取到的server都不可用,会返回一个空的server |
RandomRule | 随机策略,如果随机到的server为null或者不可用的话,会while不停的循环选取 |
RetryRule | 重试策略 一定时限内循环重试。默认继承RoundRobinRule,也支持自定 注入,RetryRule会在每次选取之后,对选举的server进行判断,是 否为null,是否alive,并且在500ms内会不停的选取判断。而 RoundRobinRule失效的策略是超过10次,RandomRule是没有失效时间的概念,只要serverList没都挂 |
BestAvailableRule | 最小 连接数策略 遍历serverList,选取出可用的且连接数最小的一个server。该算法里面有一个LoadBalancerStats的成员变量,会存储所有server的 运行状况和连接数。如果选取到的server为null,那么会调用 RoundRobinRule重新选取 |
AvailabilityFilteringRule | 可用过滤策略 扩展了轮询策略,会先通过默认的轮询选取一个server,再去判断该server是否超时可用,当前连接数是否超限,都成功再返回 |
ZoneAvoidanceRule | 区域权衡策略(默认策略) 扩展了轮询策略,继承了2个过滤器:ZoneAvoidancePredicate和 AvailabilityPredicate,除了过滤超时和链接数过多的server,还会过滤掉不符合要求的zone区域里面的所有节点, 在一个区域/机房 内的服务实例中轮询。先过滤再轮询 |
轮询策略的修改
# 服务名称
product:
ribbon:
# 负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
服务雪崩效应:是一种因“服务提供者的不可用”(原因)导致“服务调用者不可用”(结果),并将不可用逐渐放大的现象
服务雪崩原因
雪崩的解决方案
生产者改造(添加延迟)
@RestController
public class CommodityController {
@Value("${server.port}")
int port;
@Autowired
CommodityService commodityService;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println(port+"生产者被调用");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return commodityService.selectCommodityById(id);
}
}
消费者改造(对controller中熔断策略进行配置,在启动类上开启熔断)
@RestController
public class CommodityController {
@Autowired
RestOperations restTemplate;
@Autowired
DiscoveryClient discoveryClient;
// @RequestMapping("/selectCommodityById/{id}")
// public Commodity selectCommodityById(@PathVariable int id){
// System.out.println("消费者被调用");
// // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
//
// // 通过服务id(服务名)获取服务列表
// // List product = discoveryClient.getInstances("product");
// // http://192.168.1.16:9000
// // System.out.println(product.get(0).getMetadata());
// // 无需ip与端口号,直接填写服务名称即可
// return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
// }
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "selectCommodityById",
// 线程池细节属性配置
threadPoolProperties = {
// 线程数
@HystrixProperty(name="coreSize",value = "1"),
// 等待队列长度
@HystrixProperty(name="maxQueueSize",value="20")
},
commandProperties = {
// 每一个属性都是一个HystrixProperty
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000")
}
)
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println("消费者被调用");
return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
}
}
@SpringBootApplication
@EnableDiscoveryClient
//开启熔断服务
@EnableCircuitBreaker
public class ConsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumeApplication.class, args);
}
}
修改一下消费者controller(添加失败回调)
@RestController
public class CommodityController {
@Autowired
RestOperations restTemplate;
@Autowired
DiscoveryClient discoveryClient;
// @RequestMapping("/selectCommodityById/{id}")
// public Commodity selectCommodityById(@PathVariable int id){
// System.out.println("消费者被调用");
// // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
//
// // 通过服务id(服务名)获取服务列表
// // List product = discoveryClient.getInstances("product");
// // http://192.168.1.16:9000
// // System.out.println(product.get(0).getMetadata());
// // 无需ip与端口号,直接填写服务名称即可
// return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
// }
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "selectCommodityById",
// 线程池细节属性配置
threadPoolProperties = {
// 线程数
@HystrixProperty(name="coreSize",value = "1"),
// 等待队列长度
@HystrixProperty(name="maxQueueSize",value="20")
},
commandProperties = {
// 每一个属性都是一个HystrixProperty
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000")
},
fallbackMethod = "selectCommodityByIdFallBack"
)
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println("消费者被调用");
return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
}
public Commodity selectCommodityByIdFallBack(int id){
return new Commodity();
}
}
其实就是线程池隔离策略
如果不进行任何设置,所有熔断方法使用一个Hystrix线程池(10个线程),那么这样的话会导致问题, 这个问题并不是扇出链路微服务不可用导致的,而是我们的线程机制导致的,如果方法A的请求把10个 线程都用了,方法2请求处理的时候压根都没法去访问B,因为没有线程可用,并不是B服务不可用
threadPoolKey参数即表明开启舱壁模式
修改HystrixCommand的commandProperties属性
@RestController
public class CommodityController {
@Autowired
RestOperations restTemplate;
@Autowired
DiscoveryClient discoveryClient;
// @RequestMapping("/selectCommodityById/{id}")
// public Commodity selectCommodityById(@PathVariable int id){
// System.out.println("消费者被调用");
// // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
//
// // 通过服务id(服务名)获取服务列表
// // List product = discoveryClient.getInstances("product");
// // http://192.168.1.16:9000
// // System.out.println(product.get(0).getMetadata());
// // 无需ip与端口号,直接填写服务名称即可
// return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
// }
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "selectCommodityById",
// 线程池细节属性配置
threadPoolProperties = {
// 线程数
@HystrixProperty(name="coreSize",value = "1"),
// 等待队列长度
@HystrixProperty(name="maxQueueSize",value="20")
},
commandProperties = {
// 每一个属性都是一个HystrixProperty
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),
// 统计窗口时间的设置
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
// 统计窗口内的最小请求数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
// 统计窗口内错误请求阈值的设置 50%
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
// 自我修复的活动窗口时间
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
},
fallbackMethod = "selectCommodityByIdFallBack"
)
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println("消费者被调用");
return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
}
public Commodity selectCommodityByIdFallBack(int id){
return new Commodity();
}
}
application.yaml开放健康检查接口
# springboot中暴露健康检查等断点接口
management:
endpoints:
web:
exposure:
include: "*"
# 暴露健康接口的细节
endpoint:
health:
show-details: always
HystrixCommand的commandProperties属性也可以使用yaml方式书写
# 配置熔断策略:
hystrix:
command:
default:
circuitBreaker:
# 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。默认false关闭的
forceOpen: false
# 触发熔断错误比例阈值,默认值50%
errorThresholdPercentage: 50
# 熔断后休眠时长,默认值5秒
sleepWindowInMilliseconds: 3000
# 熔断触发最小请求次数,默认值是20
requestVolumeThreshold: 2
execution:
isolation:
thread:
# 熔断超时设置,默认为1秒
timeoutInMilliseconds: 2000
当我们来调整maxQueueSize属性,也需要调整queueSizeRejectionThreshold(队列最大阈值,超出的请求将会被拒绝)两个属性必须同时配置
Feign是Netflix开发的一个轻量级RESTful的HTTP服务客户端(用它来发起请求,远程调用的), 是以Java接口注解的方式调用Http请求
SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解(OpenFeign)
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
dependencies>
启动类修改(启用feign功能)
@SpringBootApplication
@EnableDiscoveryClient
//开启熔断服务
// @EnableCircuitBreaker
//开启Feign远程调用(自带熔断)
@EnableFeignClients
public class ConsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumeApplication.class, args);
}
}
FeignClient接口(尽量与controller保持一致)
@FeignClient(name = "product")
public interface CommondityFeign {
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id);
}
修改controller
@RestController
public class CommodityController {
// @Autowired
// RestOperations restTemplate;
// @Autowired
// DiscoveryClient discoveryClient;
@Autowired
private CommondityFeign commondityFeign;
// @RequestMapping("/selectCommodityById/{id}")
// public Commodity selectCommodityById(@PathVariable int id){
// System.out.println("消费者被调用");
// // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
//
// // 通过服务id(服务名)获取服务列表
// // List product = discoveryClient.getInstances("product");
// // http://192.168.1.16:9000
// // System.out.println(product.get(0).getMetadata());
// // 无需ip与端口号,直接填写服务名称即可
// return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
// }
// @HystrixCommand(
// // 线程池标识,要保持唯一,不唯一的话就共用了
// threadPoolKey = "selectCommodityById",
// // 线程池细节属性配置
// threadPoolProperties = {
// // 线程数
// @HystrixProperty(name="coreSize",value = "1"),
// // 等待队列长度
// @HystrixProperty(name="maxQueueSize",value="20")
// },
// commandProperties = {
// // 每一个属性都是一个HystrixProperty
// @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),
//
// // 统计窗口时间的设置
// @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
// // 统计窗口内的最小请求数
// @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
// // 统计窗口内错误请求阈值的设置 50%
// @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
// // 自我修复的活动窗口时间
// @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
// },
// fallbackMethod = "selectCommodityByIdFallBack"
// )
// @RequestMapping("/selectCommodityById/{id}")
// public Commodity selectCommodityById(@PathVariable int id){
// System.out.println("消费者被调用");
// return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
// }
// public Commodity selectCommodityByIdFallBack(int id){
// return new Commodity();
// }
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "selectCommodityById",
// 线程池细节属性配置
threadPoolProperties = {
// 线程数
@HystrixProperty(name="coreSize",value = "1"),
// 等待队列长度
@HystrixProperty(name="maxQueueSize",value="20")
},
commandProperties = {
// 每一个属性都是一个HystrixProperty
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),
// 统计窗口时间的设置
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
// 统计窗口内的最小请求数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
// 统计窗口内错误请求阈值的设置 50%
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
// 自我修复的活动窗口时间
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
},
fallbackMethod = "selectCommodityByIdFallBack"
)
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
return commondityFeign.selectCommodityById(id);
}
public Commodity selectCommodityByIdFallBack(int id){
return new Commodity();
}
}
application.yaml添加ribbon.eureka.enabled=true(要不然集群的时候会爆服务找不到)
ribbon:
eureka:
enabled: true
Feign 本身已经集成了Ribbon依赖和自动配置,因此我们不需要额外引入依赖,可以通过 ribbon.xx 来进 行全局配置,也可以通过服务名.ribbon.xx 来对指定服务进行细节配置配置
Feign默认的请求处理超时时长1s,有时候我们的业务确实执行的需要一定时间,那么这个时候,我们就 需要调整请求处理超时时长,Feign自己有超时设置,如果配置Ribbon的超时,则会以Ribbon的为准
product:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#对所有操作都进行重试
OkToRetryOnAllOperations: true
#对当前选中实例重试次数,不包括第一次调用
MaxAutoRetries: 0
#切换实例的重试次数
MaxAutoRetriesNextServer: 0
默认Feign关闭熔断器
当有两个超时时间设置(Feign/hystrix),熔断的时候是根据这两个时间的最小值来进行的,即处理时长超过最短的那个超时时间了就熔断进入回退降级逻辑
feign:
hystrix:
enabled: true
此时注意之前的服务降级已经失效,需要重新配置
新建服务降级类实现CommondityFeign,书写默认方法
@Component("commindityFallback")
public class CommindityFallback implements CommondityFeign {
@Override
public Commodity selectCommodityById(int id) {
return new Commodity();
}
}
在CommondityFeign设置回调class
@FeignClient(name = "product",fallback = CommindityFallback.class)
public interface CommondityFeign {
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id);
}
Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数 即可开启请求与响应的压缩功能
feign:
hystrix:
enabled: true
compression:
request:
enabled: true #默认不开启
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型,此处也是默认值
min-request-size: 2048 # 设置触发压缩的大小下限,此处也是默认值
response:
enabled: true #默认不开启
Spring Cloud GateWay不仅提供统一的路由方式(反向代理)并且基于 Filter(定义过滤器对请求过滤,完成一些功能) 链的方式提供了网关基本的功能,例如:鉴权、流量控制、熔断、路径重写、日志监控等
客户端向Spring Cloud GateWay发出请求,然后在GateWay Handler Mapping中找到与请求相匹配 的路由,将其发送到GateWay Web Handler;Handler再通过指定的过滤器链来将请求发送到我们实际 的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前 (pre)或者之后(post)执行业务逻辑。 Filter在“pre”类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post” 类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等。
导入依赖
<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>springcloudartifactId>
<groupId>cn.winktogroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>gatewayartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-commonsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webfluxartifactId>
dependency>
dependencies>
project>
application.yaml
server:
port: 9300
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
# 去除uri第一个参数,如/commondity/selectCommodityById/1 ---> /selectCommodityById/1
filters:
- StripPrefix=1
main:
web-application-type: reactive
eureka:
client:
service-url:
# 客户端与EurekaServer交互的地址,是一个map
defaultZone: http://127.0.0.1:8099/eureka/,http://127.0.0.1:8199/eureka/
instance:
#使用ip注册,否则会使用主机名注册
prefer-ip-address: true
#自定义实例显示格式
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
启动类
@SpringBootApplication
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
时间前
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
时间后
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
时间间
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver]
cookie
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- Cookie=chocolate, ch.p
header
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- Header=X-Request-Id, \d+
host
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- Host=**.somehost.org,**.anotherhost.org
method
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- Method=GET,POST
请求路径正则匹配
请求参数
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- Query=green
请求参数且值符合正则表达式
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- Query=red, gree.
远程地址匹配
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径
uri: http://127.0.0.1:9100
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- RemoteAddr=192.168.1.1/24
spring:
cloud:
gateway:
routes:
- id: commondity
# 匹配的路径(动态路由:lb://+服务名称)
uri: lb://product
# 匹配规则
predicates:
- Path=/commondity/**
filters:
- StripPrefix=1
过滤器生命周期来分类
过滤器范围来分类
书写WinktoFilter实现GlobalFilter(完成过滤器功能),OrderedFilter(过滤器排序)
@Component
public class WinktoFilter implements GlobalFilter, OrderedFilter {
private static List<String> blackList = new ArrayList<>();
static {
blackList.add("127.0.0.1");
blackList.add("0:0:0:0:0:0:0:1");
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String ip= Objects.requireNonNull(request.getRemoteAddress()).getHostString();
for (int i = 0; i < blackList.size(); i++) {
if (ip.equals(blackList.get(i))){
return response.writeWith(Mono.just(response.bufferFactory().wrap("Black household".getBytes())));
}
}
// 放行
return chain.filter(exchange);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
// 返回值表示当前过滤器的顺序(优先级),数值越小,优先级越高
@Override
public int getOrder() {
return 0;
}
}
分布式集群环境中可能有很多个微服务,我们不可能一个一个去修改配置 然后重启生效,在一定场景下我们还需要在运行期间动态调整配置信息,比如:根据各个微服务的负载 情况,动态调整数据源连接池大小,我们希望配置内容发生变化的时候,微服务可以自动更新。
进入https://github.com.ipaddress.com/获取ip
进入https://fastly.net.ipaddress.com/github.global.ssl.fastly.net获取ip
打开C:\Windows\System32\drivers\etc,新建文件github
140.82.114.4 github.com
199.232.5.194 github.global.ssl.fastly.net
打开cmd,输入ipconfig /flushdns,更新DNS缓存
新建config模块
导入依赖
<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>springcloudartifactId>
<groupId>cn.winktogroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>configartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
dependencies>
project>
application.yaml
server:
port: 9400
eureka:
client:
service-url:
# 客户端与EurekaServer交互的地址,是一个map
defaultZone: http://127.0.0.1:8099/eureka/,http://127.0.0.1:8199/eureka/
instance:
#使用ip注册,否则会使用主机名注册
prefer-ip-address: true
#自定义实例显示格式
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
spring:
application:
name: config
cloud:
config:
server:
git:
# 项目地址:https://github.com/winkto/winkto.git
uri: https://github.com/winkto/winkto.git
username: winkto
password: ******
search-paths: winkto
# 分支
label: main
启动类
@SpringBootApplication
@EnableDiscoveryClient
// 开启配置服务器功能
@EnableConfigServer
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
测试
http://localhost:9400/main/application-dev.yaml
消费者导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-clientartifactId>
dependency>
dependencies>
修改application.yaml为bootstrap.yaml,并添加分布式配置中心的地址(bootstrap.yml是系统级别的,优先级比application.yml高,应用启动时会检查这个配置文件, 在这个配置文件中指定配置中心的服务地址,会自动拉取所有应用配置并且启用)
spring:
application:
name: consume
cloud:
#config客户端配置,和ConfigServer通信,并告知ConfigServer希望获取的配置信息在哪个文件中
config:
# ConfigServer配置中心地址
uri: http://localhost:9400
name: application
profile: dev
label: main
contrller
@RestController
public class CommodityController {
@Autowired
private CommondityFeign commondityFeign;
@Value("${person.name}")
private String name;
@Value("${person.age}")
private int age;
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "selectCommodityById",
// 线程池细节属性配置
threadPoolProperties = {
// 线程数
@HystrixProperty(name="coreSize",value = "1"),
// 等待队列长度
@HystrixProperty(name="maxQueueSize",value="20")
},
commandProperties = {
// 每一个属性都是一个HystrixProperty
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),
// 统计窗口时间的设置
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
// 统计窗口内的最小请求数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
// 统计窗口内错误请求阈值的设置 50%
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
// 自我修复的活动窗口时间
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
},
fallbackMethod = "selectCommodityByIdFallBack"
)
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
return commondityFeign.selectCommodityById(id);
}
@RequestMapping("/person")
public String person(){
return name+"=="+age;
}
}
当我们修改GitHub上面的值时,服务端(Config Server)能 实时获取最新的值,但客户端(Config Client)读的是缓存,无法实时获取最新值
发现数据并没有更新
开启手动刷新
消费者bootstrap.yml中添加配置
management:
endpoints:
web:
exposure:
# include: refresh
include: "*"
消费者controller上加上手动刷新的标识
@RestController
@RefreshScope
public class CommodityController {
@Autowired
private CommondityFeign commondityFeign;
@Value("${person.name}")
private String name;
@Value("${person.age}")
private int age;
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "selectCommodityById",
// 线程池细节属性配置
threadPoolProperties = {
// 线程数
@HystrixProperty(name="coreSize",value = "1"),
// 等待队列长度
@HystrixProperty(name="maxQueueSize",value="20")
},
commandProperties = {
// 每一个属性都是一个HystrixProperty
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),
// 统计窗口时间的设置
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
// 统计窗口内的最小请求数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
// 统计窗口内错误请求阈值的设置 50%
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
// 自我修复的活动窗口时间
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
},
fallbackMethod = "selectCommodityByIdFallBack"
)
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
return commondityFeign.selectCommodityById(id);
}
@RequestMapping("/person")
public String person(){
return name+"=="+age;
}
}
由于上github经常卡,程序拉不到最新的文件,于是我换了gitee,和之前的操作一样,当前gitee上的配置文件
刷新config文件(秒到有没有!!!,唯一体验不好的就是广告太多了)
我们手动让它刷新一下(在线发送一个post请求,注意不能是get)
http://localhost:9100/actuator/refresh
在微服务架构中,可以结合消息总线(Bus)实现分布式配置的自动更新(Spring Cloud Config + Spring Cloud Bus)
消息总线Bus,即使用MQ消息代理构建一个共用的Topic,通过这个Topic连接各个 微服务实例,MQ广播的消息会被所有在注册中心的微服务实例监听和消费。
Spring Cloud Bus(基于MQ的,支持RabbitMq/Kafka) 是Spring Cloud中的消息总线方案, Spring Cloud Config + Spring Cloud Bus 结合可以实现配置信息的自动更新
config和consume导入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
config和consume配置rabbitmq信息
spring:
rabbitmq:
host: 106.54.85.216
port: 5672
username: winkto
password: blingbling
config配置暴露bus-refresh端口
management:
endpoints:
web:
exposure:
# include: bus-refresh
include: "*"
访问config,通知消息队列配置文件已经更新
http://127.0.0.1:9400/actuator/bus-refresh
Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服 务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服 务解决方案,通过阿里中间件来迅速搭建分布式应用系统
Nacos就是注册中心+配置中心的组合(Nacos=Eureka + Config + Bus)
Nacos 官方:https://github.com/alibaba/nacos/releases
Nacos 百度云:https://pan.baidu.com/s/1LLC30x5k6lxh865oxkQu1Q提取码:6i9j
安装方式:直接解压即可
启动方式
sh startup.sh -m standalone
cmd startup.cmd -m standalone
管理端
http://106.54.85.216:8848/nacos/index.html#/login
我们将项目回退到4.7的代码
父项目
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<packaging>pompackaging>
<modules>
<module>productmodule>
<module>product-duplicatemodule>
modules>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.12.RELEASEversion>
<relativePath/>
parent>
<groupId>cn.winktogroupId>
<artifactId>springcloudaliartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springcloudaliname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR12version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.4.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>8source>
<target>8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<executions>
<execution>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
project>
生产者
导入依赖
<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>springcloudaliartifactId>
<groupId>cn.winktogroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>productartifactId>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
dependencies>
project>
实体类
public class Commodity {
private int id;
private String name;
private double price;
private String desc;
private String images;
private int stock;
private String type;
private boolean deteled;
}
mapper
@Repository
public interface CommodityMapper {
public Commodity selectCommodityById(int id);
}
mapper映射文件
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.winkto.mapper.CommodityMapper">
<select id="selectCommodityById" parameterType="int" resultType="Commodity">
select * from commodity where id=#{id}
select>
mapper>
service
public interface CommodityService {
public Commodity selectCommodityById(int id);
}
@Service
public class CommodityServiceImpl implements CommodityService {
@Autowired
CommodityMapper commodityMapper;
@Override
public Commodity selectCommodityById(int id) {
return commodityMapper.selectCommodityById(id);
}
}
controller
@RestController
public class CommodityController {
@Value("${server.port}")
int port;
@Autowired
CommodityService commodityService;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
System.out.println(port+"生产者被调用");
return commodityService.selectCommodityById(id);
}
}
启动类
@SpringBootApplication
@MapperScan("cn.winkto.mapper")
public class ProductduplicateApplication {
public static void main(String[] args) {
SpringApplication.run(ProductduplicateApplication.class, args);
}
}
application.yaml
mybatis:
type-aliases-package: cn.winkto.bean
mapper-locations: classpath:mapper/*.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: blingbling123.
url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
application:
name: product
cloud:
nacos:
discovery:
server-addr: 106.54.85.216:8848
server:
port: 9001
将生产者拷贝一份为product-duplicate
消费者
导入依赖
<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>springcloudaliartifactId>
<groupId>cn.winktogroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>consumeartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
dependencies>
project>
public class Commodity {
private int id;
private String name;
private double price;
private String desc;
private String images;
private int stock;
private String type;
private boolean deteled;
}
config
@Configuration
public class WinktoConfig {
// 旧版注册
// @Bean
// @LoadBalanced
// public RestTemplate restTemplate(){
// return new RestTemplate();
// }
// 新版注册
@Bean
@LoadBalanced
public RestOperations restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
feign
@FeignClient(name = "product",fallback = CommindityFallback.class)
public interface CommondityFeign {
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id);
}
@Component("commindityFallback")
public class CommindityFallback implements CommondityFeign {
@Override
public Commodity selectCommodityById(int id) {
return new Commodity();
}
}
controller
@RestController
public class CommodityController {
// @Autowired
// RestOperations restTemplate;
// @Autowired
// DiscoveryClient discoveryClient;
@Autowired
private CommondityFeign commondityFeign;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
return commondityFeign.selectCommodityById(id);
}
}
启动类
@SpringBootApplication
@EnableDiscoveryClient
//开启熔断服务
// @EnableCircuitBreaker
//开启Feign远程调用(自带熔断)
@EnableFeignClients
public class ConsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumeApplication.class, args);
}
}
application.yaml
server:
port: 9100
spring:
application:
name: consume
cloud:
nacos:
discovery:
server-addr: 106.54.85.216:8848
# springboot中暴露健康检查等断点接口
management:
endpoints:
web:
exposure:
# include: refresh
include: "*"
# 暴露健康接口的细节
endpoint:
health:
show-details: always
保护阈值:可以设置为0-1之间的浮点数,它其实是一个比例值(当前服务健康实例数/当前服务总实例数)
nacos是服务注册中心,服务消费者要从nacos获取某一个服务的可用实例信息,对于 服务实例有健康/不健康状态之分,nacos在返回给消费者实例信息的时候,会返回健康实例。这个时候在一些高并发、大流量场景下会存在一定的问题
如果服务A有100个实例,98个实例都不健康了,只有2个实例是健康的,如果nacos只返回这两个 健康实例的信息的话,那么后续消费者的请求将全部被分配到这两个实例,流量洪峰到来,2个健康的实例也扛不住了,整个服务A就扛不住,上游的微服务也会导致崩溃,产生雪崩效应
保护阈值的意义在于 当服务A健康实例数/总实例数 < 保护阈值 的时候,说明健康实例真的不多了,这个时候保护阈值会被触发(状态true)
nacos将会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者,消费者可能访问到不健康的实例,请求失败,但这样也比造成雪崩要好,牺牲了一些请求,保证了整个系统的一个可用。
Nacos客户端引入的时候,会关联引入Ribbon的依赖包,我们使用OpenFiegn的时候也会引入Ribbon的依赖,Ribbon包括Hystrix都按原来方式进行配置即可
消费者application.yaml
server:
port: 9100
spring:
application:
name: consume
cloud:
nacos:
discovery:
server-addr: 106.54.85.216:8848
# springboot中暴露健康检查等断点接口
management:
endpoints:
web:
exposure:
# include: refresh
include: "*"
# 暴露健康接口的细节
endpoint:
health:
show-details: always
# 配置熔断策略:
hystrix:
command:
default:
circuitBreaker:
# 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。默认false关闭的
forceOpen: false
# 触发熔断错误比例阈值,默认值50%
errorThresholdPercentage: 50
# 熔断后休眠时长,默认值5秒
sleepWindowInMilliseconds: 3000
# 熔断触发最小请求次数,默认值是20
requestVolumeThreshold: 2
execution:
isolation:
thread:
# 熔断超时设置,默认为1秒
timeoutInMilliseconds: 2000
product:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#对所有操作都进行重试
OkToRetryOnAllOperations: true
#对当前选中实例重试次数,不包括第一次调用
MaxAutoRetries: 0
#切换实例的重试次数
MaxAutoRetriesNextServer: 0
Namespace:命名空间,对不同的环境进行隔离,比如隔离开发环境、测试环境和生产环境
Group:分组,将若干个服务或者若干个配置集归为一组,通常习惯一个系统归为一个组
Service:某一个服务,比如商品微服务
DataId:配置集或者可以认为是一个配置文件
概念 | 描述 |
---|---|
Namespace | 代表不同的环境,如开发dev、测试test、生产环境prod |
Group | 代表某项目 |
Service | 某个项目中具体xxx服务 |
DataId | 某个项目中具体的xxx配置文件 |
使用Nacos之后,分布式配置就简单很多,Github不需要了(配置信息直接配置在Nacos server中),Bus也不需要了(依然可以完成动态刷新)
dataid完整格式
${prefix}-${spring.profile.active}.${file-extension}
导入依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
application.yaml更名为bootstrap.yaml,并添加nacos地址
spring:
cloud:
nacos:
discovery:
server-addr: 106.54.85.216:8848
config:
server-addr: 106.54.85.216:8848
# 命名空间,不指定默认为public
namespace: 03f3f10e-9154-41df-8bbb-5a79e743bf40
# 分组,不指定默认为DEFAULT_GROUP
group: DEFAULT_GROUP
file-extension: yaml
controller添加接口
@RestController
@RefreshScope
public class CommodityController {
// @Autowired
// RestOperations restTemplate;
// @Autowired
// DiscoveryClient discoveryClient;
@Autowired
private CommondityFeign commondityFeign;
@Value("${message}")
private String message;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id){
return commondityFeign.selectCommodityById(id);
}
@RequestMapping("/hello")
public String hello(){
return message;
}
}
若一个微服务希望从配置中心Nacos server中获取多个dataId的配置信息,则只需扩展多个 dataId即可
修改bootstrap.yaml
spring:
cloud:
nacos:
discovery:
server-addr: 106.54.85.216:8848
config:
server-addr: 106.54.85.216:8848
# 命名空间,不指定默认为public
namespace: 03f3f10e-9154-41df-8bbb-5a79e743bf40
# 分组,不指定默认为DEFAULT_GROUP
group: DEFAULT_GROUP
file-extension: yaml
extension-configs:
- data-id: m1
refresh: true
- data-id: m2
refresh: true
Sentinel是一个面向云原生微服务的流量控制、熔断降级组件
替代Hystrix,针对问题:服务雪崩、服务降级、服务熔断、服务限流
Sentinel-dashboard1.8.2 官方:https://github.com/alibaba/Sentinel/releases
Sentinel-dashboard1.8.2 百度云:https://pan.baidu.com/s/1j6Lht2cZRxrgkQjiHezakA提取码:r9pf
端口:8080
访问地址:http://localhost:8080/
消费者导入依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
bootstrap.yaml修改(配置sentinel dashboard,删除原有hystrix配置,删除 原有OpenFeign的降级配置)
server:
port: 9100
spring:
application:
name: consume
cloud:
nacos:
discovery:
server-addr: 106.54.85.216:8848
config:
server-addr: 106.54.85.216:8848
# 命名空间,不指定默认为public
namespace: 03f3f10e-9154-41df-8bbb-5a79e743bf40
# 分组,不指定默认为DEFAULT_GROUP
group: DEFAULT_GROUP
file-extension: yaml
extension-configs:
- data-id: m1
refresh: true
- data-id: m2
refresh: true
sentinel:
transport:
dashboard: 127.0.0.1:8080
port: 8719
# springboot中暴露健康检查等断点接口
management:
endpoints:
web:
exposure:
include: "*"
# 暴露健康接口的细节
endpoint:
health:
show-details: always
启动静态化微服务,使用 Sentinel 监控静态化微服务,发现控制台没有任何变化,因为懒加载,我们只需要发起一次请求触发即可
修改消费者controller
@RestController
@RefreshScope
public class CommodityController {
// @Autowired
// RestOperations restTemplate;
// @Autowired
// DiscoveryClient discoveryClient;
@Autowired
private CommondityFeign commondityFeign;
@Value("${message}")
private String message;
@Value("${message1}")
private String message1;
@Value("${message2}")
private String message2;
@RequestMapping("/selectCommodityById/{id}")
public Commodity selectCommodityById(@PathVariable int id) throws InterruptedException {
Thread.sleep(10000);
return commondityFeign.selectCommodityById(id);
}
@RequestMapping("/hello")
public String hello(){
return message+"==="+message1+"==="+message2;
}
}
使用并发软件测试即可
Sentinel 降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这 个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后, 在接下来的降级时间窗口之内,对该资源的调用都自动熔断,这里的降级其实是Hystrix中的熔断