软件设计 架构 阮一峰
2024-03-04
几乎所有我喜欢的软件架构师,都认同康威定律(Conway Law),认为这个定律非常重要,足以影响到所有系统。而且,你没法与之抗争,想要抗拒康威定律注定要失败。
康威定律的最好表述是:“任何系统的构成,都反映了设计这个系统的组织结构。”
它的出处是 Melvin Conway 在 1968 年写的一篇文章。后来,弗雷德·布鲁克斯(Fred Brooks)在著名的《人月神话》(The Mythical Man-Month)引用了这条定律。
Melvin Conway 观察到,软件系统的架构看起来与构建它的开发团队的组织结构非常相似。
最初的描述是,如果一个团队编写一个编译器,那么它将是一个单通道编译器;但是,如果两个团队共同开发,那么它将是一个双通道编译器。这个描述后来被发现,广泛适用于大量系统。
正如我的同事 Chris Ford 对我说的:“软件耦合是由人类交流促成的。” 如果我可以轻松与代码作者交谈,那么我就更容易对代码有更深入的了解,因此我的代码更容易耦合到该代码。
应对康威定律的第一步是不要与之抗争。我仍然记得一位技术主管,他刚刚被任命为 一个大型新项目的架构师,该项目由分布在世界各地不同城市的六个团队组成。“我做出了第一个架构决定”,他告诉我:“就是这个系统将有六个主要的子系统。我不知道它们会是什么子系统,但肯定会有六个。”
为了适应康威定律,现在有一种策略,就是一旦定下软件架构,就相应改变组织结构,让紧密耦合模块的开发者更容易沟通。
Redis 架构 分布式锁
2022-01-18
SET lockKey endpointFlag EX 10 NX
架构 微服务 etcd
2021-12-09
简介
etcd 是 CoreOS 的一个子项目,KV 数据库,原是设计用于存储集群共享配置数据,基于 Apache 协议开源。
PS: CoreOS 2018 年被 RedHat 收购,然后更名为 Container Linux, 后来和 RedHat 的 Project Atomic 合并,形成 Fedora CoreOS 项目。
本质是一个 KV 存储,从这个角度看和 Redis 很像。但:
| Item |
etcd |
Redis |
| 性能 |
高 |
非常高 |
| 存储方式 |
磁盘 + 内存缓存 |
内存 |
| 持久化 |
boltdb |
AOF + rdb |
| 数据类型 |
string |
丰富 |
| API |
gRPC |
RESP |
| 一致性 |
Raft |
复制 |
PS:性能:更高 QPS + 更低延迟
PS:RESP:REdis Serialization Protocol(TCP 文本协议)
作用
本质就是一个轻量级的 KV 存储系统,由于其高并发、支持分布式(强一致性)、支持版本控制、支持实时更新通知等特点,所以常用于:
- 服务发现:帮助分布式系统中的服务相互找到并建立连接。
- 配置管理:集中管理和动态更新分布式系统的配置信息。
- 分布式锁:确保在分布式环境中,同一时间只有一个客户端能获取锁,避免资源冲突。
概念
-
Raft 算法
Raft 是一种用于管理复制日志的一致性算法,节点之间通过复制日志来达成一致状态。
当客户端发起写请求时,领导者(Leader)节点接收请求并将其记录到日志中,接着把日志条目复制到其他跟随者(Follower)节点。
只有当多数节点都成功复制了该日志条目后,该条目才会被提交,数据更新才会被应用。
-
角色相关概念
- Node(节点)/Member(成员):指 etcd 集群中的单个服务器实例,每个节点都有一个唯一标识,存储着部分或全部的数据副本,并且参与集群的一致性协议。
- Leader(领导者):负责处理客户端写请求和协调日志复制的节点。同一时刻集群中只有一个领导者,它会接收客户端的写操作,并将这些操作广播给其他跟随者节点。
- Follower(跟随者):跟随领导者的指令,接收并复制领导者发送的日志条目。跟随者不直接处理客户端的写请求,只响应领导者的请求。
- Candidate(候选人):在 Raft 算法的选举过程中,当跟随者在一定时间内没有收到领导者的心跳信息时,它会转变为候选人状态。
候选人会发起新一轮的选举,向其他节点请求投票。如果获得多数节点的投票,候选人就会成为新的领导者。
- Peer(对等节点):指集群中地位平等的其他节点,节点之间相互通信以实现数据复制、选举等功能。
-
Term(任期)
逻辑时间概念,用于划分不同的选举周期,确保选举的正确性和防止过期的领导者重新掌权,同时在日志复制和消息传递中也起到重要的同步和协调作用。
每个任期从一次选举开始,有一个唯一的递增编号。
在一个任期内,最多只能有一个领导者。
如果选举失败或领导者故障,会开启一个新的任期。
结构


和 Redis 对比
- 两者都是 key-value 类型存储
- Redis 是 C 写的,而 etcd 是 Go 写的
- Redis 支持多种数据类型,而 etcd 不支持
- Redis 支持 Lua 编程,而 etcd 不支持
- Redis 支持简单的权限控制,而 etcd 不支持
- Redis 采用 RESP 私有协议,而 etcd 采用 GRPC 或 HTTP (JSON)
- Redis 支持复制集的方式同步数据,而 etcd 则是通过 Raft 实现强一致性
- Redis 通过 RDB (快照) / AOF (增量) 的方式持久化
部署 (Ubuntu)
sudo apt install -y etcd
etcd --version
sudo systemctl start etcd
sudo systemctl status etcd
部署 (Docker)
docker-compose.yml:
version: "3"
networks:
etcd_net:
driver: bridge
ipam:
driver: default
config:
- subnet: ${NETWORK_CONFIG_SUBNET}
services:
etcd-0:
networks:
etcd_net:
ipv4_address: ${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}
image: quay.io/coreos/etcd:latest
ports:
- ${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:4001:4001
- ${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:2380:2380
- ${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:2379:2379
hostname: etcd-0
environment:
- GOMAXPROCS=2
command: >-
/usr/local/bin/etcd
-name etcd-0
-advertise-client-urls http://etcd-0:2379,http://etcd-0:4001
-listen-client-urls http://${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:2379,http://${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:4001
-initial-advertise-peer-urls http://etcd-0:2380
-listen-peer-urls http://${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:2380
-initial-cluster-token etcd-cluster
-initial-cluster etcd-0=http://etcd-0:2380,etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380
-initial-cluster-state new
etcd-1:
networks:
etcd_net:
ipv4_address: ${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}
image: quay.io/coreos/etcd:latest
ports:
- ${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:4001:4001
- ${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:2380:2380
- ${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:2379:2379
hostname: etcd-1
environment:
- GOMAXPROCS=2
command: >-
/usr/local/bin/etcd
-name etcd-1
-advertise-client-urls http://etcd-1:2379,http://etcd-1:4001
-listen-client-urls http://${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:2379,http://${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:4001
-initial-advertise-peer-urls http://etcd-1:2380
-listen-peer-urls http://${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:2380
-initial-cluster-token etcd-cluster
-initial-cluster etcd-0=http://etcd-0:2380,etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380
-initial-cluster-state new
etcd-2:
networks:
etcd_net:
ipv4_address: ${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}
image: quay.io/coreos/etcd:latest
ports:
- ${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:4001:4001
- ${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:2380:2380
- ${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:2379:2379
hostname: etcd-2
environment:
- GOMAXPROCS=2
command: >-
/usr/local/bin/etcd
-name etcd-2
-advertise-client-urls http://etcd-2:2379,http://etcd-2:4001
-listen-client-urls http://${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:2379,http://${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:4001
-initial-advertise-peer-urls http://etcd-2:2380
-listen-peer-urls http://${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:2380
-initial-cluster-token etcd-cluster
-initial-cluster etcd-0=http://etcd-0:2380,etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380
-initial-cluster-state new
etcdctl
etcdctl - A simple command line client for etcd.
ENDPOINTS=ip:port,ip:port,ip:port
# set
etcdctl --endpoints=$ENDPOINTS put test1 111
etcdctl --endpoints=$ENDPOINTS put test2 222
etcdctl --endpoints=$ENDPOINTS put test3 333
# get
etcdctl --endpoints=$ENDPOINTS get test1
etcdctl --endpoints=$ENDPOINTS get test1 --write-out="json"
etcdctl --endpoints=$ENDPOINTS get test --prefix
# delete
etcdctl --endpoints=$ENDPOINTS del test1
etcdctl --endpoints=$ENDPOINTS del test --prefix
# 集群信息
etcdctl --write-out=table --endpoints=$ENDPOINTS endpoint status
etcdctl --write-out=table --endpoints=$ENDPOINTS member list
示例 (Golang)
go get go.etcd.io/etcd/client/v3
package main
import (
"context"
"fmt"
"time"
etcd "go.etcd.io/etcd/client/v3"
)
func main() {
cli, err := etcd.New(etcd.Config{
Endpoints: []string{"192.168.31.204:2379"},
DialTimeout: 3 * time.Second,
})
if err != nil {
fmt.Printf("etcd ConnectError: %v\n", err)
return
}
defer cli.Close()
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
resp, err := cli.Get(ctx, "test1")
cancel()
if err != nil {
fmt.Printf("etcd GetError: %v\n", err)
}
for _, kv := range resp.Kvs {
fmt.Printf("etcd KeyVlue: %s, %s\n", kv.Key, kv.Value)
}
}
示例 (Python)
python3 -m pip install -u etcd3
import etcd3
etcd = etcd3.client(host='etcd-host-01', port=2379)
testkey = '/key'
testval = '1234'
etcd.put(testkey, testval)
v = etcd.get(testkey)
print(v)
assert v == testval
etcd.delete(testkey)
参考资料与拓展阅读
架构 日志 Loki
2021-12-05
Loki
Grafana 公司出品的一个日志系统。才出来没两年,是一个相对较年轻的项目,不过已经有一定知名度了。
业界最为知名的日志系统是 ELK,它对日志做全文索引,搜索起来最快、最灵活,同时大量索引导致存储成本相对较高。
Loki 则将日志分成时间戳、标签、正文三部分,标签就是索引,存储在
Promtail
Grafana
Grafana 是一个数据面板,常用于监控系统。它本身不会收集和存储数据,而是通过接入其他数据源来实现。
通过内置的插件,Loki 可以支持各种关系型数据库和时序数据库(Zabbix 一般配套使用 MySQL 做存储,Prometheus 本身就可以认为是一个时序数据库),也支持 Loki,Elasticsearch 这样的数据源。
实验
Install Loki & Promtail
# 获取最新版本号
# LOKI_VERSION=$(curl -s https://api.github.com/repos/grafana/loki/releases/latest | jq -r .tag_name)
LOKI_VERSION=$(curl -s https://api.github.com/repos/grafana/loki/releases/latest | grep -Po '"tag_name": "\Kv[0-9.]+')
# 下载 loki & promtail
curl -O -L "https://github.com/grafana/loki/releases/download/${LOKI_VERSION}/loki-linux-amd64.zip"
curl -O -L "https://github.com/grafana/loki/releases/download/${LOKI_VERSION}/promtail-linux-amd64.zip"
# loki : 18M -> 57M
# promtail: 21M -> 74M
# 解压 & 设置
unzip loki-linux-amd64.zip promtail-linux-amd64.zip
sudo mv -n loki-linux-amd64 /usr/local/bin/loki
sudo mv -n promtail-linux-amd64 /usr/local/bin/promtail
# chmod a+x /usr/local/bin/{loki,promtail} # already 755
# 下载配置文件
sudo -E wget -qO /etc/loki.config.yaml "https://raw.githubusercontent.com/grafana/loki/${LOKI_VERSION}/cmd/loki/loki-local-config.yaml"
sudo -E wget -qO /etc/promtail.config.yaml "https://raw.githubusercontent.com/grafana/loki/${LOKI_VERSION}/clients/cmd/promtail/promtail-local-config.yaml"
ls -l /etc/{loki,promtail}.config.yaml
# 启动 loki
loki -config.file /etc/loki.config.yaml
# 在另一个终端查看
browse http://localhost:3100/metrics
# 启动 promtail
Install Grafana
Install on Debian or Ubuntu
sudo apt-get install -y apt-transport-https software-properties-common wget
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
# Bate 版本
# echo "deb https://packages.grafana.com/oss/deb beta main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install -y grafana
# 无法创建主目录"/usr/share/grafana"
# sudo systemctl daemon-reload
# sudo systemctl enable grafana-server
sudo systemctl start grafana-server
browse http://localhost:3000
参考资料与拓展阅读
开发者 架构
2021-08-22
阅读了来自公众号PM圈子的一篇文章《如何搭建一个拖垮公司的技术架构?》,反过来说的话,大概就是要注意以下方面。
开发者 架构 阮一峰
2019-11-19
阮一峰的博文(容错,高可用和灾备)中说:
- 容错:发生故障时,如何让系统继续运行。
飞机的四个引擎坏了一个还能继续飞行,汽车的四个轮子坏了一个也能将就驾驶。
- 高可用:系统中断时,如何尽快恢复。
汽车的备胎,用于快速恢复正常驾驶(允许短暂的业务中断)。
- 灾备:系统毁灭时,如何抢救数据。
飞机的弹射装置,保证最核心的“资产” —— 驾驶员能够存活。
架构 缓存
2019-02-12
什么地方需要缓存?
- DB 查询,加上缓存可以减小数据库压力,同时提升性能。
- 某些费时费资源的操作,加上缓存可以避免重复计算。
缓存方式
- 本地缓存
- 进程内缓存(内存)
- 磁盘缓存
- 缓存服务(redis/memcache)
- 数据库:有些任务可以提前进行计算,将结果存在数据库中。
本地缓存的问题是多个节点之间容易出现数据不一致的情况。我听说过 Java 的一些本地缓存组件,应该其中有一些可以做到多个节点之间的数据同步。如果要是自己实现的话,可以在服务中增加一个刷新缓存的接口调用,其中一个节点刷新缓存时,调用其他节点的刷新接口。也可以引入 MQ,避免这个调用造成的耦合和可能的性能损耗。
缓存策略
- 提前进行一些计算,将内容缓存起来。如有必要,可以选择合适的时间间隔进行数据刷新。
- DB 缓存可以由数据库中间层来做,也可以有客户端库来做,或者就在应用的数据库层中实现。
查询时,先尝试本地缓存,再尝试缓存服务(两道缓存,避免击穿),最后再进行数据库查询。
缓存的数据应该是这样的:
- 高命中(缓存命中率需要做好监控)
- 较少变更
- 尽可能保证数据变更之后(不一致问题)不会产生严重影响
如果一致性要求很高的话,要反复确实是否必须使用缓存,如果确定的话,缓存刷新策略需要考虑清楚。
穿透
数据库没有数据,缓存也没有数据。这样的请求直接穿过缓存读数据库,给数据库照成压力。
- 一般是非法请求所致,对于这一部分请求应该有机制可以进行过滤掉
恶意请求,或者高频请求
- 可以对没有数据的请求也进行缓存,可以给一个相对小一点的 TTL
- 布隆过滤器 参考:2021/03/07, 布隆过滤器
- 将空 Key 单独存到一个 Redis SET 中应该也可以
比如 system:cache:empty_keys:hhmm, TTL = 123,每次用 SISMEMBER 查询当前分钟和上一分钟
击穿
某个热 Key 失效,导致大量请求打到数据库。
热 Key 应该预热,然后有一个比较大的 TTL,甚至没有过期时间。最后,通过定时刷新任务来更新这些热 Key。
雪崩
大量 Key 同时过期, 导致请求直接打到数据库,然后影响整个系统。
大面积的击穿。
- 比如系统刚启动的时候,批量写入大量数据,这些数据有相同的 TTL,就会同时过期。我们应该给过期时间加入一些随机,将过期时间点分散在一个区间内。
- 对于热 Key 的处理, 同击穿部分。
其他:
- 为了防止 Redis 奔溃,导致系统崩溃,应该在本地进程中也设置一个缓存。
LocalCache -> RedisCache -> DB
- 为了防止数据库奔溃,数据库请求应该由一个队列来处理。
- 网关部分对于大量来不及处理的请求应该丢弃。
- 缓存应该能够按重要性划分一下级别,如果遇到问题能够快速丢弃不重要的数据
还应该可以快速丢弃指定服务的所有缓存。
这应该叫做缓存降级。
预热
根据之前的经验,或者开发者预判,将部分数据事先写入缓存。
如果提供相关工具,让系统维护人员能够方便快捷地管理缓存数据,能够手动介入缓存的生命周期就更好了。至少在后台提供一个 缓存预热 的按钮。
- 提供指定热 Key,让定时任务负责刷新。
智能预热:给访问量大的 Key 延长 TTL, 启动定时刷新。
- 需要对缓存的访问有一个简单的监控,方便作为之后预热的依据。
缓存更新
另起一篇:缓存更新策略
架构 DNS
2018-05-02
历史
从阿帕网(ARPANET)时代一直到互联网的早期,网络节点比较少,都是通过本地 hosts 文件来实现主机名到 IP 地址的映射。
根据维基百科的信息,斯坦福研究所(SRI-NIC,Stanford Research Institute Network Information Center)负责维护一个公共 HOSTS.TXT 文件(RFC 606、RFC 608),各个网络节点定期通过 FTP 下载最新版本进行同步。
PS:这个时候如果有主机名重复了谁来管?打电话过去让他们改名?
这套机制一直运行了十几年。随着互联网规模扩大,公共 HOSTS.TXT 文件变得越来越大,变化也越来越频繁。每当有新的主机加入网络、IP 地址发生变化,都需要重新同步整个文件。
这种模式存在几个明显问题:
- 中央管理机构压力越来越大;
- 全网频繁同步效率较低;
- 主机数量增长后难以维护;
- 名称冲突和管理问题越来越复杂。
后来人们开始设计域名和域名相关的公共设施。RFC 819《The Domain Naming Convention for Internet User Applications》和 RFC 881《The Domain Names Plan and Schedule》提出了域名系统的早期设计思路。
最终在 1983 年形成了下面两个 RFC 文档:
- RFC 882, DOMAIN NAMES - CONCEPTS and FACILITIES
- RFC 883, DOMAIN NAMES - IMPLEMENTATION and SPECIFICATION
几年后(1987),正式的 DNS 标准 RFC 1034 和 RFC 1035 推出。我们现在常见的 A / MX / NS / CNAME / PTR / SOA 等记录类型都是在这个标准中定义的。
- RFC 1034, Domain Names Concepts and Facilities
- RFC 1035, Domain Names Implementation and Specification
这套标准一直运行到现在。虽然几十年来不断增加新的记录类型、支持国际化域名(IDN)、引入 DNSSEC、安全加密传输等扩展能力,但其核心设计思想基本没有改变:
DNS 可以说是互联网历史上寿命最长、最成功的基础设施之一。并且,在可以预见的未来,DNS 仍然会是互联网最重要的基础设施之一。
DNS over TCP
最初 DNS 主要运行于 UDP 53 端口。RFC 1035 同时规定了 TCP 支持,最早 TCP 主要用于:
今天绝大多数 DNS 服务仍同时支持 UDP 和 TCP。
DNS 的管理权问题
互联网本是美国国防部高级研究计划局(DARPA)的产物,早期域名分配几乎由南加州大学教授乔恩·波斯特尔一人代管。1998年,克林顿政府为避免让联合国下属的国际电信联盟(ITU)——一个由各国政府和官营电信主导的机构——接管互联网,刻意设计了一条"私有化"路线:成立非营利组织 ICANN,负责全球域名系统(DNS)和顶级域名分配,与美国商务部签合同但通过"私人身份"运作,以此保持互联网开放、不受单一政府直接操控。然而 ICANN 总部在加州、受美国法律管辖,全球13台根服务器多数也在美国手里,等于事实上的互联网域名最高仲裁者仍是美国。这让包括中国、俄罗斯在内的大量国家长期不满,伊拉克战争期间".iq"域名被暂停解析一事更被视为美国"悬剑在手"的象征。
2013年斯诺登曝光美国大规模监听后,国际社会对"美国垄断互联网命门"的反弹到达顶点,联合国层面甚至出现推动另建政府主导治理体系的呼声。为消解这一合法性危机、顺应互联网去中心化叙事,奥巴马政府于2014年宣布启动移交程序——将美国商务部对 ICANN 的"合同监管权"解除。2016年3月,ICANN 正式提交脱离美国政府联系的过渡计划。
真正的问题不在"美国是否善良",而在于:这根命门本来就不该由任何一个国家独握。移交的本质是把制度合法性从"美国庇护下的开放"转向"多方利益攸关体(multi‑stakeholder)自我维持的透明与问责"。但争议也正在于此——保守派担心脱离美国后,ICANN 会被威权国家通过政府咨询渠道逐步蚕食;支持者则认为根服务器的分布式结构和自下而上的共识机制本身就是防火墙。美国让出的不是善意,而是一张越来越护不住的垄断椅子。
新的发展
| 年份 |
技术/标准 |
RFC |
主要创新 |
| 1983 |
DNS 概念与实现 |
RFC 882, RFC 883 |
首次提出 DNS,替代 HOSTS.TXT |
| 1987 |
DNS 正式标准 |
RFC 1034, RFC 1035 |
建立现代 DNS 架构,至今仍是核心基础 |
| 1997 |
动态更新(DDNS) |
RFC 2136 |
DNS UPDATE 接口,允许客户端动态修改 DNS 记录 |
| 1998 |
NOTIFY |
RFC 1996 |
主 DNS 主动通知从 DNS 更新,不用定时轮询 |
| 1999 |
IXFR(增量同步) |
RFC 1995 |
只同步变更部分,降低 Zone Transfer 成本 |
| 1999 |
EDNS0 |
RFC 2671 -> 6891 |
打破 UDP 512 字节限制(拓展到 KB 级别) |
| 2000 |
SRV 记录 |
RFC 2782 |
DNS 服务发现机制 |
| 2003 |
国际化域名(IDNA) |
RFC 3490 系列 |
支持中文、日文等 Unicode 域名 |
| 2005 |
DNSSEC |
RFC 4033/4034/4035 |
DNS 数据签名与验证 |
| 2008 |
Source Port Randomization |
RFC 5452 |
提高抗缓存投毒能力 |
| 2010 |
NSEC3 |
RFC 5155 |
防止 DNSSEC Zone Walking(枚举所有域名) |
| 2011 |
CAA 记录 |
RFC 6844 -> 8659 |
允许域名所有者声明:仅承认某些 CA 给该域名颁发的证书 |
| 2013 |
DNS Cookies |
RFC 7873 |
防御 DNS 放大攻击 |
| 2016 |
DNS over TLS (DoT) |
RFC 7858 |
DNS 加密传输 |
| 2018 |
QNAME Minimization |
RFC 7816 |
降低 DNS 查询隐私泄露 |
| 2018 |
DNS over HTTPS (DoH) |
RFC 8484 |
DNS 走 HTTPS 通道 |
| 2020 |
SVCB / HTTPS RR |
RFC 9460 |
服务发现和 HTTPS 配置优化 |
| 2021 |
Oblivious DoH (ODoH) |
RFC 9230 |
隐藏客户端 IP |
| 2022 |
DNS over QUIC (DoQ) |
RFC 9250 |
基于 QUIC 的 DNS |
| 2023 |
DDR |
RFC 9462 |
自动发现 DoH/DoT 服务 |
RFC 2136: Dynamic Updates in the Domain Name System (DNS UPDATE)
复用 DNS 报文格式,但把 Opcode 设为 5(UPDATE),直接在 UDP/TCP 53 端口上向权威 DNS 服务器发送增删改指令(四段式结构):
| 段 |
作用 |
| Zone |
声明你要改哪个 zone(如 home.arpa.或 example.com.) |
| Prerequisite |
前置条件——"只有当 host A == 1.2.3.4时才更新"(原子 compare-and-swap,防竞态) |
| Update |
实际操作——ADD / DELETE 某条 RR |
| Additional |
认证载体——通常是 TSIG(RFC 2845)或 GSS-TSIG(RFC 3645, Kerberos) |
注意:DDNS 本身可以说是一个需求,就是动态调整 DNS,虽然 RFC 2136 经常被称之为 DDNS,但实质是 DDNS 的一种技术方案。
有一些脚本或者路由器插件轮询 IP 然后通过云服务商 API 来动态修改 DNS 记录,在很多场景下也被称之为 DDNS,不要混淆。
云服务商 DNS 都不对外提供 UPDATE 操作,它们通过 HTTP API(IAM/Key 鉴权)来提供 DNS 记录更新。
RFC 2136 DDNS 的典型阵地是你自有/私有部署的受控权威服务器(BIND、PowerDNS、Windows AD DNS、Infoblox 等),其中最常见生产场景是:
客户端发起 DHCP 请求时会带上 hostname(Option 12),DHCP 服务器分配地址后,通过 TSIG/GSS-TSIG 向可写 zone 发 UPDATE,自动注册内部名 A 记录及对应 PTR(反向区也要可被管理)。
-
DNSSEC
安全
-
DNSCrypt
2011 年提出。
DNSCrypt 在 DNS 请求和解析服务器之间建立加密通信,并验证服务器身份。
它并非 IETF 标准,但曾经被 OpenDNS 等服务广泛采用。
-
DNS over TLS(DoT)
2016 年成为标准。RFC 7858 Specification for DNS over Transport Layer Security (TLS)
DNS 全程加密,默认使用 TCP 853 端口。
-
DNS over HTTPS(DoH)
2018 年成为标准。RFC 8484 DNS Queries over HTTPS (DoH)
特点:
- 基于 HTTPS;
- 默认使用 HTTPS 443 端口;
- 更容易穿透网络限制;
-
可以复用现有 Web 基础设施。
-
DNS over QUIC(DoQ)
RFC 9250。
基于 QUIC,兼具 UDP 的低延迟和 TLS 的安全性。
-
DNS over Tor
通过 Tor 网络转发 DNS 查询,其主要目标是隐藏客户端来源地址,提高匿名性。
它不是 DNS 标准协议,而是一种部署方案。
-
Oblivious DNS-over-HTTPS(ODoH)
ODoH 在 DoH 基础上增加代理节点,确保代理服务器看不到查询内容,DNS 服务器看不到客户端 IP,从而进一步提升隐私保护能力。
参考资料与拓展阅读