SpringCloud
SpringCloud
1.服务注册中心
1.1.服务注册中心
定义:服务注册中心就是在整个微服务架构中单独抽取一个服务,这个服务不完成项目中任何业务功能,仅仅用来在微服务中记录微服务以及对整个系统微服务进行健康状态检查,以及服务元数据信息存储
1.2.服务注册中心组件开发
常用注册中心组件:eureka(netflix),zookeeper(java),consul(go),nacos(阿里巴巴)
1.1.Eureka
(cloud的版本号要和boot的版本号对应)
<!-- 父组件pom.xml配置 -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mk</groupId>
<artifactId>springcloud_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 继承springboot的父项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<!-- 自定义properties属性 -->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring.cloud-version>Hoxton.SR6</spring.cloud-version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 维护springcloud版本依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2.1.eureka
Eureka包含两个组件:Eureka Server和Eureka Client
2.2.开发Eureka Server 服务注册中心
<-- eureka server组件的pom.xml配置 --> <!-- 引入springbootweb 不需要写版本,因为是从父引用的 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 引入eureka server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
"application.yaml" # eureka server端口号 server: port: 8761 # eureka server服务注册中心地址 eureka: client: service-urle: defaultZone: http//localhost:8761/eureka # 关闭eureka client立即注册 fetch-registry: false # 让当前应用仅仅是服务注册中心 register-with-eureka: false spring: application: # 指定服务名称 注意:服务名不能出现下划线 name: EUREKA-SERVER
// 入口类加上注解开启当前应用是一个服务注册中心 @EnableEurekaServer 或 @EnableDiscoveryClient
2.3.开发Eureka Client 服务注册中心
<-- eureka client组件的pom.xml配置 --> <!-- 引入springbootweb 不需要写版本,因为是从父引用的 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 引入eureka client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
"application.yaml" server: port: 8989 spring: application: name: EUREKA-CLIENT eureka: client: service-urle: # 指定服务注册中心地址 defaultZone: http//localhost:8761/eureka
// 让当前微服务作为一个erueka client客户端进行服务注册 @EnableEurekaClient 或 @EnableDiscoveryClient
1.2.Consul
3.1.consul服务注册中心
简介:consul是基于go语言进行开发服务注册中心 轻量级服务注册中心 google公司
作用:管理微服务中所有服务注册 发现 管理服务元数据信息存储(服务名 地址列表)心跳健康
3.2.consul安装
官网下载地址:https://developer.hashicorp.com/consul/downloads 选择AMD64
3.3.启动consul
在consul安装目录中打开cmd
输入:consul agent -dev(学习用这个) / consul agent -server(集群模式下)
3.4.访问consul管理界面
localhost:8500
3.5.管理界面基本介绍
dc1 数据中心名称 datacenter 默认为dc1
指定数据中心名称:consul agent -dev -datacenter=自定义名称
Services 当前consul服务中注册服务列表 默认client和sever同时启动自己注册自己,会出现一个带绿
✔
的consul服务Node consul的集群节点
3.6.配置consul环境变量
右键此电脑->属性->高级系统设置->环境变量->系统变量中的Path->编辑->添加consul所在目录
下次启动不需要在consul安装目录中打开cmd执行命令,直接打开命令面板执行consul agent -dev
4.开发Consul Client
<-- consul client组件的pom.xml配置 --> <!-- 引入springbootweb 不需要写版本,因为是从父引用的 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
"application.yaml" server: port: 8082 spring: application: name: CONSUL-CLIENT # 向consul server服务注册地址 cloud: consul: host: localhost port: 8500 discovery: # 执行注册当前服务的服务名称 service-name: ${spring.application.name} # 关闭健康检查 注意:在生产情况下不推荐关闭健康检查 register-health-check: true
// 通用服务注册客户端注解 代表consul client,zookeeper client,nacos client // 具体是什么,取决于我们pom文件中引入的依赖是什么 @EnableDiscoveryClient
1.3.服务间通信
1.用spring框架中的RestTemplate
RestTemplate restTemplate = new RestTemplate();
// get请求
restTemplate.getForObject(String url, Class<T> responseType)
// post请求
restTemplate.postForObject(String url,Object request,Class<T> responseType)
// put请求
restTemplate.put(String url, Object request)
// delete请求
restTemplate.delete(String url)
2.Ribbon负载均衡组件
1.定义:
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过spring cloud的封装,可以让我们轻松地将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用
2.作用:
负载均衡客户端组件,就是用来实现请求调用时的负载均衡
2.1.负载均衡的使用
<!-- 引入ribbon依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifacId>spring-cloud-starter-netflix-ribbon</artifacId>
</dependency>
// 使用ribbon + restTemplate
@Autowired
private LoadBalancerClient loadBalanced;
public void test(){
RestTemplate restTemplate = new RestTemplate();
// loadBalanced.choose:轮询到CONSUL-ORDER中的某个服务
ServiceInstance serviceInstance = loadBalanced.choose("CONSUL-ORDER");
// 发送get请求
restTemplate.getForObject(serviceInstance.getUri() + "/order",String.class);
}
BeanFactory.java
@Configuration
public class BeanFactory {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
加了@LoadBalanced之后调用方式改为
restTemplate.getForObject("http://CONSUL-ORDER/order", String.class);
2.2.Ribbon组件实现负载均衡原理
1.原理:
根据调用服务id去服务注册中心获取对应服务id的服务列表,并将服务列表拉取本地进行缓存然后在本地通过默认的轮询的负载均衡策略在现有列表中选择一个可用节点提供服务
2.注意:
客户端负载均衡
2.3.Ribbon组件支持哪些负载均衡策略
2.4.修改负载均衡策略
方式一:注解方式
注册一个Bean
@Bean
public IRule myRule(){
//return new RoundRobinRule();
//return new RandomRule();
return new RetryRule();
}
方式二:配置文件
CONSUL-ORDER:
ribbon:
NFLoadBalancerRuleClassName=com:
netflix:
loadbalancer:
RandomRule:
3.OpenFeign(重点)
1.定义:
伪HttpClient客户端对象,默认集成了Ribbon,实现请求负载均衡
2.为什么使用OpenFeign?
a.解决RestTemplate路径写死
b.解决RestTemplate不能自动转换响应结果为对应对象
c.解决RestTemplate必须集成Ribbon实现负载均衡
3.1.OpenFeign的使用
OpenFeign的使用至少需要3
个java文件
1.调用方(class)
2.被调用方(class)
3.被调用方Client.java(interface) 用于通信对接注册中心
<!-- 在服务调用方引入OpenFeign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
// 入口类
@SpringBootApplication
@EnableDiscoveryClient // 开启服务注册
@EnableFeignClients // 开启Feign
/**
* 需要新建一个包一般命名为openfeign
* 在包下创建需要调用的服务的接口
*/
// 例如Category需要调用Product中的接口
// 调用商品服务接口
@FeignClient("CONSUL-PRODUCT") // 用来书写被调用服务服务id
public interface ProductClient {
@GetMapping("product")
String product(); // 对应的就是Product中的接口方法和接口路径
}
3.2.OpenFeign参数传递之零散和对象
a.queryString方式传递参数(?name=zhangsan)
"ProductClient.java interface" // 调用test服务(属于调用方) @GetMapping("test") String test(@RequestParam("name") String name, @RequestParam("age") Integer age); // 定义接收问号参数接口 "Product.java" @PostMapping("test") public String test(@RequestParam("name") String name, @RequestParam("age") Integer age){ log.info("name:{} age:{} " + name, age); return "test ok...当前提供服务端口为:" + port; } // 调用方 "Category.java @Autowired private ProductClient productClient; @GetMapping("category") public String category(){ String test = productClient.test("zs", 18); }
b.路径传递参数(url/zhangsan/18)
"ProductClient.java interface" // 调用test服务 @GetMapping("test/{id}") String test(@PathVariable("id") Integer id); // 定义接收路径参数接口 "Product.java" @PostMapping("test1") public String test1(@PathVariable("id") Integer id){ log.info("id:{} " + id); return "test1 ok...当前提供服务端口为:" + port; } // 调用方 "Category.java" @GetMapping("category") public String category(){ String product = productClient.test1(1001); }
c.对象传递参数(json)
"ProductClient.java interface" // 声明调用商品服务中test2服务 传递一个商品对象 @PostMapping("test2") String test2(@RequestBody Product product); // 定义接收对象类型参数接口 "Product.java" @PostMapping("test2") public String test2(@RequestBody Product product){ log.info("product:{} " + product); return "test2 ok...当前提供服务端口为:" + port; } // 调用方 "Category.java" @GetMapping("category") public String category(){ Date date = new Date(); Product product = new Product(1001, "苹果手机", 6999.00, date); String product = productClient.test2(product); }
d.对象传递参数(form表单)
"ProductClient.java interface" // 声明调用商品服务中test2服务 传递一个商品对象 @PostMapping("test2") String test2(@RequestPart("product") Product product); // 定义接收对象类型参数接口 "Product.java" @PostMapping("test2") public String test2(@RequestPart("product") Product product){ log.info("product:{} " + product); return "test2 ok...当前提供服务端口为:" + port; } // 调用方 "Category.java" @GetMapping("category") public String category(){ Date date = new Date(); Product product = new Product(1001, "苹果手机", 6999.00, date); String product = productClient.test2(product); }
e.数组传递参数
"ProductClient.java interface" // 声明调用商品服务中test2服务 传递一个商品对象 @GetMapping("test3") String test3(@RequestParam("ids") String[] ids); // 定义接收对象类型参数接口 "Product.java" @GetMapping("test3") public String test3(String[] ids){ for (String id : ids){ log.info("id: {}", id); } return "test3 ok...当前提供服务端口为:" + port; } // 调用方 "Category.java" @GetMapping("category") public String category(){ String s = productClient.test3(new String[]{"1", "2", "3"}); }
f.集合传递参数
注意:springmvc不能直接接收集合类型参数,如果想要接收集合类型参数必须将集合放入对象当中
所以我们可以先定义一个Vo来存放List集合
"ProductVo.java" @Data @AllArgsConstructor @NoArgsConstructor public class ProductVo{ List<String> ids; }
"ProductClient.java interface" // 声明调用商品服务中test2服务 传递一个商品对象 @GetMapping("test4") String test4(@RequestParam("ids") String[] ids); // 定义接收对象类型参数接口 "Product.java" @GetMapping("test4") public String test4(ProductVo productVo){ for (String id : productVo.getIds()){ log.info("id: {}", id); } return "test4 ok...当前提供服务端口为:" + port; } // 调用方 "Category.java" @GetMapping("category") public String category(){ String s = productClient.test4(new String[]{"1", "2", "3"}); }
3.3.OpenFeign响应处理
"ProductClient.java interface"
// 声明调用商品服务根据类别id和页数查询一组商品信息
@GetMapping("test7")
Map<String, Object> test7(@RequestParam("page") Integer page,
@RequestParam("rows") Integer rows,
@RequestParam("categoryId") Integer categoryId);
// 声明调用商品服务根据类别id查询一组商品信息
@GetMapping("test6")
List<Product> test6(@RequestParam("categoryId") Integer categoryId);
// 声明调用根据id查询商品信息接口
@GetMapping("/test5/{id}")
Product test5(@PathVariable("id") Integer id);
// =========================定义接收对象类型参数接口============================
"Product.java"
// 定义一个接口根据类别id查询所有商品信息(分页)
@GetMapping("test7")
public Map<String, Object> findCategoryByIdAndPage(Integer page, Integer rows, Integer categoryId){
log.info("当前页:{} 每页显示的记录数:{} 当前类别id:{}");
// 根据类别id分页查询符合当前页的集合数据
// 根据类别id查询当前类别总条数
List<Product> list = new ArrayList<>();
list.add(new Product(1, "苹果", 9999.99, new Date()));
list.add(new Product(2, "华为", 6999.99, new Date()));
list.add(new Product(3, "小米", 3999.99, new Date()));
Map<String, Object> map = new HashMap();
int total = 1000;
map.put("rows", list);
map.put("total", total);
return map;
}
// 定义一个接口根据类别id查询所有商品信息
@GetMapping("test6")
public List<Product> findCategoryById(@RequestParam("categoryId") Integer categoryId){
log.info("类别id:{}", categoryId);
List<Product> list = new ArrayList<>();
list.add(new Product(1, "苹果", 9999.99, new Date()));
list.add(new Product(2, "华为", 6999.99, new Date()));
list.add(new Product(3, "小米", 3999.99, new Date()));
return list;
}
// 定义一个接口接收id类型参数,返回一个基于id查询的对象
@GetMapping("/test5/{id}")
public Product test5(@PathVariable("id") Integer id){
log.info("id: {}", id);
return new Product(id, "答辩", 10.00, new Date());
}
// ==================================调用方==================================
"Category.java"
@GetMapping("category")
public String category(){
Product product = productClient.test5(1121);
List<Product> products = productClient.test6(8848);
// Map可以写成String处理,因为map中取出的rows没办法直接强转
// map.get("rows") 这样的写法会有问题
Map<String, Object> result = productClient.test7(1, 10, 1);
// 可以通过把类型改成String后用FastJson进行反序列化处理
// 自定义json反序列化 对象转为json序列化 json字符串转为对象
JSONObject jsonObject = JSONObject.parseObject(result);
Object rows = jsonObject.get("rows");
Object total = jsonObject.get("total");
// 二次json反序列化
List<Product> pro = jsonObject.parseArray(rows.toString(), Product.class);
pro.forEach(pro -> log.info("product:{}", pro));
}
3.4.OpenFeign细节
a.OpenFeign默认超时处理
定义:使用OpenFeign组件在进行服务间通信时要求被调用服务必须在1s内给予响应,一旦服务执行业务逻辑时间超过1s,OpenFeign组件将直接报错(Read timed out executing get url)
yaml
修改OpenFeign默认超时时间(以下所有配置均写在调用端)
# 配置类别调用 商品服务 OpenFeign默认超时时间,默认1s feign: client: config: CONSUL-PRODUCT: # 连接超时时间,默认2s connectionTimeout: 5000 # 请求处理超时时间,默认5s readTimeout: 5000
# 配置类别调用 全局服务 OpenFeign默认超时时间,默认1s feign: client: config: default-config: # 连接超时时间,默认2s connectTimeout: 5000 # 请求处理超时时间,默认5s readTimeout: 5000
b.OpenFeign日志展示
定义:OpenFeign为了更好方便在开发过程中调试openFeign数据传递,和响应处理,openFeign在设计时添加了日志功能,默认openFeign白志功能需要手动开启的
# 展示OpenFeign日志 logging: level: com: mk: feignclient: debug
feign为每一个客户端提供一个日志对象
NONE:不记录任何日志
BASIC:仅仅记录请求方法,url,响应状态代码及执行时间
HEADERS:记录Basic级别的基础上,记录诸求和响应的header
FULL:记录请求和响应的header,body和元数据
@Configuration public class OpenfeignConfig { @Bean Logger.Level feignLoggerLevel(){ return Logger.Level.FULL;//FULL是详细日志 } }
4.服务雪崩
定义:
在某个时刻
微服务系统中所有微服务均不可用
的这种现象,称为服务雪崩
引发:
在微服务之间进行服务调用是由于某一个服务故障,导致级联服务故障的现象,称为雪崩效应。雪崩效应描述的是提供方不可用,导致消费方不可用并将不可用逐渐放大的过程
根本原因:
在调用链路中链路中某一服务因为执行业务时间过长,或者是大规模出现异常导致自身服务不可用,并把这种不可用放大的情况
4.1.如何解决微服务系统服务雪崩
a.服务熔断(Hystrix)
----------------------------> 官方不再维护,现已经不推荐使用作用:防止服务雪崩现象
熔断机制:所有微服务中必须引入Hystrix组件
断路器打开关闭条件
1、当满足一定的阀值的时候(默认10秒内超过20个请求次数) 2、当失败率达到一定的时候(默认10秒内超过50%的请求失败) 3、到达以上阀值,断路器将会开启 4、当开启的时候,所有请求都不会进行转发 5、一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5
b.服务降级
定义:服务压力剧增的时候根据当前的业务情况及流量对一些服务和页面有策略的降级,以此缓解服务器的压力,以保证核心任务的进行。同时保证部分甚至大部分任务客户能得到正确的响应。也就是当前的请求处理不了了或者出错了,给—个默认的返回
通俗定义:当网站服务流量突然增加时,为了保证系统核心服务正常运行,有策略关闭系统中边缘服务,以保证核心服务正常运行(例如说暂时关掉用户修改信息功能,查看优惠券功能....以节省资源为某个流量巨大的服务提供资源)
c.服务熔断和服务降级的异同点
共同点
1.目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
2.最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
3.粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改)﹔
4.自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;sentinel异同点
1.触发原因不太一样,服务熔断一般是某个服务〈下游服务)故障引起,而服务降级一般是从整体负荷考虑
2.管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务边缘服务开始)总结
熔断必会触发降级,所以熔断也是降级一种,区别在于熔断是对调用链路的保护,而降级是对系统过载的一种保护处理
4.2.Hystrix使用
a.为每一个调用接口提供自定义备选方案
<!-- 引入histrix依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
// 入口类
@EnableDiscoveryClient // 代表服务注册中心客户端
@EnableHystrix // 开启断路器(推荐)
@EnableCircuitBreaker // 也可以使用这个(过期)
// 假设该服务是商品服务(商品服务由类别服务进行调用,即该服务为服务方)
// 下面demo为商品服务在大量请求涌入之后(并不是真正宕机,可以理解为暂时宕机)的失败快速处理
@GetMapping("demo")
// 熔断之后处理,fallbackMethod,书写失败之后快速执行的备选方法
@HystrixCommand(fallbackMethod = "demoFallBack")
public String demo(Integer id){
log.info("demo ok!!!");
// 这边只要失败就会进入熔断备选方法中
if (id < 0) {
throw new RuntimeException("id有误");
}
return "demo ok!!";
}
// 备选方法
public String demoFallBack(Integer id){
return "当前活动过于火爆,请稍候再试";
}
"HystrixClient"(interface)
// 假设该服务是类别服务(商品服务由类别服务进行调用,即该服务为调用方)
// 下面demo为商品服务在真正宕机时的失败快速处理
// fallback:当调用服务不可用时,默认的备选处理
@FeignClient(value = "HYSTRIX-PRODUCT", fallback = Product.class)
@GetMapping("demo")
String demo(@RequestParam("id") Integer id);
// 定义一个实现类实现HystrixClient
// Product.java
@Configuration
public class Product implements HystrixClient {
@Override
public String demo(Integer id) {
return "当前服务已挂,请稍后再试" + "id:" + id;
}
// ProductController.java
@GetMapping("demo")
@HystrixCommand(fallbackMethod = "demoFallBack") // 熔断之后处理,fallbackMethod,书写失败之后快速执行的备选方法
public String demo(@RequestParam("id") Integer id){
log.info("demo ok!!!");
// 这边只要失败就会进入熔断备案方法中
if (id < 0) {
throw new RuntimeException("id有误"); }
return "demo ok!!";
}
// CategoryController.java
@Autowired
private HystrixClient hystrixClient;
@GetMapping("category")
public String demo(){
String demo = hystrixClient.demo(1121);
b.Histrix默认备选处理
@GetMapping("default")
// 熔断之后处理,defaultFallback快速执行默认的备选方法
@HystrixCommand(defaultFallback = "defaultFallBack")
public String demo2(Integer id){
log.info("demo2 ok!!!");
// 这边只要失败就会进入熔断备案方法中
if (id < 0) {
throw new RuntimeException("id有误");
}
return "demo2 ok!!";
}
// 默认处理方法
public String defaultFallBack(){
return "网络连接失败,请重试!!!";
}
4.3.Hystrix DashBoard(已不推荐使用,了解即可)
1.仪表盘:
用来显示状态信息
2.作用:
监控每一个@HystrixCommond注解创建一组度量,构建一组信息,然后通过图形化方式展示当前方法
@Hystrixconmond的状态信息
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
// 入口类
@EnableHystrixDashboard // 开启当前应用为仪表板应用
启动DashBoard
localhost:当前应用端口号/hystrix
5.GateWay网关介绍
1.定义:
统一服务入口,可方便实现对平台众多服务接口进行管控
2.网关的作用:
a.统一所有微服务入口
b.网关可由实现请求路由转发(router dispatcher),以及请求过程中的负载均衡
c.访问服务的身份认证、防报文重放与防数据篡改、功能调用的业务鉴权、响应数据的脱敏、流量与并发控制,甚至基于API调用的计量或者计费等等
5.1.网关的使用
a.开发独立springboot应用
@SpringBootApplication
@EnableDiscoveryClient
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
b.引入网关依赖
<!-- 网关不需要引入springboot依赖,他用的是webflux -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
c.配置编写(yaml)
server:
port: 7979
spring:
application:
name: GATEWAY
# 向consul server 服务注册地址
cloud:
consul:
host: localhost
port: 8500
gateway:
routes:
# 路由对象唯一标识(自定义)
- id: category_router
# 类别服务地址
uri: http://localhost:9012
# 断言,用来配置路由规则(地址包含category都转向9012服务器)
predicates:
- Path=/category
- id: product_router
uri: http://localhost:9013
# 断言,用来配置路由规则
predicates:
# 可以匹配多个路径
- Path=/product,/test/**
d.配置编写(java)
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
return builder.routes()
.route("category_router", r -> r.path("/category/**")
.uri("http://localhost: 9012"))
.route("product_route", r -> r.path("/product/**")
.uri("http://localhost: 9013"))
.build();
}
}
5.2.网关实现负载均衡
以上配置的问题:
现有网关配置方式在uri的属性中路径直接写死为服务的某一个节点,这样没有办法实现请求的负载均衡
server:
port: 7979
spring:
application:
name: GATEWAY
# 向consul server 服务注册地址
cloud:
consul:
host: localhost
port: 8500
gateway:
routes:
- id: category_router # 路由对象唯一标识
uri: lb://CONSUL-PRODUCT # lb(loadblance):\\ + 服务中心注册名
predicates: # 断言,用来配置路由规则
- Path=/category
filters: # 过滤
- AddRequestHeader=name,zhangyf
5.3.网关细节
5.3.1.断言(10个)
1.定义:
当请求到达网关时,网关前置处理
2.常用断言类型:
下面的圆点均代表横杠-,因为Typora输入横杠接文字会变成圆点
a.路径断言
- Path=/category
b.规定时间后断言 该路由规则必须在规定
时间之后
才能生效
- After=2023-02-09T09:41:42.484+08:00[Asia/Shanghai]
c.规定时间前断言 该路由规则必须在规定
时间之前
才能生效
- Before=2023-02-09T09:41:42.484+08:00[Asia/Shanghai]
d.规定时间段内断言 该路由规则必须在规定
时间段内
才能生效 (eg:2.9-2.10)
Between=2023-02-09T09:41:42.484+08:00[Asia/Shanghai],
2023-02-10T09:41:42.484+08:00[Asia/Shanghai]
e.Cookie断言 该路由规则匹配到指定的key和value才能生效
- Cookie=name,zhangyf
- Cookie=name,[A-Za-z0-9]+
f.Header断言 该路由规则匹配到指定的Header(name,value)才能生效
- Header=X-Request-Id,\d+
d.请求方式断言 该路由规则匹配到指定的请求方式才能生效
- Method=GET
5.3.2.过滤(30个)
定义:当请求满足断言的所有条件之后,会向后端服务转发,在向后端服务转发之前会经过一些过滤
2.常用过滤类型:
a.请求头过滤 用来给路由对象的所有转发请求加入指定请求头信息
- AddRequestHeader=name,zhangyf
public void test(HttpServletRequest request){ request.getHeader(name); }
b.请求参数过滤 用来给路由对象的所有转发请求加入指定请求参数
- AddRequestParameter=color,blue
// 发送参数的key和形参中接收的key必须相同 public void test(String color){ log.info("color:{}", color); }
c.用来路由对象的所有转发请求的响应加入指定头信息
- AddResponseHeader=x-Response-Red,Blue
d.用来给路由对象的所有转发请求的url加入指定前缀信息
- PrefixPath=/mypath
如:浏览器访问网址地址为/list;前缀路径为/mypath;则最终路径为/mypath/list
e.用来给路由对象的所有转发请求的url去掉n个前缀
- StripPrefix=2 // 去掉2个前缀
f.全局filter
@Configuration public class customerGlobalFilter implements GlobalFilter,ordered { //类似javaweb doFilter // exchange :交换request response封装了request responseoverride public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterchain chain){ // httprequest对象 serverHttpRequest request = exchange.getRequest(); // httpresponse对象 serverHttpResponse response = exchange.getResponse(); system.out.println("经过全局Filter处理.......") ; //放行filter继续向后执行 Mono<Void> filter = chain.filter(exchange); system.out.println("响应回来Filter处理......"); return filter; } //order排序 int数字:用来指定filter执行顺序﹒默认顺序按照自然数字进行排序―-1在所有filter执行之前执行override public int getOrder(){ return -1; } }
5.4.通过web界面查看所有规则
a.http://localhost:7979/actuator/gateway/routes
b.查看网关路由规则详细路径必须在网关配置文件中暴露当前路径
management:
endpoints:
web:
exposure:
include: "*"
6.Config统一配置中心
作用:用来实现微服务系统中服务配置统一管理组件netflix config => spring config
组件:统一配置中心服务端(集中管理配置文件)、统一配置中心客户端client
6.1.config的使用
a.创建独立springboot应用
b.引入config server依赖
<!-- 引入config server依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
c.配置application.yaml
server:
port: 8848
spring:
application:
name: CONFIG
# 向consul server 服务注册地址
cloud:
consul:
host: localhost
port: 8500
config:
server:
git:
# 去gitee上读取配置信息(6.3)
uri: https://gitee.com/stephen_curry_zyf/configs.git
# 选择分支
default-label: master
d.开启统一配置中心
// 入口类
@EnableConfigServer
6.2.config client配置客户端组件
a.创建独立springboot应用
b.引入config client依赖
<!-- 引入config client相关依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
c.配置bootstrap.yaml
spring:
cloud:
consul:
host: localhost
port: 8500
config:
discovery:
# 告诉当前configclient去注册中心的configserver拉取配置(默认带轮询)
service-id: CONFIG-SERVER
# 开启当前configclient这是一个服务id(而不是一个具体的地址)要去服务注册中心上找
enabled: true
# 获取哪个配置文件 1.确定分支 2.确定文件名 3.确定环境
label: master
name: configclient
profile: prod
6.3.将配置放在gitee上
configclient.yaml
configclient-prod.yaml
configclient-dev.yaml
6.4.总结
a.先把服务需要的配置写在gitee上
b.config_server去读取gitee上的所有服务配置,作为所有服务的配置中心
c.config_client(所有的服务再从config_server读取配置)
7.Bus组件及自动刷新配置
1.定义:
springcloud bus 利用轻量级消息中间件将分布式系统中所有服务连接到一起
2.作用:
利用bus广播特性当某一个状态(配置文件)发生改变时通知到bus中所有服务节点更新当前状态(更新自身配置)
a.在所有服务中引入bus依赖
<!-- 在所有服务中引入bus依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
b.配置yaml
spring:
rabbitmq:
host: 192.168.79.135
port: 5672
username: ems
password: 123
virtual-host: /ems
c.重启所有微服务时出现,在引入bus依赖之后启动出现如下错误
1.错误原因:
引入bus依赖启动立即根据配置文件bus配置连接mq服务器,但是此时mq配置信息都在远端,因此bus连接不到mq直接报,阻止了应用启动
2.解决方案:
允许项目启动时bus组件立即连接mq这个失败,因为获取远端配置之后可以再以远端配置初始化bus组件
# 代表在启动时还没有拉取远端配置完成是的失败都是允许的
spring:
cloud:
config:
fail-fast: true
3.暴露所有隐藏断点
management:
endpoints:
web:
exposure:
include: "*"
所有配置完成之后用用postman向configserver所在的端口发一个post请求
刷新所有服务:http://localhost:port/actuator/bus-refresh
刷新指定服务:http://localhost:port/actuator/bus-refresh/服务id(CONFIG-CLIENT)
7.1.WebHooks实现配置自动刷新
1.钩子hooks
根据仓库触发事件执行对应操作
2.WebHooks
根据远程仓库触发对应事件发送一个web请求这个请求默认就是PosT方式请求
3.在远端仓库中配置WebHooks
a.添加WebHooks
此处会添加失败,因为http://localhost:port/actuator/bus-refresh是本地端口服务,gitee无法访问我们的本地端口
解决(netapp内网传统)
netapp官方网站:https://natapp.cn
在我的隧道中,可以配置隧道信息,主要是配置server的端口号
下载netapp客户端,在netapp同级目录下配置config.ini
[default]
authtoken=034dbb1270a8b05b
clienttoken=
logto=none
loglevel=DEBUG
http_proxy=
配置完成后,点击运行netapp.exe即可,然后取Forwarding中的地址放入WebHooks中
然后需要在java配置一个UrlFilter,具体代码网上抄
com
mk
↓filters
UrlFilter.java
然后在入口类加上@ServletComponentScan(basePackges = "com.mk.filters")
8.SpringCloud Alibaba介绍
8.1springc loud alibaba特性特点提供组件
a.Flow control and service degr adation
服务流量控制和﹑服务降级(熔断) => sentinel 替换springcloud原有Hystrix组件b.Service registration and discovery
服务注册和发现组件 => nacos 替换consul和eureka组件c.Distributed configuration
统─配置中心组件 => nacos 替换springcloudconfig组件(自动配置刷新)d.Event-driven
事件驱动利用RocketMQ => 事件驱动 替换Bus组件实现消息总线e. Message Bus
消息总线(异步处理)f. Distributed Transaction
分布式事务 => Seatag. Dubbo RPC
集成Dubp实现服务间通信 => DubboRPC 替换原始项目中RestTemplate Openfeign
8.2微服务项目实战开发
架构:springcloud NetFlix + SpringCloud Spring + SpringCloud Alibaba
1.服务注册中心 -> Nacos
2.服务间通信负载均衡 -> HttpRest a.RestTemplale+Ribbon b.Openfeign
3.服务流控和服务降级 -> sentinel
4.服务网关组件 -> Gateway
5.统—配置中心组件 -> Nacos
8.3.SpringCloud Alibaba环境搭建
<!-- 父项目搭建 springcloud_parent -->
<?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>org.example</groupId>
<artifactId>springcloudalibaba_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 继承springboot的父项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<!-- 自定义properties属性 -->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring.cloud-version>Hoxton.SR6</spring.cloud-version>
<spring.cloud.alibaba-version>2.2.1.RELEASE</spring.cloud.alibaba-version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 维护springcloud版本依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 维护springcloud alibaba版本依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
9.Nacos安装和配置
nacos官方网址:https://nacos.io/zh-cn/docs/quick-start.html
nacos下载地址:https://github.com/alibaba/nacos
当前推荐的稳定版本为2.1.1
下载完成之后:
a.把nacos-server-2.1.1.tar.gz拖入linux
b.配置jdk环境变量
将jdk-8u171-linux-x64.rpm拖入linux
rpm -ivh jdk-8u171-linux-x64.rpm // 安装jdk
find / -name java // 找到jdk安装位置
vim /bin/profile // 编辑修改环境变量
source /etc/profile // 重新加载配置
b.解压nacos安装包
tar -zxvf nacos-server-2.1.1.tar.gz
tar -zxvf nacos-server-1.4.1.tar.gz
c.查看nacos目录结构
d.启动nacos服务(默认nacos启动以集群模式启动,必须满足多个节点)
单机启动:在bin目录中 ./startup.sh -m standalone
f.访问nacos网页
http://192.168.220.128:8848/nacos
默认用户名密码为nacos
9.1.nacos client开发
a.创建springboot单体项目
b.引入nacos依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
c.application.yaml配置
server:
port: 8989
spring:
application:
name: NACOS-CLIENT
cloud:
nacos:
server-addr: 192.168.79.135:8848
d.入口类加注解
@EnableDiscoveryClient
9.2.nacos细节和服务间通信
还是利用OpenFeign进行服务间通信...
9.3.nacos统一配置中心
定义:
开发微服务作为统一配置中心客户端将配置交给nacos进行管理
a.开发configclient微服务 创建一个独立的springboot应用
b.在nacos网站的配置管理中新建配置,Data ID需要标明生产环境
c.自身引入nacos config依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
** 直接启动项目出现错误,发现当前项目在启动时,并不会等待拉取配置完成之后在启动,因此直接报错
解决方案:让当前项目在启动时预先拉取配置,在以拉取的配置信息启动
修改项目application.yaml为bootstrap.yaml
d.加入注解运行配置自动刷新
@RefreshScope // 自动刷新配置
9.3.1.统一配置中心细节
dataId细节:
代表完整配置文件名称 -> spring.cloud.nacos.config.name
完整配置文件名称– prefix(前缀) + env(环境) + file-extension(后缀)
1.dataId = application-prod + yaml ==> application-prod.yaml
2.dataId = application + prod + yaml ==> application-prod.yaml
9.3.2.统一配置中心nacos三个重要概念
1.命名空间(namespace)
默认nacos安装完成之后会有一个命名空间,这个命名空间的名字为pubic
2.组(group)
默认nacos中在配置管理文件时不显示指定group名称时默认的组名称为DEFAULT_GROUP
改造后的bootstrap.yaml
spring:
cloud:
nacos:
config:
server-addr: 192.168.79.136:8848
#1.告诉config server的地址
namespace: 1c918e0d-1a76-438d-a9e6-e19fe5c0c620
#2.告诉我从哪个命名空间获取配置
#3.告诉从哪个组进行配置获取
group: CONFIG-CLIENT
#4.从这个组拉取哪个配置文件
name: configclient-dev
#5.拉取这个配置文件哪个后缀
file-extension: yaml
3.文件名(dataId)
获取一个配置文件的唯一标识
9.4.nacos的数据持久化
定义:
管理的配置信息持久化;nacos默认持久化方式为内嵌数据库derby
缺点:
无法友好的展示数据
9.4.1.nacos的mysql数据持久化
要求:
安装mysql版本5.65+
9.5 nacos的集群搭建
给运维干
9.6.nginx实现nacos高可用
给运维干
10.sentinel介绍
作用:
alibaba开源用来对现有微服务系统进行保护 => 替换Hystrix
用于解决服务雪崩 => 服务熔断机制(服务降级),服务流控等
简介:
Sentinel以"流量"为突破口,致力于流量控制、断路、负载保护等多个领域,保障服务可靠性。
概念:
资源(Resource):java中一切皆资源,服务/demo;是一个微服务访问路径rest接口
规则(Rule):流量规则,熔断规则,负载规则...
sentinel的使用:
Sentinel(项目中实现流量控制,熔断,降级) ----> Hystrix
Sentinel DashBoard(哨兵仪表盘组件) ----> Hystrix DashBoard
Sentinel 和 Sentinel DashBoard 相辅相成,缺一不可
10.1.安装sentinel dashboard
下载地址:https://github.com/alibaba/Sentinel/tags
1.要有jdk环境
2.默认端口号为8080,一般需要指定端口启动
3.指定端口启动:
java -jar -Dserver.port=9191 sentinel-dashboard-1.8.6.jar
如果实时监控空白可在cmd中执行此命令
4.访问dashboard管理界面
默认用户名密码:sentinel
10.2.Sentinel的使用
1.引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.配置yaml
server:
port: 8998
spring:
application:
name: SENTINEL-CLIENT
cloud:
nacos:
server-addr: 192.168.79.136:8848
sentinel:
# 开启sentinel保护
enabled: true
transport:
# 指定dashboard地址
dashboard: 192.168.79.136:9191
# 指定sentinel组件与sentinel dashboard组件通信地址
port: 8719
# 实时监控一直空白,可加这个(若无效)或者cmd运行sentinel的jar包
clientIp: 192.168.79.136
查看dashboard
注意:dashboard信息必须在指定服务进行资源调用后才能进行初始化
10.3.系统吞吐量(Qps - Query-Per-Second)
定义:
系统每秒的请求数
10.4.响应时间(Rt - Response-Time)
定义;
每个请求响应时间(单位毫秒)
10.5.Sentinel对系统保护规则(5个)
10.5.1.流控规则(flow control)
定义:
其原理是监控应用流量的Qps或并发线程数指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
高级选项
a.流控模式
1.直接模式
当配置资源在运行过程超过当前规则配置的阈值之后对该资源请求做的处理是什么
2.关联模式(/demo/test 如果demo和test的Qps=10,若请求为20个/s,test接口不会有任何问题,但是demo会直接报错,
相当于反向关联
)
当配置资源在运行过程中超过当前规则配置的阈值之后对他所关联资源进行请求做什么样的处理
3.链路模式(问题)
当配置资源在运行过程超过当前规则配置的阈值之后对它链路中资源请求做什么的处理
b.流控效果
只适用于Qps限流
1.快速失败:直接拒绝请求(100个请求/s,Qps=10,直接报错)
2.Warm Up:冷启动,预热(100个请求/s,Qps=10,第一次处理2个,第二次4个,缓慢增至10个)
3.排队等待:始终匀速通过(100个请求/s,Qps=10,不会报错,而是每次通过10个直至处理完成)
10.5.2.降级规则(degrade service)
定义:
监控应用中资源调用请求,达到指定阏值时自动触发熔断降级
1.RT(Response-Time)
根据请求响应时间熔断
一秒内发送一组请求(5个)若响应时间均超过0.1毫秒,则触发熔断,并且熔断时间为25秒
2.异常比例
根据请求调用过程中出现异常百分比进行熔断
一秒内发送一组请求(5个)若异常比例大于10%,则触发熔断,并且熔断时间为25秒
3.异常比例
根椐请求掉用过程中异常数进行熔断
一秒内发送一组请求(5个)若异常数量大于1个,则触发熔断,并且熔断时间为30秒
官方建议:若异常数较低,则把时间窗口设置的大一些
因为如果异常数为1,时间窗口也为1,其实是在不停的打开熔断,非常消耗资源
10.5.3.热点规则(param flow)
热点:
经常访问的数据
定义:
其原理为我们希望统计某个热点数据中访问频次最高的Top K数据,并对其访问进行限制
使用热点参数限流时,不能使用资源路径,必须使用资源别名
@GetMapping("demo") // 代表这是一个sentinel资源 // blockHandler:自定义限流,热点,降级后的处理 // fallback:自定义发生业务异常时后的处理 @SentinelResource(value = "aaa", blockHandler = "a", fallback = "b") public String demo(Integer id){ log.info("demo ok ...."); if(id < 0) { throw new RuntimeException("id无效"); } return "demo ok !!!"; } // 参数中必须有id,需和资源接口中参数保持一致 public String a(Integer id, BlockException e) { if(e instanceof FlowException){ return "当前请求过于火爆,您已被流控"; } if(e instanceof DegradeException){ return "当前请求过于火爆,您已被降级"; } if(e instanceof ParamFlowException){ return "当前请求过于火爆,您已被参数限流"; } return "服务器快爆炸了,请稍后再试"; } // 参数中必须有id,需和资源接口中参数保持一致 public String b(Integer id) { return "服务器出错啦" + id; }
a.基本选项
资源名:写的就是@SentinelResource中value的值
参数索引:是指要对请求中的第几个参数做限流,0就是代表第一个参数
单击阈值:当这个参数每秒发送的请求大于10时,触发限流
b.例外项
参数类型:要限流参数的类型
参数值:要对某个或某几个参数做特定限流,例如写入12和14,限流阈值分别为1和3
当参数传12的时候,每秒请求大于1时限流
当参数传14的时候,每秒请求大于3时限流
10.5.4.系统规则(system flow)
定义:
系统自适应限流从整体纬度对应用入口流量进行控制
10.5.5.授权规则(黑白名单控制规则(blacklist & whitelist))
定义:
需要根据调用来源来判断该次请求是否允许放行,这时候可以使用sentinel的来源访问控制(黑白名单控制)的功能。
来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
11.总结
1.springcloud & springcloud alibaba
springcloud:微服务工具集
springcloudalibaba:微服务一站式解决方案
2.springcloud中五大组件
a.服务注册中心 eureka,consul
b.服务间通信组件 restemplate + ribbon,openfeign
c.服务熔断 hystrix,hystrix dashboadr
d.服务网关 zuul1.x,zuul2.x,gateway
e.服务配置中心 config + bus 自动配置刷新
3.springcloud alibaba特有组件
a.服务注册中心 nacos
b.服务熔断 sentinel,sentinel dashboard
c.服务配置中心 nacos
4.整合springcloud和springcloud alibaba五大组件
a.服务注册中心 nacos
b.服务间通信组件 openfeign
d.服务熔断 sentinel,sentinel dashboard
d.服务网关 gateway
e.服务配置中心 nacos
最后一点补充:通常在父项目下会创建一个子项目用来放一些公共的实体类和公共的工具类
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构