LDAP 是一棵按名字组织的目录树,天生读多写少。 定位靠 DN,筛选靠过滤器,性能靠「范围写小」。
这是全书的最后一个应用协议。前面那些协议传的是「数据」,LDAP 传的是「关于人的数据」, 所以它的结构长得像一个组织架构图。 Demo 把 DN 拆开、把过滤器筛一遍、把三种搜索范围的代价摆出来。
一、目录不是数据库
LDAP 的祖先是一个叫 X.500 的标准,那套东西重到几乎没人实现得动, 于是有人做了一个「轻量版」,名字里的 L 就是 lightweight。 它和数据库的差别不是性能,是定位:
| 维度 | 目录(LDAP) | 数据库 |
|---|---|---|
| 读写比例 | 读极多、写极少 | 读写都多 |
| 擅长 | 按名字查一条 | 任意条件组合查询 |
| 事务 | 没有 | 有,且很严格 |
| 表间关联 | 没有,只有一棵树 | 有外键和连接 |
「读多写少」这四个字决定了一切:一个人一天登录几十次,改一次手机号, 所以目录被优化成了「按名字取一条」的机器,代价就是放弃了事务和关联。 它的典型用途只有三类:登录认证、组织架构、设备清单。 共同点是「有唯一的名字,按名字查,很少改」—— 订单和库存完全没有这个形状,所以它们不在 LDAP 里。
二、DN:一条倒着写的路径
LDAP 里每个条目都有一个全局唯一的名字,叫 DN,可分辨名。
它的写法和文件路径正好相反:最左边是最具体的一层,最右边是最笼统的一层。
举个完整的例子:cn=张三,ou=研发部,dc=example,dc=com。
读起来是「人、部门、公司、顶级域」,而文件路径会写成 /com/example/研发部/张三。
一个是自下而上,一个是自上而下。
为什么要倒着写:因为这样从左往右读,就是「从叶子往根走」,
而每往右一层,范围就大一圈——这个顺序让「向上归并」变得自然,也正是权限继承需要的方向。
每一小段叫 RDN,dc 是域名组件、ou 是组织单位、cn 是通用名,uid 是账号名。
这几个前缀不是随便起的,是 schema 规定的,写错前缀服务器会直接拒绝。
三、过滤器与搜索范围
过滤器是 LDAP 的查询语言,它用前缀表达式:
& 是与、| 是或、! 是非,全部写在括号最前面。
比如 (&(title=工程师)(cn=王五)) 就是「既是工程师、又叫王五」。
这个写法来自 Lisp 那一脉,
好处是不用记运算符优先级——括号已经把结构说清楚了,从左往右读就是。
代价是人读起来别扭,所以真实的查询通常由界面拼出来,而不是手写。
比过滤器更影响性能的是「搜索范围」:
| 范围 | 查多深 | 代价 |
|---|---|---|
| base | 只查这一个条目 | 最快,读一条属性 |
| one | 只查直接子节点 | 快,一层而已 |
| sub | 整个子树 | 慢,可能遍历几万条 |
第三行是 LDAP 性能问题的头号来源:
在 dc=example,dc=com 上做 sub 搜索,等于把整个目录扫一遍。
企业目录动辄十万条,这一下就是几十秒,而且是把服务器的内存和磁盘一起拖住。
所以有三条铁律:范围写小、过滤器写准、只要需要的属性。
第三条常被忘:
默认会回全部属性,而你往往只要一个邮箱——指定属性列表,能把返回的数据量砍掉一大半。
最后说一句现实:
你可能从没直接用过 LDAP,但一定用过它——Windows 域里的 AD 就是 LDAP 加 Kerberos,
公司的统一登录、邮件系统的通讯录,背后都是这棵树。
四、动手 Demo:LDAP 用「路径」找人,用「过滤器」筛人
把一个 DN 拆成四层,再拿五个过滤器筛三个条目,最后看三种搜索范围的代价。
dn = "cn=张三,ou=研发部,dc=example,dc=com"
for i, p in enumerate(dn.split(","), 1):
print(i, p)
entries = {
"cn=张三": "工程师",
"cn=李四": "经理",
"cn=王五": "工程师",
}
hit = [k for k, v in entries.items() if v == "工程师"]
print(len(hit), "、".join(hit))
实跑输出:
--------------------------------------------------------------------
LDAP 用「路径」找人,用「过滤器」筛人
--------------------------------------------------------------------
**LDAP 是一棵目录树**,**每个条目用一个 DN(可分辨名)定位,写法和文件路径反过来读。**
**下面把 DN 拆开看,再拿过滤器筛一遍。**
层 属性 值 位置
--------------------------------------------------------
1 cn 张三 **第 1 级(从叶子往上数)**
2 ou 研发部 **第 2 级(从叶子往上数)**
3 dc example **第 3 级(从叶子往上数)**
4 dc com **第 4 级(从叶子往上数)**
**注意顺序**:**最左边是「最具体」的一层(这个人),最右边是「最笼统」的一层(顶级域)。**
**所以 DN 读起来是「从人到公司」**,**而文件路径是从根往下写**,比如 `/com/example/研发部/张三`。
**两者正好相反。**
**这个「倒着写」的设计当初是为了让树能从上往下遍历。**
**第二件事是「过滤器」,也就是 search filter 的语法**:
过滤器 命中 命中的条目
--------------------------------------------------------------------
`(objectClass=person)` 3 cn=张三、cn=李四、cn=王五
`(title=工程师)` 2 cn=张三、cn=王五
`(&(title=工程师)(cn=王五))` 1 cn=王五
`(|(cn=李四)(cn=张三))` 2 cn=张三、cn=李四
`(!(title=工程师))` 1 cn=李四
**第三到第五行是「前缀表达式」**:**`&` 是与、`|` 是或、`!` 是非,都写在括号最前面。**
**这个写法和 Lisp 一样,好处是不用考虑运算符优先级**——**括号已经把结构说清楚了。**
**第三件事是「搜索范围」,它比过滤器更影响性能**:
范围 查多深 代价
-----------------------------------------------------------
**base** **只查这一个条目** **最快,读一条属性**
**one** **只查直接子节点** **快,一层**
**sub** **整个子树** **慢,可能遍历几万条**
**第三行是 LDAP 性能问题的头号来源**:
**在 `dc=example,dc=com` 上做 sub 搜索,等于把整个目录扫一遍。**
**企业目录动辄十万条,这一下就是几十秒。**
**所以「范围写小、过滤器写准、只要需要的属性」是三条铁律。**
**最后说清它和数据库的区别**:**LDAP 是「读多写少」的目录**,
**它的强项是「按名字查一条」和「按属性筛一批」,弱项是事务和复杂关联。**
**所以它用在「登录认证、组织架构、设备清单」这类地方,不用在「订单、库存」这类地方。**
第一张表最该盯的是「位置」那一列:第 1 级是「张三」,第 4 级是「com」,越往右越笼统。
而文件路径的 /com/example/研发部/张三 是反过来的,同一个树,两种读法。
第二张表里第三到第五行是前缀表达式:&、|、! 都写在括号最前面,不看优先级。
第四行 (|(cn=李四)(cn=张三)) 命中两条,
顺序还和写的时候一样——LDAP 不保证返回顺序,要排序得在客户端做。
第三张表是这三张里最实用的:base 只读一条、one 只读一层、sub 读整棵子树,
三者的代价差着好几个数量级。
很多「登录变慢」的工单,最后查出来就是某段代码用了 sub。
30 秒实验
- 算一下:十万条、每条 1 KB,sub 全扫要读多少数据?(约 100 MB,而且是一条条读出来的)
- 想一下:为什么 LDAP 用 DN 而不是数字主键?
- 想一下:为什么「只要需要的属性」也能明显提速?
常见误区
- 以为「LDAP 是数据库」。 它是目录:读多写少、无事务、无关联,形状完全不同。
- 以为「DN 从左往右是从上往下」。 正好相反,最左边是最具体的叶子。
- 以为「sub 搜索最方便所以到处用」。 它是性能头号杀手,十万条的目录能扫出几十秒。
自测三问(点开对答案)
1. DN 应该怎么读? —— **从最具体读到最笼统**,也就是从人到公司,和文件路径相反。 2. `(&(a=1)(b=2))` 是什么意思? —— **两个条件同时满足**,`&` 是与,写在整个表达式的最前面。 3. LDAP 适合什么场景? —— **按名字查一条、按属性筛一批**:认证、组织架构、设备清单。第八章到此结束。下一章讲网络安全:前面所有协议都假设「对方是善意的」, 最后一章把这个假设拿掉。
- ☐ (本章完,下一节 9.1 TCP/IP与网络安全)