2026年1月

团队里越来越多的人开始 vibe coding ,但我还是比较反感的,主要原因如下:

  1. 会生成大量无意义代码。打个比方,它生成的代码就像一个初级开发,不知道从哪里 copy 来的高级代码。有时乍一看,真高端,仔细阅读,完全没起到作用,这啥玩意嘛。
  2. 难以理解。vibe coding 生成的代码量要远远大于常规开发,AI 阅读代码的速度是真的快,但一旦交给人去理解,往往要浪费很多时间。
  3. 最重要的一点,传播性强,像病毒。在一个没有 CodeReview 的项目中,原本屎山代码就算了,好歹代码简单,现在还塞了一堆你不认识的元素进去。项目一旦有人大量使用了,后来的人如果不用,维护起来十分困难。

S.EE 上线文件分享功能了。

跟那些套路网盘不一样,没倒计时、没广告、没限速,给链接就能下。

功能一览

  • 直链分享:上传完直接拿到文件直链,可以嵌 Markdown 、README 、论坛、博客,想怎么用怎么用
  • 断点续传:大文件不怕断网,支持 tus 协议
  • 全球 CDN:下载走 CDN ,速度有保障
  • 短域名:官方提供 fs.to / files.to / fileshare.to,也支持绑自己的域名

示例

直链链接可以在分享页面里获取

适合谁用?

  • 运维直接 wget 拉文件
  • 开发者放 Release
  • 设计师发素材
  • 任何「给个链接就能下」的场景

关于稳定性

我们的 SM.MS 图床从 2015 年运营到现在 10 年了,S.EE 是同一个开发团队。

价格

文件分享需要付费账号,套餐详情: https://s.ee/pricing/

相关链接

有问题随时反馈 🙏

另外 SM.MS 付费用户近期会迁移到 S.EE ,届时我们会邮件通知。

大佬们好,我最近花了几个月的时间做了一个英雄联盟云顶之弈自动挂机的软件,初衷是为了全自动刷通行证和宝典。

Github 地址:Tft-Hextech-Helper

软件截图

具体的方案是纯粹的视觉识别:OCR+模版匹配。不涉及任何的内存读取,操作则控制鼠标进行点击。同时使用了 LOL 官方公开的 API 接口来实现自动排队,自动接收对局等,目前可以做到全流程打通,挂一整天完全不用手动。我高强度挂机了两天,从青铜打到了白银三,期间没有被封过。

这里想跟各位大佬交流一下,这种完全不读取内存的方案,存在被封号的可能性吗?会不会检查玩家的鼠标移动是否平滑之类的?希望大佬们积极解答,也欢迎使用和 star 。

记得之前有个切换年付巨便宜的 bug ,现在又出现了这个升级高级版订阅随机延长免费试用时长 bug 。。。

https://www.microsoft.com/en-us/microsoft-365/college-student-pricing

前提条件:edu 邮箱验证。步骤:

  1. 订阅个人版 https://checkout.microsoft365.com/acquire/purchase?language=en-US&market=US&requestedDuration=Month&scenario=microsoft-365-student&client=poc&campaign=StudentFree12M
  2. 升级高级版 https://checkout.microsoft365.com/acquire/purchase?language=en-US&market=US&requestedDuration=Month&scenario=microsoft-365-premium&client=poc&campaign=StudentPremiumFree12M

升级高级版虽然只显示 1 个月试用,但订阅成功后的邮件里通知 renewal date 都到 5 年后 2031 了。。。无法绑卡但是套 gpay 付款成功,左下角可修改地区。

记得之前有个切换年付巨便宜的 bug ,现在又出现了这个升级高级版订阅随机延长免费试用时长 bug 。。。

https://www.microsoft.com/en-us/microsoft-365/college-student-pricing

前提条件:edu 邮箱验证。步骤:

  1. 订阅个人版 https://checkout.microsoft365.com/acquire/purchase?language=en-US&market=US&requestedDuration=Month&scenario=microsoft-365-student&client=poc&campaign=StudentFree12M
  2. 升级高级版 https://checkout.microsoft365.com/acquire/purchase?language=en-US&market=US&requestedDuration=Month&scenario=microsoft-365-premium&client=poc&campaign=StudentPremiumFree12M

升级高级版虽然只显示 1 个月试用,但订阅成功后的邮件里通知 renewal date 都到 5 年后 2031 了。。。无法绑卡但是套 gpay 付款成功,左下角可修改地区。

可以单纯作为 YouTube 下载器的备用软件。

PS: 如果在使用基于 yt-dlp 的软件时出现没法正常下载的情况,基本都是节点不行,需要更换可用节点


📌 转载信息
原作者:
_BIGFA
转载时间:
2026/1/19 17:55:53

我大概用了两周的 coding agent 工作流:Superpowers(obra/superpowers)。今天看到它出现在 claude-plugins-official 列表里(我这边 /plugin 能看到 source),顺便分享下它的工作流思路。

来源:

它有一套最近大家在聊的很火的 skills,用来把 agent 拉回 “先想清楚、再动手、再验证” 的节奏,核心链路大概是:

  • brainstorming(/superpowers:brainstorm):任何 “要开始写 / 改功能” 之前先用它,把需求 / 约束 / 成功标准摸清楚,先出设计再实现(分段确认)

  • writing-plans(/superpowers:write-plan):把实现拆成很小、可验证的 steps,避免一口气写一大坨

  • executing-plans(/superpowers:execute-plan):按批次执行计划,每批做一次 review/checkpoint

  • test-driven-development:强调 red/green/refactor(先失败的测试,再最小实现)

  • verification-before-completion + systematic-debugging:先验证再宣称 “修好了”,遇到 bug 用更系统的方法追根因

安装我就不复读了:README 里 Claude Code/Codex/OpenCode 都有对应的入口和指令。我个人感觉最有用的是先从 brainstorming 那套问答 / 设计确认开始。

我也在不断熟悉这套工作流中,感觉它就是我想要的 coding agent 工作流。



📌 转载信息
转载时间:
2026/1/19 17:55:41

看了其他佬友的,正常是 2 年,也有 5 年
4 年是什么原理?已有佬友在评论区展现 us 区 + Gpay,欢迎大家分享经验!
注意注意,先领取普通版 365,再去尝试 Premium 团队版 365!

分享下我的踩坑流程,希望能给大家带来帮助

1. 我先在 tw 区的链接,edu 的认证什么的都很顺利,但是卡在了绑卡上,试了 PayPal 和直接输入卡号都不行
https://checkout.microsoft365.com/acquire/purchase?language=zh-TW&market=TW&requestedDuration=Month&scenario=microsoft-365-student&client=poc&campaign=StudentFree12M

2. 于是换成 us 区,还是用之前的 edu(之前屯的 tw 的 edu)使用以下链接 https://checkout.microsoft365.com/acquire/purchase?language=en-US&market=US&requestedDuration=Month&scenario=microsoft-365-student&client=poc&campaign=StudentFree12M
这次还是卡在绑卡上,发现可以使用 Gpay,绑定,顺利通过!

3. 领取普通版本的 365 之后,尝试领取 Premium 版本的 365,也是 us 区,使用如下链接
https://checkout.microsoft365.com/acquire/purchase?language=en-US&market=US&requestedDuration=Month&scenario=microsoft-365-premium&client=poc&campaign=StudentPremiumFree12M

4. 它会一直显示错误,刷新也错误,本来我听别人说封车了,但是不要放弃!在不断尝试刷新、隐私网页再登陆领取,最后和之前一样的流程,绑定 Gpay 之后领取成功了

5. 最后在这里查看订阅时长:https://account.microsoft.com/services/

30 年 2 月 17 日到期,总共领取 4 年!
不清楚为什么和其他人不同,可能和地区或者邮箱有关吧,但是我个人用的是 us 区 + tw 的 edu,这种混搭感觉也不是很靠谱,佬友们参考看看吧

领到的佬友们记得取消自动续期哦


📌 转载信息
转载时间:
2026/1/19 17:55:24

PostgreSql 基于 Pacemaker+Corosync+pcs 的高可用实践

简介

在 PostgresSql HA 方案中,流复制方案集性能,可靠性,部署成本低等优点,也是目前被普遍采用的方案
在流复制 HA 集群管理工具中,Pacenmaker+Corosysnc 是相对程序可靠的

功能特性

  • 快速故障转移
  • 支持多节点集群
  • 支持同步和异步复制
  • 提供读写 vip 和只读 vip

基础架构 / 原理

  1. Pacemaker + Corosync 作为集群基础软件,Corosync 负责集群通信和成员关系管理,Pacemaker 负责资源管理。
  2. 集群用到资源包括 PostgreSQL 和 VIP 等,PostgreSQL 对应的 Resource Agent (RA) 为 expgsql,expgsql 负责实施 PostgreSQL 的起停,监视,failover 等操作。
  3. 集群初始启动时 expgsql 通过比较所有节点的 xlog 位置,找出 xlog 最新的节点作为 Master,其它节点作为 Slave 通过读写 VIP 连接到 Master 上进行 WAL 复制。
  4. 集群启动后 expgsql 不断监视 PostgreSQL 的健康状况,当 expgsql 发现 PostgreSQL 资源故障时报告给 Pacemaker,由 Pacemaker 实施相应动作。
    如果是 PostgreSQL 进程故障,原地重启 PostgreSQL,并且该节点上的 fail-count 加 1。
    fail-count 累加到 3 时不再分配 PostgreSQL 资源到这个节点。如果该节点为 Master,会提升一个 Slave 为 Master,即发起 failover。
  5. Corosync 发现节点故障 (主机或网络故障) 时,Pacemaker 也根据情况实施相应动作。
    对多节点集群,未包含过半节点成员的分区将主动释放本分区内的所有资源,包括 PostgreSQL 和 VIP。
    合法的分区中如果没有 Master,Pacemaker 会提升一个 Slave 为 Master,即发起 failover。
  6. Master 上的 expgsql 会不断监视 Slave 的复制健康状况,同步复制下会选定一个 Slave 作为同步 Slave。
  7. 当同步 Slave 出现故障时,Master 上的 expgsql 会临时将同步复制切换到异步复制,防止 Master 上的写操作被 hang 住。如果故障 Slave 恢复或存在另一个健康的 Slave,再切换到同步复制。
  8. 为防止集群分区后,Slave 升级为新 Master 而旧 Master 切换到异步复制导致脑裂和数据双写,引入分布式锁服务进行仲裁。Slave 升级为新 Master 和旧 Master 切换到异步复制前必须先取得锁,避免这两件事同时发生。失去锁的 Master 会主动停止 PostgreSQL 进程,防止出现双主。
  9. 如果分布锁服务发生故障而所有 PostgreSQL 节点都是健康的,expgsql 会忽视锁服务,即不影响集群服务。但在分布锁服务故障期间,Master 发生节点故障 (注意区分节点故障和资源故障),集群将无法正常 failover。
  10. 同步复制下只有同步 Slave 才有资格成为候选 Master,加上有分布式锁的防护,可以确保 failover 后数据不丢失。
  11. 集群初始启动和每次 failover 时通过 pg_ctl promote 提升 Slave 为 Master 并使时间线加 1,同时记录 Master 节点名,时间线和切换时的 xlog 位置到集群 CIB。
  12. 集群重启时根据集群 CIB 中记录的信息确定 Master 节点,并保持时间线不变。
  13. expgsql 启动 PostgreSQL 前会检查该节点的时间线和 xlog,如果和集群 CIB 中记录的信息有冲突,将报错。需要人工通过 cls_repair_by_pg_rewind 等手段修复。
  14. 读写 VIP 和 Master 节点绑定,只读 VIP 和其中一个 Slave 绑定,应用只需访问 VIP,无需关心具体访问哪个节点。

集群常规命令

pcs status //查看集群状态
pcs resource show //查看资源
pcs resource create ClusterIP IPaddr2 ip=192.168.0.120 cidr_netmask=32 //创建一个虚拟IP资源
pcs resource cleanup //xx表示虚拟资源名称,当集群有资源处于unmanaged的状态时,可以用这个命令清理掉失败的信息,然后重置资源状态
pcs resource list //查看资源列表
pcs resource restart //重启资源
pcs resource enable //启动资源
pcs resource disable //关闭资源
pcs resource delete //删除资源
crm_mon -Arf -1 //查看同步状态和资源 

pg_ctl 常用命令

Usage:
  pg_ctl init[db] [-D DATADIR] [-s] [-o OPTIONS]
  pg_ctl start      [-D DATADIR] [-l FILENAME] [-W] [-t SECS] [-s] [-o OPTIONS] [-p PATH] [-c]
  pg_ctl stop       [-D DATADIR] [-m SHUTDOWN-MODE] [-W] [-t SECS] [-s]
  pg_ctl restart    [-D DATADIR] [-m SHUTDOWN-MODE] [-W] [-t SECS] [-s] [-o OPTIONS] [-c]
  pg_ctl reload     [-D DATADIR] [-s]
  pg_ctl status     [-D DATADIR]
  pg_ctl promote    [-D DATADIR] [-W] [-t SECS] [-s]
  pg_ctl logrotate  [-D DATADIR] [-s]
  pg_ctl kill       SIGNALNAME PID

集群实践

环境介绍

操作系统版本

CentOS Linux release 7.9.2009 (Core)

软件版本

pgsql:(14)
PCS 相关版本:

[root@k8s-master01 pgsql_cluster]#  rpm -qa|grep pacemaker
pacemaker-cli-1.1.23-1.el7_9.1.x86_64
pacemaker-cluster-libs-1.1.23-1.el7_9.1.x86_64
pacemaker-1.1.23-1.el7_9.1.x86_64
pacemaker-libs-1.1.23-1.el7_9.1.x86_64

[root@k8s-master01 pgsql_cluster]#  rpm -qa|grep pcs
pcs-0.9.169-3.el7.centos.3.x86_64

[root@k8s-master01 pgsql_cluster]# rpm -qa|grep corosync
corosync-2.4.5-7.el7_9.2.x86_64
corosynclib-2.4.5-7.el7_9.2.x86_64
地址信息
192.168.28.151   pg-151
192.168.28.152   pg-152
192.168.28.153   pg-153
192.168.28.41    vip-master
192.168.28.141   vip-slave

基础环境准备

配置 hostname [all service]
cat /etc/hosts
192.168.28.151   pg-151
192.168.28.152   pg-152
192.168.28.153   pg-153
关闭防火墙.selinux [all service]
#systemctl disable firewalld #systemctl stop firewalld #systemctl status firewalld #sestatus

SELinux status:                 disabled
服务器时间同步
ntpdate ntp1.aliyun.com

集群软件安装

安装 pcs
yum -y install libsmb*

yum install -y pacemaker pcs corosync

yum install  -y autoconf automake libtool

yum install   -y docbook-style-xsl

yum install   -y gcc-c++ glib2-devel

需要注意的是,安装时可能会报 Missing Dependency :kernel-header
安装 安装 kernel-headers 即可解决问题,如下

wget http://mirror.centos.org/centos/7/updates/x86_64/Packages/kernel-headers-3.10.0-1160.102.1.el7.x86_64.rpm
rpm -ivh kernel-headers-3.10.0-1160.102.1.el7.x86_64.rpm
pacemaker resource-agents 更新 [all servers]
wget  https://github.com/ClusterLabs/resource-agents/releases/tag/v4.12.0
unzip resource-agents-4.12.0.zip
./autogen.sh
/configure
make && make install

确认支持 PG12 以上版本
/usr/lib/ocf/resource.d/heartbeat/pgsql 文件,1918 行,包含 ocf_version_cmp “$version” “12”

 if is_replication || [ "$OCF_RESKEY_rep_mode" = "slave" ]; then if [ `printf "$version\n9.1" | sort -n | head -1` != "9.1" ]; then
            ocf_exit_reason "Replication mode needs PostgreSQL 9.1 or higher." return $OCF_ERR_INSTALLED fi
        ocf_version_cmp "$version" "12"
        rc=$?
        if [ $rc -eq 1 ]||[ $rc -eq 2 ]; then # change the standby method for PosrgreSQL 12 or later. 
启动服务 [all service]
systemctl start pcsd
systemctl enable pcsd
systemctl enable corosync
systemctl enable pacemaker

集群设置

1. 设置 hacluster 用户密码 [all service]
echo hacluster|passwd hacluster --stdin
2. 集群认证 (任意节点)
pcs cluster auth -u hacluster -p hacluster pg-151 pg-152 pg-153 
3. 同步配置 (任意节点)
pcs cluster setup --last_man_standing=1 --name pgcluster pg-151 pg-152 pg-153 --force 

注意 --force 会强行覆盖原有集群配置

4. 启动集群 (任意节点)
#启动集群
pcs cluster start --all
#查看集群状态
pcs status --full 

安装 PostgresSQl14 [all service]

yum 安装
sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo yum install -y postgresql14-server
sudo /usr/pgsql-14/bin/postgresql-14-setup initdb
sudo systemctl enable postgresql-14
sudo systemctl start postgresql-14
数据库目录创建 [all servers]

mkdir -p /data/postgresql/pgdata/pg-5432
mkdir -p /data/postgresql/pg_archive/pg-5432
chown postgres:postgres /data/postgresql
chmod 700 /data/postgresql

数据库用户环境变量配置 [all servers]
su - postgres
-bash-4.2$ cat .bash_profile
[ -f /etc/profile ] && source /etc/profile
PGDATA=/data/postgresql/pgdata/pg-5432
export PGDATA
export PATH=/usr/pgsql-14/bin:$PATH # If you want to customize your settings, # Use the file below. This is not overridden # by the RPMS.
[ -f /var/lib/pgsql/.pgsql_profile ] && source /var/lib/pgsql/.pgsql_profile
主节点数据库 (pg-151) 配置
  1. 初始化数据库
initdb -D /data/postgresql/pgdata/pg-5432/ 
  1. 修改配置文件和 hba
    postgresql.conf

pg_hba.conf (用于设置主机访问)

local all all                                     trust
host   all all 192.168.28.0/24          md5
# IPv4 local connections:
host    all all 127.0.0.1/32            trust
# IPv6 local connections:
host    all all             ::1/128                 trust
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     trust
host    replication     repluser        192.168.28.0/24         md5
host    replication     all 127.0.0.1/32            trust
host    replication     all             ::1/128                 trust
创建复制用户 (master 节点)

先运行 matser 数据库
pg_ctl start
再创建复制用户

psql (14.10)
Type "help" for help.
postgres=# create user repluser with replication password 'repluser'; 
创建 slave 节点 (pg-152 pg-152)
pg_basebackup -h pg-151 -U repluser -p 5432  -D /data/postgresql/pgdata/pg-5432/ -X stream -P 
停止 master 节点 (pg-151)

pg_ctl stop

配置 pgsql 集群

1. 设置 cluster_setup.sh 脚本
pcs cluster cib pgsql_cfg
pcs -f pgsql_cfg property set no-quorum-policy="ignore"
pcs -f pgsql_cfg property set stonith-enabled="false"
pcs -f pgsql_cfg resource defaults resource-stickiness="INFINITY"
pcs -f pgsql_cfg resource defaults migration-threshold="1"
pcs -f pgsql_cfg resource create vip-master IPaddr2 \
   ip="192.168.28.41" \
   nic="eth0" \
   cidr_netmask="24" \
   op start   timeout="60s" interval="0s"  on-fail="restart" \
   op monitor timeout="60s" interval="10s" on-fail="restart" \
   op stop    timeout="60s" interval="0s"  on-fail="block"
pcs -f pgsql_cfg resource create vip-slave IPaddr2 \
   ip="192.168.28.141" \
   nic="eth0" \
   cidr_netmask="24" \
   meta migration-threshold="0" \
   op start   timeout="60s" interval="0s"  on-fail="stop" \
   op monitor timeout="60s" interval="10s" on-fail="restart" \
   op stop    timeout="60s" interval="0s"  on-fail="ignore"
pcs -f pgsql_cfg resource create pgsql pgsql \
   pgctl="/usr/pgsql-14/bin//pg_ctl" \
   psql="/usr/pgsql-14/bin//psql" \
   pgdata="/data/postgresql/pgdata/pg-5432" \
   config="/data/postgresql/pgdata/pg-5432/postgresql.conf" \
   rep_mode="async" \
   node_list="pg-151 pg-152 pg-153" \
   master_ip="192.168.28.41" \
   repuser="repluser" \
   primary_conninfo_opt="password=repluser keepalives_idle=60 keepalives_interval=5 keepalives_count=5" \
   restart_on_promote='true' \
   op start   timeout="60s" interval="0s"  on-fail="restart" \
   op monitor timeout="60s" interval="4s"  on-fail="restart" \
   op monitor timeout="60s" interval="3s"  on-fail="restart" role="Master" \
   op promote timeout="60s" interval="0s"  on-fail="restart" \
   op demote  timeout="60s" interval="0s"  on-fail="stop" \
   op stop    timeout="60s" interval="0s"  on-fail="block" \
   op notify  timeout="60s" interval="0s"
pcs -f pgsql_cfg resource master msPostgresql pgsql \
   master-max=1 master-node-max=1 clone-max=5 clone-node-max=1 notify=true
pcs -f pgsql_cfg resource group add master-group vip-master
pcs -f pgsql_cfg resource group add slave-group vip-slave
pcs -f pgsql_cfg constraint colocation add master-group with master msPostgresql INFINITY
pcs -f pgsql_cfg constraint order promote msPostgresql then start master-group symmetrical=false score=INFINITY
pcs -f pgsql_cfg constraint order demote  msPostgresql then stop  master-group symmetrical=false score=0
pcs -f pgsql_cfg constraint colocation add slave-group with slave msPostgresql INFINITY
pcs -f pgsql_cfg constraint order promote msPostgresql then start slave-group symmetrical=false score=INFINITY
pcs -f pgsql_cfg constraint order demote  msPostgresql then stop  slave-group symmetrical=false score=0
pcs cluster cib-push pgsql_cfg

运行脚本

chmox +x cluster_setup.sh
sh cluster_setup.sh 
2. 如何修改集群配置
cibadmin --query > tmp.xml
//将当前集群配置信息保存到tmp.xml文件中 cibadmin --query > tmp.xml
vi tmp.xml
//使用编辑器对XML文件进行修改  vim tmp.xml
cibadmin --replace --xml-file tmp.xml
//将修改后的XML文件替换掉当前集群的配置信息 cibadmin --replace --xml-file tmp.xml 

cibadmin 是用于操作 Heartbeat CIB 的低级管理命令。它可以用来转储、更新或修改所有或部分 CIB,删除整个 CIB 或执行各种 CIB 管理操作。

集群配置信息是 Pacemaker 集群中 CIB 信息的关键组成部分,Pacemaker 的集群配置信息决定了集群最终应该如何工作以及集群最终的运行状态,因为只有一个正确的集群配置才能驱动集群资源运行在正常的状态。通常情况下,集群的配置信息由集群配置选项 (crm_config)、集群节点 (nodes)、集群资源 (resources) 和资源约束 (constraints) 四个配置段组成,通过 cibadmin --query 可查。

3. 集群状态检查

1. 启动集群并设置开机自启动
数据库无需配置自启动,由集群软件自动拉起

pcs cluster start --all
pcs cluster enable --all 

2. 查看集群状态

[root@k8s-master01 pgsql_cluster]# pcs status Cluster name: pgcluster Stack: corosync Current DC: pg-153 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum Last updated: Tue Nov 21 16:47:19 2023 Last change: Tue Nov 21 16:38:42 2023by root via crm_attribute on pg-151 3 nodes configured 7 resource instances configured Online: [ pg-151 pg-152 pg-153 ]

Full list of resources: Master/Slave Set: msPostgresql [pgsql]
     Masters: [ pg-151 ]
     Slaves: [ pg-152 pg-153 ]
 Resource Group: master-group vip-master (ocf::heartbeat:IPaddr2): Started pg-151 Resource Group: slave-group vip-slave  (ocf::heartbeat:IPaddr2): Started pg-152 Daemon Status: corosync: active/enabled pacemaker: active/enabled pcsd: active/enabled 

3、查看本机集群状态及资源状态
crm_mon -Afr -1

[root@k8s-master01 pgsql_cluster]# crm_mon -Afr -1
Stack: corosync
Current DC: pg-153 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum
Last updated: Tue Nov 21 16:47:54 2023
Last change: Tue Nov 21 16:38:42 2023 by root via crm_attribute on pg-151

3 nodes configured
7 resource instances configured

Online: [ pg-151 pg-152 pg-153 ]

Full list of resources:

 Master/Slave Set: msPostgresql [pgsql]
     Masters: [ pg-151 ]
     Slaves: [ pg-152 pg-153 ]
 Resource Group: master-group
     vip-master (ocf::heartbeat:IPaddr2):       Started pg-151
 Resource Group: slave-group
     vip-slave  (ocf::heartbeat:IPaddr2):       Started pg-152

Node Attributes:
* Node pg-151:
    + master-pgsql                      : 1000      
    + pgsql-data-status                 : LATEST    
    + pgsql-master-baseline             : 00000000220000A0
    + pgsql-status                      : PRI       
* Node pg-152:
    + master-pgsql                      : -INFINITY 
    + pgsql-data-status                 : STREAMING|ASYNC
    + pgsql-status                      : HS:async  
* Node pg-153:
    + master-pgsql                      : -INFINITY 
    + pgsql-data-status                 : STREAMING|ASYNC
    + pgsql-status                      : HS:async  

Migration Summary:
* Node pg-153:
* Node pg-152:
* Node pg-151:

4. 查看主节点流复制状态 (master)

su - postgres
-bash-4.2$ psql
psql (14.10)
Type "help" for help.

postgres=# select client_addr,sync_state from pg_stat_replication;
  client_addr   | sync_state 
----------------+------------
 192.168.28.153 | async 192.168.28.152 | async
(2 rows)

5. 验证 vip [all service]

ipconfig
验证数据同步

在主节点上创建一个新表

postgres=# create table ysl(id int);
CREATE TABLE
postgres=# insert into ysl values(1);
INSERT 0 1
postgres=# select * from ysl;
 id 
----
  1
(1 row)

在 slave 节点上查看

-bash-4.2$ psqlpsql (14.10)
Type "help" for help.

postgres=# select * from ysl; id 
----
  1
(1 row)

故障模拟

停掉主库 pg-151,备库 pg-152 提升为主节点

-bash-4.2$ pg_ctl stop
waiting for server to shut down.... done

[root@k8s-master01 pgsql_cluster]# pcs status
Cluster name: pgcluster
Stack: corosync
Current DC: pg-153 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum
Last updated: Tue Nov 21 16:59:30 2023
Last change: Tue Nov 21 16:59:29 2023 by root via crm_attribute on pg-152

3 nodes configured
7 resource instances configured

Online: [ pg-151 pg-152 pg-153 ]

Full list of resources:

 Master/Slave Set: msPostgresql [pgsql]
     Masters: [ pg-152 ]
     Slaves: [ pg-153 ]
     Stopped: [ pg-151 ]
 Resource Group: master-group
     vip-master (ocf::heartbeat:IPaddr2):       Started pg-152
 Resource Group: slave-group
     vip-slave  (ocf::heartbeat:IPaddr2):       Started pg-153

Failed Resource Actions:
* pgsql_monitor_3000 on pg-151 'not running' (7): call=92, status=complete, exitreason='',
    last-rc-change='Tue Nov 21 16:58:59 2023', queued=0ms, exec=0ms

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled

注意 master 节点挂掉之后,使用 crm_mon -Arf -1 命令看到末尾有 "You have to remove /var/lib/pgsql/tmp/PGSQL.lock file to force start." 字样,master 宕机启动时,需要删除临时锁文件方可进行集群角色转换。即执行 rm -rf /var/lib/pgsql/tmp/PGSQL.lock
拉起数据库不需要执行 pg_ctl start, 只需要 pcs resource cleanup

[root@k8s-master01 pgsql_cluster]# pcs resource cleanup
Cleaned up all resources on all nodes
Waiting for 1 reply from the CRMd. OK
[root@k8s-master01 pgsql_cluster]# pcs status
Cluster name: pgcluster
Stack: corosync
Current DC: pg-153 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum
Last updated: Tue Nov 21 17:01:57 2023
Last change: Tue Nov 21 17:01:26 2023 by hacluster via crmd on pg-151

3 nodes configured
7 resource instances configured

Online: [ pg-151 pg-152 pg-153 ]

Full list of resources:

 Master/Slave Set: msPostgresql [pgsql]
     Masters: [ pg-152 ]
     Slaves: [ pg-153 ]
     Stopped: [ pg-151 ]
 Resource Group: master-group
     vip-master (ocf::heartbeat:IPaddr2):       Started pg-152
 Resource Group: slave-group
     vip-slave  (ocf::heartbeat:IPaddr2):       Started pg-153

Failed Resource Actions:
* pgsql_start_0 on pg-151 'unknown error' (1): call=127, status=complete, exitreason='My data may be inconsistent. You have to remove /var/lib/pgsql/tmp/PGSQL.lock file to force start.',
    last-rc-change='Tue Nov 21 17:01:28 2023', queued=1ms, exec=615ms

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled

此时可以看到 pg-153,pg-151 成为 pg-152 的备机

postgres=# select * from pg_stat_replication;
  pid  | usesysid | usename  | application_name |  client_addr   | client_hostname | client_port |     
    backend_start         | backend_xmin |   state   |  sent_lsn  | write_lsn  | flush_lsn  | replay_ls
n | write_lag | flush_lag | replay_lag | sync_priority | sync_state |          reply_time           
-------+----------+----------+------------------+----------------+-----------------+-------------+-----
--------------------------+--------------+-----------+------------+------------+------------+----------
--+-----------+-----------+------------+---------------+------------+-------------------------------
 13234 |    16384 | repluser | pg-153           | 192.168.28.153 |                 |       60624 | 2023
-11-21 08:59:27.197402+00 |        12918 | streaming | 0/2D004748 | 0/2D004748 | 0/2D004748 | 0/2D00474
8 |           |           |            |             0 | async      | 2023-11-21 09:04:16.90261+00
 31795 |    16384 | repluser | pg-151           | 192.168.28.151 |                 |       44678 | 2023
-11-21 09:04:05.741284+00 |        12918 | streaming | 0/2D004748 | 0/2D004748 | 0/2D004748 | 0/2D00474
8 |           |           |            |             0 | async      | 2023-11-21 09:04:15.822551+00
(2 rows)

参考:

PgSQL Replicated Cluster:


📌 转载信息
原作者:
xxdtb
转载时间:
2026/1/19 17:53:11

如果加入家庭组后还是《free》。
如果账号突然《账号无权限,已跳过自动刷新》。

都去验证一下年龄,验证通过。等个几分钟就好了!

https://antigravity.google/docs/faq#why-am-i-ineligible-for-google-one-ai-quotas-even-though-i-am-a-subscribed-to-a-plan

verify your age

希望能帮到大家

上面是我的经验之谈,我的号是这么恢复的,可能我接触得少。看完大家的回复,得出结论,方法不一定有效,大家可以试试,不行也没办法了。


📌 转载信息
原作者:
xsc
转载时间:
2026/1/19 17:52:54

Skill 与 MCP 深度对比

首先,官方文档非常值得细细品味,看完官方文档倒也可以考虑不用看其他的文章了(针对 skill,mcp 这一块感觉描述一般,mcp 太多东西了):

# Agent Skills

# Agent Skills 概览

# 通过 MCP 将 Claude Code 连接到工具


一、Token 占用对比,这个大家应该都知道,不过多描述

1.1 MCP:全量加载模式

MCP 在会话启动时全量加载所有工具定义到上下文:

会话启动 → 加载所有 MCP 服务器 → 注入全部工具定义(JSON-Schema

就拿我使用的 mcp 来说

MCP 服务器工具数Token 占用
auggie-mcp1~1k
grok-search5~3k
memory9~5k
sequential-thinking1~2k
多服务器叠加-轻松突破 10k+

随便用几个占用就非常大了

1.2 Skill:按需加载模式

Skill 采用渐进式披露,仅在需要时加载:

阶段加载内容Token 占用
启动时name + description 索引~10-100 tokens/skill
触发时完整 SKILL.md 内容~1,000-5,000 tokens/skill

我使用的 skill:90K 的 Skills 目录,/context 仅显示 513 tokens,和 mcp 对比起来,非常直观。

1.3 简单对比

维度MCPSkill
启动时占用全量(10k+ tokens)索引(~500 tokens)
使用时增量无(已加载)按需逐步加载


二、设计模式对比

2.1 MCP:全量注入 + JSON-Schema

什么是 MCP?

MCP(Model Context Protocol)即模型上下文协议,采用 Host-Client-Server 三层架构:

Host (Claude Code)
    │
    ├── Client ←──JSON-RPC──→ context7 Server              
    ├── Client ←──JSON-RPC──→ sequential-thinking Server   
    └── Client ←──JSON-RPC──→ your Server 
角色是什么
HostAI 应用程序(比如 Claude Code、VS Code、Cursor 等)
Client通信管道,负责与 Server 建立连接、发送 JSON-RPC 请求、接收响应
Server提供工具 / 上下文的程序(== 日常说的「安装 MCP」就是安装 Server==)
JSON-RPCClient 与 Server 之间传输的数据格式(所有 MCP 都遵守此协议)
Tool Schema启动时 Server 返回的「工具说明书」,告诉 Claude 有哪些工具、怎么调用

Host 和 Client 由应用自动管理,开发者 / 使用者只需关注 Server。再比如 FastMCP 框架会自动处理协议细节。

MCP Server 目录结构(这里以 Python 为例)

my-mcp-server/
├── pyproject.toml            # 包定义(必需)
│   ├── name = "mcp-server-xxx"
│   └── version = "1.0.0"
├── src/
│   ├── server.py             # 入口,初始化 FastMCP
│   ├── tools.py              # Tool 定义(单文件,适合 <10 个工具)
│   ├── tools/                # Tool 定义(多文件,按功能拆分)
│   │   ├── __init__.py
│   │   ├── search.py
│   │   └── fetch.py
│   └── resources.py          # Resource 提供者(可选)
└── README.md

tools.pytools/ 二选一,可根据数量决定。

Tool 定义示例

从这里就可以看出,其实和接口非常像,请求 -> 回调数据,实际上多数的 mcp 也都可以理解成三方接口。

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

@mcp.tool() async def get_weather(location: str) -> str:
    """Get weather for a location.""" return f"Weather in {location}: 72°F, Sunny" if __name__ == "__main__":
    mcp.run(transport="stdio")

客户端配置结构(mcpServers)

配置示例

{ "args": [ "-y", "@upstash/context7-mcp" ], "command": "npx", "type": "stdio" } 

mcp 服务器的 json 格式,让配置起来非常方便,cv 万岁

2.2 Skill:渐进式披露 + Markdown

什么是 Skill?

Skill 是 Claude Code 的知识 / 流程包,本质是一个 Markdown 文件夹,用于:

  • 注入领域知识(如代码规范、业务逻辑)
  • 定义工作流程(如代码审查 SOP、部署流程)
  • 封装可复用的脚本和模板 ==(这直接导致 Skill 与 MCP 在使用体验上趋于相似)==

额外说明:

Skill 只能提供指导,无法执行外部 API 调用、网络请求、会话管理等操作,毕竟只是 md 文件夹,但是能指导 cc 运行脚本,包括但不限于自带的脚本资源,这句话应该会有嚼头。

Skill 目录结构(双层架构)

以下规范取自 skill-creator Skill, 我觉得配置比 cc 官方文档里面的描述更详细

skill-name/ ├── SKILL.md (必需)  ├── YAML frontmatter ──→ 索引层(始终加载,~100 words)   ├── name: (必需)   └── description: (必需,唯一触发机制)  └── Markdown 正文 ────→ 内容层(触发时加载,建议 <500 行) └── Bundled Resources (可选) ──→ 内容层(按需加载) ├── scripts/ - 可执行脚本(Python/Bash) ├── references/ - 参考文档(Claude 判断需要时加载) └── assets/ - 输出资源(模板、图片,不加载到上下文) 

双层架构说明

  • 索引层:YAML frontmatter 中的 name/description,始终在上下文中
  • 内容层:SKILL.md 正文 + 捆绑资源,仅在触发时选择性加载

核心设计原则

description 是唯一触发机制

# ✅ 好的 description(包含触发场景) description: >
Comprehensive document creation and editing with tracked changes.
Use when Claude needs to work with .docx files for:
(1) Creating new documents, (2) Modifying content,
(3) Working with tracked changes, (4) Adding comments
# ❌ 差的 description(太模糊) description: A useful document tool

渐进式披露模式

是不是觉得模式 1,3 非常相似?我也觉得。不过 skill-creator 还是分成了这三种,就全罗列出来了

模式 1:高层指南 + 引用

# PDF Processing ## Quick start
[核心代码示例]

## Advanced features - **Form filling**: See [FORMS.md](references/FORMS.md)
- **API reference**: See [REFERENCE.md](references/REFERENCE.md)

Claude 仅在需要时加载 FORMS.md 或 REFERENCE.md。

模式 2:按领域 / 变体组织

cloud-deploy/
├── SKILL.md (工作流 + 选择指南)
└── references/
├── aws.md    ← 用户选 AWS 时才加载
├── gcp.md
└── azure.md

模式 3:条件详情

## Editing documents
For simple edits, modify XML directly.

**For tracked changes**: See [REDLINING.md](references/REDLINING.md)
**For OOXML details**: See [OOXML.md](references/OOXML.md)

工作流程

用户请求 → Claude 扫描所有 Skill 的 description → 匹配 → 加载 SKILL.md 正文 → 按需加载 references → 执行

特点

  • 宽松定义:Markdown 格式,自然语言描述
  • 懒加载:渐进式加载
  • 可编排:Markdown 可表达流程顺序和条件分支
  • 本质:流程 / 知识包(SOP 手册)


三、版本管理与更新便利性

结论先行:MCP 在版本管理上绝对优于 Skill。

3.1 MCP:成熟的包管理生态

核心机制

MCP 直接复用 npm/PyPI 成熟生态,版本管理由包管理器自动处理。

工作原理

  • MCP 服务器以 npm 包(js/ts)或 PyPI 包(Python)形式发布
  • 使用 npxuvx 命令启动时,包管理器会自动处理下载和缓存
  • 版本号定义在 package.json(npm)或 pyproject.toml(PyPI)中

更新行为

模式行为启动速度适用场景
默认使用本地缓存,不查询 registry日常使用
强制更新每次查询 registry,版本变化时才下载(@latest--refresh需要最新版本时

获取方式对比

获取方式配置示例更新机制
npx (npm)npx -y @pkg/server默认用缓存,需 @latest 或清缓存强制更新
uvx (pip)uvx mcp-server-fetch默认用缓存,需 --refresh 或清缓存强制更新
本地命令auggie --mcp需手动 pip install --upgrade

配置示例

// ~/.claude.json { "mcpServers": { "memory": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-memory"] } } } 

如何更新

推荐方式:手动清理缓存

当 MCP 服务器有新版本时,清理缓存即可:

# 清理 npx 缓存 rm -rf ~/.npm/_npx #(不推荐!不推荐!不推荐!) # 清理 uvx 缓存
uv cache clean #(不推荐!不推荐!不推荐!) # 上述的清理会清理系统中所有的缓存,但mcp的缓存位置各有不同,需要主动寻找,且无规律可言,最好还是让cc自己来吧 

工作流程

  1. 清理缓存
  2. 重启 Claude Code
  3. 自动下载最新版本

优点:日常启动快,需要更新时才清理缓存

不推荐使用 @latest 或 --refresh

虽然可以自动更新,但会显著影响启动速度(每次都查询 registry)。

配置示例:

{ "mcpServers": { "memory": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-memory@latest"] // npx 使用 @latest }, "fetch": { "command": "uvx", "args": ["--refresh", "mcp-server-fetch"] // uvx 使用 --refresh } } } 


3.2 Skill:普遍缺乏版本管理

现状:一般来说 Skill 没有版本管理

与 MCP 不同,一般来说 Skill 没有任何版本管理机制

  • 没有版本号定义
  • 没有版本锁定
  • 手动管理

Skill 在某些时候更像可以随意定制的小玩具,在原本的基础上又能随意修改

Skill 的获取与更新方式

获取方式更新方式版本控制
GitHub 克隆git pull 手动更新依赖 Git commit hash
下载 ZIP 文件手动替换文件
CC 直接生成即时生效

问题

  • 用户不知道 Skill 是否有新版本
  • 无法回滚到特定版本(除非使用 Git)


例外:官方 Marketplace(需登录)

唯一的版本管理方案是官方 Marketplace,但有严格限制:

  • 必须登录:API Key 用户无法使用
  • 非标准化:没有强制的版本号格式

Marketplace 版本管理示例

// marketplace.json { "name": "my-plugins", "plugins": [ { "name": "review-plugin", "source": "./plugins/review-plugin", "version": "2.1.0" // ← 需要手动维护 } ] } 

限制

  • 仅适用于官方 Marketplace 中的 Skill
  • 无法用于自定义或第三方 Skill
  • API Key 用户完全无法使用

ps:这一块应该是这样的吧,没有官号诶


Skill 的独特优势

尽管缺乏版本管理,Skill 还有一个 MCP 无法比拟的优势:

即时生效:修改 Skill 文件后无需重启服务,下次触发时自动加载最新内容。

对比 MCP

  • MCP:修改后需重启 Claude Code 才能生效
  • Skill:适合快速迭代和调试,改完即用

适用场景

  • 个人定制化 Skill(频繁调整)
  • 快速原型验证


四、我推荐:使用 Slash Command 稳定触发

事先声明: 这里的 /ccg 不是使用的 skill,我才发现,哎呀!!

但是我都按照这个写了,不想再改了。

/ccg 的工作流程是 /cmd → 自己的脚本
请你们假装 是 /cmd → skill → skill 的脚本

4.1 Skill 的触发不确定性问题

Skill 依赖 Claude 自动匹配描述,存在以下问题:

  • 该触发时未触发(描述不够精准)
  • 不该触发时误触发(描述过于宽泛)

实际体验:虽然 Skill 理论上能够自动触发,但实际触发率较低。比如我即使在 CLAUDE.md 中明确写了 "让 Codex 和 Gemini 参与协作",Claude 偶尔还是会忽略。所以之前我都会主动在需求后面添加这句话。

解决方案:使用 cmd 可以 100% 稳定触发。例如:/ccg:feat 需求描述 总比主动描述或者期待 cc 记得安心。

4.2 最佳实践:Skill + Command 工作流组合

为什么需要 Command

Skill 可能包含复杂的命令调用和参数配置,这些细节难以通过自然语言稳定触发。例如:

  • 外部工具调用:/home/nobug/.claude/bin/codeagent-wrapper --backend gemini
  • 带参数的指令:--backend codex--SESSION_ID xxx
  • 多阶段工作流:需要按特定顺序执行多个步骤

这些精确的指令和参数通过语言描述很难稳定触发,而 Command 可以将这些细节明确定义在文件中。

模式 1 示例(简单流程):

场景:应用主题到 Artifact

├── ~/.claude/skills/theme-factory/
│   └── SKILL.md                    # 主题定义、应用指南
└── ~/.claude/commands/
    └── theme-factory.md            # 内容:"Execute the theme-factory skill"

用户输入 /theme-factory 应用深色主题
    ↓
Command 触发 → Claude 加载 SKILL.md → 自主执行

模式 2 示例(复杂流程):

场景:前端专项开发

└── ~/.claude/commands/ccg/
    └── frontend.md                 # 完整工作流定义

用户输入 /ccg:frontend 实现响应式导航栏
    ↓
Claude 加载 frontend.md 内容
    ↓
按照 Command 中定义的 6 个阶段执行:
  1. Prompt 增强(可选)
  2. 研究(代码检索)
  3. 构思(调用 Gemini 分析)
  4. 计划(调用 Gemini 规划)
  5. 执行(Claude 实施)
  6. 优化(调用 Gemini 审查)

外部工具说明:像 codeagent-wrapper 这样的工具是外部可执行文件(位于 ~/.claude/bin/),不是 Skill。Skill 只能提供指导,无法执行外部 API 调用、网络请求、会话管理等操作,这些必须由外部工具完成。

核心原则

  • 稳定性优先:复杂流程用 Command 定义,确保稳定执行
  • 外部工具必须用 Command:Skill 无法执行外部命令
  • 简单场景用 Skill:减少维护成本,提高灵活性


第一次写文章,本来只是想说 mcp 的包管理很爽 ,其他的都是为了这碟醋包的饺子


📌 转载信息
原作者:
Shuiyell
转载时间:
2026/1/19 17:52:41

又到年终汇报时间,又要搞 word 格式,又要 ppt 形式述职,下面总结一下经验可以给佬们参考。

  1. 先根据每个月的月总按照公司模板生成年总需要填写的内容,这里我用的 Google AI Studio - Gemini 3 pro。没有月总的可以看看自己 Git 提交记录找找今年做了什么。

  2. 使用 Z.ai 生成 PPT。

这里导出的 PPTX 打开后,会发现样式有变化。

可以使用先导出 PDF,之后使用 Python 脚本将 PDF 转为图片,图片再转为 PPTX,这样就解决样式变化的问题了。

  1. 先安装库
pip install pymupdf python-pptx
  1. 创建文件 pdf2pptx.py,内容:
import fitz  # PyMuPDF
from pptx import Presentation
from pptx.util import Inches
import io
import os

def pdf_to_pptx(pdf_path, pptx_path, dpi=300):
    """
    将 PDF 转换为 PPTX(每一页作为一张全屏图片)
    :param pdf_path: 输入 PDF 文件路径
    :param pptx_path: 输出 PPTX 文件路径
    :param dpi: 渲染清晰度,300 已经非常清晰,如果文件太大可以降到 200
    """
    # 1. 打开 PDF
    pdf_doc = fitz.open(pdf_path)
    prs = Presentation()

    # 2. 获取 PDF 第一页的尺寸,并设置为 PPT 的幻灯片尺寸
    # PDF 默认单位是点 (points), 1 inch = 72 points
    first_page = pdf_doc[0]
    width_pts, height_pts = first_page.rect.width, first_page.rect.height
    
    prs.slide_width = Inches(width_pts / 72)
    prs.slide_height = Inches(height_pts / 72)

    print(f"开始转换: {pdf_path}")
    print(f"总页数: {len(pdf_doc)}")

    # 3. 逐页处理
    for page_num in range(len(pdf_doc)):
        page = pdf_doc.load_page(page_num)
        
        # 渲染页面为图片 (设置缩放倍数以提高清晰度)
        zoom = dpi / 72
        mat = fitz.Matrix(zoom, zoom)
        pix = page.get_pixmap(matrix=mat, alpha=False)
        
        # 将图片存入内存流中,避免产生临时文件
        img_stream = io.BytesIO(pix.tobytes("png"))
        
        # 添加一张空白幻灯片 (6 是空白布局)
        slide_layout = prs.slide_layouts[6]
        slide = prs.slides.add_slide(slide_layout)
        
        # 将图片插入幻灯片,铺满全屏
        slide.shapes.add_picture(img_stream, 0, 0, width=prs.slide_width, height=prs.slide_height)
        
        print(f"正在处理第 {page_num + 1} 页...")

    # 4. 保存文件
    prs.save(pptx_path)
    pdf_doc.close()
    print(f"转换完成!已保存至: {pptx_path}")

if __name__ == "__main__":
    # === 使用说明 ===
    # 将下面的文件名替换为你实际的文件名
    input_pdf = "input.pdf" 
    output_pptx = "output.pptx"

    if os.path.exists(input_pdf):
        pdf_to_pptx(input_pdf, output_pptx)
    else:
        print(f"错误:找不到文件 {input_pdf}")
  1. 将 pdf 文件重名为 input.pdf,然后执行:
python pdf2pptx.py
  1. 输出文件 output.pptx。可以再让 AI 帮你输出一份口述草稿配合起来就行了。


有尝试过在线 ILovePDF 网站去转,但是样式还是有问题。

试过 NotebookLM 生成 PPT,效果一般般,没想到 Z.ai 生成的效果还挺好看的


📌 转载信息
转载时间:
2026/1/19 17:52:31

介绍
发布和订阅使用了 pg 的逻辑复制功能,通过发布端创建 publication 与表绑定,订阅端创建 subscription 同时会在发布端创建逻辑复制槽实现逻辑复制功能

逻辑复制基于 发布(Publication) 与 订阅(Subscription)模型

一个 发布者(Publisher) 上可以有多个发布,一个 订阅者(Subscriber) 上可以有多个 订阅 。
一个发布可被多个订阅者订阅,一个订阅只能订阅一个发布者,但可订阅同发布者上的多个不同发布。

典型用法

迁移,跨 PostgreSQL 大版本,跨操作系统平台进行复制。
CDC,收集数据库(或数据库的一个子集)中的增量变更,在订阅者上为增量变更触发触发器执行定制逻辑。
分拆,将多个数据库集成为一个,或者将一个数据库拆分为多个,进行精细的分拆集成与访问控制。

复制标识

一个被纳入发布中的表,必须带有复制标识(Replica Identity),只有这样才可以在订阅者一侧定位到需要更新的行,完成 UPDATE 与 DELETE 操作的复制。
默认情况下,主键 (Primary Key)是表的复制标识,非空列上的唯一索引 (UNIQUE NOT NULL)也可以用作复制标识。
如果没有任何复制标识,可以显式将复制标识设置为 FULL,也就是把整个行当作复制标识
使用 FULL 模式的复制标识效率很低(因为每一行修改都需要在订阅者上执行全表扫描,很容易把订阅者拖垮),所以这种配置只能是保底方案。
使用 FULL 模式的复制标识还有一个限制,订阅端的表上的复制身份所包含的列,要么与发布者一致,要么比发布者更少

创建发布订阅

环境介绍

version: pg16
发布端: 5433
订阅端: 5434
主机 IP: 192.168.28.11

创建发布订阅

在发布者端,wal_level 必须被设置为 logical,而 max_replication_slots 中设置的值必须至少是预期要连接的订阅数加上保留给表同步的连接数。max_wal_senders 应该至少被设置为 max_replication_slots 加上同时连接的物理复制体的数量。
订阅者必须配置 max_replication_slots。它必须设置为至少是订阅者数,加上一些用于表同步的预留。max_logical_replication_workers 必须至少被设置为订阅数加上保留给表同步的连接数。此外,可能需要调整 max_worker_processes 以容纳复制工作者,至少为 (max_logical_replication_workers + 1)。注意,一些扩展和并行查询也会从 max_worker_processes 中取得工作者槽。

发布端
  1. 修改配置文件
vim postgres.conf
wal_level = logical 
max_wal_senders = 100 #max_wal_senders大于max_replication_slots 
vim pg_hba.conf
host    all    repl    订阅者IP/32    md5

重启 pgsql ,配置生效

su postgres
pg_ctl restart -D /data/postgresql/pgdata/pg-5433/ 
  1. 发布者节点创建用户,赋权
su postgres
psql -P 5433
psql (16.9 (Ubuntu 16.9-0ubuntu0.24.10.1))
Type "help" for help.
#创建用户
postgres=# create user repuser password 'repuser' replication;
CREATE ROLE
#在postgres库中创建表,并插入一条数据
postgres=# create table test1(id int primary key,name varchar(10) );
postgres=# insert into test1 values(1,'a'); # 赋予复制用户select表权限
postgres=# grant select on table test1 to repuser; 
  1. 创建表 test1 的发布
create publication pub_test01 for table test1;
订阅端
  1. 修改 posetgres.conf
vim postgres.conf
max_replication_slots=8
max_logical_replication_workers=10
max_worker_processes=8

max_logical_replication_workers 逻辑复制进程数,应大于订阅节点的数量,并且给表同步预留一些进程数量
2. 重启 pgsql

pg_ctl restart -D /data/postgresql/pgdata/pg-5434/ 
  1. 创建订阅
CREATE SUBSCRIPTION subs_test01  CONNECTION 'host=192.168.28.11 dbname=postgres port=5433 user=repuser password=repuser' PUBLICATION pub_test01;
  1. 查看表是否同步
    在发布端执行 test1 表的 insert、delete、update、truncate 操作,看订阅端是否能同步

新加表同步

发布端
  1. 新建表
    create table test3 (id int,name varchar(10));
  2. 设置复制标识
    alter table test3 REPLICA IDENTITY full;
  3. 将表加入发布
    ALTER PUBLICATION pub1 ADD TABLE test3;
订阅端

在订阅端执行刷新
ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION ;
在发布端执行 test3 表的 insert、delete、update、truncate 操作,看订阅端是否能同步

发布订阅查询状态语句

发布端
select * from pg_replication_slots; --查看所有复制槽 select * from pg_stat_replication; --查看复制槽的同步状态 select * from pg_publication; --查看所有发布 select * from pg_publication_tables; --查看所有发布对应的表 select usename,a.pubname,c.*,pubinsert,pubupdate,pubdelete,pubtruncate from pg_publication a,pg_user b,pg_publication_tables c where a.pubowner=b.usesysid and c.pubname=a.pubname;
订阅端
select * from pg_subscription; --查看所有订阅 select srrelid::regclass from pg_subscription_rel; --查看订阅的所有表 select usename,datname,subname,srrelid::regclass,srsublsn,subconninfo from pg_subscription a,pg_user b,pg_subscription_rel c ,pg_database d where a.oid=c.srsubid and a.subowner=b.usesysid and a.subdbid=d.oid;

删除发布订阅

删除订阅

订阅在运行的情况下,需要一下操作:

ALTER SUBSCRIPTION pub_test01;
##首先需要停止当前的订阅 ALTER SUBSCRIPTION subs_test01 (slot_name =NONE);
##然后将订阅的复制槽设置成空
drop subscription subs_test01;
##最后就可以删除订阅了。 ##另外一个问题,复制订阅中,如果订阅的服务停止,或无法再次连接的情况下,需要关注 发布端的数据wal log 无法清理以及膨胀的问题。 ##所以在复制订阅中的订阅停止后,如果确认订阅无法再次恢复,或者不确认多长时间恢复,则需要删除复制槽 select * from pg_replication_slots;

select pg_drop_replication_slot(slot_name) from pg_replication_slots where slot_name = 'pub_test01';

📌 转载信息
原作者:
xxdtb
转载时间:
2026/1/19 17:52:23

概述
使用 postgresql + etcd + patroni + haproxy + keepalived 可以实现 PG 的高可用集群,其中,以 postgresql 做数据库,Patroni 监控本地的 PostgreSQL 状态,并将本地 PostgreSQL 信息 / 状态写入 etcd 来存储集群状态,所以,patroni 与 etcd 结合可以实现数据库集群故障切换(自动或手动切换),而 haproxy 可以实现数据库读写分离 + 读负载均衡(通过不同端口实现),keepalived 实现 VIP 跳转,对 haproxy 提供了高可用,防止 haproxy 宕机。

Patroni 介绍

Patroni 是一个基于 Python 的用于实现 PostgreSQL HA 解决方案的框架。为了最大程度的兼容性,它支持多种分布式配置存储,包括 ZooKeeper、etcd、Consul 或 Kubernetes。旨在帮助数据库工程师、DBA、DevOps 工程师和 SRE 快速部署数据中心(或任何地方)的 HA PostgreSQL 环境。

当前支持的 PostgreSQL 版本从 9.3 到 16。支持自动化故障转移、物理复制和逻辑复制、提供 RESTful API 接口,允许外部应用或运维工具直接操作 PostgreSQL 集群,进行如启停、迁移等操作,与 Linux watchdog 集成,以避免脑裂现象。

项目地址: GitHub - patroni/patroni: A template for PostgreSQL High Availability with Etcd, Consul, ZooKeeper, or Kubernetes

ETCD 介绍

etcd 是一个分布式键值存储数据库,支持跨平台,拥有强大的社区。etcd 的 Raft 算法,提供了可靠的方式存储分布式集群涉及的数据。etcd 广泛应用在微服务架构和 Kubernates 集群中,不仅可以作为服务注册与发现,还可以作为键值对存储的中间件。从业务系统 Web 到 Kubernetes 集群,都可以很方便地从 etcd 中读取、写入数据。
etcd 完整的 cluster(集群)至少有三台,这样才能选举出一个 master 节点,两个 slave 节点。如果小于 3 台则无法进行选举,造成集群不可用。Etcd 使用 2379 和 2380 端口。
2379 端口:提供 HTTP API 服务,和 etcdctl 交互
2380 端口:集群中节点间通讯
项目地址:[[GitHub - etcd-io/etcd: Distributed reliable key-value store for the most critical data of a distributed system]]

环境说明

服务器信息
服务器名ip 地址os数据库读写端口只读端口组件组件端口
k8s-mater01192.168.28.11ubuntu 24.101543325433PostgreSQL,Patroni、Etcd,haproxy、keepalived8008
k8s-mater02192.168.28.12ubuntu 24.101543325433PostgreSQL,Patroni、Etcd,haproxy、keepalived8008
k8s-mater01192.168.28.13ubuntu 24.101543325433PostgreSQL,Patroni、Etcd,haproxy、keepalived8008
vip192.168.28.10PostgreSQL,Patroni、Etcd,haproxy、keepalived8008
软件信息
软件名版本
Patroni4.0.6
Etcd3.5.16
Keepalived2.3.1
Haproxy2.9.10-1ubuntu1.2
PostgreSQL16.9
watchdog5.16
python3.12.7
架构图

这个架构中,PostgreSQL 提供数据服务,Patroni 负责主从切换,etcd 提供一致性存储,HAProxy 提供访问路由,Keepalived 提供网络 VIP 高可用,Watchdog 提供节点存活及脑裂防护机制。 六者协同组成一个企业级高可用数据库集群

预先准备

网络设置
防火墙设置

关闭防火墙 (相对简单,但是不安全)
放行对应的端口(安全)

安装 PostgreSQL

通过 apt 安装 PostgreSQL (所有节点)
sudo apt install curl ca-certificates  
sudo install -d /usr/share/postgresql-common/pgdg  
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc  
. /etc/os-release  
sudo sh -c "echo 'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $VERSION_CODENAME-pgdg main' > /etc/apt/sources.list.d/pgdg.list" sudo apt update  
sudo apt -y install postgresql
数据库目录设置 (所有节点)
mkdir -p /data/postgresql/pgdata/
mkdir -p /data/postgresql/pg_archive/
chown -R postgres:postgres /data/postgresql/
postgres 用户设置
设置家目录
mkdir -p /home/postgres/
chown -R postgres:postgres /home/postgres/
vim /etc/passwd
#修改postgres 家目录为/home/postgres postgres:x:114:113:PostgreSQL administrator,,,:/home/postgres:/bin/bash 
设置环境变量
vim /home/postgres/.bashrc

[ -f /etc/profile ] && source /etc/profile
export PATH=/usr/lib/postgresql/16/bin:$PATH # If you want to customize your settings, # Use the file below. This is not overridden # by the RPMS.
[ -f /var/lib/pgsql/.pgsql_profile ] && source /var/lib/pgsql/.pgsql_profile
设置 sudo 免密
vim /etc/sudoers
#行末新增
postgres  ALL=(ALL)       NOPASSWD: ALL 

安装 ETCD 集群 (所有节点)

安装 ectd
下载 etcd
wget https://github.com/etcd-io/etcd/releases/download/v3.6.4/etcd-v3.6.4-linux-amd64.tar.gz

tar -xf etcd-v3.6.4-linux-amd64.tar.gz --strip-components=1 -C /usr/local/bin etcd-v3.6.4-linux-amd64/etcd etcd-v3.6.4-linux-amd64/etcdctl


mkdir -p /etc/etcd/
mkdir -p /data/etcd/

编辑 etcd 配置文件
touch /etc/etcd/etcd-pg.config.yml

#节点1配置文件
vim /etc/etcd/etcd-pg.config.yml

#节点名
name: pg-etcd01
#数据目录 
data-dir: /data/etcd
snapshot-count: 5000
#选举和心跳参数
heartbeat-interval: 100
election-timeout: 1000
#存储新能优化
quota-backend-bytes: 8589934592
max-request-bytes: 10485760
max-concurrent-requests: 5000
#自动压缩与碎片整理
auto-compaction-mode: periodic
auto-compaction-retention: "2h"
#集群通信配置
listen-peer-urls: "http://192.168.28.11:12380"
listen-client-urls: "http://192.168.28.11:12379,http://127.0.0.1:12379"
max-snapshots: 3
max-wals: 5
cors:
initial-advertise-peer-urls: "http://192.168.28.11:12380"
advertise-client-urls: "http://192.168.28.11:12379"
discovery:
discovery-fallback: 'proxy'
discovery-proxy:
discovery-srv:
initial-cluster: "pg-etcd01=http://k8s-etcd01:12380,pg-etcd02=http://k8s-etcd02:12380,pg-etcd03=http://k8s-etcd03:12380"
initial-cluster-token: 'etcd-cluster-pg'
initial-cluster-state: 'new'
strict-reconfig-check: false
enable-v2: true
enable-pprof: true
proxy: 'off'
proxy-failure-wait: 5000
proxy-refresh-interval: 30000
proxy-dial-timeout: 1000
proxy-write-timeout: 5000
proxy-read-timeout: 0


#节点2配置文件
name: pg-etcd02
data-dir: /data/etcd

snapshot-count: 5000
#选举和心跳参数
heartbeat-interval: 100
election-timeout: 1000
#存储新能优化
quota-backend-bytes: 8589934592
max-request-bytes: 10485760
max-concurrent-requests: 5000
#自动压缩与碎片整理
auto-compaction-mode: periodic
auto-compaction-retention: "2h"
#集群通信配置
listen-peer-urls: "http://192.168.28.12:12380"
listen-client-urls: "http://192.168.28.12:12379,http://127.0.0.1:12379"
max-snapshots: 3
max-wals: 5
cors:
initial-advertise-peer-urls: "http://192.168.28.12:12380"
advertise-client-urls: "http://192.168.28.12:12379"
discovery:
discovery-fallback: 'proxy'
discovery-proxy:
discovery-srv:
initial-cluster: "pg-etcd01=http://k8s-etcd01:12380,pg-etcd02=http://k8s-etcd02:12380,pg-etcd03=http://k8s-etcd03:12380"
initial-cluster-token: 'etcd-cluster-pg'
initial-cluster-state: 'new'
strict-reconfig-check: false
enable-v2: true
enable-pprof: true
proxy: 'off'
proxy-failure-wait: 5000
proxy-refresh-interval: 30000
proxy-dial-timeout: 1000
proxy-write-timeout: 5000
proxy-read-timeout: 0


#节点3配置文件
name: pg-etcd03
data-dir: /data/etcd
snapshot-count: 5000
#选举和心跳参数
heartbeat-interval: 100
election-timeout: 1000
#存储新能优化
quota-backend-bytes: 8589934592
max-request-bytes: 10485760
max-concurrent-requests: 5000
#自动压缩与碎片整理
auto-compaction-mode: periodic
auto-compaction-retention: "2h"
#集群通信配置
listen-peer-urls: "http://192.168.28.13:12380"
listen-client-urls: "http://192.168.28.13:12379,http://127.0.0.1:12379"
max-snapshots: 3
max-wals: 5
cors:
initial-advertise-peer-urls: "http://192.168.28.13:12380"
advertise-client-urls: "http://192.168.28.13:12379"
discovery:
discovery-fallback: 'proxy'
discovery-proxy:
discovery-srv:
initial-cluster: "pg-etcd01=http://k8s-etcd01:12380,pg-etcd02=http://k8s-etcd02:12380,pg-etcd03=http://k8s-etcd03:12380"
initial-cluster-token: 'etcd-cluster-pg'
initial-cluster-state: 'new'
strict-reconfig-check: false
enable-v2: true
enable-pprof: true
proxy: 'off'
proxy-failure-wait: 5000
proxy-refresh-interval: 30000
proxy-dial-timeout: 1000
proxy-write-timeout: 5000
proxy-read-timeout: 0
创建 etcd 服务
vim /etc/systemd/system/etcd-pg.service 
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
ExecStart=/usr/local/bin/etcd --config-file=/etc/etcd/etcd-pg.config.yml
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

启动 etcd-pg 服务

systemctl daemon-reload
systemctl start etcd-pg.service
systemctl enable etcd-pg.service
检查 etcd 集群健康状态
root@k8s-master01:~# etcdctl --endpoints="k8s-etcd01:12379,k8s-etcd02:12379,k8s-etcd03:12379" member list  -w=table

+------------------+---------+-----------+-------------------------+----------------------------+------------+
|        ID        | STATUS  |   NAME    |       PEER ADDRS        |        CLIENT ADDRS        | IS LEARNER |
+------------------+---------+-----------+-------------------------+----------------------------+------------+
| 2bb79737c88dd84d | started | pg-etcd03 | http://k8s-etcd03:12380 | http://192.168.28.13:12379 |      false |
| 354b7a6aa8551f4a | started | pg-etcd02 | http://k8s-etcd02:12380 | http://192.168.28.12:12379 |      false |
| 84101e54de967367 | started | pg-etcd01 | http://k8s-etcd01:12380 | http://192.168.28.11:12379

为了简化命令,可以通过 alisa 配置

cd ~
vim .profile
alias  etcdctlpg="etcdctl --endpoints="k8s-etcd01:12379,k8s-etcd02:12379,k8s-etcd03:12379" " source .profile

root@k8s-master01:~# etcdctlpg member list -w=table
+------------------+---------+-----------+-------------------------+----------------------------+------------+
|        ID        | STATUS  |   NAME    |       PEER ADDRS        |        CLIENT ADDRS        | IS LEARNER |
+------------------+---------+-----------+-------------------------+----------------------------+------------+
| 2bb79737c88dd84d | started | pg-etcd03 | http://k8s-etcd03:12380 | http://192.168.28.13:12379 |      false |
| 354b7a6aa8551f4a | started | pg-etcd02 | http://k8s-etcd02:12380 | http://192.168.28.12:12379 |      false |
| 84101e54de967367 | started | pg-etcd01 | http://k8s-etcd01:12380 | http://192.168.28.11:12379 |      false |

Etcd 可视化工具

安装 watchdog (所有节点)

watchdog 防止脑裂。Patroni 支持通过 Linux 的 watchdog 监视 patroni 进程的运行,当 patroni 进程无法正常往 watchdog 设备写入心跳时,由 watchdog 触发 Linux 重启。

# 安装软件,linux内置功能 sudo apt install -y watchdog
# 初始化watchdog字符设备 sudo modprobe softdog
# 修改/dev/watchdog设备权限 sudo chmod 666 /dev/watchdog
sudo chown postgres:postgres /dev/watchdog
# 启动watchdog服务 sudo systemctl start watchdog
sudo systemctl enable watchdog


安装 Patroni (所有节点)

安装
1. pip3 install --break-system-packages  psycopg2-binary
2. pip3 install --break-system-packages  patroni[etcd]
3. pip3 install --break-system-packages python-json-logger   
4. mkdir -p /etc/patroni 
创建配置文件
创建 Patroni 服务
cat /etc/systemd/system/patroni-5433.service 
[Unit]
Description=Patroni high-availability PostgreSQL
After=syslog.target network.target etcd.service
Requires=etcd-pg.service

[Service]
Type=simple
User=postgres
Group=postgres
# 使用watchdog进行服务监控 ExecStartPre=-/usr/bin/sudo /sbin/modprobe softdog ExecStartPre=-/usr/bin/sudo /bin/chown postgres /dev/watchdog
PermissionsStartOnly=true
WorkingDirectory=/home/postgres/
ExecStart=/usr/local/bin/patroni /etc/patroni/patroni-5433.yaml
ExecReload=/bin/kill -HUP 
KillMode=process
Restart=always
RestartSec=10
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

systemctl daemon-reload
配置免密登录
su postgres
cd ~
#备库从主库同步WAL日志使用,主备倒换后,主库降备库,新备库使用,所以备库也配置
touch .pgpass
vim .pgpass
192.168.28.11:5433:*:replica:replica 192.168.28.12:5433:*:replica:replica 192.168.28.13:5433:*:replica:replica 
创建 日志目录
sudo mkdir -p /var/log/patroni/
sudo chown -R postgres:postgres /var/log/patroni/
启动 Patroni

根据节点依次启动 Patroni

sudo systemctl daemon-reload
sudo systemctl restart patroni-5433.service
sudo systemctl enable patroni-5433.service
sudo systemctl status patroni-5433.service

查看服务状态,默认情况下,根据配置文件中,initdb 内容,Patroni 会自动对数据库进行初始化操作,并创建用户,配置文件,拉起数据库并建立主从关系及流复制

查看状态
root@k8s-master01:~# patronictl  -c /etc/patroni/patroni-5433.yaml list
+ Cluster: pg_patroni_etcd () ---+-----------+----+-----------+ | Member | Host               | Role    | State     | TL | Lag in MB | + | pg_patroni_5433_01 | 192.168.28.11:5433 | Leader  | running | 17 | | | pg_patroni_5433_02 | 192.168.28.12:5433 | Replica | streaming | 17 | 0 | | pg_patroni_5433_03 | 192.168.28.13:5433 | Replica | streaming | 17 | 0 | + 

通过 alisa 设置简易命令

alias  patr5433="patronictl  -c /etc/patroni/patroni-5433.yaml" 

patroni 维护命令 (所有节点)

列出节点信息
patronictl -c /etc/patroni/patroni-5433.yaml list
重做备库

reinit 先是移除了整个 data 目录。然后选择正确的节点进行备份恢复。

patronictl -c /etc/patroni/patroni-5433.yaml reinit [nodename]
查看配置
patronictl -c /etc/patroni/patroni-5433.yaml show-config
更改参数
patronictl -c /etc/patroni/patroni-5433.yaml edit-config
#重载参数
patronictl -c /etc/patroni/patroni-5433.yaml reload [nodename]
重启节点 / 关闭节点
  1. 仅重启当前节点
patronictl -c /etc/patroni/patroni-5433.yaml restart [clustername] [nodename] 
  1. 如果节点是 pending 状态的,才会执行重启操作
patronictl -c /etc/patroni/patroni-5433.yaml restart [clustername] --pending 
  1. 重启所有成员
patronictl -c /etc/patroni/patroni-5433.yaml restart [clustername]
维护模式 脱离 patroni 的集群管理
patronictl pause

patronictl pause 暂时将 Patroni 集群置于维护模式并禁用自动
在某些情况下,Patroni 需要暂时退出集群管理,同时仍然在 DCS 中保留集群状态。可能的用例是集群上不常见的活动,例如主要版本升级或损坏恢复。在这些活动期间,节点经常因为 Patroni 不知道的原因而启动和停止,有些节点甚至可以暂时提升,这违反了只运行一个主节点的假设。因此,Patroni 需要能够与正在运行的集群 “分离”,在 Pacemaker 中实现与维护模式相当的功能。

patronictl resume

patronictl resume 将使 Patroni 集群退出维护模式,并重新启用自动故障转移。
自动拉起所有数据库

switchover 主备切换
patronictl switchover
# Switchover
root@k8s-master01:~# patr5433   switchover
Current cluster topology
+ Cluster: pg_patroni_etcd (7540584003720074567) ---+-----------+----+-----------+
| Member             | Host               | Role    | State     | TL | Lag in MB |
+--------------------+--------------------+---------+-----------+----+-----------+
| pg_patroni_5433_01 | 192.168.28.11:5433 | Leader  | running   | 22 |           |
| pg_patroni_5433_02 | 192.168.28.12:5433 | Replica | streaming | 22 |         0 |
| pg_patroni_5433_03 | 192.168.28.13:5433 | Replica | streaming | 22 |         0 |
+--------------------+--------------------+---------+-----------+----+-----------+
Primary [pg_patroni_5433_01]: 
Candidate ['pg_patroni_5433_02', 'pg_patroni_5433_03'] []: pg_patroni_5433_02
When should the switchover take place (e.g. 2025-08-26T12:26 )  [now]: now
Are you sure you want to switchover cluster pg_patroni_etcd, demoting current leader pg_patroni_5433_01? [y/N]: y 
2025-08-26 11:26:32.31189 Successfully switched over to "pg_patroni_5433_02"
+ Cluster: pg_patroni_etcd (7540584003720074567) ---+---------+----+-----------+
| Member             | Host               | Role    | State   | TL | Lag in MB |
+--------------------+--------------------+---------+---------+----+-----------+
| pg_patroni_5433_01 | 192.168.28.11:5433 | Replica | stopped |    |   unknown |
| pg_patroni_5433_02 | 192.168.28.12:5433 | Leader  | running | 22 |           |
| pg_patroni_5433_03 | 192.168.28.13:5433 | Replica | running | 22 |         0 |
+--------------------+--------------------+---------+---------+----+-----------+
root@k8s-master01:~# patr5433   list
+ Cluster: pg_patroni_etcd (7540584003720074567) ---+-----------+----+-----------+
| Member             | Host               | Role    | State     | TL | Lag in MB |
+--------------------+--------------------+---------+-----------+----+-----------+
| pg_patroni_5433_01 | 192.168.28.11:5433 | Replica | streaming | 23 |         0 |
| pg_patroni_5433_02 | 192.168.28.12:5433 | Leader  | running   | 23 |           |
| pg_patroni_5433_03 | 192.168.28.13:5433 | Replica | streaming | 23 |         0 |
+--------------------+--------------------+---------+-----------+----+-----------+
接口切换
 数据库从 pg_patroni_5433_01 switchover 到 pg_patroni_5433_02
[root@pgtest1 ~]# curl -s http://192.168.28.11:8008/switchover -XPOST -d '{"leader":"pg_patroni_5433_01","candidate":"pg_patroni_5433_02"}'
Successfully switched over to "pg_patroni_5433_02" 
failover 切换

patronictl failover

# Failover
[postgres@pgtest1 ~]$ patronictl -c /etc/patroni/patroni-5433.yaml  failover
Candidate ['pg_patroni_5433_01', 'pg_patroni_5433_02','pg_patroni_5433_03'] []: pg_patroni_5433_01
Current cluster topology
... ...
Are you sure you want to failover cluster pg_cluster, demoting current master pg_patroni_5433_02? [y/N]: y
2021-10-28 03:47:56.13486 Successfully failed over to "pg_patroni_5433_01"
... ...

获取主节点 dsn 信息
root@k8s-master01:~# patronictl -c /etc/patroni/patroni-5433.yaml  dsn
host=192.168.28.12 port=5433 

安装 Haproxy (所有节点)

安装 Haproxy
sudo apt install haproxy
编辑 Haproxy 配置文件
sudo vim /etc/haproxy/haproxy.cfg 

global
 maxconn 2000
 ulimit-n 16384
 log 127.0.0.1 local0 err
 stats timeout 30s

defaults
 log global
 mode http
 option httplog
 timeout connect 5000
 timeout client 50000
 timeout server 50000
 timeout http-request 15s
 timeout http-keep-alive 15s

listen status_page
    bind *:8888
    stats enable
    stats uri /haproxy-status
    stats auth    admin:admin
    stats realm "Welcome to the haproxy load balancer status page of k8s-master"

frontend monitor-in
 bind *:33305
 mode http
 option httplog
 monitor-uri /monitor
# 主库读写端口
listen master
    bind *:15433
    mode tcp
    option tcplog
    balance roundrobin
    option httpchk OPTIONS /master
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server pgtest1 192.168.28.11:5433 maxconn 1000 check port 8008 inter 5000 rise 2 fall 2
    server pgtest2 192.168.28.12:5433 maxconn 1000 check port 8008 inter 5000 rise 2 fall 2
    server pgtest3 192.168.28.13:5433 maxconn 1000 check port 8008 inter 5000 rise 2 fall 2

#从库读端口
listen replicas
    bind *:25433
    mode tcp
    option tcplog
    balance roundrobin
    option httpchk OPTIONS /replica
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server pgtest1 192.168.28.11:5433 maxconn 1000 check port 8008 inter 5000 rise 2 fall 2
    server pgtest2 192.168.28.12:5433 maxconn 1000 check port 8008 inter 5000 rise 2 fall 2
    server pgtest3 192.168.28.13:5433 maxconn 1000 check port 8008 inter 5000 rise 2 fall 2
启动 haproxy
sudo systemctl enable haproxy
sudo systemctl start haproxy
HAProxy 监控页面

登录地址:[[http://192.168.28.11:8888/haproxy-status]] (也可以通过各个节点 IP + 端口登录)

默认用户密码:admin/admin

安装 Keepalived (所有节点)

安装
sudo apt install -y keepalived
配置文件
主服务配置文件
vim /etc/keepalived/keepalived.conf

global_defs {

router_
id LVS_DEVEL00

script_
user root enable_script_security } vrrp_script check_haproxy { script "/etc/keepalived/check_haproxy.sh" interval 2 weight 5 fall 3 rise 5 timeout 2 } vrrp_instance VI_1 { state Master interface ens18 virtual_router_id 80 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 12345 } virtual_ipaddress { 192.168.28.10/24 } track_script { check_haproxy } }
备库节点 1 配置文件
vim /etc/keepalived/keepalived.conf
global_defs {

router_
id LVS_DEVEL01

script_
user root enable_script_security } vrrp_script check_haproxy { script "/etc/keepalived/check_haproxy.sh" interval 2 weight 5 fall 3 rise 5 timeout 2 } vrrp_instance VI_1 { state BACKUP interface ens18 virtual_router_id 80 priority 90 advert_int 1 authentication { auth_type PASS auth_pass 12345 } virtual_ipaddress { 192.168.28.10/24 } track_script {

check_
haproxy } }
备库节点 2 配置文件
vim /etc/keepalived/keepalived.conf

global_defs {

router_
id LVS_DEVEL02

script_
user root enable_script_security } vrrp_script check_haproxy { script "/etc/keepalived/check_haproxy.sh" interval 2 weight 5 fall 3 rise 5 timeout 2 } vrrp_instance VI_1 { state BACKUP interface ens18 virtual_router_id 80 priority 80 advert_int 1 authentication { auth_type PASS auth_pass 12345 } virtual_ipaddress { 192.168.24.15/24 } track_script {

check_
haproxy } }
检查脚本
vim /etc/keepalived/check_haproxy.sh

#!/bin/bash
count=`ps aux | grep -v grep | grep haproxy | wc -l`
if [ $count -eq 0 ]; then exit 1
else exit 0
fi 

赋予执行权限

chmod +x /etc/keepalived/check_haproxy.sh
依次启动
sudo systemctl start keepalived
sudo systemctl enable keepalived

📌 转载信息
原作者:
xxdtb
转载时间:
2026/1/19 17:52:08

感谢 @elky 大佬开源此项目

大佬沉迷 coding ,更新了也不发贴

我近期也是疯狂的给大佬提需求 (偶尔提交几个 PR

更新说明: 本次更新距离上次版本已有较长时间,带来了多项功能新增和改进

1. 中转站 & 公益站余额监控

  • 支持通过 New-API 接口获取账户余额信息

2. 模型正则映射

  • 通过正则表达式自动匹配和映射模型别名,无需手动逐个修改模型配置

3. 模块管理与 OAuth 登录

  • 模块化系统架构,支持功能模块的独立管理
  • 集成 OAuth 2.0 协议,支持第三方平台登录认证 (目前只支持 Linux Do)

4. 端点密钥分离与智能分配

  • 密钥与端点解耦,单个密钥支持配置多个端点
  • 支持自动获取模型功能

5. 请求详情对话视图

  • 请求体和响应体以对话形式展示
  • 优化了调试信息的可读性

后续计划

  • 支持上游 New-api 签到
  • 增加中转站的 周 / 天 / 小时 等限制
  • 工单系统,用于额度申请等
  • 基于提供商模型列表的健康监控,用于展示每个提供商模型的健康度
  • Chat / 生图 / 操练场

📌 转载信息
原作者:
AAEE86
转载时间:
2026/1/19 17:46:21

pgBackRest
简介

pgBackRest 旨在提供一个简单可靠,容易纵向扩展的 PostgreSQL 备份恢复系统。pgBackRest 并不依赖像 tar 和 rsync 这样的传统备份工具,而是通过在内部实现所有备份功能,并使用自定义协议来与远程系统进行通信。 消除对 tar 和 rsync 的依赖可以更好地解决特定于数据库的备份问题。 自定义远程协议提供了更多的灵活性,并限制执行备份所需的连接类型,从而提高安全性。

相关网站

pgBackRest 主页:http://pgbackrest.org
手册:pgBackRest User Guide - RHEL
pgBackRest
Github 主页:GitHub - pgbackrest/pgbackrest: Reliable PostgreSQL Backup & Restore

pgbackRest 特征
  • 并行备份和还原
  • 本地或远程操作
  • 完整,增量和差异备份
  • 备份轮换和存档到期
  • 备份完整性
  • 页面校验和
  • 备份恢复
  • 流压缩和校验和
  • 增量还原
  • 并行,异步 WAL Push&Get
  • 表空间和链接支持
  • S3、Azure 和 GCS 兼容对象存储支持
  • 加密
  • 与 PostgreSQL > = 8.3 的兼容性
pgbackRest 安装
ip软件角色
192.168.1.11postgres,pgbackrestprimary
192.168.1.12postgres,pgbackreststandby
192.168.1.13postgres,standby
192.168.1.16pgbackrest远程备份工具端

ubuntu (所有节点) :

sudo apt-get install pgbackrest
创建所需目录并赋予权限
su - root
mkdir -p -m 770 /var/log/pgbackrest
chown postgres.postgres /var/log/pgbackrest/
chown postgres.postgres -R /etc/pgbackrest/
主从 pgbackrest 配置文件
su postgres

vim /etc/pgbackrest.conf

[pg_5433]
pg1-port=5433
pg1-path=/data/postgresql/pgdata/pg-5433/
pg1-socket-path=/var/run/postgresql/

[global]
repo1-host=192.168.28.14
repo1-host-user=postgres
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
process-max=3

[global:archive-push]
compress-level=3 

主库参数解释:

[pg_5433] 部分
这个 pg_5433 是备份集群的名字,多个备份集群可以添加 比如:[test-1],[test-2] 等等
pg1-path=/data/postgresql/pgdata/pg-5433/: 指定 PostgreSQL 数据库实例的数据目录路径。这是 pgBackRest 需要备份的主要内容所在的位置。
pg1-socket-path=/var/run/postgresql/: 指定 PostgreSQL 服务器的套接字文件(socket file)路径。pgBackRest 使用这个路径来通过 UNIX 套接字连接到数据库。
pg1-user=postgres: 定义 pgBackRest 连接到 PostgreSQL 实例时应该使用的用户名。这个用户需要有足够的权限来读取数据库文件和执行备份相关的操作。
[global] 部分
这部分的配置适用于 pgBackRest 的全局设置,影响所有备份和恢复操作。
repo1-host=192.168.28.14: 指定远程备份仓库的主机地址。这表明备份数据将被存储在指定 IP 地址的服务器上。pgBackRest 支持多个备份仓库,这里的 repo1 表示第一个仓库。
repo1-host-user=postgres: 定义访问远程备份仓库主机时使用的用户名。postgres 将以这个用户的身份在远程主机上执行操作。
log-level-file=detail: 设置文件日志记录的详细级别。detail 级别会记录更详细的操作信息,有助于故障排查和监控备份过程。
log-path=/var/log/pgbackrest: 指定日志文件的存储路径。pgBackRest 会将运行日志写入这个目录下,便于后续的日志分析和问题定位。

远程备份工具端配置文件
su postgres
vim /etc/pgbackrest.conf

[pg_5433]
pg1-path=/data/postgresql/pgdata/pg-5433/
pg1-port=5433
pg1-socket-path=/var/run/postgresql/
pg1-host-config=/etc/pgbackrest.conf
pg1-user=postgres
pg1-host=192.168.28.11
pg1-host-port=22
pg1-host-user=postgres

pg2-path=/data/postgresql/pgdata/pg-5433/
pg2-port=5433
pg2-socket-path=/var/run/postgresql/
pg2-host-config=/etc/pgbackrest.conf
pg2-user=postgres
pg2-host=192.168.28.12
pg2-host-port=22
pg2-host-user=postgres

pg3-path=/data/postgresql/pgdata/pg-5433/
pg3-port=5433
pg3-socket-path=/var/run/postgresql/
pg3-host-config=/etc/pgbackrest.conf
pg3-user=postgres
pg3-host=192.168.28.13
pg3-host-port=22
pg3-host-user=postgres

[global]
backup-standby=y
process-max=3
start-fast=y
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
backup-user=postgres
log-level-console=info
log-level-file=debug
buffer-size=16MiB
compress-type=gz 

[global:archive-push]
compress-level=3 

工具端参数解释

[pg_5433]
这个 pg_5433 是备份集群的名字,多个备份集群可以添加 比如:[test-1],[test-2] 等等
pg1-path :指定了数据库实例的数据目录路径。
pg1-port :定义了实例的端口号,通常 PostgreSQL 默认端口是 5432。
pg1-socket-path:指定了 UNIX 套接字文件的路径,用于本地连接。
pg1-user :定义 pgBackRest 连接到各个 PostgreSQL 实例时使用的用户名。
pg1-host-config-path :指定远程主机上 pgBackRest 配置文件的路径。
pg1-host:定义了各实例所在的主机地址。
pg1-host-port :指定了用于 SSH 连接的端口号,默认为 22。
pg1-host-user:定义了 SSH 连接时使用的用户名。

[global] 部分
这部分定义了全局备份策略和行为。
backup-standby=y:启用备份从备用服务器进行,这有助于减少对生产数据库的性能影响。
process-max=3:定义了 pgBackRest 同时执行任务的最大进程数。
start-fast=y:启用快速启动模式,尝试减少备份期间的停机时间。
repo1-path:指定了备份仓库的路径。
repo1-retention-full=2:定义了完整备份的保留数量,超过这个数量的旧备份将被删除。
backup-user:指定执行备份操作的用户。
log-level-console:设置控制台日志级别。
log-level-file:设置文件日志的详细级别。
buffer-size:定义了缓冲区大小,用于优化性能。
compress-type=gz:指定了压缩类型,这里使用的是 gzip。
[global:archive-push] 部分
这部分专门用于配置归档推送操作。

compress-level=3:定义了压缩级别,数值越高,压缩效果越好,但需要更多的 CPU 资源。
ssh 免密登陆配置

在各个服务器上,生成 postgres 的 ssh 密钥

su postgres
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N "" 

在主从服务器上 postgres 用户 配置备份服务器 postgres 的用户公钥
登录备份服务器 192.168.28.14

su postgres
cd ~
cat ./.ssh/id_rsa.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDnvcOPSQgi2zKWqNHsFjKC0zp4X5+yG1eNf5fEr25r2+NlBGMRrBFh2pONh2pWSLiglbhOZA5Pr1ILpllwP34eiGNjxTp0ys0U1YjnOuvgY7iwwR+xkXJmywDb0g0ALSEi3TS0lu5z3u4mBcW03q4m/oS++Fi+7ieDinyQAZOXXOyvj8k7g7/NiUXzONN83do/+KC5htVm9Q77A2DrDmZWQGbypMKQYPY66RjcWvApPOVYbrUxHlndq3fU4IhHPOVwiAdpHm5bh8dyb9k1FWcIS9sxLVm4KsUbt99VeDC8ri7iglMKen+gcktIyo80rGRoIdzJrD6JPP8cTlhpTwV/uW42kWgS9lZ8I/Ahk7lWoDdiF/pVNkMiiTOgZ2/YGV88CE0khpOtRl3nPHFlUZHi1QLdfH9omI0FZWeLYAuQbKWBGZ8GgfAweKjEtMy/J43NO5qGK6JZ0KB2ve03JowCGbW65cmTuPQgz3Hwo5I0fv3YEy88LK9nVnLub44zunGqJ4JBAc2H/WrmSqLYtLtljo/5EuKmc34SS75WimY9wh1nTmhVPODuLzurXjz28zx245tkcLeImbn4C8Gge4I7TgtPj8VkWTXC6WlrTTLjebLuMjYR3qFfuGqfD2vuLEHU4CBGHAnpDCG51v96gBpw+m9Cman6f9KvA3iZRBOXHw== postgres@k8s-node01

在主从服务器上添加备份服务器的公钥

root@k8s-master01:~# su postgres
postgres@k8s-master01:/root$cd ~     
postgres@k8s-master01:~$vim ./.ssh/authorized_keys

同样的在备份服务器的 postgres 的用户上配置主从服务器上 postgres 用户的公钥

设置 postgresql 归档

在主库上修改 archive 归档

patronictl -c /etc/patroni/patroni-5433.yaml edit-config archive_mode = on archive_command = 'pgbackrest --stanza=pg_5433 archive-push %p'
max_wal_senders = 3 

stanza 名需要和配置文件一致
修改完之后,通过 patronictl 重启所有数据库

patronictl -c /etc/patroni/patroni-5433.yaml restart pg_patroni_etcd pg_patroni_5433_01
patronictl -c /etc/patroni/patroni-5433.yaml restart pg_patroni_etcd pg_patroni_5433_02
patronictl -c /etc/patroni/patroni-5433.yaml restart pg_patroni_etcd pg_patroni_5433_03
初始化备份

在备份服务器上执行

su postgres
pgbackrest --stanza=pg_5433 --log-level-console=info stanza-create

#删除方式
pgbackrest --stanza=pg_5433 stop
pgbackrest --stanza=pg_5433 stanza-delete --force 
检查配置
pgbackrest --stanza=pg_5433 --log-level-console=info check

#执行结果如下
postgres@k8s-node01:~$ pgbackrest --stanza=pg_5433  --log-level-console=info check
2025-10-27 15:40:57.655 P00   INFO: check command begin 2.50: --backup-standby --buffer-size=16MiB --exec-id=3943474-ea8cda56 --log-level-console=info --log-level-file=debug --pg1-host=192.168.28.25 --pg1-host-config=/etc/pgbackrest.conf --pg1-host-port=22 --pg1-host-user=postgres --pg1-path=/data/postgresql/pgdata/pg-5433/ --pg1-port=5433 --pg1-user=postgres --repo1-path=/var/lib/pgbackrest --stanza=pg_5433
WARN: option 'backup-standby' is enabled but standby is not properly configured
2025-10-27 15:41:00.980 P00   INFO: check repo1 configuration (primary)
2025-10-27 15:41:01.387 P00   INFO: check repo1 archive for WAL (primary)
2025-10-27 15:41:06.703 P00   INFO: WAL segment 000000230000000000000059 successfully archived to '/var/lib/pgbackrest/archive/pg_5433/16-1/0000002300000000/000000230000000000000059-0c80b7903193612ee31642ac12c2548bba36bc1b.gz' on repo1
2025-10-27 15:41:06.806 P00   INFO: check command end: completed successfully (9159ms)

完全备份
在备份机上操作即可
pgbackrest --stanza=pg_5433 --log-level-console=info backup --type=full
增量备份
pgbackrest pgbackrest --stanza=pg_5433 --log-level-console=info backup --type=incr
差异备份
pgbackrest pgbackrest --stanza=pg_5433 --log-level-console=info backup --type=diff
查看备份信息
备份恢复

这个恢复操作的一台新的 postgresql 主机上操作,并配置好 pgbackrest.conf 文件 和 备份机可以免密登陆 ssh 登陆

 # 全量恢复
$ pgbackrest --stanza=pg_5433 restore --pg1-path=/data/postgresql/pgdata/pg-5433/
 
 
 
# 指定某个备份恢复
pgbackrest --stanza=pg_5433 --set=20251027-134949F restore
 
# 基于lsn恢复 # 指定备份策略,获取对应的lsn:lsn start/stop
pgbackrest --stanza=pg_5433 --set=20250713-195747F_20250713-195909I info
pgbackrest --stanza=pg_5433 --type=lsn --target="0/41000028" restore
 
# 基于时间点恢复

pgbackrest --stanza=pg_5433 --delta --type=time "--target=2025-10-27 14:11:02+08" restore

常用命令
# 创建存储库
pgbackrest --stanza=pg_5433 stanza-create
# 删除存储库
pgbackrest --stanza=pg_5433 stanza-delete
# 更新存储库
pgbackrest --stanza=pg_5433 stanza-upgrade
 
# 启用备份
pgbackrest --stanza=pg_5433 start
# 停用备份
pgbackrest --stanza=pg_5433 stop
 
# 备份数据
pgbackrest --stanza=pg_5433 backup
# 恢复备份
pgbackrest --stanza=pg_5433 restore
# 查看备份
pgbackrest --stanza=pg_5433 info
 
# 检查备份是否过期
pgbackrest expire --stanza=pg_5433
pgbackrest expire --set=20250713-195747F_20250713-195909I --stanza=demo
 
# 检查配置
pgbackrest --stanza=pg_5433 check
 
# 获取存储库信息 # 疑似有bug,执行报错:ERROR: [032]: unable to determine cipher passphrase for ''
pgbackrest --stanza=pg_5433 --config=/pg14/pgbackrest/etc/pgbackrest.conf repo-get /pg14/pgbackrest/lib
pgbackrest_exporter 监控备份状态

一般我们可以通过 pgbackrest info 去查看备份状态,但是这样并不直观
所以可以借助 exporter 获取 pgbackrest 的状态,从而及时监控到备份信息
安装 pgbackrest_exporter
pgbackrest_exporter 支持二进制运行,同时也支持 docker 运行,此处用二进制服务运行的方式
下载对应服务器版本的二进制包

wget https://ghfast.top/https://github.com/woblerr/pgbackrest_exporter/releases/download/v0.21.0/pgbackrest_exporter-0.21.0-linux-x86_64.tar.gz
tar -zxvf pgbackrest_exporter-0.21.0-linux-x86_64.tar.gz
mv pgbackrest_exporter-0.21.0-linux-x86_64/pgbackrest_exporter /usr/local/bin/pgbackrest_exporter
chown postgres:postgres /usr/local/bin/pgbackrest_exporter

创建 pgbackrest_exporter.service

touch /usr/lib/systemd/system/pgbackrest-exporter.service 
vim /usr/lib/systemd/system/pgbackrest-exporter.service
[Unit]
Description=pgbackrest-exporter Service
After=network.target
[Service]
Type=simple
User=postgres
ExecStart=/usr/local/bin/pgbackrest_exporter --backrest.config=/etc/pgbackrest.conf
Restart=on-failure
RestartSec=10
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Alias=pgbackrest-exporter.service                                 

启动 pgbackrest_exporter.service

systemctl daemon-reload 
systemctl start pgbackrest-exporter.service
systemctl enable pgbackrest-exporter.service

通过 grafana 展示页面
grafana id:17709

ON_Backs 通知 脚本

#!/usr/bin/env python3 """
Patroni钉钉通知脚本 - 增强诊断和错误修复版
主要解决:日志显示发送成功但实际未收到消息的问题
作者:资深SRE/数据库高可用架构师
"""
import os import string import subprocess import sys import json import logging import requests import socket import traceback from datetime import datetime import time # 新增时间模块用于重试 import patroni # 钉钉Webhook配置 - 添加详细的验证逻辑 DINGTALK_WEBHOOK = 'https://oapi.dingtalk.com/robot/send?access_token=********' if not DINGTALK_WEBHOOK: print("错误:钉钉Webhook环境变量未设置,请配置 DINGTALK_WEBHOOK_URL", file=sys.stderr) sys.exit(1) # 高级日志配置 - 确保诊断信息完整 logger = logging.getLogger('patroni-dingtalk') logger.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s') # 文件日志 file_handler = logging.FileHandler("/var/log/patroni/dingtalk_diagnostic.log") file_handler.setFormatter(formatter) logger.addHandler(file_handler) # 控制台日志 stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) logger.addHandler(stdout_handler) def verify_dingtalk_network(): """验证网络连通性到钉钉服务器""" target_host = "oapi.dingtalk.com" target_port = 443 try: # 创建socket连接检查 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(3) result = sock.connect_ex((target_host, target_port)) sock.close() if result == 0: logger.info(f"网络连通性检查: 可访问 {target_host}:{target_port}") return True else: logger.error(f"无法连接钉钉服务器 {target_host}:{target_port}, 错误代码: {result}") return False except socket.gaierror: logger.error("DNS解析故障 - 无法解析钉钉服务器地址") return False except Exception as e: logger.exception(f"网络检查异常: {str(e)}") return False def send_with_retry(message_data): """带重试机制的消息发送函数 - 解决偶发网络问题""" headers = {'Content-Type': 'application/json'} max_retries = 3 retry_delay = 2 # seconds for attempt in range(max_retries): try: # 详细记录实际发送的内容 logger.debug( f"发送请求 (尝试 #{attempt + 1}):\nURL: {DINGTALK_WEBHOOK}\nBody: {json.dumps(message_data, indent=2)}") response = requests.post( DINGTALK_WEBHOOK, json=message_data, headers=headers, timeout=(3, 5) # 连接超时3秒,读取超时5秒 ) # 详细记录响应信息 logger.debug(f"钉钉响应状态码: {response.status_code}") logger.debug(f"钉钉响应体: {response.text[:500]}...") # 只记录前500字符 # 钉钉成功响应格式: {"errcode":0,"errmsg":"ok"} if response.status_code == 200: json_response = response.json() if json_response.get('errcode') == 0: logger.info(f"钉钉API确认消息已发送! (尝试#{attempt + 1})") return True else: # 钉钉API业务错误 logger.error(f"钉钉API错误: [{json_response.get('errcode')}] {json_response.get('errmsg')}") # 特殊处理常见错误码 if json_response.get('errcode') == 130101: logger.error("常见原因: 钉钉机器人关键词不匹配 - 检查消息中是否包含设置的keyword") elif json_response.get('errcode') in [310000, 310001]: logger.error("常见原因: 被钉钉限流 - 请降低通知频率或添加特殊关键词") else: logger.warning(f"HTTP错误码: {response.status_code}") except requests.exceptions.RequestException as e: logger.exception(f"网络请求异常 (尝试 #{attempt + 1}): {str(e)}") except json.JSONDecodeError: logger.error(f"响应JSON解析失败: {response.text[:200]}") # 如果不是最后一次尝试则延时重试 if attempt < max_retries - 1: logger.info(f"{retry_delay}秒后将重试...") time.sleep(retry_delay) return False def parse_event_args(): """解析Patroni回调参数-增强容错""" if len(sys.argv) < 4: logger.error(f"错误: 参数不足! 期望至少4个参数,实际收到 {len(sys.argv)}") logger.info(f"完整参数列表: {sys.argv}") return None try: # 解析基础参数 ([0]=脚本路径, [1]=事件类型, [2]=角色, [3]=集群名) PATRONI_LEADER=subprocess.run("/usr/local/bin/patronictl -c /etc/patroni/patroni-5433.yaml dsn -r leader", shell=True, capture_output=True, text=True).stdout.strip() PATRONI_MEBERS=json.loads(subprocess.run("/usr/local/bin/patronictl -c /etc/patroni/patroni-5433.yaml list -f json", shell=True, capture_output=True, text=True).stdout.strip()) PATRONI_MEBERS_HOST=[] for i in PATRONI_MEBERS: PATRONI_MEBERS_HOST.append(i.get('Host')) PATRONI_MEBERS_HOST=str(PATRONI_MEBERS_HOST) len_members=len(PATRONI_MEBERS) leader_num=0 replica_num=0 for i in PATRONI_MEBERS: if i.get('Role')=="Leader": i.get('State')=="running" leader_num+=1 elif i.get('Role')=="Replica": i.get('State')=="streaming" replica_num+=1 else: continue if len_members>=2 and leader_num==1 and replica_num==len_members: PATRONI_HA_STATE='green' elif leader_num==1 and replica_num>=1 and replica_num < len_members: PATRONI_HA_STATE='yellow' else: PATRONI_HA_STATE='red' event_info = { 'script_path': sys.argv[0], 'event_type': sys.argv[1], 'node_role': sys.argv[2], 'cluster_name': sys.argv[3], 'leader': PATRONI_LEADER, 'mebers': PATRONI_MEBERS_HOST, 'len_members': len_members, 'hostname': socket.gethostname(), 'ipaddress': socket.gethostbyname(socket.gethostname()), 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'ha_state': PATRONI_HA_STATE, 'old_role': os.getenv('PATRONI_OLD_ROLE', None) # 角色变更时存在 } logger.debug(f"解析事件成功: {json.dumps(event_info, indent=2)}") return event_info except Exception as e: logger.exception(f"参数解析错误: {str(e)}") return None def build_safe_message(event): """构建安全的消息格式 - 兼容钉钉要求""" if not event: logger.error("无法构建消息: 事件数据为空") return None # 确保包含关键词 (避免钉钉关键词检查失败) # 根据事件类型设置不同的标题和颜色 if event['event_type'] in ['stop', 'failover']: title= f"**<font color={'#FF0000'}>**🔴 Patroni故障事件**</font>**" elif event['event_type'] in ['start', 'promote']: title = f"**<font color={'#008000'}>**🟢 Patroni恢复事件**</font>**" elif event['event_type'] in ['reload', 'restart']: title = f"**<font color={'#FFA500'}>**🟡 Patroni维护事件**</font>**" else: title = f"**<font color={'#0000FF'}>**🔔 Patroni状态变更**</font>**" # 使用最简单的markdown格式确保兼容性 message_content = f"""

### {title}

- **事件类型**: {event['event_type']}

- **集群名称**: {event['cluster_name']}

- **集群当前Leader**: {event['leader']}

- **集群当前成员**: {event['mebers']}

- **集群成员数量**: {len(event['mebers'].split(','))}

- **集群状态**: **<font color={'#008000' if event['ha_state'] == 'green' else '#FFFF00' if event['ha_state'] == 'yellow' else '#FF0000'}>{event['ha_state']}</font>**

- **当前节点角色**: {event['node_role']}

- **当前主机名称**: {event['hostname']}

- **当前主机IP**: {event['ipaddress']}

- **集群状态**: {event['ha_state']}

- **发生时间**: {event['timestamp']}

"""
# 如果是角色变更事件,添加额外信息 if event.get('old_role'): message_content += f"- **变更前角色**: {event['old_role']}\n" # 必须包含关键字"Patroni"两次以上避免误过滤 message_content += "\n> **Patroni数据库高可用系统**" return { "msgtype": "markdown", "markdown": { "title": title, "text": message_content }, "at": { "isAtAll": False # 不@所有人 } } if __name__ == "__main__": logger.info("=" * 60) logger.info("🚦 Patroni钉钉通知脚本启动 | 诊断模式开启") logger.info(f"收到参数: {sys.argv}") logger.info(f"环境变量 WEBHOOK={DINGTALK_WEBHOOK[:20]}...") # 部分展示避免泄露 # 步骤1: 网络连通性验证 if not verify_dingtalk_network(): logger.critical("网络诊断失败! 消息无法发送 - 请检查网络连接或防火墙设置") sys.exit(10) # 步骤2: 解析事件 event_data = parse_event_args() if not event_data: logger.error("无法解析事件数据,消息发送中止") sys.exit(20) # 步骤3: 构建消息(安全格式) message_payload = build_safe_message(event_data) if not message_payload: logger.error("消息体构建失败") sys.exit(30) # 步骤4: 发送消息(带重试) logger.info("尝试发送消息到钉钉...") success = send_with_retry(message_payload) if success: logger.info("✅ 消息发送确认成功") else: logger.error("❌ 消息发送失败 - 请查看上述诊断信息") logger.info("=" * 60) sys.exit(0 if success else 40) # 非0退出码便于外部监控

📌 转载信息
原作者:
xxdtb
转载时间:
2026/1/19 17:45:53

之前看到有佬友共享 token 被人薅了 还是转发下保险 本来是自用的 正好弄了台 azure (线路貌似一般) 顺便测测有什么 bug 目前只会记录错误的请求 不用担心代码泄漏

还是得先装 auggie:

npm install -g @augmentcode/auggie@prerelease 

然后是 cc 添加 mcp 的指令 用别的工具的自行配置下吧 (以下地址已不可用 请用更新 3 中的网站)

claude mcp add-json augment-context-engine-mcp --scope user ‘{“type”:“stdio”,“command”:“auggie”,“args”:[“–mcp”],“env”:{“AUGMENT_API_TOKEN”:“05e8092b03bb0c314bb9f762b2edf4c4”,“AUGMENT_API_URL”:“https://ace-test.heroman.wtf/”}}’

更新:去掉 UA 校验可供第三方使用 增加遗漏的 /checkpoint-blobs 路径
更新 2: 支持佬友魔改的 prompt-enhancer https://linux.do/t/topic/1280612 顺便优化了下转发逻辑
更新 3: 尽量使用新站 https://acemcp.heroman.wtf 旧的可能随时失效 望佬友周知
更新 4: 今天号被狙击了 我开了一个月 plan 了 给佬友们继续用用 旧站已经关了 都用上面的新站吧


📌 转载信息
转载时间:
2026/1/19 17:38:37

那天不小心在 vscode 的 codex 扩展中误删了对话,经过一番折腾终于找回来了,下面是我的操作:

01. 先去找 codex 的全局配置目录:

windows 上默认是:

  • %USERPROFILE%\.codex\
在文件管理器的路径输入框中,直接复制上面的路径,然后粘贴再按回车就可以了

如果是在 linux 上默认是:~/.codex

在里面可以看到两个目录:

  • sessions/:正常会话记录

  • archived_sessions/:被归档的会话记录(会话被删除之后会放到这里)


02. 根据内容找到对话 .jsonl

对话通常是 .jsonl(JSON Lines,一行一个 JSON 事件)

可以看到里面有很多这样的文件:

rollout-2026-01-13T10-28-22-<ID>.jsonl

归档文件很多,随便点击几个进去可以看到一些曾经的聊天记录,如果归档文件多,一个个找不太现实,这时最好用对话里你记得的一句独特文本去搜

我让 ai 帮我写命令找


Get-ChildItem "$env:USERPROFILE\.codex\archived_sessions" -Filter *.jsonl |

Select-String '关键字' -List

命中后,会得到包含这句关键字的.jsonl 文件


03. 恢复对话 sessions

操作前建议先备份一下:

把命中的 .jsonl 复制一份到临时目录(你也可以复制到别的安全地方)

然后把它移回 sessionssessions 往往按年月日分目录,你可以照文件名里的日期建,比如:

$env:USERPROFILE\.codex\sessions\2026\01\13 


04. 重启 VS Code

这时候应该就能看到对话了


05. 一些想法

我想既然对话可以这样恢复,那么应该也可以导出迁移到别的设备上,不过我还没尝试过,有兴趣的小伙伴可以试一下..


📌 转载信息
原作者:
d2wstudy
转载时间:
2026/1/19 17:38:31

背景信息请参考:Chrome 144 下 MCP 自动化配置大幅简化,LLM 可更方便地控制你已登录的浏览器会话了

适用场景

  • WSL(Ubuntu)+ Windows 侧浏览器(Edge/Chrome)。
  • 通过 Codex CLI 使用 chrome-devtools-mcp 连接已打开的浏览器。

配置

替换 <你的用户名> 为真实路径即可。

codex 配置

[mcp_servers.chrome-devtools] args = [
    "-y",
    "chrome-devtools-mcp@latest",
    "--auto-connect",
    '--user-data-dir=/mnt/c/Users/<你的用户名>/AppData/Local/Microsoft/Edge/User Data',
]
command = "npx" type = "stdio" startup_timeout_sec = 120 

如果你用的是 Chrome,把 --user-data-dir 改成:

C:\\Users\\<你的用户名>\\AppData\\Local\\Google\\Chrome\\User Data

使用步骤

  1. 在 Windows 侧先打开 Edge/Chrome(确保与 --user-data-dir 一致)。
  2. 在 Codex CLI 中调用 chrome-devtools 相关工具查看页面列表,确认连接成功。

注意事项

记得去 edge://inspect/#remote-debugging 打开权限,每次连接都需要同意


📌 转载信息
原作者:
Robin
转载时间:
2026/1/19 17:38:22


首先,教育邮箱的话,国内的看学校,有的学校可以有的不行,收不到邮件
国外的邮箱校友邮箱就可以,我的两个都是之前买的校友邮箱,一个美国的,一个英国的,实测都可以。没有的话去淘宝买,我之前买的大概 50-60 一个,完全个人控制的真实校友邮箱,第一个两年了没啥问题,固定 ip 全局隐私窗口登录就行。

其次,如果要五年的,必须美国 ip,支付方式的话选择 Google pay。实测 N26 和 dtcpay 的虚拟卡过不了 1 个月的那个验证,国内的 PayPal 我也没过。如果 1 个月高级的没有 Google pay 选项,换香港 ip 刷新后再换回美国 ip,就有了。

最后,链接的话都是用 @else 佬友贴子里的。详情看这个帖子 免费白嫖 2-5 年 Copilot(Microsoft365),可用 GPT-5.2 - 福利羊毛 / 福利羊毛,Lv1 - LINUX DO

最最后,如果只需要两年的,把网址里的 us 换成 hk,支付宝就行。1. 怎么切换香港区,第一个网址里面的 US 换成 Hk 2. 为什么教育邮箱受到邮件后点击链接登陆不了?你需要登录的不是教育邮箱,而是你订阅的邮箱 3. 第二个链接点开后为什么提示已经订阅,没资格? 同样需要把网址里的 us 换成 hk, 4. 第二个链接提示只有 1 个月? 没关系,订阅就完事,后面自己会升级的。


📌 转载信息
原作者:
Calfui
转载时间:
2026/1/19 17:37:44

关于 Agentic 搜索,七步以外,Grok 4 Fast 准,七步以内,Grok 4 Fast 又快又准。搜索准确性这块,目前我觉得除了 GPT thinking 以外,没人能跟 Grok Fast 系列抗衡,速度和 NSFW 支持就更是独一档了。

但自从 Grok 4.1 进入 Beta 之后,免费账户网页调用 Grok 4 Fast 的入口就被取消了。Grok 非 Fast 系列的搜索体验简直可以说是稀烂,比不上 Fast 一根,xAI 你真该死啊,于是我写了:

// ==UserScript==
// @name         Grok 4 Fast Unlock
// @namespace    http://tampermonkey.net/
// @version      3.3
// @description  使 Grok 免费账号使用 Grok 4 Fast
// @author       MUTED64
// @match        https://grok.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==
(function() {
    'use strict';
    const TARGET_MODEL = 'grok-4-mini-thinking-tahoe';
    const TARGET_MODE = 'MODEL_MODE_GROK_4_MINI_THINKING';
    const DISPLAY_NAME = 'Grok 4 Fast';

    let isMiniThinkingEnabled = false;
    let lastOfficialModelName = '';

    const originalFetch = window.fetch;
    window.fetch = async function(...args) {
        let [url, options] = args;
        const urlStr = (typeof url === 'string') ? url : (url?.url || '');
        if (isMiniThinkingEnabled && urlStr && (urlStr.includes('/conversations/new') || (urlStr.includes('/conversations/') && urlStr.includes('/messages')))) {
            if (options && options.body) {
                try {
                    const bodyData = JSON.parse(options.body);
                    if (bodyData.modelName) {
                        bodyData.modelName = TARGET_MODEL;
                        bodyData.modelMode = TARGET_MODE;
                        options.body = JSON.stringify(bodyData);
                        console.log(`[Grok Enhanced] 拦截: 已使用 `);
                    }
                } catch (e) {}
            }
        }
        return originalFetch.apply(this, [url, options]);
    };

    function ensureMenuItem(menu) {
        if (menu.querySelector('#mini-thinking-option')) return;
        const template = menu.querySelector('[role="menuitem"]');
        if (!template) return;
        const newItem = template.cloneNode(true);
        newItem.id = 'mini-thinking-option';

        const title = newItem.querySelector('.font-semibold');
        if (title) title.textContent = DISPLAY_NAME;
        const desc = newItem.querySelector('.text-xs.text-secondary');
        if (desc) desc.textContent = 'Grok 4 Fast';
        menu.prepend(newItem);
    }

    function syncUI() {
        const menu = document.querySelector('[role="menu"]');
        const trigger = document.querySelector('#model-select-trigger') || document.querySelector('button[aria-haspopup="menu"]:has(span)');
        const buttonTextSpan = trigger ? trigger.querySelector('.font-semibold') : null;
        if (menu) {
            ensureMenuItem(menu);
            const items = menu.querySelectorAll('[role="menuitem"]');

            items.forEach(item => {
                const check = item.querySelector('.lucide-check');
                if (!check) return;
                const itemName = item.innerText.split('\n')[0];

                if (item.id !== 'mini-thinking-option') {
                    const isCheckedByReact = check.classList.contains('opacity-100') || window.getComputedStyle(check).opacity === '1';
                    if (isCheckedByReact && !isMiniThinkingEnabled) {
                        lastOfficialModelName = itemName;
                    }
                    if (isCheckedByReact && isMiniThinkingEnabled) {

                        lastOfficialModelName = itemName;
                    }
                }

                if (isMiniThinkingEnabled) {
                    if (item.id === 'mini-thinking-option') {
                        check.style.opacity = '1';
                        check.classList.add('opacity-100');
                        check.classList.remove('opacity-0');
                    } else {
                        check.style.opacity = '0';
                        check.classList.add('opacity-0');
                        check.classList.remove('opacity-100');
                    }
                } else {
                    if (item.id === 'mini-thinking-option') {
                        check.style.opacity = '0';
                        check.classList.add('opacity-0');
                        check.classList.remove('opacity-100');
                    }
                }
            });
        }
        if (buttonTextSpan) {
            const desiredText = isMiniThinkingEnabled ? DISPLAY_NAME : lastOfficialModelName;
            if (desiredText && buttonTextSpan.textContent !== desiredText) {
                buttonTextSpan.textContent = desiredText;
            }
        }
    }

    document.addEventListener('click', (e) => {
        const menuItem = e.target.closest('[role="menuitem"]');
        if (menuItem) {
            const itemName = menuItem.innerText.split('\n')[0];
            if (menuItem.id === 'mini-thinking-option') {
                isMiniThinkingEnabled = true;
                setTimeout(() => {
                    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
                }, 30);
            } else {
                isMiniThinkingEnabled = false;
                lastOfficialModelName = itemName;
            }
            syncUI();
        }
    }, true);

    const observer = new MutationObserver(syncUI);
    observer.observe(document, { childList: true, subtree: true });
    console.log('[Grok Enhanced] 脚本加载成功。');
})();

偷偷 Enjoy 吧~


📌 转载信息
原作者:
MUTED64
转载时间:
2026/1/19 17:37:21