博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
分布式相关部分笔记整理
阅读量:5960 次
发布时间:2019-06-19

本文共 10845 字,大约阅读时间需要 36 分钟。

分布式基础

第一章分布式基础前瞻

第一节通信协议
1.1.1)网络协议TCP/IP
1.1.1.1)TCP五层模型
应用层 应用程序
传输层 TCP UDP
网络层 IP ICMP /IGMP
链路层 硬件接口
物理层
1.1.1.2)OSI七层模型

1.1.1.3)TCP 三次握手四次挥手【重点】

最开始的时候客户端和服务器都处于closed状态。主动打开连接的是客户端,被动打开连接的是服务器。

1)TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态
2)TCP客户进程也是先创建传输控制块TCB,然后向服务器发起连接请求报文,这是报文首部中的同部位SYN=1,同事选择一个初始序列号seq=x,此时TCP客户端进程进入SYN-SENT(同步已发送状态)。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但是需要消耗一个序号。
3)TCP服务器收到请求报文之后,如果同意连接,则发起确认报文。确认报文中应该是ACK=1,SYN=1,确认号是ack=x+1,同时也为自己初始化一个序列号seq=有,此时TCP服务器进入SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样需要消耗一个序号。
4)TCP客户进程收到确认之后,还要向服务器给出确认。确认报文ACK=1.ack=y+1,自己的序列号是seq=x+1,此时TCP连接建立。客户端进入ESTABLISTEN(已建立连接)状态。TCP规定。ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
5)当服务器接受到客户端确认之后也进入到ESTABLISTEN状态,此后双方进行通信。

1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。 
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。 
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。 
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
1.1.1.4)为什么三次握手的时候TCP客户端还要在发送一次确认呢
主要是为了防止已经失效的连接请求报文突然又传回了服务器,从而产生错误。
1.1.2)阻塞及非阻塞
1.1.2.1)什么是Bio【同步阻塞】

如果接受方在接受缓冲区中没有读到任何信息,那么就会阻塞,直到有TCP报文传来,才会继续去读取。

同样的发送方如果发送的长度超过缓冲区大小,也会出现阻塞直到发送的长度能够被写入缓冲区。

以上就是BIO阻塞

1.1.2.2)什么是Nio【同步非阻塞】
在底层中存在IO调度线程,不断地扫描缓冲区,如果发现有写缓冲区为空,出现一个写的事件主动的通知
不断地扫描缓冲区,如果发现有读缓冲区有值,出现一个读的事件主动的通知去读。
1.1.2.3)简单讲解下Bio,Nio,Aio
你要去吃海底捞,但是排队的人很多,你拿到号码牌:
1)你在那边等,直到叫到你的号。BIO
2)你先去做别的事,做完再看下有没有到你了。NIO
3)你去做别的事,海底捞发现到你了,主动通知你。AIO
1.1.3)通信协议
1.1.3.1)https的过程
1 客户端发起一个https请求
a)客户端支持的加密方式
b)客户端生成一个随机数(随机数1)
2 服务端收到请求之后,拿到随机数,返回
a) 证书(颁发机构(CA),证书内容本身的数字签名(使用第三方机构的私钥加密),证书持有者的公钥,证书签名用到的hash算法)
b)生成一个随机数(随机数 2 ),返回给客户端
3 客户端拿到证书以后做验证
a)根据颁发机构找到本地的根证书
b)根据CA得到根证书的公钥,通过公钥对数字签名进行解密,得到证书内容摘要A
c)用证书提供的算法对证书内容进行摘要,得到摘要B
d)通过A和B的比较,就是验证数字签名
4 验证通过之后,生成一个随机数(随机数 3),通过证书内的公钥对这个随机数加密,发送给服务器端
5(随机数1+2+3)通过对称加密得到一个秘钥(会话秘钥)
6 通过会话秘钥对内容进行对称加密传输

第二节 序列化与反序列化

1.2.1)相关概念
把对象转化为字节序列的过程称为对象的序列化,反之就是对象的反序列化。
Java中自带实现序列化操作需要继承sericalize接口
用objectOutputStream和ObjectInputStream来进行序列化和反序列化
1.2.2)SerialVersionID作用
1)保证序列化对象和反序列化对象是同一个
序列化会有个版本号,如果没有这个的话,会生成新的。
2)静态变量的序列化
序列化不保存静态变量状态
序列化之后然后修改一个静态变量的值然后在反序列化发现对象的值已经变成修改后的值。
1.2.3)使序列化失效的关键字
Transient
1.2.4)序列化的父子类问题
如果父类没有实现序列化,而子类实现序列化,那么父类中的成员没办法做序列化操作。 反之,如果父类实现了序列化,子类就不需要实现serializable
1.2.5)序列化的总结
1)在java中,只要一个类实现了java.io.serializable接口,那么她就可以被序列化
2) 通过objectoutputstream和objectinputstream对对象进行序列化和反序列化
3)对象是否被反序列化,不仅取决于对象的代码是否一致,同样还有一个重要因素(UID)
4)序列化不保存静态变量
5)要想父类对象也参与序列化操作,必须要让父类也实现serializable接口
6)Transient主要是控制控制变量是否能被序列化。如果没有被序列化的成员变量反序列化后会被设置为初始值。
7)通过序列化操作实现深度克隆。
1.2.6)主流的序列化技术?
JSON Hessian xml protobuf kryo msgpack FST thrify protostuff avro

第二章Zookeeper

第一节分布式的相关概念
2.1.1)分布式环境特点
1)分布性 异地多活
2)并发性 程序运行过程中,并发性操作是很常见的,同一个分布式系统中的多个节点,同时访问一个共享资源,如数据库或者分布式存储。 分布式并发是基于多进程
3)无序性 进程之间的消息通信会出现顺序不一致问题
2.1.2) 分布式环境下面临的问题
1)网络通信 网络本身的不可靠性 光缆被挖断火灾等等
2) 网络分区 (脑裂) 当网络发生异常,导致分布式系统中部分节点之间的网络遗憾吃不断增大,最终导致分布式架构的所有节点,只有部分节点能够正常通行。
3) 三态
在分布式架构中,除了成功,失败还有一种状态叫超时
4)分布式事务 同时成功同时失败,事务回滚
2.1.3)CAP理论
C 一致性 (Consistency) 所有节点上数据,时刻保持一致
A 可用性(Availability) 每个请求都能够收到一个响应,无论响应成功或者失败
P 分区容错 (Partition-tolerance)表示系统出现脑裂以后,可能导致某些server与集群中的其他机器失去联系。
CP / AP
仅仅适用于原子读写的Nosql场景,不适用于数据库系统
2.1.4)BASE理论
据库高可用都是徒劳)XA事务虽然可以保证数据库在分布式系统下的ACID特性,但是会带来性能上的影响。

eBay尝试了一种完全不同的方式:放宽了对事务ACID的要求,提出了BASE理论:

Basically availbale 基本可用 数据库采用分片模式,把100万用户数分布在5个实例上,破坏其中一个实例。仍然可以保证80%的用户可用。
soft-state 软状态 在基于client-server模式的系统中,server端是否有状态,决定了系统是否具备良好的水平扩展,负载均衡,故障恢复等特性。
server端承诺会维护client端状态数据,这个状态仅仅维持一小段时间,这段时间以后,server端就会丢弃这个状态,恢复正常状态。
Eventually consistent 数据的最终一致性
第二节 zookeeper的相关概念
2.2.1)zookeeper的特性

1)顺序一致性

从同一个客户端发起的事务请求。最终会严格按照顺序被应用到zookeeper中
2)原子性
所有的事务请求的处理结果在整个集群中的所有机器的应用情况是一致的,也就是说,要么整个集群的所有机器都成功地应用了某个事务,要么就都不应用。
3)可靠性
一旦服务器成功应用了某一个事务数据,并且对客户端做了响应,那么这个数据在整个集群中一定是同步并且保留下来。
4)实时性
一单一个事务被成功应用,客户端能够立即从服务器读取到事务变更后的最新数据状态。(zookeeper仅仅保证在一定时间内,接近实时)
2.2.2)zookeeper的角色
zookeeper集群,包含三种角色:leader/follower/observer

leader:接受所有follower的提案请求并且统一协调发提案的投票,负责与所有的follower进行内部的数据交换(同步)
follower:直接为客户端服务并且参与提案的投票,同事与leader进行数据交换(同步) observer:直接为客户端服务但是不参与提案的投标,只是也与leader进行数据交换(同步)

2.2.3)Observer角色说明

observer,不影响写性能情况下去扩展zookeeper
observer是一种特殊的zookeeper节点,可以帮助解决zookeeper的扩展性
大量客户端访问zookeeper集群,需要增加zookeeper集群机器数量,从而增加zookeeper集群性能,导致zookeeper写性能下降。zookeeper的数据变更需要半数以上服务器投票通过,造成网络消耗,增加投票成本。
1)observer不参与投票,只接受投票结果
2)不属于zookeeper的关键部位
2.2.4)zookeeper的数据模型
zookeeper的数据模型和文件系统类似,每一个节点称为:znode 是zookeeper中的最小数据单元,每个znode上都可以保存数据和挂载子节点,从而构成一个层次化的属性结构。
2.2.5)zookeeper的节点
持久化节点 :节点创建后会一直存在zookeeper服务器上,直到主动给删除
持久化有序节点:每个节点都会为他的一级子节点维护一个顺序
临时节点:临时节点的生命周期和客户端的会话保持一致,当客户端会话失效,该节点自动清理
临时有序节点:临时节点上多了一个顺序性的特性
2.2.6)zookeeper的会话状态
NOT CONNECTED 未连接
CONNECTING 连接中
CONNECTED 已连接
CLOSED 关闭
2.2.7)zookeeper的watcher
zookeeper 提供了分布式数据发布/订阅 ,zookeeper允许客户端向服务器注册一个watcher监听,当服务器端的节点触发指定事件的时候会触发watcher,服务端会向客户端发送一个事件通知
watcher的通知是一次性,一旦触发一次通知后,该watcher就失效了。
2.2.8)zookeeper的ACL
zookeeper提供了控制节点访问权限的功能,用于有效保证zookeeper中数据的安全性,避免误操作而导致系统出现重大事故。
create/read/write/delete/admin
2.2.9)zookeeper的连接状态
1)keeperStat:expored
在一定的时间内客户端没有收到服务器的通知,则认为当前的会话已经过期了,
2)disconnected 断开连接状态
3)keeperstat.syncConnected 客户端和服务器端在某个节点上建立连接,并且完成一次ersion,
zxid的同步
4)keeperstatus.authFailed 授权失败
2.2.10)zookeeper的节点监听状态
NodeCreated 当节点被创建时触发
NodeChildrenChanged 标识子节点被创建,被删除,子节点数据发生变化
NodeDataChanged 节点数据发生改变
NodeDeleted 节点被删除
None 当客户端和服务器连接状态发生变化的时候,事件类型就是None
第三节zookeeper的实际应用
2.3.1)zookeeper可以实现哪些场景
1)订阅发布/配置中心
2)分布式锁【3种实现】
2.1 redis setNX 存在则会返回0 不存在则返回数据
2.2 数据库 创建一个表 通过唯一索引的方式
create table(id,methodname..) methodname增加唯一索引
insert 一条数据 xxx delete 删除数据
mysql 有innodb来设置表锁或者行锁
2.3 zookeeper 有序节点
3)负载均衡
4)ID生成器
5)分布式队列
6)统一命名服务
7)master选举

2.3.2)master选举过程

第一次初始化启动的时候是Looking
1)所有集群中的server都会推荐自己为leader,然后把(myid,zxid,epoch)作为广播信息,广播给集群中的其他server,然后等待其他服务器返回。
2)每个服务器都会接受到来自集群中的其他服务器的投票,及群众的每个服务器在接受到投票之后,都会判断投票的有效性
a)判断逻辑时钟epoch 如果epoch大于自己当前的epoch,说明自己保存的epoch是过期,更新epoch,同事clear其他服务器送过来的选举数据,判断是否需要更新当前自己的选举情况
b)如果Epoch小于目前的epoch,说明对方的epoch过期,意味着对方服务器的选举轮次是过期的,只需要把自己的信息发送给对方
c)如果接收到的epoch等于当前的epoch,根据规则来判断是否有资格获得leader 接受到来自其他服务器的投票后,针对每一个投标,都需要将别人的投票和自己的投票进行pk
ZXID最大的服务器优先
3)统计投票
2.3.3)ZAB协议
拜占庭问题
paxos协议主要就是如何保证在分布式网络环境下,各个服务器如何达成一致最终保证数据的一致性问题。
ZAB协议,基于paxos协议的一个改进。

ZAB协议为分布式协调服务zookeeper专门设计的一种支持奔溃恢复的原子广播协议。

zookeeper并没有完全采用paxos算法,而是采用zab zookeeper stomic broadcast zab协议的原理:
1)在zookeeper的主备模式下,通过zab协议来保证集群中的各个副本数据的一致性
2)zookeeper是使用单一的主进程来接受并处理所有的事务请求,并采用zab协议,把数据的状态变更以事务请求的形式广播到其他节点
3)zab协议在主备模型架构中保证了同一时刻只能有一个主进程来广播服务器的状态变更
4)所有的事务请求必须由全局唯一的服务器来协调处理,这个服务器叫leader,其他叫follower
leader节点主要是负责把客户端的请求转化为一个事务提议(proposal),并且分发给集群中的所有follower节点,再等待所有follower节点反馈,一旦超过半数服务器进行了正确的反馈,nameleader就会commit这个消息。

2.3.4) ZAB协议的工作原理

1)什么情况下zab协议会进入奔溃恢复模式
a 当服务器启动时
b 当leader服务器出现网络中断 奔溃 重启的情况
c 集群中已经不存在过半的服务器与该leader保持正常通行

2)zab协议进入奔溃恢复模式会做什么

a 当leader出现问题,zab协议进入奔溃恢复模式,并且选举出新的leader。当新的leader选举出来以后,如果集群中已经有过半机器完成了leader服务器的状态同步(数据同步),退出崩溃恢复。进入消息广播模式
b 当新的机器加入到集群中的时候,如果已经存在leader服务器,那么新加入的服务器就会自觉进入数据恢复模式,找到leader进行数据同步

第三章分布式事务

3.1)事务ACID概念
Atomic (原子性) 事务必须是原子的工作单元
Consistent (一致性) 事务在完成的时候必须是所有数据都保持一致状态
Isolation (隔离性 ) 并发事务所做的修改必须和其他事务所做的修改是隔离的
Duration (持久性) 事务完成之后,对系统的影响是永久性的
3.2)mysql事务处理过程
mysql事务处理过程:
redo和undo文件
redo记录事务修改后的数据
undo是记录事务修改之前的数据,保证能够回滚

1)记录redo和undo log文件,确保日志在磁盘上的持久化

2)更新数据记录
3)提交事务,redo 写入commit记录
4)清理undo文件和释放锁

3.3)X/OpenDTP事务模型

X/Open Distributed Transaction processing Reference Model

X/Open是个组织架构,定义除了一套分布式事务标准,定义了规范的API接口

2PC(two-phase-commit) 用来保证分布式事务的完整性

J2EE遵循了这套规范,设计并且实现了java里面的分布式事务编程接口规范-JTA
XA是X/Open定义的中间件与数据库之间的接口规范,XA接口函数由数据库厂商提供。

X/OpenDTP角色

AP application 应用节点

RM resource manager 资源管理器 ---》数据库
TM transaction manager 事务管理器/事务协调者

3.4)2PC

二阶段事务提交
阶段一:提交事务请求【预事务】
1)TM向所有的AP发送事务内容,
询问是否执行事务的提交操作,并且等待各个AP响应
2)执行事务
各个AP节点执行事务操作,将undo和redo信息记录到事务日志中,尽量把提交过程中所消耗时间的操作和准备都提前完成确保后续事务提交的成功率
3)各个AP向TM反馈事务询问的响应
各个AP成功执行了事务操作,那么反馈给TM yes的response 如果失败就反馈给Tm no的response

阶段二:执行事务提交

1)执行提交事务

假设一个事务的提交过程总共需要30s,其中prepare操作需要28秒(事务日志落地磁盘及各种io操作)真正的commit只需要俩秒,namecommit阶段发生错误的概率和prepare相比是2/28 <10%

只要第一个阶段成功,那么commit阶段出现失败的概率就会比较小,大大增加了分布式事务的成功率。
2)中断事务提交

2pc存在的问题:

1)数据一致性文件
2)同步阻塞

3.5)3PC

阶段一 cancommit

阶段二 precommit
阶段三 docommit

第四章Redis

4.1)redis的数据类型
1)字符类型
2)散列类型
3)列表类型
4)集合
5)有序集合
4.2)redis的应用场景
1)数据缓存(商品数据,新闻,热点)
2)单点登录
3)秒杀,抢购
4)网站访问排名
5)应用模块开发
4.3) redis的事务处理
MULTI 去开启事务
EXEC 去执行事务
4.4)redis的过期时间
expire key seconds
ttl  获得key的过期时间
4.5)redis 的发布订阅 pub/sub 模型

publish channel message

subscribe channel [ …]

4.6)redis的持久化策略

4.6.1)RDB
RDB的持久化策略,按照规则定时将内存的数据同步到磁盘

redis在指定的情况下会触发快照 1)自己配置的快照规则 我们首先看下redis.conf这个配置文件去看下系统默认的快照规则

save 当900秒内,被更改的key的数量大于1的时候就执行快照 save 900 1
save 300 10
save 60 10000

2)save或者bgsave

执行内存数据同步到磁盘的操作,这个操作会阻塞客户端请求【save】
后台异步执行快照操作,这个操作不会阻塞客户端请求【bgsave】background
3)执行flushall的时候
清除内存所有数据,只要快照规则不为空,那么redis就会执行快照
4)执行复制的时候
redis集群

快照的实现原理:

redis会使用fork函数复制一份当前的进程副本(子进程)
父进程可以继续进行客户端请求,子进程会把内存数据同步到磁盘的临时文件上,所以不会影响到当前应用的使用。

redis的优缺点

缺点:redis可能会存在数据丢失的情况,执行快照和执行下一次快照中间的数据可能会丢失,宕机。
优点:可以最大化redis的性能
4.6.2)AOF
AOF的持久化策略,每次执行完命令后会把命令本身存储下来【类似于实时备份】
俩种持久化策略可以使用一种也可以是同时使用,如果同时使用
重启时候会优先使用AOF还原数据。

redis会把每一条命令追加到磁盘文件中,会对性能有所影响。

改成yes 即开启了aof

修改redis.conf 中的appendonly yes

重启执行对数据的变更命令,会在bin目录下生成对应的.aof文件,aof会记录所有的操作命令
如下俩个参数可以对aof文件进行优化
压缩策略

auto-aof-rewrite-percentage 100
ps:表示当前aof文件大小超过上一次aof文件大小的百分之多少的时候会进行重写,如果之前没有重写,以启动时的aof文件大小为准
auto-aof-rewrite-min-size 64mb
ps:限制允许重写最小aof文件大小,也就是文件大小肖宇64mb的时候,不需要进行优化

aof重写的原理:

aof重写的整个过程是安全的
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松

同步磁盘数据

redis每次更改数据的时候,aof机制都会将命令记录到aof文件,但是实际上由于操作系统的缓存机制,数据没有实时地写入硬盘,而是进入硬盘缓存,再通过硬盘缓存机制去刷新保存到文件。【理论上可能出现数据丢失】

appendfsync always 每次执行写入都会同步,最安全,效率最低

appendfsync everysec 每一秒执行

appendfsync no 不主动进行同步,由操作系统进行同步,这是最快,最不安全

4.7)redis集群方式

1)master/slave
2) 哨兵机制
3)集群
4.8)Redis缓存更新
俩个不同的存储,如何保证原子性
1【先删除缓存,在更新数据库】可能出现脏读
2【先更新数据库,更新成功之后,让缓存失效】减少了脏读的可能性,但是还是有概率出现脏读
3【更新数据的时候,只更新缓存,不更新数据库,然后通过异步调度去批量更新数据库】提升性能,但是无法保证强一致性。
4.9)缓存穿透

概念:缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

解决方案【俩种方式】:
1)最常见的是采取布隆过滤器,把所有可能存在的数据哈希到一个足够大的bitmap中,一个不存在的数据会被bitmap拦截掉,避免对底层存储系统的查询压力。
2)如果查询为空,把这个空结果缓存,过期时间设置不超过5分钟。

4.10)缓存击穿

概念:对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:
1)使用互斥锁
2)提前使用互斥锁
3)不过期,异步构建缓存,不会阻塞线程池
4)资源隔离组件hystrix
4.11)缓存失效/雪崩
概念:缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案:
缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

想要直接看word的可以使用下面的百度网盘链接获取word

提取码 hzon

转载地址:http://aruax.baihongyu.com/

你可能感兴趣的文章
jquery选择器详解
查看>>
C# 保留2位小数
查看>>
使用xshell远程连接Linux
查看>>
杭电ACM1007
查看>>
faster-RCNN台标检测
查看>>
Unix环境高级编程 centos中配置apue编译环境
查看>>
运算符
查看>>
数据结构之各排序算法
查看>>
网页分帧操作<frameset>,<iframe>标签
查看>>
Vue生产环境部署
查看>>
酒店之王
查看>>
html5判断用户摇晃了手机(转)
查看>>
VS下Qt4.8.4安装
查看>>
Linux df命令
查看>>
redhat6.5 配置使用centos的yum源
查看>>
取得内表的数据数
查看>>
在一个程序中调用另一个程序并且传输数据到选择屏幕执行这个程序
查看>>
“=” “:=” 区别
查看>>
pwnable.kr lotto之write up
查看>>
python之UnittTest模块
查看>>