(1)事务
事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败。
(2)本地事务
在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。
数据库事务的四大特性:ACID
数据库事务在实现时会将一次事务的所有操作全部纳入到一个不可分割的执行单元,该执行单元的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。
(3)分布式事务
随着互联网的快速发展,软件系统由原来的单体应用转变为分布式应用。
分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务。例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。
分布式事务产生的情景:
(1)CAP定理
1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标:

当分布式系统节点通过网络连接,就一定会出现分区问题(P),出现分区时系统的一致性(C)和可用性(A)就无法同时满足,此时就需要考虑是满足 CP 还是 AP。
(2)Base 理论
BASE 理论是对 CAP 的一种解决思路,包含三个思想:
分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴 CAP 定理和 BASE 理论:
(3)分布式事务解决
解决分布式事务的思想和模型:
2PC(Two-phase commit,三阶段提交) 即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase),2 是指两个阶段,P 是指准备阶段,C 是指提交阶段。

一阶段:
二阶段:
2PC 的问题:
3PC(Three-phase commit,三阶段提交)也叫三阶段提交协议,是在计算机网络及数据库的范畴下,使得一个分布式系统内的所有节点能够执行事务的提交的一种分布式算法。
3PC 主要是为了解决两阶段提交协议的阻塞问题,2PC 存在的问题是当协作者崩溃时,参与者不能做出最后的选择。因此参与者可能在协作者恢复之前保持阻塞。
除了引入超时机制之外,3PC 把 2PC 的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit 三个阶段。

CanCommit 阶段:
PreCommit 阶段:
DoCommit 阶段:
3PC 对于协调者和参与者都设置了超时时间,避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地 commit 从而进行释放资源,这种机制也侧面降低了整个事务的阻塞时间和范围。
(1)Seata 简介
Seata 是2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

(2)Seata 发展历史
早在 2007 年,阿里巴巴和蚂蚁集团内部开发了分布式事务中间件,用于解决电商、支付、物流等业务场景中应用数据的一致性问题。内部项目分别被称为 TXC (Taobao Transaction Constructor)/XTS(eXtended Transaction Service),该项目几乎在每笔订单的交易支付链路几乎都有使用。
自 2013 年以来,阿里巴巴和蚂蚁集团已在阿里云和金融云上向企业客户分别发布了分布式事务云服务产品 GTS(global transaction service)/DTX(Distributed Transaction-eXtended),在各个行业领域积累了大量用户。
2019 年 1 月,阿里巴巴集团正式开源了该项目,项目命名为 Fescar (Fast & Easy Commit and Rollback))。项目开源以来,它受到了众多开发人员的热烈欢迎和赞扬,开源一周收获了超 3k star,曾一度蝉联 GitHub Trending 排行榜第一。
2019 年 4 月,蚂蚁集团数据中间件团队加入了 Fescar 社区。为了创建一个更加开放和中立的社区,Fescar 改名为 Seata(Simple Extensible Autonomous Transaction Architecture),代码仓库从 Alibaba organization 迁移到其独立的 Seata organization。
2019 年 12 月,Seata 开源项目正式发布 1.0.0 GA 版本,标志着项目已基本可生产使用。
2023 年 10 月,为了更好的通过社区驱动技术的演进,阿里和蚂蚁集团正式将 Seata 捐赠给 Apache 基金会,该提案已通过了 Apache 基金会的投票决议,Seata 正式进入 Apache 孵化器。
(3)Seata 角色
Seata 事务管理中有三个重要的角色:

(4)Seata 方案
Seata提供了四种不同的分布式事务解决方案:
(1)Seata 下载
进入 Seata 官网,如下:

点击下载,进入下载界面:

选择对应的版本和平台,下载即可。下载完成后,解压到指定目录,解压后的目录如下:

bin:
conf:
Seata 高版本的 seata-server 文件目录有所变化:

conf 目录下的 file.conf 和 registry.conf 文件被 application.yml 文件所代替。
修改数据存储文件 file.conf,file.conf 文件如下:
- ## transaction log store, only used in seata-server
- store {
- ## store mode: file、db、redis
- mode = "file"
- ## rsa decryption public key
- publicKey = ""
- ## file store property
- file {
- ## store location dir
- dir = "sessionStore"
- # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
- maxBranchSessionSize = 16384
- # globe session size , if exceeded throws exceptions
- maxGlobalSessionSize = 512
- # file buffer size , if exceeded allocate new buffer
- fileWriteBufferCacheSize = 16384
- # when recover batch read size
- sessionReloadReadSize = 100
- # async, sync
- flushDiskMode = async
- }
-
- ## database store property
- db {
- ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
- datasource = "druid"
- ## mysql/oracle/postgresql/h2/oceanbase etc.
- dbType = "mysql"
- driverClassName = "com.mysql.jdbc.Driver"
- ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
- url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true"
- user = "mysql"
- password = "mysql"
- minConn = 5
- maxConn = 100
- globalTable = "global_table"
- branchTable = "branch_table"
- lockTable = "lock_table"
- queryLimit = 100
- maxWait = 5000
- }
-
- ## redis store property
- redis {
- ## redis mode: single、sentinel
- mode = "single"
- ## single mode property
- single {
- host = "127.0.0.1"
- port = "6379"
- }
- ## sentinel mode property
- sentinel {
- masterName = ""
- ## such as "10.28.235.65:26379,10.28.235.65:26380,10.28.235.65:26381"
- sentinelHosts = ""
- }
- password = ""
- database = "0"
- minConn = 1
- maxConn = 10
- maxTotal = 100
- queryLimit = 100
- }
- }
Seata 中文件存储有三种方式:
修改数据存储方式,如修改为 db:
- ## transaction log store, only used in seata-server
- store {
- ## store mode: file、db、redis
- mode = "db"
- ## rsa decryption public key
- publicKey = ""
-
- ## database store property
- db {
- ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
- datasource = "druid"
- ## mysql/oracle/postgresql/h2/oceanbase etc.
- dbType = "mysql"
- driverClassName = "com.mysql.jdbc.Driver"
- ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
- url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true"
- user = "mysql"
- password = "mysql"
- minConn = 5
- maxConn = 100
- globalTable = "global_table"
- branchTable = "branch_table"
- lockTable = "lock_table"
- queryLimit = 100
- maxWait = 5000
- }
- }
数据存储方式如果采用 db 方式的话,则需要创建对应的数据库表用于记录,创建数据库表的 SQL,在 Seata 项目源码的 /script/server/db 目录下可以获取:

全局事务表:
- 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;
分支事务表:
- 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;
锁记录表
- 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);
修改 Seata 的注册中心和配置中心,对应文件为 registry.conf,文件内容如下:
- registry {
- # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
- type = "file"
-
- nacos {
- application = "seata-server"
- serverAddr = "127.0.0.1:8848"
- group = "SEATA_GROUP"
- namespace = ""
- cluster = "default"
- username = ""
- password = ""
- }
- eureka {
- serviceUrl = "http://localhost:8761/eureka"
- application = "default"
- weight = "1"
- }
- redis {
- serverAddr = "localhost:6379"
- db = 0
- password = ""
- cluster = "default"
- timeout = 0
- }
- zk {
- cluster = "default"
- serverAddr = "127.0.0.1:2181"
- sessionTimeout = 6000
- connectTimeout = 2000
- username = ""
- password = ""
- }
- consul {
- cluster = "default"
- serverAddr = "127.0.0.1:8500"
- aclToken = ""
- }
- etcd3 {
- cluster = "default"
- serverAddr = "http://localhost:2379"
- }
- sofa {
- serverAddr = "127.0.0.1:9603"
- application = "default"
- region = "DEFAULT_ZONE"
- datacenter = "DefaultDataCenter"
- cluster = "default"
- group = "SEATA_GROUP"
- addressWaitTime = "3000"
- }
- file {
- name = "file.conf"
- }
- }
-
- config {
- # file、nacos 、apollo、zk、consul、etcd3
- type = "file"
-
- nacos {
- serverAddr = "127.0.0.1:8848"
- namespace = ""
- group = "SEATA_GROUP"
- username = ""
- password = ""
- dataId = "seataServer.properties"
- }
- consul {
- serverAddr = "127.0.0.1:8500"
- aclToken = ""
- }
- apollo {
- appId = "seata-server"
- ## apolloConfigService will cover apolloMeta
- apolloMeta = "http://192.168.1.204:8801"
- apolloConfigService = "http://192.168.1.204:8080"
- namespace = "application"
- apolloAccesskeySecret = ""
- cluster = "seata"
- }
- zk {
- serverAddr = "127.0.0.1:2181"
- sessionTimeout = 6000
- connectTimeout = 2000
- username = ""
- password = ""
- nodePath = "/seata/seata.properties"
- }
- etcd3 {
- serverAddr = "http://localhost:2379"
- }
- file {
- name = "file.conf"
- }
- }
Seata 支持多种注册中心和配置中心,例如使用 nacos,配置如下:
- registry {
- # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
- type = "nacos"
-
- nacos {
- application = "seata-server"
- serverAddr = "127.0.0.1:8848"
- group = "SEATA_GROUP"
- namespace = ""
- cluster = "default"
- username = "nacos"
- password = "nacos"
- }
- }
-
- config {
- # file、nacos 、apollo、zk、consul、etcd3
- type = "nacos"
-
- nacos {
- serverAddr = "127.0.0.1:8848"
- namespace = ""
- group = "SEATA_GROUP"
- username = "nacos"
- password = "nacos"
- dataId = "seataServer.properties"
- }
- }
其中 nacos 的相关配置需要修改为实际使用的配置,Seata 提供了部分配置中心的默认配置 config.txt,在 Seata 项目源码的 script/config-center/ 路径下可以找到:

启动 Seata,使用如下命令启动:
- # 直接启动方式
- bin/seata-server.sh
-
- # 添加配置方式启动
- bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 -e test
Windows 平台下启动 seata-server.bat,Linux 平台下启动 seata-server.sh
微服务集成 Seata,详细步骤如下:
引入依赖:
- <dependency>
- <groupId>com.alibaba.cloudgroupId>
- <artifactId>spring-cloud-starter-alibaba-seataartifactId>
- <version>2.2.5.RELEASEversion>
- <exclusions>
- <exclusion>
- <groupId>io.seatagroupId>
- <artifactId>seata-spring-boot-starterartifactId>
- exclusion>
- exclusions>
- dependency>
- <dependency>
- <groupId>io.seatagroupId>
- <artifactId>seata-spring-boot-starterartifactId>
- <version>1.4.2version>
- dependency>
在 application.yml 文件中配置 Seata 的相关配置:
- seata:
- registry:
- type: nacos
- nacos:
- server-addr: 127.0.0.1:8848
- namespace: ""
- group: "SEATA_GROUP"
- application: seata-tc-server
- username: nacos
- password: nacos
- # 事务组名称
- tx-service-group: seata-demo
- service:
- # 事务组与cluster的映射关系
- vgroup-mapping:
- seata-demo: SH
客户端数据库创建 undo_log 表:
- CREATE TABLE IF NOT EXISTS `undo_log`
- (
- `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
- `xid` VARCHAR(128) 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 'create datetime',
- `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
- UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
- ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
- ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
(1)XA 模式
XA 模式是从 1.2 版本支持的事务模式。XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。
Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。
(2)优缺点
优点:
缺点:

—阶段:
二阶段:
配置 Seata 事务模式为 XA 模式:
- seata:
- data-source-proxy-mode: XA
使用注解 @GlobalTransantion
(1)AT 模式
AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。
AT 模式是 Seata 的默认模式。
在 AT 模式下,用户只需关注自己的业务 SQL,用户的业务 SQL 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
(2)优缺点
优点:
缺点:
(1)AT 模式原理分析

在一阶段,Seata 会拦截业务 SQL 并解析 SQL 语义,找到业务 SQL 要更新的业务数据,在业务数据被更新前,将其保存成 before image,然后执行业务 SQL 更新业务数据,在业务数据更新之后,再将其保存成 after image,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

二阶段如果是提交的话,因为业务 SQL 在一阶段已经提交至数据库,所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的业务 SQL,还原业务数据。回滚方式便是用 before image 还原业务数据。

注意在还原前要首先要校验脏写,对比数据库当前业务数据和 after image,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
配置 Seata 事务模式为 AT 模式:
- seata:
- data-source-proxy-mode: AT
使用注解 @GlobalTransantion
(2)写隔离
AT 模式写隔离:
以一个示例来说明:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待全局锁 。

tx1 二阶段全局提交,释放全局锁 ,tx2 拿到全局锁提交本地事务。

如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的全局锁等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程全局锁在 tx1 结束前一直是被 tx1 持有的,所以不会发生脏写的问题。
(3)读隔离
在数据库本地事务隔离级别“读已提交(Read Committed)”或以上的基础上,Seata(AT 模式)的默认全局隔离级别是“读未提交(Read Uncommitted)”。
如果应用在特定场景下,必需要求全局的“读已提交”,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

SELECT FOR UPDATE 语句的执行会申请全局锁 ,如果全局锁被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到全局锁拿到,即读取的相关数据是已提交的,才返回。
(1)TCC 模式
TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。
(2)优缺点
优点:
缺点:

在两阶段提交协议中,资源管理器(RM, Resource Manager)需要提供“准备”、“提交”和“回滚” 3 个操作;而事务管理器(TM, Transaction Manager)分 2 阶段协调所有资源管理器,在第一阶段询问所有资源管理器“准备”是否成功,如果所有资源均“准备”成功则在第二阶段执行所有资源的“提交”操作,否则在第二阶段执行所有资源的“回滚”操作,保证所有资源的最终状态是一致的,要么全部提交要么全部回滚。
资源管理器有很多实现方式,其中 TCC(Try-Confirm-Cancel)是资源管理器的一种服务化的实现;TCC 是一种比较成熟的分布式事务解决方案,可用于解决跨数据库、跨服务业务操作的数据一致性问题;TCC 其 Try、Confirm、Cancel 3 个方法均由业务编码实现,故 TCC 可以被称为是服务化的资源管理器。
TCC 的 Try 操作作为一阶段,负责资源的检查和预留;Confirm 操作作为二阶段提交操作,执行真正的业务;Cancel 是二阶段回滚操作,执行预留资源的取消,使资源回到初始状态。
声明接口:
- @LocalTCC
- public interface TccService {
-
- /**
- * 一阶段try逻辑
- */
- @TwoPhaseBusinessAction(name = "prepare", commitMethod = "commit", rollbackMethod = "rollback")
- void prepare(@BusinessActionContextParameter(paramName = "param") String param);
-
- /**
- * 二阶段提交方法
- */
- boolean commit(BusinessActionContext context);
-
- /**
- * 二阶段回滚方法
- */
- boolean rollback(BusinessActionContext context);
- }
具体业务逻辑可以在该接口的实现类中完成。
TCC 模式问题:
(1)Saga 模式
Saga 模式是 Seata 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
适用场景:
(2)优缺点
优势:
缺点:

目前 Seata 提供的 Saga 模式是基于状态机引擎来实现的,机制是:
| XA | AT | TCC | Saga | |
|---|---|---|---|---|
| 一致性 | 强一致 | 弱一致 | 弱一致 | 最终一致 |
| 隔离性 | 完全隔离 | 基于全局锁隔离 | 基于资源预留隔离 | 无隔离 |
| 代码入侵 | 无 | 无 | 需要编写三个接口 | 需要编写状态机和补偿业务 |
| 性能 | 差 | 好 | 非常好 | 非常好 |
| 场景 | 对一致性、隔离性有较高要求的业务 | 基于关系型数据库的大多数分布式事务 | 对性能要求较高的事务 有非关系型数据库参与的事务 | 业务流程长、业务流程多 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口 |