跳转至

日志传送备服务

备服务流复制WAL日志

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

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

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

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

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

规划

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

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

备服务操作

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

在启动时,后备机通过恢复归档位置所有可用的 WAL 来开始,这称为restore_command。一旦它到达那里可用的 WAL的末尾并且restore_command失败,它会尝试恢复sd_wal目录中可用的任何WAL。如果那也失败并且流复制已被配置,后备机会尝试连接到主服务并且从在归档或sd_wal中找到的最后一个可用记录开始流式传送WAL。如果那失败并且没有配置流复制,或者该连接后来断开,后备机会返回到步骤 1并且尝试再次从归档里的文件恢复。这种尝试归档、sd_wal和流复制的循环会一直重复知道服务停止或者一个触发器文件触发了故障转移。

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

为备服务准备主控机

连续归档和时间点恢复(PITR)中所述,在主服务上设置连续归档到一个后备服务可访问的归档目录。即使主服务垮掉该归档位置也应当是后备服务可访问的,即它应当位于后备服务本身或者另一个可信赖的服务,而不是位于主控服务上。

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

制作一个基础备份所述取得一个基础备份来引导后备服务。

建立备服务

要建立后备服务,恢复从主服务取得的基础备份(使用一个连续归档备份进行恢复)。在后备服务的集簇数据目录中创建一个恢复命令文件recovery.conf,并且打开standby_mode。将restore_command设置为一个从WAL归档中复制文件的简单命令。如果你计划为了高可用性目的建立多个后备服务,将recovery_target_timeline设置为latest来使得该后备服务遵循发生在故障转移到另一个后备服务之后发生的时间线改变。

注意

不要把 sd_standby或相似的工具和这里描述的内建后备模式一起使用。如果文件不存在,restore_command应该立即返回,如果必要该服务将再次尝试该命令。使用类似sd_standby的工具请见日志传送的替代方法

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

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

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

seaboxsql.conf的一个简单例子是:

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
restore_command = 'cp /path/to/archive/%f %p'
archive_cleanup_command = 'sd_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和认证选项(见sd_hba.conf),这样后备服务可以连接到主服务上的伪数据库replication(见认证)。

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

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

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

  • 流复制认证

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

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

# 允许来自 192.168.1.100 的用户 "foo" 在提供了正确的口令时作为一个
# 复制后备机连接到主控机。
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     foo             192.168.1.100/32        md5

主服务的主机名和端口号、连接用户名和口令在seaboxsql.conf文件中指定。在后备服务上还可以在~/.sdpass文件中设置口令(在database域中指定replication)。例如,如果主服务运行在主机IP192.168.1.50、端口5432上,并且口令为foopass,管理员可以在后备服务的seaboxsql.conf文件中增加下列行:

# 后备机要连接到的主控机运行在主机 192.168.1.50 上,
# 端口号是 5432,连接所用的用户名是 "foo",口令是 "foopass"。
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
  • 流复制监控

流复制的一个重要健康指标是在主服务上产生但还没有在后备服务上应用的 WAL 记录数。你可以通过比较主服务上的当前 WAL写位置和后备服务接收到的最后一个 WAL位置来计算这个滞后量。这些位置分别可以用主服务上的pg_current_wal_lsn和后备服务上的pg_last_wal_receive_lsn来检索(详见表 备份控制函数表 恢复信息函数)。后备服务的最后WAL 接收位置也被显示在 WAL接收者进程的进程状态中,即使用ps命令显示的状态(详见标准 Unix 工具章节)。

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

在一台热后备上,WAL接收者进程的状态可以通过pg_stat_wal_receiver视图检索到。pg_last_wal_replay_lsn和该视图的received_lsn的差别表示WAL的接收速度大于它被重放的速度。

复制槽

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

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

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

  • 查询和操纵复制槽

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

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

槽可以通过SQL函数(见复制函数)创建并且移除。

  • 配置实例

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

seaboxs=# SELECT * FROM pg_create_physical_replication_slot('node_a_slot');
  slot_name  | lsn
-------------+-----
 node_a_slot |

seaboxs=# SELECT slot_name, slot_type, active FROM pg_replication_slots;
  slot_name  | slot_type | active 
-------------+-----------+--------
 node_a_slot | physical  | f
(1 row)

要配置后备机使用这个槽,在后备机的seaboxsql.conf中应该配置 primary_slot_name。这里是一个简单的例子:

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_sendershot_standby,并且配置基于主机的认证)。你还将需要设置下游后备服务中的primary_conninfo指向级联后备服务。

同步复制

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

同步复制能够保证一个事务的所有修改都能被传送到一台或者多台同步后备服务。这扩大了由一次事务提交所提供的标准持久化级别。在计算机科学理论中这种保护级别被称为2-safe 复制。而当synchronous_commit被设置为remote_write时,则是 group-1-safe(group-safe 和 1-safe)。

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

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

同步后备可以是物理复制后备或者是逻辑复制订阅者。它还可以是任何其他物理或者逻辑WAL复制流的消费者,它懂得如何发送恰当的反馈消息。除内建的物理和逻辑复制系统之外,还包括sd_receivewalsd_recvlogical之类的特殊程序,以及一些第三方复制系统和定制程序。同步复制支持的细节请查看相应的文档。

  • 基本配置

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

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

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

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

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

  • 多个同步备实例

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

方法FIRST指定一种基于优先的同步复制并且让事务提交等待,直到它们的WAL记录被复制到基于优先级选中的所要求数量的同步后备上为止。在列表中出现较早的后备被给予较高的优先级,并且将被考虑为同步后备。其他在这个列表中位置靠后的后备服务表示可能的同步后备。如果任何当前的同步后备由于任何原因断开连接,它将立刻被下一个最高优先级的后备所替代。

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

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

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

方法ANY指定一种基于规定数量的同步复制并且让事务提交等待,直到它们的WAL记录至少被复制到列表中所要求数量的同步后备上为止。

synchronous_standby_names的基于规定数量的多同步后备的例子:

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

在这个例子中,如果有四台后备服务s1s2s3以及s4正在运行,事务提交将会等待来自至少其中任意两台后备服务的回复。s4是一台异步后备,因为它的名字不在该列表中。

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

  • 性能规划

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

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

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

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

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

  • 高可用性规划

synchronous_commit被设置为onremote_apply或者remote_write时,synchronous_standby_names指定产生的事务提交要等待其回应的同步后备的数量和名称。如果任一同步后备崩溃,这类事务提交可能无法完成。

高可用的最佳方案是确保有所要求数量的同步后备。这可以通过使用synchronous_standby_names指定多个潜在后备服务来实现。

在基于优先的同步复制中,出现在该列表前部的后备服务将被用作同步后备。后面的后备服务将在当前同步后备服务失效时取而代之。

在基于规定数量的同步复制中,所有出现在该列表中的后备服务都将被用作同步后备的候选。即使其中的一个失效,其他后备仍将继续担任候选同步后备的角色。

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

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

如果实在无法保持所要求数量的同步后备,那么应该减少synchronous_standby_names中指定的事务提交应该等待其回应的同步后备的数量(或者禁用),并且在主服务上重载配置文件。

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

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

在后备机上连续归档

当在一个后备机上使用连续归档时,有两种不同的情景:WAL 归档在主服务 和后备机之间共享,或者后备机有自己的 WAL归档。当后备机拥有其自身的 WAL 归档时,将archive_mode设置为always,后备机将在收到每个 WAL 段时调用归档命令, 不管它是从归档恢复还是使用流复制恢复。共享归档可以类似地处理,但是archive_command必须测试要被归档的文件是否 已经存在,以及现有的文件是否有相同的内容。这要求archive_command中有更多处理,因为它必须当心 不要覆盖具有不同内容的已有文件,但是如果完全相同的文件被归档两次时应返回成功。并且如果两个服务尝试同时归档同一个文件,所有这些都必须 在没有竞争情况的前提下完成。

如果archive_mode被设置为on,归档器 在恢复或者后备模式中无法启用。如果后备服务被提升,它将在被提升后开始归档,但是它将不会归档任何不是它自身产生的 WAL。要在归档中得到完整的 一系列 WAL 文件,你必须确保所有WAL 在到达后备机之前都被归档。对于基于 文件的日志传输来说天然就是这样,因为后备机只能恢复在归档中找到的文件,而启用了流复制时则不是这样。当一台服务不在恢复模式中时,在onalways模式之间没有差别。