背景
因为 AD 是一个多主系统,多个 DC 上可以同时被写入同一 DN 的 object,不过之后当这些 object 在 DC 之间 sync 时,DC 通过将其中一个 rename 为 CNF 对象的方式来解决冲突。那么,DC 会保留哪一个对象呢?
- 组里面同事的观点是:object level conflict 应该和 attribute level confilict 类似,每个 DC 会按照特定的规则(VersionNumber、Timestamp、InvocationId)处理,这样可以保证所有的 DC 按照同样的规则处理后得到的结果是一样的,以达到最终一致。
- ChatGPT 的回答不太稳定,但都是保留特定一端的对象。
- 去年问的时候说 永远是 target(sync 的接收端)重命名它自己的对象,source 保留不动。
- 今年问的时候回答如下,保留 target 端的 local 对象,重命名 source 端的 object。
🎯 你问的是:
DC B 向 DC A replicate 时,A 检测到有 CN 冲突,于是生成
CNF
对象。此时,A 会保留 自己的对象(GUID A),还是来自 B 的对象(GUID B)?✅ 简明答案:
A 会保留它本地已有的对象(GUID A),并将来自 B 的对象(GUID B)保存为一个
CNF
重命名对象。🔍 原因与机制详解:
当 Active Directory 的两个 DC 上独立创建了同一个 CN(但 GUID 不同)时:
- 系统无法通过 USN 或属性版本号判断“哪一个是更新的”,因为它们是两个完全不同的对象。
- 同步时(例如 DC B 向 DC A 复制),DC A 会尝试将对象 B 引入本地。
- 由于目标命名容器(如 OU)中已经有一个同名的 CN 对象(对象 A),CN 冲突发生。
- 解决办法是:
- 保留本地已有对象 A(GUID A)不变
- 把从 B 复制来的对象 B(GUID B)改名为 CNF 名称,并写入 A 的目录
举例,原始 CN 都是
CN=charlesfeng
冲突后,DC A 上会出现:CN=charlesfeng → 本地已有对象 A(GUID A) CN=charlesfeng\0ACNF:<GUID> → 从 B 复制来的冲突对象 B(GUID B),被改名为 CNF 对象
🔁 总结规则
冲突场景 决定谁留下 冲突对象去向 CN 冲突,GUID 不同 本地现有对象保留 新对象改名为 CNF 属性冲突(同一对象 GUID) 属性版本号较高的赢 替换属性 同 GUID 同 CN(误删重建或恢复) 看 USN / 版本 标准复制处理
那么就还是实践看结果吧。
Cutting off the Replication Chain
我们在 domain SG2TDSO1000094D.extest.microsoft.com
中有两台 DC,SG2TDSO1000094
和 SG2TDSO400002J
。
Alias | DC | Originating DSA |
---|---|---|
B | SG2TDSO1000094 | 9aaeaf75-2fdf-48a1-8bea-00c6d2d110bd |
A | SG2TDSO400002J | cf415bca-7f4e-44f5-a525-2f6c4891b8d7 |
为了构造 DN 相同但 objectGuid 不同的两个对象,我们需要先禁用两台 DC 之间的 replication。可以通过命令 repadmin /options <DC> +/-DISABLE_OUTBOUND_REPL/DISABLE_INBOUND_REPL
来做到这一点。
PS C:\Users\administrator> repadmin /options SG2TDSO400002J.SG2TDSO1000094D.extest.microsoft.com +DISABLE_OUTBOUND_REPL
Current DSA Options: IS_GC
New DSA Options: IS_GC DISABLE_OUTBOUND_REPL
PS C:\Users\administrator> repadmin /options "SG2TDSO400002J.SG2TDSO1000094D.extest.microsoft.com" +DISABLE_INBOUND_REPL
Current DSA Options: IS_GC DISABLE_OUTBOUND_REPL
New DSA Options: IS_GC DISABLE_INBOUND_REPL DISABLE_OUTBOUND_REPL
PS C:\Users\administrator> repadmin /options "SG2TDSO400002J.SG2TDSO1000094D.extest.microsoft.com"
Current DSA Options: IS_GC DISABLE_INBOUND_REPL DISABLE_OUTBOUND_REPL
官方文档 中其实没有提到参数 /options
,但另一篇文章有提到 Inbound 是 repsFrom
该 DC 从其他 DC 接收更改,Outbound 是 repsTo
该 DC 向其他 DC 发送更改。可以理解为 traffic 方向 🥸。(这跟 ChatGPT 的理解是一致的,虽然不知道他又是从哪里学来的,让给官方链接也全是 error,比如 repadmin /options | Microsoft Learn、MS-ADTS: Disable Replication Option Flags。(可能就是曾经存在,但现在已经被删吧,毕竟你也你知道你软的 doc support。。))
我的操作都是在 DC B SG2TDSO1000094
上进行的。因为这个 flag 是 disable 反着来的,所以一开始就很担心被绕晕 😵💫。想着在实践之前就记录下期望的结果总不会错了吧,结果测试完发现关于 inbound/outbound 理解似乎出了偏差? 怎么是反着来的。Anyway,被删除线划掉的是预期的结果,没被划掉的是实际上的测试结果。T
代表此方向的 change 可以被 replicate,反之 F
则不行。
Flags on B | Replication Chain | B -> A | A -> B |
---|---|---|---|
+DISABLE_INBOUND_REPL, +DISABLE_OUTBOUND_REPL | Bidirection unavailable | F | F |
+DISABLE_INBOUND_REPL | Outbound available | ||
+DISABLE_OUTBOUND_REPL | Inbound available | ||
Bidirection available | T | T |
普通对象的 CNF
Round 1:冲突对象在 DC A 上的创建时间晚于 B
理论 | 解决冲突的 DC | 保留 |
---|---|---|
Version、Timestamp、Invocationid | B | A |
Always retain local object | B | B |
Always retain remote object | B | A |
+DISABLE_OUTBOUND_REPL
、+DISABLE_INBOUND_REPL
,A/B 停止互相 replicate。- DC B 创建 object
charlesfengtoconflict
,objectGuid 为1bd8b315-04ee-4e3a-a27e-f1d3c363eba6
,创建时间为 23:56:16。
- DC A 创建 object
charlesfengtoconflict
,objectGuid 为9758a20f-0675-4f32-9775-12fbae0b3f2d
,创建时间为 23:56:30。
-
-DISABLE_OUTBOUND_REPL
,仍有+DISABLE_INBOUND_REPL
,A 可以 replicate 给 B。 -
DC B 解决冲突,保留 A 的(不过不能说明基准是更晚 timestamp 还是 always 保留对方的,因为此 case 两种情况相同),把 B 自己原有的对象 rename 为 CNF 对象,更新时间为 23:58:53。
-DISABLE_INBOUND_REPL
,A/B 互相可以 replicate。- DC A 同步结束,保留 B 解决完的结果,即 A 自己原有的对象不变,B 创建的 CNF 对象被同步。但 CNF 对象的属性
cn
被 local 修改,注意 version 为 1 而不是 2,(猜想是因为此 CNF 对象是被复制过来新创建的,version 从 local 开始计算,)local 更新时间为 00:05:05。
- DC B 同步结束,没有任何改变。
Round 2:冲突对象在 DC B 上的创建时间晚于 A
理论 | 解决冲突的 DC | 保留 |
---|---|---|
Version、Timestamp、Invocationid | B | B |
Always retain local object | B | B |
Always retain remote object | B | A |
+DISABLE_OUTBOUND_REPL
、+DISABLE_INBOUND_REPL
,A/B 停止互相 replicate。- DC A 创建 object
charlesfengtoconflict2
,objectGuid 为4b2970ff-a18b-4ea7-9b11-dcb8228ed650
,创建时间为 00:14:30。
- DC B 创建 object
charlesfengtoconflict2
,objectGuid 为64ce6d01-e64f-43c4-8399-6377f41db224
,创建时间为 00:15:51。
-DISABLE_OUTBOUND_REPL
,仍有+DISABLE_INBOUND_REPL
,A 可以 replicate 给 B。- DC B 解决冲突,保留 B 自己的(timestamp 更晚的而不是坚持保留对方的),把 A 的对象 rename 为 CNF 对象,更新时间为 00:17:28。
-DISABLE_INBOUND_REPL
,仍有+DISABLE_OUTBOUND_REPL
,只允许 B replicate 给 A。- DC A 同步结束,保留 B 解决完的结果,即保留 B 创建的对象,而 A 自己原有的对象变成 CNF 对象。A 自己原有对象上的属性
cn/name
version++。两个对象 metadata 都被更新,local 更新时间为 00:20:56。
-DISABLE_OUTBOUND_REPL
,A/B 互相可以 replicate。- DC B 同步结束,CNF 对象 metadata 被再次更新,更新时间为 00:25:26。(注意现在是在 DC B 上,对比下图和 2.5 的图,可以看出 CNF 对象上不同的 metadata 包含
cn/name
。基于猜想cn
是 local 维护的,那么这里 B 的第二次更新则是由 2.7 中 A 对name
的本地更新导致的。为什么 2.7 中 A 要更新 name,1.7 中不必呢?因为 2.7 中 CNF 对象的原对象原本就在 A 上,A 可以根据 objectGuid 找到此对象并进行 rename 操作,所以产生了本地写请求;而 1.7 中 CNF 对象的原对象由 B 创建,A 上本来就没有,所以只需要同步最新的数据就行了,不需要本地 rename。)
小结
CNF 对象都是在 DC B 解决 A replicate to B 时产生的,一次保留的是 A 另一次是 B,说明跟 source/target 无关。但是,两次被保留的对象都是 timestamp 更大的,所以从实践看仍遵循 attribute level conflict 的处理规则。
再者,我们可以从一个更复杂的 case 来思考这个问题。如果复制拓扑是线性链条 A ---> B <--- C,A/C 之间不会互相 replicate。在 A/C 上面分别创建 DN 相同的对象,当它们都跟 B sync 时,B 需要解决冲突,B 会保留哪一个呢?假设冲突对象在 DC A 上的创建时间晚于 C,列出以下表格帮助分析。
理论 | 解决冲突的 DC | 保留 |
---|---|---|
Version、Timestamp、Invocationid | B | A |
Always retain local object | B | 取决于 A/C 谁先与 B 同步 |
Always retain remote object | B | 取决于 A/C 谁先与 B 同步 |
如果如 ChatGPT 所言,总是保留一端的对象,那么实际解决冲突时,因为 B 自己本身并没有 conflict 对象,一切都将取决于 A/C 与 B 之前的同步顺序。如果 B 总是保留 local 对象,那么它会保留它最早接收到的对象的原始 CN,后到的对象会被重命名为 CNF 对象。
那么问题就出现了。如果 A 先与 B 同步,C 再与 B 同步,那么 B 解决冲突时会保留 A 创建的对象并将 C 创建的对象 rename CNF。可是这样的结果如果因为网络问题不能被 replicate 到全局,我们此时让 A/C 互相同步并且由 C 来解决冲突,那么 C 会保留自己的,并将 A 创建的对象 rename CNF。 现在让 A/B/C 三者 full mesh 都可以同步,那么会发现 B/C 仍有 object level conflict(objectGuid 不一致但 CN 一致),仍需要解决 CNF,但是相关的 CNF 对象也已经被创建了,CNF 对象也会有重名问题,这样没有办法达到最终一致。
不过,如果按照统一的方式处理(Version、Timestamp、Invocationid),不管是 A/B/C 最后都会保留 A 而将 C 上创建的对象 rename CNF,从而达到最终一致。就算因为网络分区/可达性,由不同的 DC 来处理冲突,保留的结果和被 rename CNF 的对象都是确定的,objectGuid 也是一样的,这样就算这些对象之后需要在不同 DC 之间同步,最多也就是 attribute level conflict,而不会再有 CNF conflict 了。
对象被重复删除时会产生 CNF 对象吗?
不会,因为它们 objectGuid 相同,实际是同一对象,只是 attribute level conflict。不过说到这里了,那就试一试。
Round 3:被删除对象在 DC A 上的删除时间晚于 B
- DC B 创建 object
charlesfengtodelete
,objectGuid 为874a4067-875e-43eb-aaba-6c8032c10256
,创建时间为 00:35:22。
- DC A 从 B 上 replicate 此对象,local 创建时间为 00:35:23。
+DISABLE_OUTBOUND_REPL
、+DISABLE_INBOUND_REPL
,A/B 停止互相 replicate。- DC B 删除此对象,删除时间为 00:39:41。
- DC A 删除此对象,删除时间为 00:41:27。
-DISABLE_INBOUND_REPL
,仍有+DISABLE_OUTBOUND_REPL
,B 可以 replicate 给 A。- A 收到 B 的 change,保留 A 自己的 change(因为 version 相同都为 2,但 A timestamp 更大),没有任何变动。
-DISABLE_OUTBOUND_REPL
,仍有+DISABLE_INBOUND_REPL
,只允许 A replicate 给 B。- B 收到 A 处理之后的 change,将属性更新为 A 解决完冲突后的对象的值。
cn
version++ 从 2 更新为 3,更新时间为 00:45:59。
-DISABLE_INBOUND_REPL
,A/B 互相可以 replicate。- A/B 同步结束,没有任何改变。A 上
cn
version 不变,仍然是 2。
Round 4:被删除对象在 DC B 上的删除时间晚于 A
- DC B 创建 object
charlesfengtodelete2
,objectGuid 为387e14f0-8cc6-4aaa-8ef6-acbbc5a21f17
,创建时间为 00:57:08。
- DC A 从 B 上 replicate 此对象,local 创建时间为 00:57:23。
+DISABLE_OUTBOUND_REPL
、+DISABLE_INBOUND_REPL
,A/B 停止互相 replicate。- DC A 删除此对象,删除时间为 00:59:18。
- DC B 删除此对象,删除时间为 01:00:10。
-DISABLE_INBOUND_REPL
,仍有+DISABLE_OUTBOUND_REPL
,B 可以 replicate 给 A。- A 收到 B 的 change,保留 B 的 change(因为 version 相同都为 2,但 B timestamp 更大),并将 metadata 更新为 B 上对象的值。
cn
version++,更新时间为 01:01:33。
-DISABLE_OUTBOUND_REPL
,仍有+DISABLE_INBOUND_REPL
,只允许 A -> B。- B 收到 A 的 change,没有任何变动。
-DISABLE_INBOUND_REPL
,A/B 互相可以 replicate。- A/B 同步结束,没有任何改变。
总结
- 解决 object level conflict、创建 CNF 对象的逻辑和 attribute level conflict 类似。虽然他们 objectGuid 不一样,数据库层面可以被视为两个对象,但是考虑到 AD 中 DN 也是唯一的,所以在解决冲突时使用 VersionNumber 也算 make sense 吧。(ChatGPT 又胡说啦 😷)
- 根据 object/attribute level conflict resolution 的上述实践,我们还可以看出:
- object metadata AttID 为 3 的属性
cn
看起来不参与 replicate,(或者说参与,但是被特殊处理了,)且始终维护Originating DSA
为 local DC,单纯修改属性cn
不会产生 change for replication。猜想是因为属性cn
总是本地写入/构造的,因此Originating DSA
总是为 local DC。 - 其他属性被更新则会导致 metadata 更新,并产生 change for replication。同时,属性
cn
也总是会被同步修改并导致 version++。
- object metadata AttID 为 3 的属性
参考
Appendix 1: Attribute Mapping
通过 ldp 查看 metadata 和命令 repadmin /showobjmetadata
有一点点小差别,但结合起来看,我们可以知道有如下表的对应关系。
AttID | Attribute |
---|---|
0 | objectClass |
3 | cn |
20001 | instanceType |
20002 | whenCreated |
20030 | isDeleted |
200a9 | showInAdvancedViewOnly |
20119 | nTSecurityDescriptor |
90001 | name |
9030d | lastKnownParent |
9030e | objectCategory |
9080a | isRecycled |
Appendix 2: Replication Does Not Resume After Re-enabling Inbound Replication
你提到:
+DISABLE_INBOUND_REPL
后又执行了-DISABLE_INBOUND_REPL
,但仍然没有发生同步。这种情况确实可能发生,以下是可能原因和排查建议:
🧠 原理回顾
当你执行:
repadmin /options <DC> +DISABLE_INBOUND_REPL
你是临时告诉这台 DC 不接收复制,这会阻止来自其他 DC 的入站复制请求。
当你随后执行:
repadmin /options <DC> -DISABLE_INBOUND_REPL
你是恢复入站复制能力,但它不会自动触发一次同步。 它只是让这台 DC 重新具备“可以同步”的资格,但并不会马上进行一次复制。
✅ 如何触发手动同步
你需要手动触发一次同步,命令如下:
repadmin /syncall <DC主机名或FQDN> /AeD
含义:
参数 作用 /A
所有 Naming Context(如 domain、config、schema) /e
跨站点 /D
显示每个对象的详细同步信息 例如:
repadmin /syncall SG2TDSO400002J.SG2TDSO1000094D.extest.microsoft.com /AeD
PS C:\Users\administrator> repadmin /syncall "SG2TDSO400002J.SG2TDSO1000094D.extest.microsoft.com" /AeD
Syncing all NC's held on SG2TDSO400002J.SG2TDSO1000094D.extest.microsoft.com.
Syncing partition: CN=Schema,CN=Configuration,DC=SG2TDSO1000094D,DC=extest,DC=microsoft,DC=com
CALLBACK MESSAGE: The following replication is in progress:
From: 9aaeaf75-2fdf-48a1-8bea-00c6d2d110bd._msdcs.SG2TDSO1000094D.extest.microsoft.com
To : b36c1250-bbd1-44c1-a74a-ca121ab8e538._msdcs.SG2TDSO1000094D.extest.microsoft.com
CALLBACK MESSAGE: The following replication completed successfully:
From: 9aaeaf75-2fdf-48a1-8bea-00c6d2d110bd._msdcs.SG2TDSO1000094D.extest.microsoft.com
To : b36c1250-bbd1-44c1-a74a-ca121ab8e538._msdcs.SG2TDSO1000094D.extest.microsoft.com
CALLBACK MESSAGE: SyncAll Finished.
SyncAll terminated with no errors.
Syncing partition: CN=Configuration,DC=SG2TDSO1000094D,DC=extest,DC=microsoft,DC=com
CALLBACK MESSAGE: The following replication is in progress:
From: 9aaeaf75-2fdf-48a1-8bea-00c6d2d110bd._msdcs.SG2TDSO1000094D.extest.microsoft.com
To : b36c1250-bbd1-44c1-a74a-ca121ab8e538._msdcs.SG2TDSO1000094D.extest.microsoft.com
CALLBACK MESSAGE: The following replication completed successfully:
From: 9aaeaf75-2fdf-48a1-8bea-00c6d2d110bd._msdcs.SG2TDSO1000094D.extest.microsoft.com
To : b36c1250-bbd1-44c1-a74a-ca121ab8e538._msdcs.SG2TDSO1000094D.extest.microsoft.com
CALLBACK MESSAGE: SyncAll Finished.
SyncAll terminated with no errors.
Syncing partition: DC=SG2TDSO1000094D,DC=extest,DC=microsoft,DC=com
CALLBACK MESSAGE: The following replication is in progress:
From: 9aaeaf75-2fdf-48a1-8bea-00c6d2d110bd._msdcs.SG2TDSO1000094D.extest.microsoft.com
To : b36c1250-bbd1-44c1-a74a-ca121ab8e538._msdcs.SG2TDSO1000094D.extest.microsoft.com
CALLBACK MESSAGE: The following replication completed successfully:
From: 9aaeaf75-2fdf-48a1-8bea-00c6d2d110bd._msdcs.SG2TDSO1000094D.extest.microsoft.com
To : b36c1250-bbd1-44c1-a74a-ca121ab8e538._msdcs.SG2TDSO1000094D.extest.microsoft.com
CALLBACK MESSAGE: SyncAll Finished.
SyncAll terminated with no errors.