Nacos
- Nacos是一个更易于构建云原生应用的动态服务发现/服务配置和服务管理平台
- 核心功能:
- 服务注册: Nacos Client会通过发送REST请求向Nacos Server注册自己的服务, 提供自己的元数据, 如ip地址/端口等信息; Nacos Server收到注册请求后, 就会把这些信息存储在Map中
- 服务心跳: 在服务注册后, Nacos Client会维护一个定时心跳来持续通知Nacos Server, 说明服务一致处于可用状态, 防止被剔除 默认5s发送一次心跳
- 服务发现: 服务消费者在调用服务提供者的服务时, 会发送一个REST请求给Nacos Server, 获取上面注册的服务清单, 并且缓存在Nacos Client本地; 同时在Nacos Client本地开启一个定时任务定时拉取最新的注册表信息到本地缓存
- 服务健康检查: Nacos Server会开启一个定时任务来检查注册服务实例的健康情况, 对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false; 如果某个实例超过30s没有收到心跳, 直接剔除该实例
主流注册中心对比
指标 | Nacos | Eureka | Consul | CoreDNS | Zookeeper |
---|---|---|---|---|---|
一致性协议 | CP/AP | AP | CP | - | CP |
健康检查 | TCP/HTTP/MySQL/Client Beat | Client Beat | TCP/HTTP/gRPC/Cmd | - | - |
负载均衡策略 | 权重/metadata/Selector | Ribbon | Fabio | RoundRobin | - |
雪崩保护 | 有 | 有 | 无 | 无 | 无 |
自动注销实例 | 支持 | 支持 | 支持 | 不支持 | 支持 |
访问协议 | HTTP/DNS | HTTP | HTTP/DNS | DNS | TCP |
监听支持 | 支持 | 支持 | 支持 | 不支持 | 支持 |
多数据中心 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
跨注册中心同步 | 支持 | 不支持 | 支持 | 不支持 | 不支持 |
SpringCloud集成 | 支持 | 不支持 | 支持 | 不支持 | 支持 |
Dubbo集成 | 支持 | 不支持 | 支持 | 不支持 | 支持 |
K8S集成 | 支持 | 不支持 | 支持 | 支持 | 不支持 |
基本使用
集群部署1
apiVersion: v1
kind: Service
metadata:
name: nacos-svc
spec:
type: LoadBalancer
ports:
- port: 8848
name: server
targetPort: 8848
- port: 9848
name: client-rpc
targetPort: 9848
- port: 9849
name: raft-rpc
targetPort: 9849
## 兼容1.4.x版本的选举端口
- port: 7848
name: old-raft-rpc
targetPort: 7848
selector:
app: nacos
---
apiVersion: v1
kind: Service
metadata:
name: nacos-headless
labels:
app: nacos-headless
spec:
type: ClusterIP
clusterIP: None
ports:
- port: 8848
name: server
targetPort: 8848
- port: 9848
name: client-rpc
targetPort: 9848
- port: 9849
name: raft-rpc
targetPort: 9849
## 兼容1.4.x版本的选举端口
- port: 7848
name: old-raft-rpc
targetPort: 7848
selector:
app: nacos
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nacos-cm
data:
mysql.host: "server.passnight.local"
mysql.db.name: "nacos_devtest"
mysql.port: "3306"
mysql.user: "nacos"
mysql.password: "*****************"
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nacos
spec:
serviceName: nacos-headless
replicas: 3
template:
metadata:
labels:
app: nacos
annotations:
pod.alpha.kubernetes.io/initialized: "true"
spec:
containers:
- name: nacos
imagePullPolicy: Always
image: nacos/nacos-server:latest
resources:
requests:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 8848
name: client
- containerPort: 9848
name: client-rpc
- containerPort: 9849
name: raft-rpc
- containerPort: 7848
name: old-raft-rpc
env:
- name: NACOS_REPLICAS
value: "3"
- name: MYSQL_SERVICE_HOST
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.host
- name: MYSQL_SERVICE_DB_NAME
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.db.name
- name: MYSQL_SERVICE_PORT
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.port
- name: MYSQL_SERVICE_USER
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.user
- name: MYSQL_SERVICE_PASSWORD
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.password
- name: SPRING_DATASOURCE_PLATFORM
value: "mysql"
- name: MYSQL_SERVICE_DB_PARAM
value: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
- name: NACOS_SERVER_PORT
value: "8848"
- name: NACOS_APPLICATION_PORT
value: "8848"
- name: PREFER_HOST_MODE
value: "hostname"
- name: NACOS_SERVERS
value: "nacos-0.nacos-headless.default.svc.cluster.local:8848 nacos-1.nacos-headless.default.svc.cluster.local:8848 nacos-2.nacos-headless.default.svc.cluster.local:8848"
selector:
matchLabels:
app: nacos
注意这里会报错
error: error validating "nacos.yml": error validating data: ValidationError(StatefulSet.spec.template.metadata): unknown field "spec" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta; if you choose to ignore these errors, turn validation off with --validate=false
需要添加一层spec
标签2
再登录会报错No DataSource set
需要配置MySQL8
的一些常用配置项3并自己执行初始化脚本4; 需要执行对应版本的sql脚本nacos/distribution/conf/nacos-mysql.sql at 2.0.3 · alibaba/nacos (github.com) docker:latest对应的是2.0.3
这里把节点亲和性给去掉了; 允许所有节点部署Nacos
然后使用用户名nacos
密码nacos
就可以登陆了
java应用代码
使用nacos首先要引入Maven依赖
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后再application.properties
添加必要的配置
# 这个对应服务名
spring.application.name=stock-service
spring.cloud.nacos.server-addr=192.168.100.73:8848
# 后面三项式默认配置
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.namespace=public
之后就可以在服务列表中看到相应的服务了
Nacos服务发现
在服务注册后, 就可以通过服务名进行调用和负载均衡了
首选准备带负载均衡功能的客户端:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
然后准备两个服务
@AllArgsConstructor
@RestController
@RequestMapping("/OrderController")
public class OrderController {
private final RestTemplate restTemplate;
@GetMapping("/add")
public String add() {
String response = restTemplate.getForObject("http://stock-service/StockService/reduce", String.class);
return "In order-service, stock-service response: " + response;
}
}
@RestController
@RequestMapping("/StockService")
public class StockController {
@GetMapping("/reduce")
public String reduce() {
return "reduce stock";
}
}
之后调用订单服务, 订单服务就会调用到库存服务 默认的负载均衡器是robbin
passnight@passnight-s600:~$ curl localhost:8010/OrderController/add
In order-service, stock-service response: reduce stock
配置中心使用
- Nacos提供用于配置存储和其他元数据的key/value存储; 为分布式系统中的外部化配置提供服务器端和客户端支持. 使用Spring Cloud Alibaba Nacos Config, 可以在Nacos Server中集中管理配置
- 使用Nacos有以下好处
- 易维护
- 时效性
- 安全性
Dubbo
- Dubbo以一款高性能的RPC框架
- 面向接口代理的高性能RPC调用: 为开发者屏蔽调用的底层细节
- 智能负载均衡
- 服务自动注册与发现
- 高扩展: 基于微内核+插件设计; 几乎所有的核心能力都支持第三方实现
- 运行期流量调度: 通过配置路由规则, 可以实现灰度发布/同机房优先等功能
- 可视化的服务治理与运维: 提供可视化的运维工具和服务治理工具
使用
安装dubbo-admin
version: "3.0"
services:
dubbo-admin:
image: apache/dubbo-admin:0.6.0
container_name: dubbo-admin
ports:
- "20018:38080"
volumes:
- /opt/docker/dubbo-admin/data:/data
environment:
admin.registry.address: nacos://192.168.100.71:8848
admin.config-center: nacos://192.168.100.71:8848
admin.metadata-report.address: nacos://192.168.100.71:8848
restart: always
然后使用docker-compose up -d
启动即可
暴露服务
-
引入dubbo依赖
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.23</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.10.0</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-nacos</artifactId> <version>2.7.23</version> </dependency>
原理
负载均衡机制
RandomLoadBalance
: 基于权重的随机负载均衡RoundRobinLoadBalance
: 基于权重的轮询负载均衡 差分轮询算法LeastActiveLoadBalance
: 最少活跃数负载均衡机制, 总是选择响应速度最快的服务ConsistentHashLoadBalance
: 基于一致性哈希算法的负载均衡机制
Sentinel
基本概念
-
在微服务调用链路中某个服务故障, 引起整个链路中的微服务都不可用, 即雪崩
-
解决雪崩的方法
- 超时处理: 设定超时时间, 请求超过一定时间没有响应就返回错误信息, 不会无休止等待
- 仓壁模式: 限制每个业务所能够使用的资源, 以避免部分业务耗尽整个系统的资源; 即线程隔离
- 熔断降级: 由断路器统计业务执行的异常比例, 如果超出阈值则熔断该业务, 拦截访问该业务的一切请求
- 流量控制: 限制业务访问的QPS; 避免服务因为流量突增而故障
-
实现服务保护的技术对比:
技术 Sentinel Hystrix 隔离策略 信号量 线程池/信号量 熔断降级策略 基于慢调用比例或异常比例 基于失败比例 实时指标实现 滑动窗口 滑动窗口(基于RxJava) 规则配置 支持多种数据源 支持多种数据源 扩展性 多个扩展点 插件形式 基于注解的支持 支持 支持 线瘤 基于QPS, 支持基于调用关系的限流 有限的支持 流量整形 支持慢启动/匀速排队模式 不支持 系统自适应保护 支持 不支持 控制台 开箱即用, 可配置规则, 查看秒级监控, 机器发现等 不完善 常见的框架的适配 Servlet/Spring Cloud/ Dubbo/ gRPC等 Servlet/ Spring Cloud Netfix
基本使用
-
安装
dashboard
version: "2.1" services: sentinel-dashboard: image: bladex/sentinel-dashboard:1.8.0 container_name: sentinel-dashboard ports: - "20019:8858"
-
引入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
配置dashboard地址
spring.cloud.sentinel.transport.dashboard=server.passnight.local:20019
-
之后就可以在dashboard中看到请求信息了
-
选中端点并配置流量限制; 然后就可以实现流控了:
限流规则
- 簇点链路: 即项目中的调用链路, sentinel默认监控SpringMVC中的端点
- 流控模式:
-
关联: 统计与当前资源相关的另一个资源, 触发阈值时, 触发当前资源的限流 当read QOS达到阈值时, 对write限流
-
链路模式: 根据请求来源进行限流; 使用
@SentinelResource
将服务中的方法设置为资源 -
热点参数请求: 根据参数进行限流, 需要添加
@SentinelResource
才能生效 查询下, 对不同资源分别限流
-
- 流控效果:
- 快速失败: 达到阈值后, 新的请求会立即被拒绝, 并抛出异常 默认处理方式
- 预热模式: 对超出阈值的请求同样抛出异常, 但阈值会动态变化, 从最小值增加到最大值; 初识阈值为 t h r e s h o l d c o l d F a c t o r \frac{threshold}{coldFactor} coldFactorthreshold; 等待预热时间后达到阈值QPS
- 排队等待: 将请求放入队列, 直到预期等待时长请求超出等待时长才会拒绝请求 流量整形
隔离和降级
-
熔断降级: 断路器统计服务调用的异常比例/慢比例; 若超过阈值就会熔断该服务, 当服务恢复时自动放行该服务的请求 熔断时解决雪崩的重要手段, 即当服务异常则停止其提供服务
-
熔断策略: 熔断策略有三种, 慢调用/异常比例/异常数
- 慢调用: 业务的相应时长(RT), 大于指定时长请求认定为慢调用请求, 在指定时间内, 如果请求数量超过最小数量, 慢调用比例大于设定值, 则触发熔断
- 异常比例/异常数: 指定事件内的调用, 若调用次数超过指定请求数, 且异常比例/异常数超过阈值, 则触发熔断
-
授权规则: 根据调用方来源进行控制, 有白名单和黑名单两种方式例如绕过网关则block, 可以通过添加http header实现
-
自定义异常: 实现
BlockExceptionHandler
接口即可
规则持久化
-
Sentinel控制台的规则管理有三种模式:
-
原始模式: 将规则保存在内存当中, 重启服务会丢失 默认模式
-
pull模式: 控制台将配置的规则推送到Sentinel客户端, 而客户端会将规则持久化, 之后定时去本地文件或数据库中查询, 更新本地规则
-
push模式: 控制台将配置规则推送到配置中心, 服务监听配置中心 需要自己实现
-
实现原理
Hystrix实现原理5
- 根据Hystrix的流程图来看, Hystrix主要实现的功能有三点:
- 缓存请求: 若请求结果在缓存当中, 则直接返回缓存中的结果
- 断路保护: 若命中了断路规则, 则直接断路, 执行fallback函数
- 池化请求: 由线程池(信号量)创建并执行请求; 因此若发生异常, 可以将异常隔离在Hystrix的线程池当中
Seata
项目准备
CREATE TABLE account_t
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id VARCHAR(64),
money DOUBLE
);
CREATE TABLE storage_t
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
commodity_code VARCHAR(64),
count INT
);
CREATE TABLE order_t
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id VARCHAR(64),
commodity_code VARCHAR(64),
count INT,
money DOUBLE
);
INSERT INTO account_t (user_id, money) VALUE ('user1', 1000);
INSERT INTO storage_t(commodity_code, count) VALUE ('00001', 10);
请求
curl -X POST -d '{"userId": "user1", "commodityCode": "00001", "count": 2, "money": 200}' --header "Content-Type: application/json" server.passnight.local:8010/OrderController/create-order
触发库存不足异常:
curl -X POST -d '{"userId": "user1", "commodityCode": "00001", "count": 20, "money": 200}' --header "Content-Type: application/json" server.passnight.local:8010/OrderController/create-order
OrderService
因为rpc调用失败, 且标注了@Transactional
所以事务回滚; StockService
因为异常所以没有扣减库存; 但UserService
正常执行, 账户被扣减; 这不符合业务要求.
基本概念
- Seata事务管理中有三个重要角色:
- 事务协调者(Transaction CVoordinator): 维护全局和分支事务的状态, 协调全局事务提交或回滚
- 事务管理器(Transactrion Manager): 定义全局事务的范围, 开始全局事务, 提交或回滚全局事务
- 资源管理器(Resource Manager): 管理分支事务处理的资源, 与事务TC交谈以注册分支事务和报告分支事务的状态, 并驱动分支事务提交或回滚
- Seata分布式解决方案
- XA模式: 强一致分阶段事务模式, 牺牲一定的可用性, 无业务侵入
- TCC模式: 最终一致性的分阶段事务模式, 有业务侵入
- AT模式: 最终一致性的分阶段事务模式, 无业务侵入 是Seata的默认模式
- SAGA模式: 长事务模式, 有业务侵入
基本使用
部署TC服务
version: "3"
services:
seata-server:
container_name: seata-server
image: seataio/seata-server:2.0.0
hostname: seata-server
ports:
- "20020:8091"
- "20021:7091"
volumes:
- "/opt/docker/seata/config:/seata-server/resources"
- "/etc/localtime:/etc/localtime"
- "/etc/timezone:/etc/timezone"
environment:
- SEATA_PORT=8091
- STORE_MODE=file
建表
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
添加
[lock.DataBaseDistributedLocker] [ <init>] [] : The distribute lock table is not config, please create the target table and config it
store.db.distributed-lock-table=distributed_lock
分布式事务
XA模式
- Seata XA模式是在数据库XA模式上做了简单的封装 Seata的RM仅转发事务到db的RM
- 特点
- 优点:
- 强一致性, 满足ACID原子
- 常用的数据库都支持, 实现简单, 且没有代码侵入
- 缺点
- 一阶段需要锁定资源, 等到二阶段结束后才能释放, 性能较差
- 依赖关系型数据库实现事务 例如redis不支持XA, 则无法使用XA模式
- 优点:
实现
-
引入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>
-
启动
Seata
@EnableSeataSpringConfig
-
配置
Seata
# seata seata.registry.type=nacos seata.registry.nacos.server-addr=192.168.100.71:8848 seata.registry.nacos.namespace=public seata.registry.consul.cluster=default seata.config.nacos.group=SEATA_GROUP seata.registry.nacos.application=seata-server seata.tx-service-group=seata-sell # use XA mode seata.data-source-proxy-mode=XA seata.service.vgroup-mapping.seata-sell=default
-
在方法上标注
@GlobalTransactional
@Override @GlobalTransactional public Long createOrder(Order order) { orderMapper.insert(order); accountService.deduct(order.getUserId(), order.getMoney()); stockService.deduct(order.getCommodityCode(), order.getCount()); return order.getId(); }
AT模式
-
AT模式同样是分阶段提交的事务模型, 不过弥补了XA模型中锁定周期过长的缺陷 相比于XA模式, 它会直接提交事务; 而非等待执行
-
回滚方式: RM会拦截事务, 并生成undo快照, 失败则执行undo操作
-
二阶段提交只需要删除
undo log
; 并且报告TC的操作可以异步, 因为资源已经释放 -
可能存在的问题:
事务1和事务2同时执行
update account set money = money - 10 where id = 1
事务1 事务2 获取锁, 保存快照 {id: 1, money: 100}
执行sql, set money = 90
提交事务, 释放DB锁 获取DB锁, 保存快照: {id : 1, money: 90}
执行sql, set money = 80
提交事务 释放DB锁 回滚, set money = 100
该过程丢失了
事务2
的更新 -
为了解决该问题, 使用全局锁实现写隔离, 全局锁有类似以下结构:
xid(事务id) table(表) pk(行号) 使用该数据结构隔离其他事务更新; 这样执行流程就变成了:
事务1 事务2 获取锁, 保存快照 {id: 1, money: 100}
执行sql, set money = 90
提交事务, 释放DB锁 获取DB锁, 保存快照: {id : 1, money: 90}
执行sql, set money = 80
; 但需要等待全局锁回滚, set money = 100
, 但需要等待DB锁 此时产生了死锁任务超时, 回滚业务, 释放全局锁 获取DB锁, 根据快照恢复数据 -
尽管加了全局锁, 但该全局锁由Seata管理, 因此非Seata管理的服务可以访问该数据; 且锁的粒度是行;
-
对于非Seata管理的全局事务, AT模式模式通过以下方式管理
事务1 事务2 获取锁, 保存快照 before-image
:{id: 1, money: 100}
执行sql, set money = 90
提交事务, 释放DB锁 获取DB锁 保存快照 after-image
:{id: 1, money: 90}
执行sql, set money = 80
; 无需等待全局锁提交事务, 释放DB锁 回滚, 将当前数据与 after-image
对比, 发现 90 ≠ 80 90 \ne 80 90=80; 说明数据被其他事务修改此时记录异常, 发送警告, 人工介入 -
特点
-
优点:
- 一阶段提交事务, 直接释放数据库资源, 性能较好
- 利用全局锁实现读写隔离
- 没有代码侵入, 框架自动完成代码的回滚和提交
-
缺点
- 两个阶段之间属于软状态, 属于最终一致性方案
- 框架的快照功能会影响性能, 但依旧比
XA
模式好很多
-
实现
-
在微服务中创建
undo_log
的表CREATE TABLE undo_log ( branch_id BIGINT(20) NOT NULL COMMENT 'branch transaction id', xid VARCHAR(100) NOT NULL COMMENT 'global transaction id', context VARCHAR(128) NOT NULL COMMENT 'undo_log context, such as serialization', rollback_info LONGBLOB NOT NULL COMMENT 'rollback info', log_status INT(11) NOT NULL COMMENT '0: normal status, 1: defense status', log_created DATETIME(6) NOT NULL COMMENT 'creation time', log_modified DATETIME(6) NOT NULL COMMENT 'modify datetime', UNIQUE INDEX uni_undo_log (xid, branch_id) )
-
开启seata
AT
模式:seata.data-srouce-proxy-mode=AT
TCC模式
-
TCC模式与AT模式相似, 每个阶段都是独立事务; 区别在于TCC是通过人工编码实现数据恢复的:
- Try: 资源的检测和预留
- Confirm: 完成资源操作业务
- Cancel: 预留资源释放 可以理解为Try的反向操作
-
举例(扣减用户余额30元):
- Try: 冻结金额30元
- Confirm: 清除掉冻结金额
- Cancel: 余额增增加冻结金额的值
-
特点
- 优点
- 性能好
- 不依数据库事务, 因此可以支持非事务型数据库
- 缺点
- 代码侵入性较大
- 软状态, 是最终一致性模型
- 需要考虑Confirm和Cancel的失败情况, 做好幂等处理
- 优点
-
空回滚: 某分支try阶段阻塞, 导致全局事务触发cancel操作; 没有try的操作也要执行cancel操作, 此时cancel不能修改数据, 这就是空回滚:
-
业务悬挂: 在执行空回滚之后, try恢复, 此时不能执行try操作, 这就是业务悬挂
使用TCC实现扣款功能
-
需求:
- 编写TCC业务逻辑
- Try: 冻结金额, 扣减可用金额
- configm: 删除冻结金额
- cancel: 删除冻结金额, 恢复可用金额
- 保证confirm和cancel接口的幂等性 因为这些接口可能会因为执行失败重试
- 允许空回滚
- 拒绝业务悬挂
-
创建冻结表
CREATE TABLE account_freeze_t ( xid VARCHAR(128) PRIMARY KEY NOT NULL, user_id VARCHAR(255) DEFAULT NULL COMMENT '用户id', freeze_money DOUBLE UNSIGNED DEFAULT 0 COMMENT '冻结金额', state INT(1) DEFAULT NULL COMMENT '事务状态, 0:try, 1:confirm, 2:cancel' );
SAGA模式
-
SAGA模式是seata提供的长事务解决方案; 也分为两个阶段
- 阶段1: 直接提交本地事务 与TCC不同的是, tcc是在confirm阶段提交事务
- 阶段2: 若成功则什么都不做, 若失败则通过编写补偿业务回滚
-
特点:
- 优点:
- 基于事件驱动实现异步调用, 吞吐量高
- 一阶段直接提交, 性能较好
- 不用编写TCC三个阶段, 实现简单
- 缺点:
- 软状态持续时间不确定, 时效性差
- 没有锁, 没有事务隔离, 因此有脏写问题
- 优点:
引用
[google cloud platform - Kubernetes: Error validating data: ValidationError(Deployment.spec): unknown field “containers” in io.k8s.api.apps.v1.DeploymentSpec - Stack Overflow ↩︎
https://github.com/nacos-group/nacos-docker/issues/251#issuecomment-1102186326 ↩︎
https://github.com/nacos-group/nacos-docker/issues/251#issuecomment-1613891832 ↩︎