SpringCloud
SpringCloud-Alibaba
<spring-cloud.version>Hoxton.SR8</spring-cloud.version> <mybatis.plus.version>3.3.0</mybatis.plus.version> <mysql.version>5.1.47</mysql.version> <alibaba.version>2.2.5.RELEASE</alibaba.version>
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency>
|
Http客户端
RestTemplate
- spring自带,编程式发起网络请求,访问其他服务
1、配置
@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }
|
2、发起请求
String url = "http://localhost:8091/user/" +order.getUserId();
User user= restTemplate.getForObject(url, User.class);
String url = "http://userservice/user/" +order.getUserId();
User user= restTemplate.getForObject(url, User.class);
|
@GlobalTransactional public ResponseResult fashOrder(Integer seckillId, OrderVO orderVO) throws InterruptedException { RLock lock = redissonClient.getLock("lock:seckillInfo" + seckillId); boolean isLock = lock.tryLock(5, 30, TimeUnit.SECONDS); try { if (isLock) { SeckillCombined seckillCombined = seckillCombinedMapper.selectByPrimaryKeyAndProductId(seckillId, productId); }else{ return ResponseResult.errorResult(SalesHttpCodeEnum.UNKOWN); } }catch (Exception e) { }finally { lock.unlock(); } }
|
Feign
Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
Feigin是通过寻找业务是接口通过url绑定的业务:根据当前接口绑定的url寻找业务
Dubbo是通过业务绑定的接口:根据接口,寻找绑定该接口的业务
- Dubbo:Dubbo是一个由阿里巴巴开发的高性能RPC(远程过程调用)框架,用于构建分布式服务。它着重于远程方法调用和服务治理,包括服务注册、发现、负载均衡和熔断等。
- OpenFeign:OpenFeign是一个声明式的HTTP客户端框架,用于方便地编写HTTP API客户端。它的主要目的是简化RESTful服务的调用,而不是像Dubbo那样用于RPC。
编写消费者(客户端)
- 在要使用远程调用的服务上
- 提供者(服务端)不需要配置
1、依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
|
2、开启自动装配
- 启动类上开启自动装配 @EnableFeignClients
3、编写远程接口 (Feign模块)
@FeignClient("userservice") public interface UserClient {
@GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }
|
@GetMapping("/user/{id}") public User queryById(@PathVariable("id") Long id) { }
|
4、调用请求
@Resource private UserClient userClient;
User user = userClient.findById(order.getUserId());
|
自定义配置

日志
1、在配置文件配置
feign: client: config: default: logger-level: Basic xxx服务: logger-level: Basic
|
2、在java代码中配置
public class DefauleFeignConfiguration { @Bean public Logger.Level logLevel(){ return Logger.Level.BASIC; } }
|
- 局部配置:(在发起请求的类上) 加入到 @FeignClient 注解中
@FeignClient(value = "服务名称",configuration = DefauleFeignConfiguration.class)
|
- 全局配置:(启动类上) 加入到 @EnableFeignClients 注解中
@EnableFeignClients(defaultConfiguration = DefauleFeignConfiguration.class)
|
熔断降级
@Component public class IArticleClientFallback implements IArticleClient { @Override public ResponseResult saveArticle(ArticleDto articleDto) { return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"获取数据失败"); } }
|
@FeignClient(value = "leadnews-article",fallback = (IArticleClientFallback.class))
|
@Configuration @ComponentScan("com.heima.api.article.fallback") public class InitConfig { }
|
feign: # 开启feign对hystrix熔断降级的支持 hystrix: enabled: true # 修改调用超时时间 client: config: default: connectTimeout: 2000 readTimeout: 2000
|
性能优化
使用HttpClient
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
|
feign:
httpclient: enabled: true max-connections: 200 max-connections-per-route: 50
|
实践方案
1、继承

2、抽取模块

①把需要抽取的类加入到 feign-api 模块(maven)中
- 创建模块
- 引入依赖
- 将 客户端 的 ***client、实体类、feign配置集成到 该模块中
@FeignClient(value = "userservice",configuration = DefauleFeignConfiguration.class) public interface UserClient {
@GetMapping("/user/{id}") User findById(@PathVariable("id") Long id);
}
|
②在客户端模块中依赖添加 feign-api 模块依赖
<dependency> <groupId>cn.itcast</groupId> <artifactId>feign-api</artifactId> <version>1.0</version> </dependency>
|
③由于当前服务模块启动没有扫描到 feign-api 模块 的 @FeignClient 请求类。无法自动注入
@Resource private UserClient userClient;
userClient.findById(1112);
|
- 需要在服务的启动类中的自动装配Feign的注解中加入 clients 设置扫描指定的包
@EnableFeignClients( basePackages = "扫描feign-api模块的clients包")
@EnableFeignClients( clients = {UserClient.class} )
@ComponentScan("cn.itcast.feign")
@EnableFeignClients( clients = {UserClient.class} defaultConfiguration = DefauleFeignConfiguration.class )
|
Eureka【注册中心】

服务提供者启动时向eureka注册自己的信息
服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
服务消费者利用负载均衡算法,从服务列表中挑选一个
【*】搭建服务中心
1、引入依赖 – server
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
|
2、【*】开启
@EnableEurekaServer
@SpringBootApplication public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class,args); } }
|
3、配置服务注册
server: port: 10086
spring: application: name: eurekaserver
eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka
|
4、运行界面查看所有注册的服务
【*】服务注册
1、引入依赖 – client
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
|
2、配置eureka地址:进行服务注册
spring: application: name: eurekaserver
eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka
|
总结:
1.服务注册
2.无论是消费者还是提供者,引入eureka-client依赖
【*】服务发现
1、URL
String url = "http://userservice/user/" +order.getUserId();
User user= restTemplate.getForObject(url, User.class);
|
2、开启负载均衡
@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }
|
饥饿、懒加载
ribbon: eager-load: enabled: true clients: userservice - - userservice - **service
|
Ribbon负载均衡



Ribbon – 负载均衡规则
- 默认 轮询,可通过 配置类 或者 配置文件 进行自定义规则
1、【方式一】置类更改规则为:随机
@Bean public IRule randowmIRule(){ return new RandomRule(); }
|
2、【方式二】配置文件 – 针对服务设置负载均衡规则
调用者
userservice: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
|
userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
|
Nacos【注册中心】
Nacos可以用于服务注册、发现、配置管理和健康检查等功能,是微服务架构中的关键组件之一。
Dubbo提供了服务注册、发现、负载均衡和远程调用等功能,使分布式应用程序能够相互通信。
安装
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
搭建
bin:启动脚本
conf:配置文件
Nacos的默认端口是8848
startup.cmd -m standalone
|
地址:http://127.0.0.1:8848/nacos
默认的账号和密码都是nacos
客户端
父工程:管理版本
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.5.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency>
|
客户端:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
|
配置文件
spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: GZ namespace: 309a7db3-c802-4329-94df-2b3b3f6556d9 ephemeral: false
|
集群
1、创建多个服务实例
- 服务上 ctrl + d 复制配置
- 命名
- vm设置:-Dserver.port=端口 (避免原端口冲突)
2、配置文件
spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: GZ
|

负载均衡规则
- 默认:轮询 – 即使相同集群 也会轮询访问其他集群中的服务
1、 同集群优先 - NacosRule
调用者配置文件设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:
userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
|
优先选择同集群服务实例列表
优先同集群,在本地集群的多个服务中随机负载均衡
本地集群找不到提供者,才去其它集群寻找,并且会报警告
2、权重负载均衡
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
3、环境隔离 - namespace
①控制台开启新的命名空间 – 获取命名空间的ID
② 在配置文件写入命名空间的id
spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: GZ namespace: 309a7db3-c802-4329-94df-2b3b3f6556d9
|
与Eurake区别

1.Nacos与eureka的共同点
①都支持服务注册和服务拉取
②都支持服务提供者跳方式做健康检测
2.Nacos与Eureka的区别
①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
②临时实例心跳不正常会被剔除,非临时实例则不会被剔除
③Nacos.支持服务列表变更的消息推送模式,服务列表更新更及时
④Nacos:集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
统一配置管理
1、Nacos控制台创建配置
- 格式推荐yaml全称 : [服务名称]-[环境].[格式]
- 配置内容:写入需要热更新的内容

2、读取nacos中的配置文件

①引入Nacos的配置管理依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
|
② resoure 创建 bootstrap.yml 引导文件,优先级高于 application.yml
- 服务名 - 环境 - 地址 - 文件后缀名 : 匹配 == userservice-dev.yaml文件
spring: application: name: userservice profiles: active: dev cloud: nacos: server-addr: localhost:8848 config: file-extension: yaml
|
③读取nacos中的配置文件
- 和读取本地配置文件一样,启动之前会把nacos的配置文件和本地的配置文件合并
@Value("${pattern.dateformat}") private String dateformat;
|
@Data @Component @ConfigurationProperties(prefix = "pattern") public class PatterProperties { private String dateformat; }
|
3、配置自动刷新
①**@RefreshScope**
- 热更新:即不需要项目重启,配置文件在线更改项目热更新读取
- 通过@Value注解注入,结合@RefreshScope来刷新
@RefreshScope public class UserController { @Value("${pattern.dateformat}") private String dateformat; }
|
②通过**@ConfigurationProperties注入,自动刷新**
@ConfigurationProperties(prefix = "pattern") public class PatterProperties { private String dateformat; }
|
多环境配置共享

- [服务名称 spring.application.name] - [环境 spring.profiles.activel].yaml
- 无论服务是哪个环境的配置文件,按需引入加载,但设置一个不分环境的配置,无论什么环境都会被加载,因此多环境共享配置可以写入这个文件
- 创建一个 [服务名称 spring.application.name] .yaml
例:
userservice.yaml [必定加载:多环境共享配置]
userservice-dev.yaml [按需加载]

优先级
服务名-profile.yaml > 服务名称.yaml > 本地配置

集群搭建

1、配置nacos数据库
- Nacos默认数据存储在内嵌数据库Derby中,不属于生产可用的数据库。
- 官方推荐的最佳实践是使用带有主从的高可用数据库集群,这里以单点的数据库为例
https://chen-1317386995.cos.ap-guangzhou.myqcloud.com/Java/nacos.sql
2、配置nacos
- 进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:
127.0.0.1:8845 127.0.0.1.8846 127.0.0.1.8847
|
- 然后修改application.properties文件,添加数据库配置
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0=root db.password.0=123
|
- 复制 : 将nacos文件夹复制三份,分别命名为:nacos1、nacos2、nacos3
然后分别修改三个文件夹中的application.properties,
nacos1:
nacos2:
nacos3:
然后分别启动三个nacos节点:
3、nginx反向代理
upstream nacos-cluster { server 127.0.0.1:8845; server 127.0.0.1:8846; server 127.0.0.1:8847; }
server { listen 80; server_name localhost;
location /nacos { proxy_pass http://nacos-cluster; } }
|
访问:http://localhost/nacos即可。
4、java代码更改Nacos地址
spring: cloud: nacos: server-addr: localhost:80
|
统一网关 Gateway
- 对用户请求做身份认证、权限校验
- 将用户请求路由到微服务,并实现负载均衡
- 对用户请求做限流
网关为独立服务,需要注册到Nacos中去

1、搭建
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
|
2、配置类
- 配置application.yml,包括服务基本信息、nacos地址、路由
server: port: 10010 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848
gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** - id: order-service uri: lb://orderservice predicates: - Path=/order/** - Before=2023-09-20T17:42:47.789-07:00[Asia/Shanghai]
|
3、启动项目
访问 :localhost:10010/order/101
实际:localhost:orderservice/order/101
断言工厂
读取用户配置的断言规则,进行设置判断访问条 件

routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** - id: order-service uri: lb://orderservice predicates: - Path=/order/** - Before=2023-09-20T17:42:47.789-07:00[Asia/Shanghai]
|
当前路由过滤器
- 对路由的请求或响应做加工处理,比如添加请求头
- 配置在路由下的过滤器只对当前路由的请求生效
gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** filters: - AddRequestHeader=Truth,Itcast is freaking aowsome!
|
public User queryById(@RequestHeader(value = "Truth",required = false) String truth)
|
全局路由过滤器
- 使用 default-filters: 全局生效,所有的路由实现
gateway: routes: - id: user-service - id: order-service default-filters: - AddRequestHeader=Truth,Itcast is freaking aowsome!
|
读取:
@RequestHeader(value = "Truth",required = false)
|
全局过滤器 GlobalFilter
1、自定义过滤器
过滤器优先级:越小越高
1、注解:@Order(-1)
2、实现 Ordered接口 ,实现方法
- 实现GlobalFilter接口和重写filter方法
例:请求中携带身份信息:authorization=admin
@Order(-1) @Component public class AuthorizeFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest(); MultiValueMap<String, String> params = request.getQueryParams();
String auth = params.getFirst("authorization");
if ("admin".equals(auth)){ return chain.filter(exchange); }
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
@Override public int getOrder() { return -1; } }
|
过滤器执行顺序

跨域
gateway: routes:
globalcors: add-to-simple-url-handler-mapping: true corsConfigurations: '[/**]': allowedOrigins: - "http://localhost:5500" - "http://www.leyou.com" - "*" allowedMethods: - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowedHeaders: "*" allowCredentials: true maxAge: 360000
|
MQ - 消息队列



Kafka
- Topic:Kafka将消息分门别类,每一类的消息称之为一个主题(Topic)
- broker:已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker)。消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。
安装
Kafka对于zookeeper是强依赖,保存kafka相关的节点数据,所l以安装Kafka之前必须先安装zookeeper
docker pull zookeeper:3.4.14
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.4.14
|
docker pull wurstmeister/kafka:2.12-2.3.1
docker run -d --name kafka \ --env KAFKA_ADVERTISED_HOST_NAME=192.168.200.130 \ --env KAFKA_ZOOKEEPER_CONNECT=192.168.200.130:2181 \ --env KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.200.130:9092 \ --env KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \ --env KAFKA_HEAP_OPTS="-Xmx256M -Xms256M" \ --net=host wurstmeister/kafka:2.12-2.3.1
|
<dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> </dependency>
|
使用
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.238.3:9092");
properties.put(ProducerConfig.RETRIES_CONFIG,5);
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String,String> producer = new KafkaProducer<String, String>(properties);
ProducerRecord<String,String> record = new ProducerRecord<String, String>("itheima-topic","100001","hello kafka");
producer.send(record);
producer.close();
|
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.238.3:9092");
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "group2");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(properties);
consumer.subscribe(Collections.singletonList("itheima-topic"));
while (true) { ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000)); for (ConsumerRecord<String, String> consumerRecord : consumerRecords) { System.out.println(consumerRecord.key()); System.out.println(consumerRecord.value()); } }
|
组
- 多个消费者订阅同一个主题,只能有一个消费者收到消息(一对一)
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "group1");
|
分区机制

集群

备份机制


生产者
发送类型
RecordMetadata recordMetadata = producer.send(record).get(); System.out.println(recordMetadata.offset());
|
producer.send(record, new Callback() { @Override public void onCompletion(RecordMetadata recordMetadata, Exception e) { if (e!=null){ System.out.println("记录异常信息到日志表中"); } System.out.println(recordMetadata.offset()); } });
|
消息确认机制
是否发送成功

properties.put(ProducerConfig.ACKS_CONFIG,"all");
|
失败重试
properties.put(ProducerConfig.RETRIES_CONFIG,5);
|

properties.put(ProducerConfig.CONFIG_PROVIDERS_CONFIG,"lz4");
|
消费者

微服务保护
Sentinel



安装控制台
java -jar sentinel-dashboard-1.8.6.jar
|
- 访问 默认8080端口 账号和密码都是 sentinel

JMeter测试高并发
整合
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
|
cloud: nacos: sentinel: transport: dashboard: localhost:8080
|
限流规则 - 流控

流控模式

直接

单机阈值:1s访问次数
直接设置当前端点的阈值
关联

- 两个端点 update 和 query ,对query进行流控(关联 update,当update的单机阈值达到5的时候对 query 进行限流)

链路

测试:两个接口都访问一个方法,要监控访问的方法,限流哪个接口来访问这个方法
@GetMapping("/query") public String queryTest(){ orderService.queryGoods(); return "quey"; } @GetMapping("/save") public String saveTest(){ orderService.queryGoods(); return "save"; }
|
@SentinelResource("goods") public void queryGoods(){ System.err.println("查询商品"); }
|
Sentinel默认会将Controller方法做context整合,导致链路模式的流控失效,
需要修改application.yml,添加配置:
sentinel: transport: dashboard: localhost:8080 web-context-unify: false
|

此时,query访问 good 方法被阻塞,save访问 不会被阻塞
流控效果


Warm up
- 预热模式:避免冷启动时刻高并发,在预热时间内慢慢达到最大阈值QPS

测试:给资源设置限流,最大QPS为10(单机阈值),预热时长为5s

排队等待
- 每个请求根据OPS平均时间为一个请求的处理时间,无论是否提早完成,下一个请求都需要等待这个处理时间完成

测试:

热点参数限流

- 加注解 @SentinelResource(“ “)
@SentinelResource("hot") @GetMapping("{orderId}")
|


测试:


隔离和降级

降级
Feign整合Sentinel
客户端保护 : 整合Feign和Sentinel
1、配置文件
feign: sentinel: enabled: true
|
2、给FeignClient 编写失败后的降级逻辑
方式一:FallbackClass,无法对远程调用的异常做处理
@Slf4j public class UserClientFallbackFactor implements FallbackFactory<UserClient> {
@Override public UserClient create(Throwable throwable) { return new UserClient() { @Override public User findById(Long id) { log.error("查询用户异常",throwable); return new User(); } }; } }
|
把降级逻辑类注册为Bean,类上不加配置类注解
public class DefauleFeignConfiguration {
@Bean public Logger.Level logLevel(){ return Logger.Level.BASIC; }
@Bean public UserClientFallbackFactor userClientFallbackFactor(){ return new UserClientFallbackFactor(); }
}
|
- 远程模块:远程接口上注明 fallbackFactory
@FeignClient( value = "userservice", configuration = DefauleFeignConfiguration.class, fallbackFactory = UserClientFallbackFactor.class )
|
@ComponentScan("cn.itcast.feign") @EnableFeignClients( clients = {UserClient.class}, defaultConfiguration = DefauleFeignConfiguration.class )
|
SpringCloud版本 10 以下
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
|
线程隔离


熔断降级


熔断策略 - 慢调用

统计 10 s 内最小10的请求数量 如果超过最大Rt(慢调用)的比例达到50% 触发 熔断
测试:


异常比例、异常数

- 在代码中出现异常(抛出异常 throw new RuntimeException(“异常提醒,熔断降级”) )
授权规则

给网关的请求增加请求头,而浏览器请求没有特定的请求头
- 网关 : 利用网关的过滤器 添加名为 gateway的origin 头

spring: cloud: gateway: default-filters: - AddRequestHeader=origin,gateway
|
- 服务 : 设置一个bean,获取request中名为origin的请求头
@Component public class HeaderOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { String origin = httpServletRequest.getHeader("origin"); if (StringUtils.isEmpty(origin)){ origin = "blank"; }
return origin; } }
|



- 定义bean,实现 BlockExceptionHandler 接口
@Component public class SentinelExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { String msg = "未知异常"; int status = 429;
if (e instanceof FlowException) { msg = "请求被限流了"; } else if (e instanceof ParamFlowException) { msg = "请求被热点参数限流"; } else if (e instanceof DegradeException) { msg = "请求被降级了"; } else if (e instanceof AuthorityException) { msg = "没有权限访问"; status = 401; }
response.setContentType("application/json;charset=utf-8"); response.setStatus(status); response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}"); } }
|
规则持久化
原始模式
pull 模式

push 模式

搭建
md文档https://chen-1317386995.cos.ap-guangzhou.myqcloud.com/Java/Utils/sentinel%E8%A7%84%E5%88%99%E6%8C%81%E4%B9%85%E5%8C%96.md
依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
|
服务配置
sentinel: datasource: flow: nacos: server-addr: 192.168.238.3:8848 dataId: orderservice-flow-rules groupId: SENTINEL_GROUP rule-type: flow degrade: nacos: server-addr: 192.168.238.3:8848 dataId: orderservice-degrade-rules groupId: SENTINEL_GROUP rule-type: degrade
|
重启服务 – 》
修改sentienl 源码
sentinel修改持久化jar包
- 如果nacos地址不是本地8848:java -jar -Dnacos.addr=192.168.238.3:8848 sentinel-dashboard.jar
分布式事务

Base理论

Seata


部署TC服务
sentinel的部署和集成md
微服务集成Seata
1、依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <exclusion> <artifactId>seata-spring-boot-starter</artifactId> <groupId>io.seata</groupId> </exclusion> </exclusions> </dependency>
<dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>${seata.version}</version> </dependency>
|
2、配置文件

seata: registry: type: nacos nacos: server-addr: 192.168.238.3:8848 namespace: "" group: DEFAULT_GROUP application: seata-tc-server username: nacos password: nacos tx-service-group: seata-demo service: vgroup-mapping: seata-demo: SH
|

XA模式




1、配置文件
seata: data-source-proxy-mode: XA
|
2、全局事务入口方法 上添加注解 - 实现全部服务回滚(需要在方法内抛出异常)
@GlobalTransactional public Long create(Order order) { try { } catch (FeignException e) { throw new RuntimeException(e.contentUTF8(), e); } }
|
AT模式


当事务1和事务二要进行操作时,
事务1:先拿到DB锁,先保存快照->执行业务->提交->释放DB锁
事务2:拿到DB锁,先保存快照->执行业务->提交->释放DB锁
当事务1需要回滚的时候,由于保存的快照是最开始的快照,回到这个阶段,则事务二所有的操作无效




1、配置文件
seata: data-source-proxy-mode: AT
|
2、全局事务入口方法 上添加注解 - 实现全部服务回滚(需要在方法内抛出异常)
@GlobalTransactional public Long create(Order order) { try { } catch (FeignException e) { throw new RuntimeException(e.contentUTF8(), e); } }
|
TCC模式



实践:



1、定义接口 – 和旧业务的接口一样(需要分布式事务功能的接口)
@LocalTCC public interface AccountTCCService {
@TwoPhaseBusinessAction(name = "deduct",commitMethod = "confirm",rollbackMethod = "cancel") void deduct(@BusinessActionContextParameter(paramName = "userId") String userId, @BusinessActionContextParameter(paramName = "money") int money);
boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}
|
2、实现类 – 替换之前的业务(需要分布式事务的业务)
@Component public class AccountTCCServiceImpl implements AccountTCCService {
@Resource private AccountMapper accountMapper;
@Resource private AccountFreezeMapper accountFreezeMapper;
@Override public void deduct(String userId, int money) { String xid = RootContext.getXID();
AccountFreeze oldFreeze = accountFreezeMapper.selectById(xid); if (oldFreeze != null){ return; }
accountMapper.deduct(userId,money);
AccountFreeze accountFreeze = new AccountFreeze(); accountFreeze.setUserId(userId); accountFreeze.setFreezeMoney(money); accountFreeze.setState(AccountFreeze.State.TRY); accountFreeze.setXid(xid);
accountFreezeMapper.insert(accountFreeze);
}
@Override public boolean confirm(BusinessActionContext context) { String xid = context.getXid();
int count = accountFreezeMapper.deleteById(xid);
return count == 1; }
@Override public boolean cancel(BusinessActionContext context) { String xid = context.getXid(); String userId = context.getActionContext("userId").toString(); AccountFreeze accountFreeze = accountFreezeMapper.selectById(xid);
if (accountFreeze == null){ accountFreeze = new AccountFreeze(); accountFreeze.setUserId(userId); accountFreeze.setFreezeMoney(0); accountFreeze.setState(AccountFreeze.State.CANCEL); accountFreeze.setXid(xid); accountFreezeMapper.insert(accountFreeze); return true; } if (accountFreeze.getState() == AccountFreeze.State.CANCEL){ return true; }
accountMapper.refund(accountFreeze.getUserId(),accountFreeze.getFreezeMoney());
accountFreeze.setFreezeMoney(0); accountFreeze.setState(AccountFreeze.State.CANCEL); int count = accountFreezeMapper.updateById(accountFreeze); return count == 1;
} }
|
Saga模式

高可用-集群
sentinel的集群部署md
