SpringCloud Alibaba微服务分布式架构组件演变
文章目录
- 1、SpringCloud版本对应
- 1.1 技术选型依据
- 1.2 cloud组件演变:
- 2、Eureka
- 2.1 Eureka Server : 提供服务注册服务
- 2.2 EurekaClient : 通过注册中心进行访问
- 2.3 Eureka自我保护
- 3、Eureka、Zookeeper、Consul三个注册中心的异同点
- 3.1 CP架构:
- 4、Ribbon 负载均衡服务调用
- 4.1 SpringCloud Load Balance
- 4.2 总结:
- 4.3 Ribbon工作流程:
- 4.4 自定义Ribbon 负载均衡算法:
- 4.4.1 iRule接口:
- 4.4.2 Ribbon自带的负载均衡算法:
- 4.4.3 负载均衡算法替代:
- 4.4.3.1、在非启动类包及子包下创建配置类
- 4.4.3.2、定义
- 4.4.3.3、启动类增加RibbonClient注解
- 4.5 Ribbon负载均衡算法
- 4.5.1 轮询算法原理:
- 4.5.2 轮询算法源码:
- 4.5.3 手写负载均衡算法
- 5、OpenFeign 服务接口调用
- 5.1 概述
- 5.2 Feign能干什么
- 5.3 Feign集成了Ribbon
- 5.4 应用
- 5.4.1 引入依赖
- 5.4.2 开启功能
- 5.4.3 service中远程调用
- 5.5 超时控制
- 在yml中开启OpenFeign客户端超时控制
- 5.6 日志打印
- 5.6.1 日志级别
- 5.6.2 配置类
- 5.6.3 yml指定日志以什么级别监控哪个接口
- 6.Hystrix 断路器
- 6.1 问题:服务雪崩
- 6.2 概念
- 6.3 服务降级
- 6.3.1 概念:
- 6.3.2 触发服务降级的情况:
- 6.3.3 应用
- 6.3.3.1 依赖
- 6.3.3.2 解决的问题
- 6.3.3.3 生产者:
- 6.3.3.4 消费者:
- 6.3.3.5 配置全局fallback方法
- 6.3.3.6 解耦合
- 6.4 服务熔断
- 6.4.1 概念:
- 6.4.2 注解
- 6.4.3 应用
- 6.4.4 原来的主逻辑要如何恢复呢?
- 6.5 服务限流
- 6.5.1 概念:
- 6.6 服务监控 hystrixDashboard
- 6.6.1 依赖
- 6.6.2 主启动类添加注解@EnableHystrixDashboard
- 6.6.3 访问图形化界面
- 6.6.4 调整需要监控的服务主启动类
- 6.6.5 输入监控的url
- 7、Gateway 网关
- 7.1 特性
- 7.2 Gateway与Zuul的区别
- 7.3 Gateway模型
- 7.4 三大基本概念
- 7.4.1 Route 路由
- 7.4.2 Predicate 断言
- 7.4.3 Filter 过滤
- 7.5 应用
- 7.5.1 依赖
- 7.5.2 yml配置文件
- 7.5.3 配置类方式配置网关路由
- 7.6 动态路由
- 7.7 Predicate 断言
- 7.8 Filter 过滤器
- 7.8.1 自定义过滤器
- 8、Config 服务配置
- 8.1 作用:
- 9、SpringCloud Alibaba
- 10、Nacos 服务注册和配置中心
- 10.1 应用
- 10.1.1 依赖
- 10.1.2 配置文件
- 10.2 Nacos发现实例模型
- 10.3 注册中心对比
- 10.4 Nacos 支持AP和CP模式的切换
- 10.4.1 何时选择何种模式?
- 10.5 Nacos 服务配置
- 10.5.1 SpringCloud原生注解@RefreshScope
- 10.5.2 配置
- 10.5.3 分类设计思想
- 10.6 Nacos 集群是持久化配置
- 10.6.1 Nacos支持三种部署模式
- 11、Sentinel 熔断与限流
- 11.1 是什么?
- 11.2 特征
- 11.3 特性
- 11.4 与Hystrix的区别
- 11.5 两个部分
- 11.6 应用
- 11.6.1 依赖
- 11.6.2 配置文件
- 11.7 流量配置规则
- 11.7.1 直接(默认)
- 11.7.2 关联
- 11.7.3 Warm Up 预热
- 11.7.4 排队等待
- 11.8 熔断降级
- 11.8.1 概述
- RT(平均响应时间,秒级)
- 异常比列(秒级)
- 异常数(分钟级)
- 11.8.2 Sentinel断路器没有半开状态
- 11.9 热点规则
- 11.9.1 参数例外项
- 11.9.2 运行时异常
- 11.10 系统规则
- 11.11 @SentinelResource
- 11.12 熔断框架比较
- 11.13 Sentinel的规则持久化
- 11.13.1 依赖
- 12、 Seata 处理分布式事务
- 12.1 Seata 简介2
- 12.2 Seata的安装
- 12.2.1 修改配置文件
- 12.2.2 在nacos上创建配置文件 seataServer.yaml
- 12.2.3 安装路径seata\seata-server-1.6.0\seata\script\config-center下有一个config.txt文件,修改后复制到seata路径下
- 12.2.4 通过nacos-config.sh将config.txt文件的内容上传到nacos上
- 12.2.5 通过seata-server.bat启动
- 12.3 业务说明
- 12.4 应用
- 12.4.1 依赖
- 12.4.2 配置文件
- 12.4.3 订单服务主业务TOrderServiceImpl
- 12.4.4 库存服务 TStorageServiceImpl
- 12.4.5 故障情况
- 12.4.6 使用Seata对数据源进行代理
- 12.5 seata原理
- 12.5.1 业务执行流程
- 12.5.2 AT模式
- 一阶段加载
- 二阶段提交
- 二阶段回滚
1、SpringCloud版本对应
1.1 技术选型依据
{"git": {"branch": "0e9bff9f3008546899af1a871def8e3a9cc852ff","commit": {"id": "0e9bff9","time": "2023-06-12T10:28:25Z"}},"build": {"version": "0.0.1-SNAPSHOT","artifact": "start-site","versions": {"spring-boot": "3.1.0","initializr": "0.20.0-SNAPSHOT"},"name": "start.spring.io website","time": "2023-06-12T10:30:20.458Z","group": "io.spring.start"},"bom-ranges": {"codecentric-spring-boot-admin": {"2.4.3": "Spring Boot >=2.3.0.M1 and <2.5.0-M1","2.5.6": "Spring Boot >=2.5.0.M1 and <2.6.0-M1","2.6.8": "Spring Boot >=2.6.0.M1 and <2.7.0-M1","2.7.4": "Spring Boot >=2.7.0.M1 and <3.0.0-M1","3.0.4": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"},"solace-spring-boot": {"1.1.0": "Spring Boot >=2.3.0.M1 and <2.6.0-M1","1.2.2": "Spring Boot >=2.6.0.M1 and <3.0.0-M1","2.0.0": "Spring Boot >=3.0.0-M1"},"solace-spring-cloud": {"1.1.1": "Spring Boot >=2.3.0.M1 and <2.4.0-M1","2.1.0": "Spring Boot >=2.4.0.M1 and <2.6.0-M1","2.3.2": "Spring Boot >=2.6.0.M1 and <3.0.0-M1","3.0.0": "Spring Boot >=3.0.0-M1"},"spring-cloud": {"Hoxton.SR12": "Spring Boot >=2.2.0.RELEASE and <2.4.0.RELEASE","2020.0.6": "Spring Boot >=2.4.0.RELEASE and <2.6.0","2021.0.7": "Spring Boot >=2.6.0 and <3.0.0","2022.0.3": "Spring Boot >=3.0.0 and <3.2.0-M1"},"spring-cloud-azure": {"4.8.0": "Spring Boot >=2.5.0.M1 and <3.0.0-M1","5.2.0": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"},"spring-cloud-gcp": {"2.0.11": "Spring Boot >=2.4.0-M1 and <2.6.0-M1","3.5.1": "Spring Boot >=2.6.0-M1 and <3.0.0-M1","4.3.1": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"},"spring-cloud-services": {"2.3.0.RELEASE": "Spring Boot >=2.3.0.RELEASE and <2.4.0-M1","2.4.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1","3.3.0": "Spring Boot >=2.5.0-M1 and <2.6.0-M1","3.4.0": "Spring Boot >=2.6.0-M1 and <2.7.0-M1","3.5.0": "Spring Boot >=2.7.0-M1 and <3.0.0-M1","4.0.0": "Spring Boot >=3.0.0 and <3.1.0-M1"},"spring-shell": {"2.1.10": "Spring Boot >=2.7.0 and <3.0.0-M1","3.0.4": "Spring Boot >=3.0.0 and <3.1.0-M1","3.1.0": "Spring Boot >=3.1.0 and <3.2.0-M1"},"vaadin": {"14.10.1": "Spring Boot >=2.1.0.RELEASE and <2.6.0-M1","23.2.15": "Spring Boot >=2.6.0-M1 and <2.7.0-M1","23.3.13": "Spring Boot >=2.7.0-M1 and <3.0.0-M1","24.0.6": "Spring Boot >=3.0.0-M1 and <3.1.0-M1","24.1.0": "Spring Boot >=3.1.0-M1 and <3.2.0-M1"},"wavefront": {"2.0.2": "Spring Boot >=2.1.0.RELEASE and <2.4.0-M1","2.1.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1","2.2.2": "Spring Boot >=2.5.0-M1 and <2.7.0-M1","2.3.4": "Spring Boot >=2.7.0-M1 and <3.0.0-M1","3.0.1": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"}},"dependency-ranges": {"okta": {"1.4.0": "Spring Boot >=2.2.0.RELEASE and <2.4.0-M1","1.5.1": "Spring Boot >=2.4.0-M1 and <2.4.1","2.0.1": "Spring Boot >=2.4.1 and <2.5.0-M1","2.1.6": "Spring Boot >=2.5.0-M1 and <3.0.0-M1","3.0.4": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"},"mybatis": {"2.1.4": "Spring Boot >=2.1.0.RELEASE and <2.5.0-M1","2.2.2": "Spring Boot >=2.5.0-M1 and <2.7.0-M1","2.3.1": "Spring Boot >=2.7.0-M1 and <3.0.0-M1","3.0.2": "Spring Boot >=3.0.0-M1"},"pulsar": {"0.2.0": "Spring Boot >=3.0.0 and <3.1.0-M1"},"pulsar-reactive": {"0.2.0": "Spring Boot >=3.0.0 and <3.1.0-M1"},"camel": {"3.5.0": "Spring Boot >=2.3.0.M1 and <2.4.0-M1","3.10.0": "Spring Boot >=2.4.0.M1 and <2.5.0-M1","3.13.0": "Spring Boot >=2.5.0.M1 and <2.6.0-M1","3.17.0": "Spring Boot >=2.6.0.M1 and <2.7.0-M1","3.20.5": "Spring Boot >=2.7.0.M1 and <3.0.0-M1","4.0.0-M3": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"},"picocli": {"4.7.0": "Spring Boot >=2.5.0.RELEASE and <3.1.0-M1"},"open-service-broker": {"3.2.0": "Spring Boot >=2.3.0.M1 and <2.4.0-M1","3.3.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1","3.4.1": "Spring Boot >=2.5.0-M1 and <2.6.0-M1","3.5.0": "Spring Boot >=2.6.0-M1 and <2.7.0-M1"}}
}
学习推荐版本,由SpringCloud来决定SpringBoot的版本。
1.2 cloud组件演变:
2、Eureka
包含两个组件:Eureka Server和Eureka Client
2.1 Eureka Server : 提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
2.2 EurekaClient : 通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载具法的顶载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心
跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
2.3 Eureka自我保护
3、Eureka、Zookeeper、Consul三个注册中心的异同点
组件名 | 语言 | CAP | 服务健康检查 | 对外暴露接口 | SpringCloud集成 |
---|---|---|---|---|---|
Eureka | Java | AP(高可用) | 可配支持 | HTTP | 已集成 |
Zookeeper | Go | CP(数据一致性) | 支持 | HTTP/DNS | 已集成 |
Consul | Java | CP(数据一致性) | 支持 | 客户端 | 已集成 |
Nacos | AP(高可用) | 支持 | 客户端 | 已集成 |
- C:Consistency (强一致性)
- A:Availability (可用性)
- P:Partition tolerance (分布式分区容错性)
最多只能同时较好的满足两个。
CAP理论的核心是: 一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求。
CAP理论关注粒度是数据,而不是整体系统设计的策略。
因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
- CA-单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。
- CP-满足─致性,分区容忍必的系统,通常性能不是特别高。
- AP–满足可用性,分区容忍性的系统,通常可能对—致性要求低一些。
3.1 CP架构:
当网络分区出现后,为了保证一致性,就必须拒绝接请求,否则无法保证一致性。
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP。
4、Ribbon 负载均衡服务调用
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。
Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。
我们很容易使用Ribbon实现自定义的负载均衡算法。
4.1 SpringCloud Load Balance
LB负载均衡(Load Balance)是什么?
将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡有软件Nginx,LVS,硬件F5等。
集中式LB Load Balance
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,1如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;
Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别
Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡是由服务端实现的。
Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。
4.2 总结:
Ribbon其实就是一个软负载均衡的客户端组件,
他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
4.3 Ribbon工作流程:
Ribbon在工作时分成两步:
- 第—步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server.
- 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
- 其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。
4.4 自定义Ribbon 负载均衡算法:
4.4.1 iRule接口:
4.4.2 Ribbon自带的负载均衡算法:
com.netflix.loadbalancer.RoundRobinRule:
轮询
com.netflix.loadbalancer.RandomRule:
随机
com.netflix.loadbalancer.RetryRule:
先按照RoundRobinRule的策骼获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
weightedResponseTimeRule:
对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
BestAvailableRuleo:
会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发是最小的服务
vailabilityFilteringRule:
先过滤掉故障实例,再选择并发较小的实例:
zoneAvoidanceRule
默认规则,复合判断server所在区域的性能和server的可用性选择服务器
官方文档明确给出了警告:
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
4.4.3 负载均衡算法替代:
4.4.3.1、在非启动类包及子包下创建配置类
4.4.3.2、定义
/*** Myrule : Ribbon 自定义负载均衡算法配置类** @author zyw* @create 2023/6/18*/
@Configuration
public class Myrule {@Beanpublic IRule getIRule(){//定义为随机return new RandomRule();}
}
4.4.3.3、启动类增加RibbonClient注解
自定义需要指定的服务
/*** OderMain80 :** @author zyw* @create 2023/6/16*/
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOULD-PAYMENT-SERVICE",configuration = Myrule.class)
@MapperScan("com.zyw.springcloud.dao")
public class OderMain80 {public static void main(String[] args) {SpringApplication.run(OderMain80.class,args);}
}
4.5 Ribbon负载均衡算法
4.5.1 轮询算法原理:
4.5.2 轮询算法源码:
public class RoundRobinRule extends AbstractLoadBalancerRule {private AtomicInteger nextServerCyclicCounter;public RoundRobinRule() {nextServerCyclicCounter = new AtomicInteger(0);
}public Server choose(ILoadBalancer lb, Object key) {if (lb == null) {log.warn("no load balancer");return null;}Server server = null;int count = 0;while (server == null && count++ < 10) {//获取有正常运行的(可达的)服务集合List<Server> reachableServers = lb.getReachableServers();//获取可负载服务集合List<Server> allServers = lb.getAllServers();//获取有正常运行的(可达的)服务的数量int upCount = reachableServers.size();//获取可负载服务的数量 == 服务器集群总数量int serverCount = allServers.size();if ((upCount == 0) || (serverCount == 0)) {log.warn("No up servers available from load balancer: " + lb);return null;}int nextServerIndex = incrementAndGetModulo(serverCount);server = allServers.get(nextServerIndex);if (server == null) {/* Transient. */Thread.yield();continue;}if (server.isAlive() && (server.isReadyToServe())) {return (server);}// Next.server = null;}if (count >= 10) {log.warn("No available alive servers after 10 tries from load balancer: "+ lb);}return server;}//自旋锁//rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务器重启后rest接口计数从1开始。private int incrementAndGetModulo(int modulo) {for (;;) {//获取当前值int current = nextServerCyclicCounter.get();//计算下次值int next = (current + 1) % modulo;//比较并交换if (nextServerCyclicCounter.compareAndSet(current, next))//得到当前下标值return next;}}}
4.5.3 手写负载均衡算法
/*** LoadBalancer : 自定义负载均衡算法** @author zyw* @create 2023/6/20*/
public interface LoadBalancer {//收集Eurek上所有活着的服务总数ServiceInstance instances(List<ServiceInstance> serviceInstances);}
/*** MyLB : 自定义负载均衡算法实现类** @author zyw* @create 2023/6/20*/
@Component
public class MyLB implements LoadBalancer {private AtomicInteger atomicInteger = new AtomicInteger(0);public final int getAndIncrement() {int currrnt;int next;do {currrnt = this.atomicInteger.get();//Integer.MAX_VALUE = 2147483647next = currrnt >= 2147483647 ? 0 : currrnt + 1;//自选锁,直到得到期望值} while (!this.atomicInteger.compareAndSet(currrnt, next));System.out.println("第" + next + "次访问");return next;}/*** rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务器重启后rest接口计数从1开始。* @param serviceInstances* @return*/@Overridepublic ServiceInstance instances(List<ServiceInstance> serviceInstances) {int index = getAndIncrement() % serviceInstances.size();return serviceInstances.get(index);}
}
/*** OrderController : 订单系统控制层** @author zyw* @create 2023/6/16*/
@Slf4j
@RestController
@RequestMapping("consumer/orderController")
@Api(tags={"订单系统控制层"})
public class OrderController {@Resourceprivate RestTemplate restTemplate;@Resourceprivate DiscoveryClient discoveryClient;@Resourceprivate LoadBalancer loadBalancer;@GetMapping("/lb")@ApiOperation(value = "获取负载服务的端口号", response = String.class)public String getPaymentBl(){List<ServiceInstance> serviceInstances = discoveryClient.getInstances("CLOULD-PAYMENT-SERVICE");if (serviceInstances == null || serviceInstances.size() <= 0){return null;}ServiceInstance serviceInstance = loadBalancer.instances(serviceInstances);URI uri = serviceInstance.getUri();return restTemplate.getForObject(uri+"paymentController/lb",String.class);}}
5、OpenFeign 服务接口调用
5.1 概述
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。
Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。
Feign可以与Eureka和Ribbon组合使用以支持负载均衡
5.2 Feign能干什么
Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
5.3 Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。
而与Ribbon不同的是,通过feign只需要定义
服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
5.4 应用
5.4.1 引入依赖
<!-- openfeign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
5.4.2 开启功能
//开启Feign
@EnableFeignClients
@SpringBootApplication
public class OderOpenFeignMain80 {public static void main(String[] args) {SpringApplication.run(OderOpenFeignMain80.class, args);}}
5.4.3 service中远程调用
@Component
@FeignClient(value = "CLOULD-PAYMENT-SERVICE")
public interface PaymentFeginService {@GetMapping("/paymentController/getById")public CommonResult getById(@RequestParam("id") Long id);@GetMapping("/paymentController/feign/timeout")public String paymentFeignTimeout();}
5.5 超时控制
默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
在yml中开启OpenFeign客户端超时控制
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:# 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间ReadTimeout: 5000# 指的是建立俩进阶后从服务器读取到可用资源所用的时间ConnectTimeout: 5000
5.6 日志打印
5.6.1 日志级别
级别 | 内容 |
---|---|
NONE | 默认的,不显示任何日志 |
BASIC | 仅记录请求方法、URL、响应状态码及执行时间 |
HEADERS | 除了BASIC中定义的信息之外,还有请求和响应的头信息 |
FULL | 除了HEADERS 中定义的信息之外,还有请求和响应的正文及元数据 |
5.6.2 配置类
@Configuration
public class FeginConfig {@BeanLogger.Level feignLoggerLevel(){return Logger.Level.FULL;}
}
5.6.3 yml指定日志以什么级别监控哪个接口
logging:level:# feign 日志以什么级别监控哪个接口com.zyw.springcloud.service.PaymentFeginService: debug
6.Hystrix 断路器
6.1 问题:服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的**“扇出"**。
如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。
比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。
这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或
者叫雪崩。
6.2 概念
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等Hystrix能够保证在一个依赖出
问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
"断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应
(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布
式系统中的蔓延,乃至雪崩。
6.3 服务降级
6.3.1 概念:
服务不可用了,不让客户端等待,并立刻返回一个友好提示,fallback。
6.3.2 触发服务降级的情况:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满
6.3.3 应用
6.3.3.1 依赖
<!-- hystrix --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>
6.3.3.2 解决的问题
超时导致服务器变慢(转圈)-》超时不再等待-》服务降级
出错(宕机或程序运行出错)-》出错要有兜底-》服务降级
生产者正常,消费者自己出现故障或有自我要求(自己的等待时间小于服务提供者响应时间)
6.3.3.3 生产者:
设置自身调用后超时时间的峰值,超过峰值做服务降级fallback
一旦调用服务方法失败并抛出错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
系统运行报错也会走fallbackMethod标注的方法。
/*** 超时** @param id* @return*///@HystrixCommand 服务降级规则 响应超过3000毫秒,则执行paymentInfo_TimeOutHandler方法@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")})@Overridepublic String paymentInfo_TimeOut(Integer id) {int timeNumber = 5;try {TimeUnit.SECONDS.sleep(timeNumber);} catch (InterruptedException e) {e.printStackTrace();}return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O哈哈~" + ",耗时" + timeNumber + "秒钟";}public String paymentInfo_TimeOutHandler(Integer id) {return "o(╥﹏╥)o\r\n调用支付接口超时或异常:\t" + "\t当前线程池名称" + Thread.currentThread().getName();}
主启动类添加@EnableCircuitBreaker注解
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class PaymentHystrixMain8001 {public static void main(String[] args) {SpringApplication.run(PaymentHystrixMain8001.class,args);}
}
6.3.3.4 消费者:
配置文件中开启 feign 对 hystrix 的支持
feign:hystrix:# 开启 feign 对 hystrix 的支持enabled: true
主启动类添加@EnableHystrix注解
@EnableHystrix
@EnableFeignClients
@SpringBootApplication
public class OrderHystrixMain80 {public static void main(String[] args) {SpringApplication.run(OrderHystrixMain80.class,args);}
}
HystrixCommand:
@GetMapping("/hystrix/TimeOut/{id}")@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")})@ApiOperation(value = "超时接口", response = String.class)public String paymentInfo_TimeOut(@PathVariable("id")Integer id){String result = paymentHystrixService.paymentInfo_TimeOut(id);return result;}public String paymentTimeOutFallbackMethod(Integer id) {return "我是消费者80,对方支付系统繁忙,请10秒后重试\r\no(╥﹏╥)o";}
需要设置熔断器超时峰值,否则会报错
hystrix:command:default:execution:isolation:thread:# 设置熔断器超时峰值timeoutInMilliseconds: 5000
6.3.3.5 配置全局fallback方法
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
@RequestMapping("consumer")
@Api(tags = {"整合Hystrix订单系统控制层"})
public class OrderHystirxController {@Resourceprivate PaymentHystrixService paymentHystrixService;@GetMapping("/hystrix/OK/{id}")@HystrixCommand@ApiOperation(value = "正常接口", response = String.class)public String paymentInfo_OK(@PathVariable("id") Integer id){String result = paymentHystrixService.paymentInfo_OK(id);return result;}/*** 全局fallback方法* @return*/public String payment_Global_FallbackMethod(){return "payment系统繁忙,请联系客服处理\n" +"o(╥﹏╥)o";}}
如果配置了@HystrixCommand中的fallbackMethod属性,则走专属配置的,没有则走全局的。
6.3.3.6 解耦合
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {@GetMapping("/payment/hystrix/OK/{id}")public String paymentInfo_OK(@PathVariable("id") Integer id);@GetMapping("/payment/hystrix/TimeOut/{id}")public String paymentInfo_TimeOut(@PathVariable("id")Integer id);}
@Component
public class PaymentFallbackService implements PaymentHystrixService {@Overridepublic String paymentInfo_OK(Integer id) {return "-----PaymentFallbackService fall back-paymentInfo_OK\r\no(╥﹏╥)o";}@Overridepublic String paymentInfo_TimeOut(Integer id) {return "-----PaymentFallbackService fall back-paymentInfo_TimeOut\r\no(╥﹏╥)o";}}
6.4 服务熔断
6.4.1 概念:
类似于保险丝达到最大服务访问量后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,恢复调用链路。
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,
当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。
6.4.2 注解
//=====服务熔断=====@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//是否开启断路器@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//时间窗口期@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//失败率达到多少后跳闸})@Overridepublic String paymentCircuitBreaker(@PathVariable("id") Integer id) {
涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。
1: 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
2∶请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
3∶错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过
50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
6.4.3 应用
@Service
public class PaymentServiceImpl implements PaymentService {//=====服务熔断=====@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//是否开启断路器@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//时间窗口期@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//失败率达到多少后跳闸})@Overridepublic String paymentCircuitBreaker(@PathVariable("id") Integer id) {if (id < 0) {throw new RuntimeException("******id不能负数");}String serialNumber = IdUtil.simpleUUID();return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;}public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {return "id不能负数,请稍后再试,/(ToT)/~~id: " + id;}}
@RestController
@Slf4j
@RequestMapping("/payment")
@Api(tags = {"整合hystrix支付系统控制层"})
public class PaymentController {@Resourceprivate PaymentService paymentService;//=====服务熔断=====@GetMapping("/hystrix/circuit/{id}")@ApiOperation(value = "服务熔断接口", response = String.class)public String paymentCircuitBreaker(@PathVariable("id") Integer id) {String result = paymentService.paymentCircuitBreaker(id);log.info("result:" + result);return result;}}
6.4.4 原来的主逻辑要如何恢复呢?
对于这一问题,hystrix也为我们实现了自动恢复功能。
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
6.5 服务限流
6.5.1 概念:
秒杀、高并发等操作,严禁其一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
6.6 服务监控 hystrixDashboard
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。
Netfiix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
6.6.1 依赖
<!-- hystrix-dashboard --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId></dependency>
6.6.2 主启动类添加注解@EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard//开启图形化监控界面
public class HystrixDashboardMain9001 {public static void main(String[] args) {SpringApplication.run(HystrixDashboardMain9001.class,args);}
}
6.6.3 访问图形化界面
http://localhost:9001/hystrix
6.6.4 调整需要监控的服务主启动类
@EnableCircuitBreaker//开启熔断器
@EnableEurekaClient
@SpringBootApplication
public class PaymentHystrixMain8001 {public static void main(String[] args) {SpringApplication.run(PaymentHystrixMain8001.class, args);}/*** 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的玩* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",* 只要在自己的项目里配置上下面的servlet就可以了*/@Beanpublic ServletRegistrationBean getServlet() {HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);registrationBean.setLoadOnStartup(1);registrationBean.addUrlMappings("/hystrix.stream");registrationBean.setName("HystrixMetricsStreamServlet");return registrationBean;}
}
6.6.5 输入监控的url
7、Gateway 网关
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统—的API路由管理方式。
SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
7.1 特性
- 基于Spring Framework 5, Project Reactpr和Spring Boot 2.0进行构建;动态路由:能够匹配任何请求属性;
- 可以对路由指定Predicate(断言)和Filter (过滤器);
- 集成Hystrix的断路器功能;
- 集成Spring Cloud服务发现功能;
- 易于编写的 Predicate (断言)和Filter (过滤器);
- 请求限流功能;
- 支持路径重写。
7.2 Gateway与Zuul的区别
在SpringCloud Finchley正式版之前,Spring Cloud 推荐的网关是 Netflix提供的Zuul:
- 1、Zuul 1.x,是一个基于阻塞IO的API Gateway
- 2、Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket) Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Ngink用C+实现,Zuul用Java 实现,而JVM本身会有第—次加载较慢的情况,使得Zuul的性能相对较差。
- 3、Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul.2 .x的性能较Zuul.1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
- 4、Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
- 5、Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验
7.3 Gateway模型
传统的Web框架,比如说: struts2,springmvc等都是基于Servlet APl与Servlet容器基础之上运行的。
但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty, Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)
Spring WebFlux是 Spring 5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet APl,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。
7.4 三大基本概念
7.4.1 Route 路由
路由是构建网关的基本模块,它由ID,目标URl,一系列的断言和过滤器组成,如果断言为true则匹配该路由
7.4.2 Predicate 断言
参考的是Java8的java.util.functjon.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
7.4.3 Filter 过滤
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
7.5 应用
7.5.1 依赖
<!-- gateway --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>
7.5.2 yml配置文件
server:port: 9527
eureka:instance:# eureka 服务端的实例名称hostname: cloud-gateway-serviceclient:# false 表示自己端就是注册中心,我的职责就是维护服务实例,而不需要去检索服务fetch-registry: trueservice-url:# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址defaultZone: http://eureka7001.com:7001/eureka
spring:application:name: cloud-gatewaycloud:gateway:routes:- id : payment_routh # 路由的ID,没有固定规则但要求唯一,建议配合服务名uri: http://localhost:8001 # 匹配后提供服务的路由地址predicates:- Path=/payment/getById/** # 断言,路径相匹配的进行路由- id: payment_routh2uri: http://localhost:8001 # 匹配后提供服务的路由地址predicates:- Path=/payment/lb/** # 断言,路径相匹配的进行路由
7.5.3 配置类方式配置网关路由
@Configuration
public class GateWayConfig {/*** 配置了一个id为route-name的路由规则* 当访问地址 https://localhost:9527/guoji 时会自动转发到地址:https://news.baidu.com/guonei* @param routeLocatorBuilder* @return*/@Bean//路由定位器public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();routes.route("path_route_zyw",r -> r.path("/guonei").uri("https://news.baidu.com/guonei")).build();return routes.build();}
}
7.6 动态路由
默认情况下,GateWay会根据注册中心注册的服务列表,以注册中心上微服务名称为路径创建动态路由进行转发。
spring:application:name: cloud-gatewaycloud:gateway:discovery:locator:enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由routes:- id : payment_routh # 路由的ID,没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service # 匹配后提供微服务的路由地址predicates:- Path=/payment/getById/** # 断言,路径相匹配的进行路由- id: payment_routh2uri: lb://cloud-payment-service # 匹配后提供微服务的路由地址predicates:- Path=/payment/lb/** # 断言,路径相匹配的进行路由
7.7 Predicate 断言
- predicates.After : 配置启用时间
- predicates.Before: 配置停用时间
- predicates.Between: 配置可用时间范围
- Cookie Route Predicate
需要两个参数,Cookie name和正则表达式
路由规则会通过获取对应的Cookie name值和正则表达式去匹配,如果匹配上就会执行路由。
- Header Route Predicate
属性名+正则表达式
- Method Route Predicate
规定请求方式
- Query Route Predicate
规定必传参数
spring:application:name: cloud-gatewaycloud:gateway:discovery:locator:enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由routes:- id : payment_routh # 路由的ID,没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service # 匹配后提供微服务的路由地址predicates:- Path=/payment/** # 断言,路径相匹配的进行路由- After=2023-06-26T15:32:05.258+08:00[Asia/Shanghai] # 配置启用时间- Before=2123-06-26T15:32:05.258+08:00[Asia/Shanghai] # 配置停用时间- Between=2023-06-26T15:32:05.258+08:00[Asia/Shanghai],2123-06-28T15:32:05.258+08:00[Asia/Shanghai] # 配置可用时间范围- Cookie=username,zyw- Header=X-Request-Id,\d+ # 请求头要有 X-Request-Id属性并且值为整数的正则表达式- Method=Get # 规定请求的方式- Query=idCard,\d+ # 参数名要有idCard且值必须为整数
时间格式生成工具类:
import java.time.ZonedDateTime;
public class DateTest {public static void main(String[] args) {ZonedDateTime zbj = ZonedDateTime.now();System.out.println("zbj = " + zbj);}
}
7.8 Filter 过滤器
生命周期:pre ==》 post
种类:GatewayFilter(单一的)、GlobalFilter(全局的)
7.8.1 自定义过滤器
8、Config 服务配置
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
分为服务端和客户端
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容
8.1 作用:
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
- 运行期间动态调整配置,不再需要在萦个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
- 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
- 将配置信息以REST接口的形式暴露,post、curl访问刷新均可
9、SpringCloud Alibaba
- 服务限流降级:默认支持Servlet、Feign、RestTemplate、Dubbo和RocketMQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics 监控。
- 服务注册与发现:适配 Spring Cloud服务注册与发现标准,默认集成了Ribbon的支持。
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
- 消息驱动能力:基于Spring Cloud Stream为微服务应用构建消息驱动力能力。
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。
- 网格任务支持海量子任务均匀分配到所有Worker (schedulerx-client)上执行。
组件:
- Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
- Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
- RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。Dubbo: Apache DubboTM是一款高性能Java RPC框架。
- Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
- Alibaba Cloud ACM:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品。
- Alibaba Cloud oSS:阿里云对象存储服务(Object Storage Service,简称OSS),是阿里云提供的海星、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- Alibaba Cloud SchedulerX:阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度服务。
- Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
10、Nacos 服务注册和配置中心
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos = Eureka + Config + Bus
10.1 应用
10.1.1 依赖
父工程
<!-- spring cloud alibaba 2.1.0.RELEASE --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2.1.0.RELEASE</version><type>pom</type><scope>import</scope></dependency>
子工程
<!-- SpringCloud alibaba nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-nacos-discovery</artifactId></dependency>
10.1.2 配置文件
server:port: 9001servlet:context-path: /provider
spring:# 服务名称application:# 服务名称name: nacos-payment-providercloud:nacos:discovery:server-addr: localhost:8848 # 配置Nacos地址
management:endpoints:web:exposure:include: '*'
10.2 Nacos发现实例模型
10.3 注册中心对比
10.4 Nacos 支持AP和CP模式的切换
C :所有节点在同一时间看到的数据是一致的
A:所有请求都会收到响应
10.4.1 何时选择何种模式?
一般来说,
如果不需要存储服务级别的信息且服务实例是通过nacos-cient注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如Spring cloud 和Dubo服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么CP是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以Raft协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
10.5 Nacos 服务配置
Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。
springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
10.5.1 SpringCloud原生注解@RefreshScope
修饰Controller层可以支持Nacos的动态刷新功能
10.5.2 配置
bootstarp.yml
server:port: 3377
spring:# 服务名称application:# 订单服务name: nacos-config-clientcloud:nacos:discovery:server-addr: localhost:8848 # Nacos 服务注册中心地址config:server-addr: localhost:8848 # Nacos 配置中心地址file-extension: yaml # 指定yaml格式的配置group: TEST_GROUPnamespace: 6536b558-4546-48a3-ba53-eaf9e264006d
applicaton.yml
spring:profiles:active: test # 表示测试环境
# active: dev # 表示开发环境
最后公式: s p r i n g . a p p l i c a t i o n . n a m e − {spring.application.name}- spring.application.name−{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
10.5.3 分类设计思想
namespace用于区分部署环境
Group和DataID逻辑上区分两个目标对象
默认情况:Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT
-
Nacos默认的命名空间是public,Namespace主要用来实现隔离。
-
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。
-
Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去
-
Service就是微服务;一个Service可以包含多个Cluster (集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,
这时就可以给杭州机房的Service微服务起一个集群名称(HZ) ,
给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
最后是lnstance,就是微服务的实例。
10.6 Nacos 集群是持久化配置
默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。
为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。
10.6.1 Nacos支持三种部署模式
- 单机模式-用于测试和单机试用。
- 集群模式–用于生产环境,确保高可用。
- 多集群模式–用于多数据中心场景。
11、Sentinel 熔断与限流
11.1 是什么?
分布式系统的流量防卫兵
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。
Sentinel以流星为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
11.2 特征
丰富的应用场景: Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控: Sentinel同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群的汇总运行情况。
广泛的开源生态: Sentinel提供开箱即用的与其它开源框架/库的整合模块,例如与Spring Cloud、Dubbo、gRPC的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
完善的SPI扩展点: Sentinel提供简单易用、完善的SPI扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
11.3 特性
11.4 与Hystrix的区别
Hystrix | Sentinel |
---|---|
需要手工搭建监控平台:DashBoard | 单独一个组件独立出来 |
没有一套Web界面可以进行更加颗粒化的配置流量监控、速率控制、服务熔断、服务降级 | 支持界面化的细粒度统一配置 |
11.5 两个部分
核心库(Java客户端)不依赖任何框架/库,能够运行于所有Java运行时环境,同时对 Dubbo /Spring Cloud 等框架也有较好的支持。
控制台(Dashboard)基于Spring Boot 开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。
启动命令:
java -jar sentinel-dashboard-1.7.1.jar
11.6 应用
11.6.1 依赖
<!-- SpringCloud alibaba sentinel-datasource-nacos 持久化 --><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId></dependency><!-- SpringCloud alibaba sentinel --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
11.6.2 配置文件
server:port: 8401servlet:context-path: /sentinel
spring:# 服务名称application:# 订单服务name: nacos-sentinel-servicecloud:nacos:discovery:server-addr: localhost:8848 # Nacos 服务注册中心地址namespace: 13e20987-6fcc-41d6-a358-e16b86bce522config:server-addr: localhost:8848 # Nacos 配置中心地址file-extension: yaml # 指定yaml格式的配置group: DEV_GROUPnamespace: 13e20987-6fcc-41d6-a358-e16b86bce522sentinel:transport:# 配置Sentinel dashboard地址dashboard: localhost:8080# 默认8719端口,加入被占用会自动从8719开始依次+1扫描,知道找到未被占用的端口port: 8719
management:endpoints:web:exposure:include: "*"
11.7 流量配置规则
-
资源名: 唯一名称,默认请求路径
-
针对来源: Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
-
阈值类型/单机阈值:
-
- QPS(每钟的请求数量): 当调用该api的QPS达到阈值的时候,进行限流。
- 线程数: 当调用该api的线程数达到阈值的时候,进行限流
-
是否集群: 不需要集群
-
流控模式:
-
- 直接: api达到限流条件时,直接限流
- 关联: 当关联的资源达到阈值时,就限流自己
- 链路: 只记录指定链路上的流星(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
-
流控效果:
-
- 快速失败: 直接失败,抛异常
- Warm up: 根据codeFactor (冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
- 排队等待: 匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
11.7.1 直接(默认)
==》快速失败
11.7.2 关联
11.7.3 Warm Up 预热
Warm Up ( RuleConstant.CONTROL_BEHAVIOR_MARM_UuP)方式,即预热/冷启动方式。
当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
默认 coldFactor为3,即请求QPS从threshold / 3 开始,经预热时长逐渐升致设定的QPS阈值。
11.7.4 排队等待
匀速排队( RuleConstant.CONTROL_BEHAVIOR_RATE_LINITER )方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
用于处理间隔性突发的流量,例如消息队列。
在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
11.8 熔断降级
11.8.1 概述
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException )。
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错谒。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException) 。
RT(平均响应时间,秒级)
- 平均响应时间超出阈值且在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
- 窗口期过后关闭断路器
- RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
平均响应时间(DEGRADE GRADE_RT ):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值( count,以ms为单位),那么在接下的时间窗口( DegradeRule中的timewindow,以s 为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException )。注意Sentinel默认统计的RT上限是4900ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。
每秒钟进来10个线程,我们Sentinel上配置的是希望200毫秒处理完本次任务,如果没有处理完,在未来1秒钟的时间窗口内,断路器打开,微服务不可用。
异常比列(秒级)
QPS >= 5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常比例( DEGRADE_GRADE_EXCEPTION_RATIO ):当资源的每秒请求量>= 5,并且每秒异常总数占通过量的比值超过阈值( DegradeRule中的 count )之后,资源进入降级状态,即在接下的时间窗口( DegradeRule中的 timewindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0,1.0],代表0%- 100%。
异常数(分钟级)
异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常数( DE6RADE_GRADE_EXCEPTION_COUNT ):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timewindow小于60s,则结束熔断状态后仍可能再进入熔断状态。
时间窗口一定要大于等于60S。
11.8.2 Sentinel断路器没有半开状态
半开状态:系统自动会去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。(Hystrix)
注意:异常降级仅针对业务异常,对Sentinel限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过Tracer.trace(ex)记录业务异常。
11.9 热点规则
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的Top K数据,并对其访问进行限制。
比如:
- 商品ID为参数,统计—段时间内最常购买的商品ID并进行限制
- 用户ID为参数,针对一段时间内频繁访问的用户ID进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel利用LRU策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
源码:
Entry entry = null;
try {entry = SphU.entry(resourceName,EntryType.IN,1,paramA,paramB);// Your logic here.
}catch (BlockException ex) {//Handle request rejection.
}finally{if (entry != null) {entry.exit(1,paramA,paramB);}
}
测试案例:
@GetMapping("/testHotKey")@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")public String testHotKey(@RequestParam(value = "p1",required = false)String p1,@RequestParam(value = "p2",required = false)String p2){return "----testHotKey";}//兜底方法public String deal_testHotKey(String p1, String p2, BlockException exception){//Sentinel系统默认提示:Blocked by Sentinel (flow limiting)return "deal_testHotKey,o(╥﹏╥)o";}
@SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”)
第一个参数只要QPS超过每秒1次,马上降级处理,用自定义的deal_testHotKey兜底方法
11.9.1 参数例外项
11.9.2 运行时异常
@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler 方法配置的兜底处理
主管配置出错,运行出错该走异常走异常
11.10 系统规则
Sentinel系统自适应限流从整体维度对应更入口流量进行控制,结合应用的Load、CPU使用率、总体平均RT、入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统规则支持以下的模式:
- Load自适应(仅对Linux/Unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。当系统load1超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR阶段)。系统容量由系统的maxQps * minRt估算得出。设定参考值一般是cPUcores * 2.5。
- CPU usage (1.5.0+版本):当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏。
- 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒。·并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护。
11.11 @SentinelResource
自定义异常兜底处理类
public class CustomerBlockFallback {public static CommonResult handlerFallback(@PathVariable("id") Long id,Throwable e){return CommonResult.BlockHandler("兜底异常--Fallback,exception内容:"+e.getMessage());}
}
自定义Sentinel控制台配置违规处理类
public class CustomerBlockHandler {public static CommonResult handlerblock(@PathVariable("id") Long id, BlockException exception) {return CommonResult.BlockHandler(exception.getMessage());}
}
业务层
@Service
public class PaymentServiceImpl {@Resourceprivate PaymentService paymentService;@SentinelResource(value = "getPaymentById",fallbackClass = CustomerBlockFallback.class,fallback = "handlerFallback", //fallback只负责业务异常blockHandlerClass = CustomerBlockHandler.class,blockHandler = "handlerblock", //只负责Sentinel控制台配置违规exceptionsToIgnore = {IllegalArgumentException.class} //排除该异常的兜底方法)public CommonResult<Payment> getPaymentById(Long id) {if (id<0){throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");}CommonResult<Payment> paymentById = paymentService.getPaymentById(id);if (paymentById.getData()==null){throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");}return paymentById;}
}
11.12 熔断框架比较
Sentinel | Hystrix | resilience4j | |
---|---|---|---|
隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔离/信号量隔离 | 信号量隔离 |
熔断降级策略 | 基于响应时间、异常比例、异常数 | 基于异常比例 | 基于异常比例、响应时间 |
实时统计实现 | 滑动窗口(LeapArray) | 滑动窗口(基于RxJava) | Ring Bit Buffer |
动态规则配置 | 支持多种数据源 | 支持多种数据源 | 有限支持 |
扩展性 | 多个扩展点 | 插件的形式 | 接口的形式 |
基于注解的支持 | 支持 | 支持 | 支持 |
限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 | Rate Limiter |
11.13 Sentinel的规则持久化
解决方案:保存进Nacos(官方要求)、Mysql、Redis
11.13.1 依赖
<!-- SpringCloud alibaba sentinel-datasource-nacos 持久化 --><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId></dependency>
11.13.2 配置文件
spring:# 服务名称application:# 订单服务name: nacos-payment-consumercloud:nacos:discovery:server-addr: localhost:8848 # Nacos 服务注册中心地址namespace: 13e20987-6fcc-41d6-a358-e16b86bce522config:server-addr: localhost:8848 # Nacos 配置中心地址file-extension: yaml # 指定yaml格式的配置group: DEV_GROUPnamespace: 13e20987-6fcc-41d6-a358-e16b86bce522sentinel:transport:# 配置Sentinel dashboard地址dashboard: localhost:8080# 默认8719端口,加入被占用会自动从8719开始依次+1扫描,知道找到未被占用的端口port: 8719datasource:dsl:nacos:server-addr: localhost:8848namespace: 13e20987-6fcc-41d6-a358-e16b86bce522dataId: nacos-payment-consumergroupId: DEV_GROUPdata-type: jsonrule-type: flow
resource: 资源名称;
limitApp: 来源应用;
grade: 阈值类型,0表示线程数,1表示QPS;
count: 单机阈值;
strategy: 流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior: 流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode: 是否集群。
12、 Seata 处理分布式事务
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。
此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
一次业务操作需要跨多个数据源或者需要跨多个系统进行远程调用,就会产生分布式事务问题。
案例:
用户购买商品的业务逻辑:
- 仓储服务:对给定的商品扣除仓储数量。
- 订单服务:根据采购需求创建订单。
- 账户服务:从用户账户中扣除余额。
12.1 Seata 简介2
概念:Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
分布式事务的处理过程:1 ID+ 3 组件模型
1 ID:全剧唯一的事务ID
术语3组件:
- Tc-事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
- TM-事务管理器:定义全局事务的范围︰开始全局事务、提交或回滚全局事务。
- RM-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
12.2 Seata的安装
12.2.1 修改配置文件
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.server:port: 7091spring:application:name: seata-serverlogging:config: classpath:logback-spring.xmlfile:path: ${user.home}/logs/seataextend:logstash-appender:destination: 127.0.0.1:4560kafka-appender:bootstrap-servers: 127.0.0.1:9092topic: logback_to_logstashconsole:user:username: seatapassword: seataseata:config:# support: nacos, consul, apollo, zk, etcd3type: nacosnacos:server-addr: 127.0.0.1:8848namespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976group: SEATA_GROUP##if use MSE Nacos with auth, mutex with username/password attribute#access-key: ""#secret-key: ""data-id: seataServer.yamlusername: nacospassword: nacosregistry:# support: nacos, eureka, redis, zk, consul, etcd3, sofatype: nacos# preferred-networks: 30.240.*nacos:application: seata-serverserver-addr: 127.0.0.1:8848group: SEATA_GROUPnamespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976cluster: defaultusername: nacospassword: nacos##if use MSE Nacos with auth, mutex with username/password attribute#access-key: ""#secret-key: ""store:# support: file 、 db 、 redismode: dbdb:datasource: druiddb-type: mysqldriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true&serverTimezone=GMTuser: rootpassword: 123456min-conn: 5max-conn: 100global-table: global_tablebranch-table: branch_tablelock-table: lock_tabledistributed-lock-table: distributed_lockquery-limit: 100max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'security:secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017tokenValidityInMilliseconds: 1800000ignore:urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
12.2.2 在nacos上创建配置文件 seataServer.yaml
metrics:enabled: falseexporterList: prometheusexporterPrometheusPort: 9898registryType: compact
server:maxCommitRetryTimeout: -1maxRollbackRetryTimeout: -1recovery:asynCommittingRetryPeriod: 3000committingRetryPeriod: 3000rollbackingRetryPeriod: 3000timeoutRetryPeriod: 3000rollbackRetryTimeoutUnlockEnable: falseundo:logDeletePeriod: 86400000logSaveDays: 7
store:db:branchTable: branch_tabledatasource: druiddbType: mysqldriverClassName: com.mysql.cj.jdbc.DriverglobalTable: global_tablelockTable: lock_tablemaxConn: 30maxWait: 5000minConn: 5password: rootqueryLimit: 100url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true&serverTimezone=GMTuser: rootmode: db
transport:compressor: noneserialization: seata
12.2.3 安装路径seata\seata-server-1.6.0\seata\script\config-center下有一个config.txt文件,修改后复制到seata路径下
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none#Transaction routing rules configuration, only for the client
service.vgroupMapping.my_test_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h#Log rule configuration, for client and server
log.exceptionRate=100#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
store.publicKey=#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=123456
store.redis.queryLimit=100#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
12.2.4 通过nacos-config.sh将config.txt文件的内容上传到nacos上
12.2.5 通过seata-server.bat启动
12.3 业务说明
- 这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
- 当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,
- 再通过远程调用账户服务来扣减用户账户里面的余额,
- 最后在订单服务中修改订单状态为已完成。
- 该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
12.4 应用
12.4.1 依赖
<!-- seata --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><exclusions><exclusion><artifactId>seata-all</artifactId><groupId>io.seata</groupId></exclusion></exclusions></dependency><!-- seata 版本需要与实际使用的吻合 --><dependency><groupId>io.seata</groupId><artifactId>seata-all</artifactId><version>1.6.0</version></dependency>
12.4.2 配置文件
spring:# 服务名称application:# 订单服务name: seata-order-servicecloud:nacos:discovery:server-addr: localhost:8848 # Nacos 服务注册中心地址namespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976config:server-addr: localhost:8848 # Nacos 配置中心地址file-extension: yaml # 指定yaml格式的配置group: SEATA_GROUPnamespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976sentinel:transport:# 配置Sentinel dashboard地址dashboard: localhost:8080# 默认8719端口,加入被占用会自动从8719开始依次+1扫描,知道找到未被占用的端口port: 8719alibaba:seata:# 自定义事务组名称需要与seata-server中的对应tx-service-group: default_tx_group# seata 配置, 代替file.conf和registry.conf配置
sfs:nacos:server-addr: 127.0.0.1:8848namespace: c7e4e5e4-8693-40e1-b75e-1ce1b46bf976group: SEATA_GROUPusername: nacospassword: nacos
seata:enabled: trueapplication-id : ${spring.application.name}tx-service-group: default_tx_groupuse-jdk-proxy: trueenable-auto-data-source-proxy: trueregistry:type: nacosnacos:application: seata-serverserver-addr: ${sfs.nacos.server-addr}namespace: ${sfs.nacos.namespace}group: ${sfs.nacos.group}username: ${sfs.nacos.username}password: ${sfs.nacos.username}config:type: nacosnacos:server-addr: ${sfs.nacos.server-addr}namespace: ${sfs.nacos.namespace}group: ${sfs.nacos.group}username: ${sfs.nacos.username}password: ${sfs.nacos.username}service:vgroupMapping:default_tx_group: default
12.4.3 订单服务主业务TOrderServiceImpl
@Service
@Slf4j
public class TOrderServiceImpl implements TOrderService {@Resourceprivate TOrderDao tOrderDao;@Resourceprivate StorageService storageService;@Resourceprivate AccountService accountService;/*** 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态* @param tOrder* @return*/@Overridepublic boolean create(TOrder tOrder) {//1.创建订单log.info("----->开始创建订单");tOrder.setStatus(0);tOrderDao.insert(tOrder);//2.扣减库存log.info("----->订单微服务开始调用库存,做扣减Count");storageService.decrease(tOrder.getProductId(),tOrder.getCount());log.info("----->订单微服务开始调用库存,做扣减end");//3.扣减账户log.info("----->订单微服务开始调用账户,做扣减Money");accountService.decrease(tOrder.getUserId(),tOrder.getMoney());log.info("----->订单微服务开始调用账户,做扣减end");//4.修改订单状态,0=>1log.info("----->修改订单状态开始");boolean flag = this.updateStatus(tOrder.getUserId(), 1);log.info("----->修改订单状态结束");log.info("----->下订单,结束了,O(∩_∩)O哈哈~");return flag;}@Overridepublic boolean updateStatus(Long userId,Integer status) {QueryWrapper<TOrder> wrapper = new QueryWrapper<>();wrapper.eq("user_id", userId);TOrder tOrder = new TOrder();tOrder.setStatus(status);return tOrderDao.update(tOrder, wrapper)>0;}
}
12.4.4 库存服务 TStorageServiceImpl
@Service
@Slf4j
public class TStorageServiceImpl implements TStorageService {@Resourceprivate TStorageDao dao;/*** 扣减库存** @param productId* @param count* @return*/@Overridepublic boolean decrease(Long productId, Integer count) {log.info("------>seata-storage-service中扣减库存开始");QueryWrapper<TStorage> wrapper = new QueryWrapper<>();wrapper.eq("product_id", productId);TStorage tStorage = dao.selectOne(wrapper);TStorage newTStorage = new TStorage();newTStorage.setUsed(tStorage.getUsed() + count);newTStorage.setResidue(tStorage.getResidue() - count);return dao.update(newTStorage, wrapper) > 0;}
}
12.4.5 账户服务 TStorageServiceImpl
@Service
@Slf4j
public class TAccountServiceImpl implements TAccountService {@Resourceprivate TAccountDao dao;@Overridepublic Boolean decrease(Long userId, BigDecimal money) {log.info("------>开始扣减账户");//模拟超时异常,全局事务回滚try {TimeUnit.SECONDS.sleep(20);}catch (InterruptedException e){e.printStackTrace();}QueryWrapper<TAccount> param = new QueryWrapper<>();param.eq("user_id",userId);TAccount tAccount = dao.selectOne(param);QueryWrapper<TAccount> wapper = new QueryWrapper<>();wapper.eq("user_id",userId);TAccount newTAccount = new TAccount();newTAccount.setUsed(tAccount.getUsed().add(money));newTAccount.setResidue(new BigDecimal(Double.toString(tAccount.getResidue().subtract(money).doubleValue())));log.info("------>结束扣减账户");return dao.update(newTAccount,wapper)>0;}
}
12.4.5 故障情况
当库存和账户扣减后,订单状态并没有改变,二期由于Feign的重试机制,账户余额还有可能重复扣减
12.4.6 使用Seata对数据源进行代理
MyBatis版
@Configuration
public class DataSourceMyBatisConfig {@Value("${mybatis-Plus.mapper-locations}")private String mapperLocations;@Bean@Primary//让MyBatis-Plus优先使用我们配置的数据源@ConfigurationProperties(prefix = "spring.datasource")public DataSource druidDatasource() {return new DruidDataSource();}@Beanpublic DataSourceProxy dataSourceProxy(DataSource dataSource) {return new DataSourceProxy(dataSource);}@Beanpublic SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSourceProxy);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());return sqlSessionFactoryBean.getObject();}}
MyBatis-Plus版
/*** DataSourceProxyConfig :* 使用Seata对数据源进行代理** @author zyw* @create 2023/7/10*/
@Configuration
@MapperScan("com.zyw.springcloud.dao")
public class DataSourceMyBatisPlusConfig {@Value("${mybatis-Plus.mapper-locations}")private String mapperLocations;@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSource druidDataSource(){return new DruidDataSource();}@Primary@Bean("dataSource")public DataSourceProxy dataSource(DataSource druidDataSource){return new DataSourceProxy(druidDataSource);}/*** 配置mybatis-plus的分页* @return*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//指定数据库return interceptor;}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy)throws Exception{MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSourceProxy);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));// 配置spring的本地事务sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());// 配置mybatis-plus的log打印MybatisConfiguration cfg = new MybatisConfiguration();cfg.setJdbcTypeForNull(JdbcType.NULL);cfg.setMapUnderscoreToCamelCase(true);cfg.setCacheEnabled(false);cfg.setLogImpl(StdOutImpl.class);sqlSessionFactoryBean.setConfiguration(cfg);return sqlSessionFactoryBean.getObject();}
}
12.5 seata原理
12.5.1 业务执行流程
- TM开启分布式事务(TM向TC注册全局事务记录);
- 按业务场景,编排数据库、服务等事务内资源(RM向TC汇报资源准备状态);
- TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务);
- TC汇总事务信息,决定分布式事务是提交还是回滚;
- TC通知所有RM提交/回滚资源,事务二阶段结束。
12.5.2 AT模式
前提:
-
给予支持本地ACID事务的关系型数据库。
-
Java应用,通过JDBC访问数据库。
整体机制:
-
一阶段:业务数据和回归日志记录在同一个本地事务中提交,释放本地锁和连接资源
-
二阶段:提交异步化,非常快速的完成;回滚通过一阶段的回滚日志进行反向补偿
一阶段加载
在一阶段,Seata会拦截“业务SQL” ,
1解析SQL语义,找到“业务SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,(前置镜像)
2执行“业务SQL”更新业务数据,在业务数据更新之后,
3其保存成“after image”,最后生成行锁。
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性
二阶段提交
因为业务SQL在一阶段已经提交至数据库,所有Seata框架只需将一阶段保存的快照数据和行锁删除,完成数据清理即可。
二阶段回滚
二阶段如果是回滚的话,Seata就需要回滚一阶段已经执行的“业务SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
相关文章:

SpringCloud Alibaba微服务分布式架构组件演变
文章目录 1、SpringCloud版本对应1.1 技术选型依据1.2 cloud组件演变: 2、Eureka2.1 Eureka Server : 提供服务注册服务2.2 EurekaClient : 通过注册中心进行访问2.3 Eureka自我保护 3、Eureka、Zookeeper、Consul三个注册中心的异同点3.1 CP…...

【Linux】初步理解操作系统和进程概念
一.认识操作系统 操作系统是一款纯正的 “搞管理” 的文件。 那操作系统为什么要管理文件? “管理” 又是什么? 它是怎么管理的? 为什么? 1.操作系统帮助用户,管理好底层的软硬件资源; 2.为了给用户提供一个…...
TypeScript 中的字面量类型和联合类型特性
字面量类型和联合类型是 TypeScript 中常用的类型特性。 1. 字面量类型: 字面量类型是指具体的值作为类型。例如,字符串字面量类型可以通过给定的字符串字面量来限制变量的取值范围。 let status: "success" | "error"; // status…...
react+jest+enzyme配置及编写前端单元测试UT
文章目录 安装及配置enzyme渲染测试技巧一、常见测试二、触发ant design组件三、使用redux组件四、使用路由的组件五、mock接口网络请求六、mock不需要的子组件 安装及配置 安装相关库: 首先,使用npm或yarn安装所需的库。 npm install --save-dev jest…...

自学网络安全(黑客)
一、为什么选择网络安全? 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地,网络安全行业地位、薪资随之水涨船高。 未来3-5年,是安全行业的黄金发展期,提前踏入…...

【unity小技巧】委托(Delegate)的基础使用和介绍
文章目录 一、前言1. 什么是委托?2. 使用委托的优点 二、举例说明1. 例12. 例2 三、案例四、泛型委托Action和Func1. Action委托2. Func委托 五、参考六、完结 一、前言 1. 什么是委托? 在Unity中,委托(Delegate)是一…...
【MySQL必知必会】第24章 使用游标(学习笔记)
游标 游标(cursor)是一个存储在MySQL服务器上的数据库查询,它不是一条select语句,而是被该语句检索出来的结果集游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改只能用于存储过程,不…...
rosbag回放指定话题外的其他话题的方法
假设要回放file.bag包中除/tf话题外的所有话题 方法一 将原本/tf话题转发到另一个“黑洞话题”去,这样/tf话题就没输出了 rosbag play file.bag /tf:/tf_dev_null方法二 使用filter选项,重新生产一个新的不含/tf话题的包 rosbag filter file.bag fi…...

6.2.1 网络基本服务---域名解析系统DNS
6.2.1 网络基本服务—域名解析系统DNS 因特网是需要提供一些最基本的服务的,今天我们就来讨论一下这些基本的服务。 域名系统(DNS)远程登录(Telnet)文件传输协议(FTP)动态主机配置协议&#x…...

通用文字识别OCR 之实现自动化办公
摘要 随着技术的发展,通用文字识别(OCR)已经成为现代办公环境中不可或缺的工具之一。OCR技术可以将印刷或手写文本转换为可编辑或可搜索的数字文本,极大地提高了办公效率并实现了自动化办公。本文将深入探讨OCR技术在实现自动化办…...

Spring Boot 有哪些特点?
目录 一、自动配置 二、嵌入式 Tomcat Web 服务器 三、入门 POM 四、Actuator执行器 API 五、SpringBoot初始化器 一、自动配置 Spring Boot的自动配置是Spring Boot框架提供的一种功能,它可以根据用程序的依赖和配置信息,自动配置一些常见的功能模…...

10个图像处理的Python库
在这篇文章中,我们将整理计算机视觉项目中常用的Python库,如果你想进入计算机视觉领域,可以先了解下本文介绍的库,这会对你的工作很有帮助。 1、PIL/Pillow Pillow是一个通用且用户友好的Python库,提供了丰富的函数集…...

项目里不引入外网链接的解决方法
在写轮播的时候,引入了这个外网的资源是不对的 解决方法: 去外网上把文件下载下来,放在src文件夹下即可 在下面路径下引入下载的文件即可...

Java的jdk配置成功,但是输入java -version等,命令行没有任何反应
问题:现在有很多学生出现这种情况, Java的jdk配置成功,但是输入java -version等,命令行没有任何反应 Java下载后,手动配置环境变量,并且配置好,但是在命令行中无论输入java的什么都没有反应 问…...

MySQL select查询练习
一、创表并插入数据 创表: CREATE TABLE worker (部门号 int NOT NULL,职工号 int NOT NULL,工作时间 date NOT NULL,工资 float(8,2) NOT NULL,政治面貌 varchar(10) NOT NULL DEFAULT 群众,姓名 varchar(20) NOT NULL,出生日期 date NOT NULL,性别 char(1) DEFAU…...

Github 标星 60K,不愧是阿里巴巴内部出厂的“Java 核心面试神技”
前言 作为一个 Java 程序员,你平时总是陷在业务开发里,每天噼里啪啦忙敲着代码,上到系统开发,下到 Bug 修改,你感觉自己无所不能。然而偶尔的一次聚会,你听说和自己一起出道的同学早已经年薪 50 万&#x…...
git 使用教程
git 使用手册 参考链接: https://blog.csdn.net/wanjun_007/article/details/126770712 git给远程仓库添加分支并上传文件 注意:git init 是建一个自己的本地仓 0 : 先git clone master 分支 1. 先pull master分支 2. git checkout -b &q…...

【Vue2.0源码学习】模板编译篇-模板解析阶段(HTML解析器)
文章目录 1. 前言2. HTML解析器内部运行流程3. 如何解析不同的内容3.1 解析HTML注释3.2 解析条件注释3.3 解析DOCTYPE3.4 解析开始标签3.5 解析结束标签3.6 解析文本 4. 如何保证AST节点层级关系5. 回归源码5.1 HTML解析器源码5.2 parseEndTag函数源码 6. 总结 1. 前言 上篇文…...
ARM裸机开发-串口通信
一、在使用EXYNOS4412的串口发送和接收的时候,首先要对EXYNOS4412的串口进行配置,我们使用轮询方式时的配置有哪些? 1、配置GPIO,使对应管脚作为串口的发送和接收管脚 GPA0CON寄存器[7:4][3:0] 0x22 GPA0PUD寄存器[3:0] 0 禁止上…...

Dubbo分布式服务框架,springboot+dubbo+zookeeper
一Dubbo的简易介绍 1.Dubbo是什么? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。 简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...

Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...
React父子组件通信:Props怎么用?如何从父组件向子组件传递数据?
系列回顾: 在上一篇《React核心概念:State是什么?》中,我们学习了如何使用useState让一个组件拥有自己的内部数据(State),并通过一个计数器案例,实现了组件的自我更新。这很棒&#…...