pg日志传送后备服务器

2020-09-18  本文已影响0人  robot_test_boy

连续归档可以用来创建一个高可用(HA)集群配置, 其中有一个或多个后备服务器随时准备在主服务器失效时接管操作。 这种能力被广泛地称为温备或日志传送。

主服务器和后备服务器一起工作来提供这种能力,但这些服务器只是松散地组织在一起。主服务器在连续归档模式下操作, 而每一个后备服务器在连续恢复模式下操作并且从主服务器读取WAL文件。 要启用这种能力不需要对数据库表做任何改动, 因此它相对于其他复制方案降低了管理开销。 这种配置对主服务器的性能影响也相对较低。

直接从一个数据库服务器移动WAL记录到另一台服务器通常被描述为日志传送。  PostgreSQL通过一次一文件(WAL段) 的WAL记录传输实现了基于文件的日志传送。不管 WAL 文件(16 MB)被送到一个临近的系统、同一站点的另一个系统或是在地球遥远的另一端的一个系统上,它都可以在任何距离上被简单和便宜地传送。这种技术所需的带宽取根据主服务器的事务率而变化。基于记录的日志传送具有更细的粒度并且能够在网络连接上以流的方式增量传递WAL的改变。

需要注意的是日志传送是异步的,即WAL记录是在事务提交后才被传送。 正因为如此,在一个窗口期内如果主服务器发生灾难性的失效则会导致数据丢失, 还没有被传送的事务将会被丢失。 基于文件的日志传送中这个数据丢失窗口的尺寸可以通过使用参数 archive_timeout进行限制,它可以被设置成低至数秒。 但是这样低的设置大体上会增加文件传送所需的带宽。 流复制允许更小的数据丢失窗口。

这种配置的恢复性能是足够好的,后备服务器在被激活后通常只有片刻就可以达到完全可用。 因此,这被称为一种提供高可用性的温备配置。 从一个已归档的基础备份恢复一个服务器并且前滚将需要较长时间, 因此该技术只提供了灾难恢复的一种方案,而不适合于高可用性。

如果一台后备服务器只有被提升为一台主控服务器后才能被连接, 它被称为一台温后备服务器, 而一台能够接受连接并且提供只读查询的后备服务器被称为一台 热后备服务器

规划设计

创建主服务器和后备服务器通常是明智的,因此它们可以尽可能相似, 至少从数据库服务器的角度来看是这样。特别地, 与表空间相关的路径名将被未经修改地传递,因此如果该特性被使用, 主、备服务器必须对表空间具有完全相同的挂载路径。 记住如果CREATE TABLESPACE在主服务器上被执行, 在命令被执行前,它所需要的任何新挂载点必须在主服务器和所有后备服务器上先创建好。在任何情况下硬件架构必须相同,从一个 32 位系统传送到一个 64 位系统将不会工作。

通常,不能在两个运行着不同主版本PostgreSQL的服务器之间传送日志。PostgreSQL 全球开发组的策略是不在次版本升级中改变磁盘格式, 因此在主服务器和后备服务器上运行不同次版本将会成功地工作。不过, 在这方面并没有提供正式的支持,因此我们建议让主备服务器上运行的版本尽可能相同当升级到一个新的次版本时,最安全的策略是先升级后备服务器 — 一个新的次版本发行更可能兼容从前一个次版本读取 WAL 文件

后备服务器操作(restore_command)

在后备模式中,服务器持续地应用从主控服务器接收到的 WAL。 后备服务器可以从一个 WAL 归档(restore_command) 或者通过一个 TCP 连接直接从主控机(流复制)读取 WAL。 后备服务器也会尝试恢复在后备集簇的pg_wal目录中找到的 WAL。 通常在一次数据库重启后,后备机将重播在重启之前从主控机流过来的 WAL, 但是你也可以在任何时候手动拷贝文件到pg_wal让它们被重播。

在启动时,后备机通过恢复归档位置所有可用的WAL开始, 称为restore_command

1) 一旦重播到达可用WAL的末尾并且 restore_command失败,它会尝试恢复pg_wal 目录中可用的任何WAL。

2) 如果尝试重试恢复失败且流复制已被配置, 后备机会尝试连接到主服务器并从在归档或pg_wal 中找到的最后一个可用记录开始流式传送 WAL。

3) 如果尝试重试恢复失败且没有配置流复制, 或者该连接后断开,后备机返回到步骤 1) 且尝试再次从归档里的文件恢复。

这种尝试归档、pg_wal和流复制的循环会一直重复直到服务器停止或者一个触发器文件触发了故障转移。

当pg_ctl promote(升主)被运行或一个触发器文件被找到 (trigger_file),后备模式会退出并且服务器会切换到普通操作。 在故障转移前,在归档或pg_wal中可用的任何WAL将立即被恢复, 但不会尝试连接到主控机。

为后备服务器准备主控机

在主服务器上设置连续归档到一个后备服务器可访问的归档目录。 如果主服务器垮掉该归档位置应当是后备服务器可访问的,可以位于后备服务器本身或者另一个可信赖的服务器,而不是位于主控服务器上。

如果使用流复制,在主服务器上设置认证来允许来自后备服务器的复制连接。创建一个角色并且在pg_hba.conf中提供一个或多个数据库域被设置为replication的项,还要保证在主服务器配置文件中max_wal_senders设置足够大的值。如果使用复制槽, 请确保max_replication_slots设置得足够高。

建立一个后备服务器

要建立后备服务器,恢复从主服务器取得的基础备份。 在后备服务器的集簇数据目录中创建一个恢复命令文件recovery.conf, 并且打开standby_mode。将restore_command 设置为从WAL归档中复制文件的简单命令。

如果为了高可用性而建立多个后备服务器, 将recovery_target_timeline设置为latest,使该后备服务器遵循发生在故障转移到另一个后备服务器之后发生的时间线改变。

注意:不要把pg_standby或相似的工具和这里描述的内建后备模式一起使用。 如果文件不存在,restore_command应该立即返回失败,该服务器将再次尝试该命令。

如果使用流复制,在primary_conninfo中填入一个libpq连接字符串,其中包括主机名(或IP地址)和连接到主服务器所需的任何附加细节(包括认证口令)。

如果为高性能而建立后备服务器,像主服务器一样建立WAL归档、 连接和认证,因为在故障转移后该后备服务器将作为一个主服务器工作。

如果使用一个WAL归档,可以使用archive_cleanup_command 参数来移除后备服务器不再需要的文件,可以最小化 WAL归档的尺寸。 pg_archivecleanup工具被特别设计为在典型单一后备配置下与 archive_cleanup_command共同使用。 不过要注意,如果为备份而使用归档, 有一些文件即使后备服务器不再需要也需要保留,至少被用来从最后一个基础备份恢复。

恢复命令文件recovery.conf的简单例子(认证的流复制,且清理不需要的wal归档):

standby_mode = 'on'

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'

restore_command= 'cp /path/to/archive/%f %p'

archive_cleanup_command = 'pg_archivecleanup /path/to/archive %r'

可以有任意数量的后备服务器,但如果使用流复制, 确保主服务器上max_wal_senders设置足够高, 这样允许它们能同时连接。

流复制

流复制允许一台后备服务器比使用基于文件的日志传送更能保持最新的状态。后备服务器连接到主服务器,主服务器则在WAL记录产生时将以流式传送给后备服务器而不必等到WAL文件被填充。

默认情况下流复制是异步的, 在这种情况下主服务器上提交一个事务与该变化在后备服务器上可见之间存在短暂的延迟。这种延迟比基于文件的日志传送方式中要小得多,在后备服务器的能力足以跟得上负载的前提下延迟通常低于一秒。在流复制中,不需要archive_timeout来缩减数据丢失窗口

如果使用的流复制没有基于文件的连续归档,该服务器可能在后备机收到WAL段之前回收这些旧的WAL段。如果发生这种情况,后备机将需要重新从一个新的基础备份初始化。通过设置wal_keep_segments为一个足够高的值来确保旧的WAL段不会被太早重用或者为后备机配置一个复制槽,可以避免发生这种情况。如果设置了一个后备机可以访问的 WAL 归档,就不需要这些解决方案,因为该归档可以为后备机保留足够的段,后备机总是可以使用该归档来追赶主控机。

要使用流复制,建立一个基于文件的日志传送后备服务器。 将一个基于文件日志传送后备服务器转变成流复制后备服务器的步骤是把 recovery.conf文件中的primary_conninfo设置指向主服务器。设置主服务器上的listen_addresses和认证选项 (见pg_hba.conf),这样后备服务器可以连接到主服务器上的伪数据库 replication。

在支持 keepalive 套接字选项的系统上,设置 tcp_keepalives_idle、 tcp_keepalives_interval和 tcp_keepalives_count 有助于主服务器迅速地注意到一个断开的连接。

设置来自后备服务器的并发连接的最大数目 (详见max_wal_senders)。

当后备服务器被启动并且primary_conninfo正确设置, 后备服务器将在重放完归档中所有可用的 WAL 文件之后连接到主服务器。 如果连接被成功建立,你将在后备服务器中看到一个walreceiver进程, 并且在主服务器中有一个相应的walsender进程。

1) 认证

设置好用于复制的访问权限非常重要,这样只有受信的用户可以读取 WAL 流, 因为很容易从 WAL 流中抽取出需要特权才能访问的信息。 后备服务器必须作为一个超级用户或一个具有REPLICATION 特权的账户向主服务器认证。我们推荐为复制创建一个专用的具有 REPLICATION和LOGIN特权的用户账户。 虽然REPLICATION特权给出了非常高的权限, 但它不允许用户修改主系统上的任何数据,而SUPERUSER特权则可以。

复制的客户端认证由一个在database域中指定replication的pg_hba.conf记录控制。如果后备服务器运行在主机 IP 192.168.1.100 并且用于复制的账户名为foo,管理员可以在主服务器上向 pg_hba.conf文件增加下列行:

# 允许来自 192.168.1.100 的用户 "foo" 在提供了正确的口令时作为一个

# 复制后备机连接到主控机。

# TYPE  DATABASE        USER            ADDRESS                METHOD

host    replication    foo            192.168.1.100/32        md5

主服务器的主机名和端口号、连接用户名和口令在recovery.conf 文件中指定。在后备服务器上还可以在~/.pgpass文件中设置口令 (在database域中指定replication)。如果主服务器运行在主机 IP 192.168.1.50、端口 5432上,用于复制的账户名是foo, 并且口令为foopass,管理员可以在后备服务器的 recovery.conf文件中增加下列行:

# 后备机要连接到的主控机运行在主机 192.168.1.50 上,

# 端口号是 5432,连接所用的用户名是 "foo",口令是 "foopass"。

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'

2) 监控

流复制的一个重要健康指标是在主服务器上产生但还没有在后备服务器上应用的 WAL 记录数。 可以通过比较主服务器上的当前 WAL写位置和后备服务器接收到的最后一个 WAL位置来计算这个滞后量。 这些位置分别可以用主服务器上的pg_current_wal_lsn和后备服务器上的 pg_last_wal_receive_lsn来检索 。 后备服务器的最后WAL接收位置也被显示在 WAL 接收者进程的进程状态中, 可以使用ps命令查看显示的状态。

可以通过pg_stat_replication 视图检索 WAL 发送者进程的列表。pg_current_wal_lsn 与视图的sent_lsn域之间的巨大差异表示主服务器承受着巨大的负载, 而sent_lsn和后备服务器上pg_last_wal_receive_lsn 之间的差异可能表示网络延迟或者后备服务器正承受着巨大的负载。

复制槽

复制槽提供了一种自动化的方法来确保主控机在所有的后备机收到 WAL 段 之前不会移除它们,并且主控机也不会移除可能导致 恢复冲突的行,即使后备机断开也是如此。

作为复制槽的替代,也可以使用wal_keep_segments 阻止移除旧的 WAL 段,或者使用archive_command 把段保存在一个归档中。不过,这些方法常常会导致保留的 WAL 段比需要的 更多,而复制槽只保留已知所需要的段数量。这些方法的一个优点是它们为 pg_wal的空间需求提供了界限,但目前使用复制槽无法做到。

类似地,hot_standby_feedback和 vacuum_defer_cleanup_age保护了相关行不被 vacuum 移除,但是前者在后备机断开期间无法提供保护,而后者则需要被设置为一个很高 的值以提供足够的保护。复制槽克服了这些缺点。

1) 查询和操纵复制槽

每个复制槽都有一个名字,名字可以包含小写字母、数字和下划线字符。

已有的复制槽和它们的状态可以在 pg_replication_slots 视图中看到。

槽可以通过流复制协议 或者 SQL 函数创建并且移除。

2) 配置实例

可以这样创建一个复制槽:

postgres=# SELECT * FROM pg_create_physical_replication_slot('node_a_slot');

  slot_name  | lsn

-------------+-----

node_a_slot |

postgres=# SELECT * FROM pg_replication_slots;

  slot_name  | slot_type | datoid | database | active | xmin | restart_lsn | confirmed_flush_lsn

--------+-----+--------+---------+--------+------+----------

node_a_slot | physical  |      |    | f  |    |      |

(1 row)

要配置后备机使用这个槽,在后备机的recovery.conf中应该配置 primary_slot_name:

standby_mode = 'on'

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'

primary_slot_name = 'node_a_slot'

级联复制

级联复制特性允许一台后备服务器接收复制连接并且把 WAL 记录流式传送给其他后备服务器, 就像一个转发器一样。可以被用来减小对于主控机的直接连接数并且使得站点间的带宽开销最小化。

一台同时扮演着接收者和发送者角色的后备服务器被称为一台级联后备服务器。 “更直接”(通过更少的级联后备服务器)连接到主控机的后备服务器被称为上游服务器, 而那些离得更远的后备服务器被称为下游服务器。 级联复制并没有对下游服务器的数量或分布设定限制, 尽管每个后备服务器仅连接到一台最终连接到单个主/备服务器的上游服务器。

一台级联后备服务器不仅仅发送从主控机接收到的 WAL 记录, 还要发送那些从归档中恢复的记录。因此某些上游连接中的复制连接被中断, 只要还有新的 WAL 记录可用,下游的流复制都会继续下去。

级联复制目前是异步的。同步复制设置当前对级联复制无影响。

不管在什么样的级联布置中,热备反馈都会向上游传播。

如果一台上游后备服务器被提升为新的主控机,且下游服务器的 recovery_target_timeline被设置成'latest', 下游服务器将继续从新的主控机得到流。

要使用级联复制,要建立级联后备服务器让它能够接受复制连接(设置max_wal_senders和hot_standby, 并且配置基于主机的认证)。 还需要设置下游后备服务器中的primary_conninfo指向级联后备服务器。

同步复制

PostgreSQL流复制默认是异步的。如果主服务器崩溃, 则某些已被提交的事务可能还没有被复制到后备服务器,这会导致数据丢失。 数据的丢失量与故障转移时的复制延迟成比例。

同步复制能够保证一个事务的所有修改都能被传送到一台同步后备服务器。 这扩大了由一次事务提交所提供的标准持久化级别。 在计算机科学理论中这种保护级别被称为 2-safe 复制。

在请求同步复制时,一个写事务的每次提交将一直等待, 直到收到一个确认表明该提交在主服务器和后备服务器上都已经被写入到磁盘上的预写日志中。 数据会被丢失的唯一可能性是主服务器和后备服务器在同一时间都崩溃。 这可以提供更高级别的持久性,尽管只有系统管理员要关心两台服务器的放置和管理。 等待确认提高了用户对于修改不会丢失的信心, 但是同时也不必要地增加了对请求事务的响应时间。 最小等待时间是在主服务器和后备服务器之间的来回时间。

只读事务和事务回滚不需要等待后备服务器的回复。 子事务提交也不需要等待后备服务器的响应,只有顶层提交才需要等待。 长时间运行的动作(如数据载入或索引构建)不会等待最后的提交消息。 所有两阶段提交动作要求提交等待,包括预备和提交。

同步备用可以是物理复制备用或逻辑复制订阅者。 它也可以是任何知道如何发送适当反馈消息的物理或逻辑WAL复制流消费者。 除了内置的物理和逻辑复制系统之外,它还包括特殊程序, 如pg_receivewal和pg_recvlogical 以及一些第三方复制系统和自定义程序。

1) 基本配置

一旦流复制已经被配置,配置同步复制就只需要一个额外的配置步骤: synchronous_standby_names必须被设置为一个非空值。synchronous_commit也必须被设置为on,但由于这是默认值, 通常不需要改变。这样的配置将导致每一次提交都等待确认消息, 以保证后备服务器已经将提交记录写入到持久化存储中。 synchronous_commit可以由个体用户设置,因此可以在配置文件中配置、 可以为特定用户或数据库配置或者由应用动态配置, 这样可以在一种每事务基础上控制持久性保证。

在一个提交记录已经在主服务器上被写入到磁盘后,WAL 记录接着被发送到后备服务器。 每次一批新的 WAL 数据被写入到磁盘后,后备服务器会发送回复消息, 除非在后备服务器上wal_receiver_status_interval被设置为零。 如果根据主服务器上synchronous_standby_names的设置将备用服务器选为同步备用服务器, 来自该后备的回复消息将被用来唤醒正在等待提交记录已被接收确认的用户。 这些参数允许管理员指定哪些后备服务器应该是同步后备。 注意同步复制的配置主要在主控机上。命名的后备服务器必须直接连接到主控机, 主控机对使用级联复制的下游后备服务器一无所知。

将synchronous_commit设置为remote_write 将导致每次提交都等待后备服务器已经接收提交记录并将它写出到其自身所在的操作系统的确认, 但并非等待数据都被刷出到后备服务器上的磁盘。这种设置提供了比on 要弱一点的持久性保障:在一次操作系统崩溃事件中后备服务器可能丢失数据, 尽管它不是一次PostgreSQL崩溃。不过,在实际中它是一种有用的设置, 因为它可以减少事务的响应时间。 只有当主服务器和后备服务器都崩溃并且主服务器的数据库同时被损坏的情况下, 数据丢失才会发生。

把synchronous_commit设置为remote_apply 将导致每一次提交都会等待,直到当前的同步后备服务器报告说它们已经重放了该事务, 这样就会使该事务对用户查询可见。在简单的情况下, 这允许带有因果一致性的负载均衡。

如果请求一次快速关闭,用户将停止等待。不过,在使用异步复制时, 在所有未解决的 WAL 记录被传输到当前连接的后备服务器之前,服务器将不会完全关闭。

2)多个同步后备

同步复制支持一个或者更多个同步后备服务器,事务将会等待, 直到所有同步后备服务器都确认收到了它们的数据为止。 事务必须等待其回复的同步后备的数量由synchronous_standby_names指定。 这个参数也指定后备服务器的名称方法(FIRST和ANY)的列表, 以从列出的选项中选择同步备用数据库。

FIRST方法指定了一个基于优先级的同步复制,并使事务提交等待, 直到它们的WAL记录被复制到根据其优先级选择的所请求数量的同步备用数据库。 其名称出现在清单前面的备用数据库被赋予更高的优先级,并将被视为同步。 此列表中后面出现的其他备用服务器代表潜在的同步备用服务器。 如果任何当前的同步备用服务器因任何原因断开连接, 它将被立即替换为次最高优先级的备用服务器。

基于优先级的多个同步后备的synchronous_standby_names示例为:

synchronous_standby_names = 'FIRST 2 (s1, s2, s3)'

在这个例子中,如果有四个后备服务器s1、s2、 s3和s4在运行,两个后备服务器s1 和s2将被选中为同步后备,因为它们出现在后备服务器名称列表的前部。 s3是一个潜在的同步后备,当s1或s2 中的任何一个失效,它就会取而代之。s4 则是一个异步后备因为它的名字不在列表中。

方法ANY指定基于定额的同步复制,并使事务提交等待, 直到它们的WAL记录至少被复制到列表中的所请求数量的同步备用数据库。

基于定额的多个同步备用数据库的synchronous_standby_names的一个例子:

synchronous_standby_names = 'ANY 2 (s1, s2, s3)'

在这个例子中,如果有四个备用服务器s1、s2、 s3和s4正在运行,事务提交将等待来自s1、 s2和s3中的任意两个备用服务器的回复。 s4是一个异步的备用服务器,因为它不在列表中 。

备用服务器的同步状态可以使用pg_stat_replication视图查看。

)3. 性能规划

同步复制通常要求仔细地规划和放置后备服务器来保证应用能令人满意地工作。 等待并不利用系统资源,但是事务锁会持续保持直到传输被确认。其结果是, 不小心使用同步复制将由于响应时间增加以及较高的争用率而降低数据库应用的性能。

PostgreSQL允许应用开发者通过复制来指定所要求的持久性级别。 这可以为整个系统指定,不过它也能够为特定的用户或连接指定, 甚至还可以为单个事务指定。

例如,一个应用的载荷的组成可能是这样:10% 的改变是重要的客户详情, 而 90% 的改变是不太重要的数据,即使它们丢失业务也比较容易容忍 (例如用户间的聊天消息)。

通过在应用级别(在主服务器上)指定的同步复制选项, 我们可以为大部分重要的改变提供同步复制,并且不会拖慢整体的载荷。 应用级别选项是使高性能应用享受同步复制的一种重要和实用的工具。

你应该认为网络带宽必须比 WAL 数据的产生率高。

高可用性规划

当synchronous_commit被设置为on或remote_write时, 发生的提交将等待直至同步后备服务器回应。如果上一个或者唯一一个后备服务器崩溃, 响应可能不会发生。

防止数据丢失的最好解决方案是确保你不会丢失你的上一个保持同步的后备服务器。 这可以通过使用synchronous_standby_names 命名多个潜在的同步后备服务器来实现。

在基于优先级的同步复制中,名称出现在列表中较早的备用服务器将用作同步备用服务器。 如果其中一个现有的备用服务器失败,在这些之后列出的备用服务器将接管同步备用服务器的角色。

在基于定额的同步复制中,出现在列表中的所有备用服务器将用作同步备用服务器的候选项。 即使其中一个失败,其他备用服务器仍将继续扮演同步备用服务器候选人的角色。

当一台后备服务器第一次附加到主服务器时,它将处于一种还没有正确同步的状态。 这被描述为追赶模式。一旦后备服务器和主服务器之间的迟滞第一次变成零, 我们就来到了实时的流式状态。 在后备服务器被创建之后的很长一段时间内可能都是追赶模式。如果后备服务器被关闭, 则追赶周期将被增加,增加量由后备服务器被关闭的时间长度决定。 只有当后备服务器到达流式状态后,它才能成为一台同步后备。 这个状态可以使用pg_stat_replication视图查看。

如果在提交正在等待确认时主服务器重启, 那些正在等待的事务将在主数据库恢复时被标记为完全提交。 没有办法确认所有后备服务器已经收到了在主服务器崩溃时所有还未处理的 WAL 数据。 某些事务可能不会在后备服务器上显示为已提交,即使它们在主服务器上显示为已提交。 我们提供的保证是:在 WAL 数据已经被后备服务器安全地收到之前, 应用将不会收到一个事务成功提交的显式确认。

如果你真的丢失了你的上一个后备服务器,那么你应该禁用 synchronous_standby_names并且在主服务器上重载配置文件。

如果主服务器与剩下的后备服务器是隔离的, 你应当故障转移到那些其他剩余后备服务器中的最佳候选者上。

如果在事务等待时你需要重建一台后备服务器,确保命令 pg_start_backup() 和 pg_stop_backup() 被运行在一个synchronous_commit = off 的会话中,否则那些请求将永远等待后备服务器出现。

后备服务器中的持续归档

当在备用数据库中使用连续WAL归档时,有两种不同的情况: WAL归档可以在主数据库和备用数据库之间共享,或者备用数据库可以有自己的WAL归档。 当备用数据库具有自己的WAL归档时,将archive_mode设置为 always,并且备用数据库将为每个收到的WAL段调用归档命令, 无论是从归档恢复或通过流复制。可以类似地处理共享归档,但是 archive_command必须测试正在归档的文件是否已存在, 以及现有文件是否具有相同的内容。这需要在archive_command 中更加小心,因为它必须小心不要覆盖具有不同内容的现有文件, 但如果完全相同的文件存档两次,则返回成功。 如果两个服务器试图同时归档同一个文件,则所有必须无竞争的完成。

如果archive_mode设置为on, 则在恢复或者待机模式期间不会启用归档程序。如果备用服务器已升级, 它将在升级后开始归档,但不会归档其未生成的任何WAL。 要在归档中获取完整的WAL文件系列,必须确保所有WAL在到达备用数据库之前都已存档。 这对于基于文件的日志传送是固有的,因为备用数据库只能恢复在归档中找到的文件, 但是如果启用了流复制,则不会。当服务器不处于恢复模式时, on和always模式之间没有区别

上一篇下一篇

猜你喜欢

热点阅读