“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
复制监控 — 高可用仪表盘
前言:为什么“有副本”不等于“高可用”
先说个真实场景。你搭好了Postgres流复制,primary和standby跑得挺好,团队都觉得“HA了,稳了”。然后某天primary挂了,你切到standby,结果发现数据落后了18个小时——因为副本三天前就断开了连接,但没人知道。
这不是段子,是我见过不止一次的翻车现场。Postgres的流复制本质上是一条WAL隧道:primary为每个standby启动一个walsender进程,standby那边跑walreceiver收WAL,再丢给startup process去replay。架构听着简单,但运维的痛点从来不是“怎么搭”,而是“副本死了你什么时候知道”。
HA仪表盘,就是一套把pg_stat_replication、pg_stat_wal_receiver和pg_replication_slots里的状态信号变成可视化和告警的系统。它的目标是让所有坏状态——副本断连、replay卡住、slot撑爆WAL、同步副本消失——在变成事故之前,变成一眼能看到的数字。
流复制的核心机制:四个LSN和三个lag
要理解监控什么,先得理解数据怎么流。
在primary上,每个已连接的standby对应一个walsender后端。Postgres从pg_wal/目录(如果WAL还在磁盘上)或者直接从WAL buffers(如果刚产生)读取WAL记录,然后通过复制连接流式发送。
在standby那边,walreceiver收到每条WAL记录后,先写到本地的pg_wal/(写到OS page cache,还没落盘),然后根据synchronous_commit配置决定是否fsync。之后startup process把这些记录replay到shared buffers——这一步才是“数据在standby上可见”的时刻。
这个流程里,有四个关键位置,对应pg_stat_replication里的四列:
- sent_lsn:primary已经发送到socket的最后一个字节
- write_lsn:standby已经
write()到OS page cache的最后一个字节 - flush_lsn:standby已经
fsync落盘的最后一个字节 - replay_lsn:standby已经replay到shared buffers的最后一个字节(数据真正可见)
Postgres文档明确规定了顺序:replay_lsn <= flush_lsn <= write_lsn <= sent_lsn <= pg_current_wal_lsn()。这四个值就是四个“水位标记”,它们之间的差距就是lag。
另外还有三个带_lag的列(write_lag、flush_lag、replay_lag),类型是interval,表示standby比primary慢了多少时间。这些时间是通过standby定期发送的feedback消息计算的。
核心查询:primary上的全景视图
SELECT
application_name,
client_addr,
state, -- streaming | catchup | startup | backup | stopping
sync_state, -- async | potential | sync | quorum
pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn) AS sent_lag_bytes,
pg_wal_lsn_diff(pg_current_wal_lsn(), write_lsn) AS write_lag_bytes,
pg_wal_lsn_diff(pg_current_wal_lsn(), flush_lsn) AS flush_lag_bytes,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_lag_bytes,
write_lag,
flush_lag,
replay_lag,
EXTRACT(EPOCH FROM (now() - reply_time)) AS seconds_since_reply
FROM pg_stat_replication;
这个查询把每个standby的实时状态全扒出来了。注意seconds_since_reply——如果这个值持续增大,说明standby可能已经失联,只是TCP连接还没被操作系统关掉。
standby侧的视角
在standby上,pg_stat_wal_receiver告诉你它连的是哪个primary(conninfo、sender_host),当前状态(streaming/waiting/stopping),以及最后一次消息的收发时间。这个视图是判断standby是否真正在线的唯一证据——如果这个视图没有行,说明standby根本没连上primary或者连接断了没重连。
另外两个关键函数:
pg_last_wal_replay_lsn():最后一次replay的LSNpg_last_xact_replay_timestamp():最后一次replay的事务提交时间
用now() - pg_last_xact_replay_timestamp()就能算出“standby上读到的数据比primary慢了多少秒”。这是业务最关心的指标——用户刚提交的数据,在standby上多久才能读到。
别忘了replication slots
pg_replication_slots是个容易被忽略但极其重要的视图。Slot的作用是pin住restart_lsn,防止primary回收standby还没消费的WAL段。
从Postgres 13开始,wal_status列有四个值:
reserved:正常,WAL安全extended:WAL保留量超过正常范围,但还没丢unreserved:WAL可能被回收,但还没丢lost:已经超过max_slot_wal_keep_size,WAL被回收了,consumer必须从头做base backup
safe_wal_size告诉你当前slot还能扛多少字节的WAL不被回收。
生产环境常见的五种翻车场景
故障模式1:副本断连,但没人知道
这是最常见的。没有仪表盘的时候,发现方式只有一种:某天做failover,切到standby一看LSN,发现落后了几个小时甚至几天。
原因五花八门:
- 防火墙或网络设备因为idle timeout把TCP连接掐了
- walreceiver进程crash了
- standby上的
wal_receiver_timeout到期(网络抖动导致) - standby重启后
primary_conninfo配错了
最坑的是,standby从pg_stat_replication消失后,primary的日志只会淡淡地打印一行terminating walsender process due to replication timeout,然后就没有然后了。
常见的错误做法:只检查副本数量
-- 这不够!如果两个副本掉了一个,你只知道“还有1个”,不知道哪个没了
SELECT count(*) AS replica_count FROM pg_stat_replication;
正确的做法:按名字告警
-- 每个standby必须设置application_name
SELECT
expected.name,
CASE
WHEN r.application_name IS NULL THEN 'DOWN'
WHEN r.state <> 'streaming' THEN r.state
ELSE 'ok'
END AS health
FROM
(VALUES ('standby-1'), ('standby-2')) AS expected(name)
LEFT JOIN pg_stat_replication r ON r.application_name = expected.name;
关键前提:每个standby必须在primary_conninfo里设置application_name。比如:
primary_conninfo='host=primary-ip port=5432 user=repl password=xxx application_name=standby-1'
没有这个,你没法区分哪个副本掉了。
故障模式2:副本断了但slot还在——pg_wal/爆炸
如果standby配置了primary_slot_name(用了物理复制槽),当standby断开后,slot的active变成false,但restart_lsn不会往前推进。primary会认为“这个slot还需要从restart_lsn开始的WAL”,于是所有从那个位置往后的WAL段都得保留。
结果是pg_wal/目录无限膨胀,直到磁盘写满,Postgres PANIC:
could not write to file ... No space left on device
GitLab 2017年的数据库事故就是这剧本——监控没覆盖到slot状态,等发现时已经晚了。
Postgres 13引入了max_slot_wal_keep_size作为硬限制,但光设这个不够,你还得监控wal_status:
-- 每个slot的状态必须上仪表盘
SELECT
slot_name,
slot_type,
active,
wal_status,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained,
pg_size_pretty(safe_wal_size) AS safe_left
FROM pg_replication_slots;
当wal_status变成extended或lost时,必须立刻告警——前者是警告,后者是灾难。
故障模式3:同步复制让所有写操作卡死
这是最隐蔽的坑。假设你配了:
synchronous_commit = on
synchronous_standby_names = 'FIRST 1 (standby-1)'
当standby-1断开后,primary上的每个COMMIT都会等待standby-1的ACK。客户端看到的是连接hang住,而不是报错。从应用层看,数据库“卡了”,但没有错误日志。
在pg_stat_activity里,这些等待的后端会显示:
wait_event_type = 'IPC'wait_event = 'SyncRep'
如果你只关注sync_state列,不告警“同步副本数量低于阈值”,那等发现的时候,业务已经挂了半天了。
改进方案:
- 用quorum代替FIRST 1:
ANY 2 (s1, s2, s3)允许任意一个副本挂掉,只要还有两个活着 - 硬性告警:当
sync_state IN ('sync', 'quorum')的副本数低于所需阈值时立刻报警
故障模式4:replay_lag大但flush_lag接近零——replay冲突
这个场景特别容易误诊。你看到write_lsn紧贴着sent_lsn,但replay_lsn越拉越远。数据已经收到了、落盘了,但就是replay不进去。
原因通常是recovery conflict:
- standby上有个长查询在跑,持有snapshot,导致vacuum产生的WAL无法apply
- 某个表上有锁被hold住了
症状就是:用户刚在primary上commit了一条数据,切到standby上读不到。不是网络问题,是replay被block了。
在standby上查这个:
SELECT * FROM pg_stat_database_conflicts;
重点关注confl_snapshot、confl_lock、confl_bufferpin。
解决思路:
- 开
hot_standby_feedback = on:减少snapshot冲突,但代价是primary上会产生更多bloat(因为vacuum不敢清理standby还在用的tuple) - 调大
max_standby_streaming_delay:让replay等一会再决定是干掉query还是继续等,但会增加lag - 架构层面:把长查询路由到专门的只读副本,别跟需要低延迟读的查询混在一起
故障模式5:仪表盘只在出事后才看
这是最“人”的问题。Metric配了,面板搭了,但没人日常看。等PagerDuty响了才打开Grafana——这时候MTTR已经包含了“人肉发现”的时间。
解决方案:
- 每个故障模式都要有对应的告警阈值,不能只靠人眼看
- 每个告警都要有runbook——收到告警后第一步做什么、第二步做什么
- 定期做混沌工程:手动断掉副本连接,看告警会不会响,响应对不对
总结:一个完整的HA仪表盘应该监控什么
| 维度 | 查询来源 | 关键指标 | 告警条件 |
|---|---|---|---|
| 副本在线状态 | pg_stat_replication |
每个application_name是否存在,state是否为streaming | 任何已知副本消失或state异常 |
| 复制延迟 | pg_stat_replication |
replay_lag_bytes, replay_lag (interval) | 超过阈值(根据业务容忍度) |
| 同步副本健康 | pg_stat_replication |
sync_state in ('sync','quorum')的数量 | 低于所需同步副本数 |
| Slot状态 | pg_replication_slots |
wal_status, safe_wal_size | wal_status = 'extended'或'lost' |
| Replay冲突 | pg_stat_database_conflicts |
confl_snapshot等计数 | 持续增长或超过阈值 |
| 副本自身状态 | pg_stat_wal_receiver |
是否有行,last_msg_receipt_time | 无行或长时间未收到消息 |
最后说一句:监控不是为了好看,是为了在坏事发生前给你留出反应时间。 如果你配了HA但没配这些监控,那你的HA可能只是自我安慰。