软件设计 架构 阮一峰
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
架构 日志 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 地址的映射。
根据维基百科的信息,斯坦福研究所负责维护了一个公共 hosts 文件,大家会找他同步 (rfc606, rfc608)。
PS: 这个时候如果有主机名重复了谁来管?打电话过去让他们改名?
这套机制一直运行了十几年,公共 hosts 文件已经变的很大了,变化也很频繁(IP 可能已经不再那么固定了),需要经常同步。这个时候,斯坦福研究所的网络压力也越来越大了。
后来人们开始设计域名和域名相关的公共设施 (rfc805, rfc830)。最后,在 1983 年,形成了下面两个 RFC 文档:
- RFC 882, DOMAIN NAMES - CONCEPTS and FACILITIES
- RFC 883, DOMAIN NAMES - IMPLEMENTATION and SPECIFICATION
几年后(1987),正式的 DNS 标准 RFC 1034 和 RFC 1035 推出。
这套标准一直运行到现在,可能有对其进行拓展(比如 DNS 记录类型不断添加,Unicode 字符引入),但是基本技术设计没有改变。
DNS 的管理权问题
https://zhidao.baidu.com/question/1386069665602139980.html
基本流程
比如本站域名 www.markjour.com, 其完整形式应该是 www.markjour.com.
(后面多一个小数点)
DNS 软件
- BIND
- PowerDNS
- dnsmasq
- Unbound
- CoreDNS
- SmartDNS
Cache-Only DNS Server
新的发展
- 标准的 DNS 是运行在 UDP 53 端口上的。后来的 RFC 1123 增加了 TCP 的支持, 这个方案叫做 DNS over TCP, 还是在 53 端口。
- DNSCrypt, 2011 年设计的, 实现 DNS 的加密和验证,运行于 443 端口。注意:存在于 IETF 框架之外,但是好像有很多服务器支持。
- DNS over TLS (DoT), 2016 年 5 月成为规范。
RFC 7858 Specification for DNS over Transport Layer Security (TLS)
主要作用是加密传输,防止窃听。
- DNS over HTTPS (DoH), 2018 年 10 月成为规范。
RFC 8484 DNS Queries over HTTPS (DoH)
作用和 DoT 一样。
- DNS over TOR, 2019 年。
- Oblivious DNS-over-HTTPS (ODoH), 透过代理的方式,让 DoH 服务器无法获取客户端的真实 IP。同时代理无法获取 DNS 请求的内容。
参考资料与拓展阅读