MoreRSS

site iconAnZhihe | 安志合修改

国学和传统文化爱好者,IT行业从业者,运维和SRE。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

AnZhihe | 安志合的 RSS 预览

有了AI以后……

2026-09-22 08:31:12

有了AI以后,这个世界更加抽象了~

找研发看问题,全用AI给你搞,让AI出方案,也不自己验证,直接AI出个文档方案就让去生产操作

image.png

没有AI不会干活了吗?

还有更狠的,让我用AI改他应用初始化SQL去适配达梦......

image.png

AI很强大,确实让工作提效了很多,但是也让很多人产生了 AI是万能的 幻觉

有了AI以后,活越干越多,也越干越累了,AI在这里好像并没有解放生产力,反而快成为了奴役禁锢人的工具,真是抽象啊!

达梦集群高可用连接配置

2026-09-18 14:28:41

达梦连接配置介绍

1. JDBC连接串格式

JDBC(Java Database Connectivity)是 Java 应用程序与数据库的接口规范,旨在让各数据库开发商为 Java 程序员提供标准的数据库应用程序编程接口(API)。JDBC 定义了一个跨数据库、跨平台的通用 SQL 数据库 API。

DM JDBC 驱动程序是 DM 数据库的 JDBC 驱动程序,它是一个能够支持基本 SQL 功能的通用应用程序编程接口,支持一般的 SQL 数据库访问。

通过 JDBC 驱动程序,用户可以在应用程序中实现对 DM 数据库的连接与访问,JDBC 驱动程序的主要功能包括:

  1. 建立与 DM 数据库的连接

  2. 发送 SQL 语句到数据库

  3. 处理并返回语句执行结果

1.1 格式一

host、port 不作为连接属性,直接输入值即可。

格式

jdbc:dm://[host][:port][/schemaName][?propName1=propValue1][&propName2=propValue2][&…]…

参数说明

  • host:数据库所在 IP 地址,缺省为 localhost。ipv6 地址需包含在[]中

  • port:数据库端口号,缺省为 5236

  • schemaName:指定登录使用的默认模式名,缺省使用与登录用户同名的模式

  • ?:参数分隔符,表示后面的都是参数

  • &:参数间隔符,多个参数用&分开

  • propName:连接串属性名称。详见JDBC连接串属性表;

  • propValue:连接串属性值。

示例

jdbc:dm://192.168.0.96:5236/SYSDBA?resultSetType=1003

1.2 格式二

host、port 作为连接属性,此时必须按照JDBC连接串属性表中说明进行设置,且属性名称大小写敏感。

格式

jdbc:dm://[/schemaName][?propName1=propValue1][&propName2=propValue2][&…]…

示例

jdbc:dm://?host=192.168.0.96&port=5236

1.3 格式三

使用自定义服务名,可指定多个数据库节点。

格式

jdbc:dm://GroupName[/schemaName][?GroupName=(host1:port1,host2:port2,…)][&propName1=propValue1][&propName2=propValue2][&…]…

参数说明

  • GroupName:数据库服务名,支持指定多个 host:port,若未指定服务名对应的 host:port,则将在配置文件 dm_svc.conf 中匹配相应的服务名;  

  • 若未指定服务名对应的 host:port,则在 dm_svc.conf 中匹配

示例

jdbc:dm://test?test=(192.168.0.96:5236,192.168.0.96:5237)&LOGIN_DSC_CTRL=0&LOGIN_MODE=1&CLUSTER=DSC&autoReconnect=2&epSelector=1

同一个属性在 JDBC 连接串和 dm_svc.conf 中均有设置但值不同时,以 JDBC 连接串优先。


2. JDBC连接串属性表

所有时间参数可使用(h:小时,m:分钟,s:秒,ms:毫秒),不带单位时默认 ms,有效值范围 0~2147483647。缺省值 5 分钟。

2.1 连接属性

属性 说明 必须
host 主库地址,IP 或 localhost
port 端口号
unixSocketFile UNIX socket 路径(Linux)
user 登录用户
password 登录密码
appName 客户端应用程序名称
socketTimeout 网络通信链路超时(ms)
sessionTimeout 会话超时时间(s)
connectTimeout 连接超时时间(ms),缺省 5000
StmtPoolSize 语句句柄池大小,缺省 15
PStmtPoolSize prepare 语句句柄池大小
pstmtPoolValidTime prepare 语句缓存有效时间(ms)
escapeProcess 是否进行语法转义处理
autoCommit 是否自动提交
localTimezone 客户端本地时区(分钟,-779~840)
maxRows 结果集行数限制
LobMode 大字段数据获取模式:1服务器获取,2本地缓存
ignoreCase 结果集列名是否忽略大小写
resultSetType 结果集类型:1003/1004/1005

2.2 集群与高可用属性

属性 说明 必须
loginMode 优先登录的服务器模式(0~4)
loginStatus 连接时只选择状态匹配的库(0/3/4/5)
loginDscCtrl 是否只选择 dsc control 节点
epSelector 节点选择策略(0=均匀分布,>0=固定第n个)
epSelectorDynamic 是否根据节点存活状态动态调整 epSelector
autoReconnect 连接异常处理策略(0~7,可组合)
switchTimes 尝试遍历服务名列表的次数
switchInterval 遍历间隔时间(ms)
cluster 集群类型:DW/RW/MPP/DPC/DSC/NORMAL
dbAliveCheckFreq 检测数据库存活的频率(ms)
checkFreq 循环检测连接是否需要重置的时间间隔(ms)

2.3 读写分离属性

属性 说明 必须
rwSeparate 是否启用读写分离(0~5)
rwPercent 分发到主库的事务占比(%)
rwAutoDistribute 事务分发是否由 JDBC 自动管理
rwHA 是否开启读写分离高可用
rwStandbyRecoverTime 备库故障恢复检测间隔(ms)
allowRange 允许动态负载均衡误差范围(%)

2.4 安全与加密属性

属性 说明 必须
sslFilesPath SSL 加密文件路径
sslKeystorePass SSL 加密文件口令
uKeyName Ukey 文件路径
uKeyPin Ukey 口令
OsAuthType 操作系统认证类型(0~4)
loginCertificate 登录加密公钥路径
localEncrypt 是否启用用户名密码本地加密
userEncrypt 消息通信是否加密用户名

2.5 日志与监控属性

属性 说明 必须
logDir 日志文件生成目录
logLevel 日志级别:off/assert/error/warn/sql/info/DEBUG/all
logFlushFreq 日志刷盘频率(s)
statEnable 是否启用状态监控
statDir 状态监控信息输出目录
statFlushFreq 状态监控写文件刷盘频率(s)
statSlowSqlCount 统计慢 SQL top 行数
statHighFreqSqlCount 统计高频 SQL top 行数

2.6 其他属性

属性 说明 必须
mppLocal 是否 MPP 本地连接
mppOpt MPP 集群批量插入优化
keyWords 用户关键字标识
dmsvcconf URL 属性配置文件路径
dbAliveCheckTimeout 检测数据库存活的连接超时(ms)
prepareOptimize 是否对预编译 SQL 做优化
serverOption 附加信息,格式:{key=value,...}
reconnectErrors 需要重连的异常错误码
errMap DM 错误码和 Oracle 错误码映射

3. dm_svc.conf配置文件

dm_svc.conf 是客户端配置文件,包含 DM 各接口和客户端工具需要配置的参数,必须和接口/客户端工具位于同一台机器上才能生效。初始 dm_svc.conf 文件在 DM 安装时自动生成。不同平台的生成目录有所不同。

文件位置

  • Windows 32位:%SystemRoot%\system32

  • Windows 64位:%SystemRoot%\system32

  • Windows 64位运行32位程序:%SystemRoot%\SysWOW64

  • Linux:/etc

可通过环境变量 DM_SVC_PATH 修改路径。

3.1 配置项支持情况

配置项 JDBC .NET NodeJS Go
服务名
EP_SELECTOR
AUTO_RECONNECT ×
CLUSTER ×
LOGIN_MODE
LOGIN_DSC_CTRL ×
RW_SEPARATE
RW_HA ×
CONNECT_TIMEOUT ×
SOCKET_TIMEOUT
SWITCH_TIMES
COMPRESS ×
SSL_FILES_PATH ×
LOG_DIR
LOG_LEVEL
STAT_ENABLE × ×

3.2 dm_svc.conf用法

dm_svc.conf 配置文件的内容分为全局配置区和服务配置区。全局配置区在前;服务配置区在后,以“[服务名]”开头。其中,服务名配置项可以在全局配置区,也可以在此服务名对应的服务配置区之前,即在其对应的“[服务名]”之前。

dm_svc.conf 格式如下:

# 全局配置区

 

参数配置……

 

# 服务配置区

 

[服务名1]

 

参数配置……

 

# 服务配置区

 

[服务名2]

 

参数配置…… 

服务配置区中的配置优先级高于全局配置区。全局配置区的配置项影响所有会话,除非会话所属服务配置区中单独进行了配置,因此对于全局配置区配置项的设置修改需要慎重。

如果对 dm_svc.conf 的配置项进行了修改,需要重启客户端工具,修改的配置才能生效。另外,如果 dm_svc.conf 配置文件中包含中文,则必须保证该配置文件的编码与客户端编码一致。

3.3 配置示例

场景一

配置常规环境的 dm_svc.conf。此处常规环境是指服务名中的 IP 之间互不相关(既不是主备关系、也不在一个集群中)。

例 NORMAL 中的 192.168.0.1 和 192.168.0.2 是两个互不相关的 IP。

# #开头的行表示是注释

 

# 全局配置区

 

NORMAL=(192.168.0.1:5000,192.168.0.2:5236)  

 

TIME_ZONE=(480)   #表示+8:00时区

 

DIRECT=(Y)  

 

# 服务配置区

 

# 常规环境,两个没有关系的IP

 

[NORMAL]

 

TIME_ZONE=(540)   #表示+9:00时区

 

LOGIN_MODE=(4)

 

SWITCH_TIMES=(3)  

 

SWITCH_INTERVAL=(100)

 

场景二

配置集群环境的 dm_svc.conf。一个服务名中的所有 IP 均来自于同一个集群。

以 DMDSC 集群为例。

# #开头的行表示是注释

 

# 全局配置区

DMDSC1=(192.168.1.1:5236,192.168.1.3:5236)

DMDSC2=(192.168.1.5:5236,192.168.1.7:5236)

TIME_ZONE=(480)   #表示+8:00时区

 

#DMDSC1 服务配置区

#以下配置目标连接 DMDSC1 服务名的第一个服务(192.168.1.1:5236),以间隔 1000 毫秒的节奏尝试遍历服务名中的各服务进行连接,若在遍历中成功连上符合要求的服务则立即返回;若没有,则继续。一共尝试 60 次遍历,若60次尝试后依然未连接上目标服务,则根据LOGIN_MODE的配置进行下一种连接的尝试。若连接建立时连上的是2号服务,而一段时间后 1 号服务可以正常连接了,当前连接也不会自动切换到 1 号服务。

[DMDSC1]

LOGIN_MODE=(4) #若是DSC+单机备,建议这里配置成0

SWITCH_TIMES=(60)

SWITCH_INTERVAL=(1000)

EP_SELECTOR=(1)

AUTO_RECONNECT=(1)

 

#DMDSC2 服务配置区

#以下配置目标连接 DMDSC2 服务名的第一个服务(192.168.1.5:5236),以间隔 1000 毫秒的节奏尝试遍历服务名中的各服务进行连接,若在遍历中成功连上符合要求的服务则立即返回;若没有,则继续。一共尝试 60 次遍历,若60次尝试后依然未连接上目标服务,则根据LOGIN_MODE的配置进行下一种连接的尝试。假设2号服务先连接成功,由于AUTO_RECONNECT=(2),因此当1号服务可以正常连接后当前连接会切换到1号服务。

[DMDSC2]

CLUSTER=(DSC)

LOGIN_MODE=(4) #若是DSC+单机备,建议这里配置成0

SWITCH_TIMES=(60)

SWITCH_INTERVAL=(1000)

EP_SELECTOR=(1)

AUTO_RECONNECT=(2)

 

场景三

配置常规环境和集群环境公用的 dm_svc.conf。

例 [NORMAL]为常规环境,[Data_Watch]为数据守护环境。服务名 NORMAL 中的 IP 互不相关,服务名 Data_Watch 中的 IP 为同一个数据守护环境中的 IP。

# #开头的行表示是注释

 

# 全局配置区

 

NORMAL=(192.168.0.1:5000,192.168.0.2:5236)  

 

Data_Watch=(192.168.0.3:5236,192.168.0.4:4350)

 

TIME_ZONE=(480)   #表示+8:00时区

 

DIRECT=(Y)  

 


# 服务配置区

 

# 常规环境,两个没有关系的IP

 

[NORMAL]

 

TIME_ZONE=(540)   #表示+9:00时区

 

LOGIN_MODE=(4)

 

SWITCH_TIMES=(3)  

 

SWITCH_INTERVAL=(100)

 

 

# 服务配置区

 

# 数据守护环境,一主一备,只连备库

 

[Data_Watch]

 

TIME_ZONE=(540)   #表示+9:00时区

 

LOGIN_MODE=(2)

 

SWITCH_TIMES=(3)  

 

SWITCH_INTERVAL=(100)


4. 关键参数说明

4.1 LOGIN_MODE

说明
0 优先 PRIMARY → NORMAL → STANDBY
1 只连接主库
2 只连接备库
3 优先 STANDBY → PRIMARY → NORMAL
4 优先 NORMAL → PRIMARY → STANDBY(缺省)

启用读写分离时(rwSeparate ≠ 0),loginMode 不生效

4.2 AUTO_RECONNECT

说明
0 关闭连接
1 异常时自动切换到其他库,抛 SQLException 通知上层
2 配合 epSelector=1,节点恢复后切回原节点
4 保持各节点会话动态均衡

可组合使用,如 3 表示 1+2

4.3 CLUSTER

说明
DW 主备
RW 读写分离
MPP 大规模并行处理集群
DPC 分布计算集群
DSC 数据共享集群(需配合 autoReconnect=2、epSelector=1)
NORMAL 单机库(缺省)

4.4 RW_SEPARATE

说明
0 不启用
1 启用,备库由系统自动决定
2 启用,备库由客户端选择
3 启用,备库由服务器通知
4 启用,只连接事务一致性备库
5 启用,为 GATEWAY 准备

4.5 EP_SELECTOR

说明
0 依次选取不同节点,连接均匀分布
>0 选择列表中第 n 个节点,只有当前节点无法连接时才选下一个


PostgreSQL 集群异常恢复

2026-09-07 19:36:36

问题现象:3节点的Patroni + PostgreSQL集群 postgresql-0 节点异常,一直处于starting状态,Lag in MB 显示 unknown,postgresql-0使用的pv、etcd集群排查都没有问题,集群状态及日志报错如下图所示,输出排查步骤及恢复方案

image.png

image.pngimage.png


qwen3.8-max

基于您提供的截图和描述,这是一个典型的 Patroni + PostgreSQL 集群中备库(Replica)启动失败/卡住 的问题。

1. 现象分析

  • 状态异常postgresql-0 节点在 patronictl list 中显示为 starting,且 Lag in MBunknown。这意味着 Patroni 无法成功连接到该节点的 PostgreSQL 进程来查询复制延迟。

  • 日志关键信息(第二张图):

    • Still starting up as a standby.:Patroni 认为 PG 正在作为备库启动。

    • Error communicating with PostgreSQL... /var/run/postgresql:5432 - rejecting connections这是核心错误。PostgreSQL 进程虽然可能启动了,但它处于“拒绝连接”的状态。通常发生在 PG 正在进行崩溃恢复(Crash Recovery)、正在重放 WAL 日志尚未达到一致性状态,或者启动过程中卡住了。

    • Failed to determine PostgreSQL state from the connection:因为连不上数据库,Patroni 无法判断其真实状态,只能回退到缓存的角色信息。

  • 拓扑结构postgresql-2 是 Leader (TL 266),postgresql-1 是健康的 Replica (TL 135, Lag 752MB),postgresql-0 是故障节点。

  • 手动连接报错FATAL: the database system is starting up。这证实了 PostgreSQL 进程确实活着,但它还没有完成启动/恢复过程,因此拒绝任何业务连接。这也是为什么 Patroni 报 rejecting connectionsLag in MB: unknown 的根本原因。

  • 进程状态 (ps -ef)

    • PID 61 是主进程 postgres -D ...

    • 关键进程 PID 64postgresql: startup recovering 00000008E000000000000077

  • 结论:您的 postgresql-0 节点当前正处于 WAL 日志回放(Crash Recovery / Standby Recovery)阶段。它正在努力应用 WAL 日志以达到一致性状态。在 startup 进程完成之前,数据库无法接受连接,Patroni 也就只能一直显示 starting


2. 为什么会卡在这里?

结合您第一张图的信息:

  1. 集群整体延迟大:健康的备库 postgresql-1 也有 752 MB 的延迟。这说明主库写入量很大,或者网络/磁盘 IO 存在瓶颈。

  2. Timeline (TL) 差异巨大:Leader (postgresql-2) 的 TL 是 266,而 postgresql-1 的 TL 是 135。这说明集群历史上发生过非常多次的故障切换(Failover)。

  3. 当前恢复位置postgresql-0 正在恢复的 WAL 段是 00000008E000000000000077(注意前缀 00000008 代表 Timeline 8)。

核心矛盾postgresql-0 本地的数据还停留在非常古老的 Timeline 8,而当前的 Leader 已经在 Timeline 266 上了。
PostgreSQL 备库在启动时,必须先从本地 WAL 或归档中把历史日志回放完,直到追上当前的 Timeline。由于跨度太大(从 8 到 266),它可能需要回放海量的 WAL 日志,或者正在尝试寻找跨越这些 Timeline 的历史记录,导致启动过程极其漫长,看起来像是“卡住”了。


3. 排错步骤

请按顺序在 postgresql-0 (172.24.0.107) 节点上执行以下操作:

第一步:检查 PostgreSQL 原生日志(最重要)

Patroni 的日志只告诉我们“连不上”,PG 自己的日志才会告诉我们“为什么连不上”。

# 找到 PG 日志路径,通常在 /var/log/postgresql/ 或数据目录下的 pg_log/
# 如果是容器化部署,使用 kubectl logs 或 docker logs
tail -f /var/log/postgresql/postgresql-*.log 
# 或者
tail -f /home/postgres/data/pg_log/postgresql-*.log

重点关注日志中的以下关键词:

  • redo starts at ... / redo done:看是否在进行恢复。如果一直卡在 redo starts 且没有进度,说明 WAL 回放有问题。

  • could not connect to the primary server:看是否能连通 Leader (172.24.0.116)。

  • FATALPANIC:任何致命错误。

  • waiting for WAL to become available:等待 WAL 日志。

第二步:检查进程状态

确认 PG 进程是否存在,以及处于什么状态。

ps -ef | grep postgres
  • 如果没有 postgres 进程:说明 PG 根本没起来,或者是被 Patroni 反复重启。

  • 如果有进程,但状态是 D (不可中断睡眠) 或 R (运行中) 且 CPU 占用高:可能正在大量回放 WAL。

  • 检查是否有残留的 postmaster.pid

ls -l /home/postgres/data/postmaster.pid  # 路径根据实际情况调整
  • 注意:不要随意删除此文件,除非你确定 PG 进程已经完全不存在。

第三步:手动尝试连接(绕过 Patroni)

尝试直接用 pg_isreadypsql 连接本地 socket,看具体报错。

# 检查端口是否监听
netstat -tlnp | grep 5432

# 尝试本地连接
su - postgres
psql -h /var/run/postgresql -p 5432 -U postgres -c "select 1;"
  • 如果报错 the database system is starting up:证实了日志中的猜测,PG 还在恢复中,需要等待

  • 如果报错 no pg_hba.conf entry:配置问题。

  • 如果连接直接被拒绝且无进程:PG 启动失败。

第四步:检查网络连通性

确保 postgresql-0 能访问 Leader (postgresql-2, 172.24.0.116) 的 5432 端口。

telnet 172.24.0.116 5432
# 或
nc -zv 172.24.0.116 5432

如果不通,检查防火墙、安全组或 K8s NetworkPolicy。


4. 解决方案

根据上述排查结果,选择对应的方案:

方案 A:如果只是 WAL 回放慢(最常见)

如果 PG 日志显示正在 redo 且没有报错,只是速度慢(特别是看到 pg-1 也有 700多MB 延迟,说明集群整体写入压力大或网络带宽受限):

  1. 耐心等待:不要强制重启。让 PG 完成恢复。

  2. 监控进度:观察日志中 LSN 的变化。

  3. 优化:如果长期如此,考虑增加 max_wal_senders,调整 wal_receiver_status_interval,或检查磁盘 IO 性能。

方案 B:如果 PG 进程卡死或数据损坏(推荐尝试)

如果日志显示 PANIC,或者长时间(超过 30 分钟)无任何进展,且 psql 始终报 starting up,则需要重建该副本。由于是 Patroni 集群,重建非常安全。


处理方案

针对这种情况,有两种处理策略。强烈建议直接采用方案 B,因为等待一个跨越 200 多个 Timeline 的备库自行追平几乎是不现实的,且极易出错。

方案 A:继续等待(仅适用于刚重启不久)

如果您刚刚重启该节点不到 10-20 分钟,可以观察一下 PostgreSQL 的原生日志,看 LSN 是否在跳动。

# 查看 PG 日志,确认是否在持续回放
tail -f /home/postgres/pgdata/pgroot/data/log/*.log 
# (路径根据您的实际 pg_log 位置调整)

如果日志里不断有 redo at ... 且数字在变大,说明它在干活。但考虑到 TL 差距,不建议死等

方案 B:重建该副本(推荐,最快最安全)

既然 PV 和 etcd 都没问题,利用 Patroni 的自动克隆功能,让它直接从当前的 Leader (postgresql-2) 重新全量同步一份最新的数据,是解决此问题的标准做法。

操作步骤:

1.停止 Patroni 服务
为了防止 Patroni 在我们清理数据时反复尝试拉起 PG,先停掉它。

# 如果是 systemd 管理
systemctl stop patroni

# 如果是容器环境 (如 K8s/Docker),请通过编排工具停止该 Pod/Container

2.确认并清理数据目录
从截图中可以看到,您的数据目录是:/home/postgres/pgdata/pgroot/data
警告:请务必核对路径,删除错误目录会导致灾难性后果!

# 再次确认路径
ls -ld /home/postgres/pgdata/pgroot/data

# 清空该目录下的所有内容 (保留目录本身)
rm -rf /home/postgres/pgdata/pgroot/data/*

# 确认已清空
ls -A /home/postgres/pgdata/pgroot/data

3.重新启动 Patroni

systemctl start patroni
# 或启动对应的容器/Pod

4.观察重建进度
Patroni 启动后,会发现数据目录为空,自动触发 pg_basebackup 从 Leader (172.24.0.116) 克隆数据。

  • 查看 Patroni 日志

tail -f /var/log/patroni/patroni.log
# 或 journalctl -u patroni -f

您应该会看到类似 replicating from leaderrunning pg_basebackup 的日志。

  • 查看集群状态

patronictl list

状态变化预期:

    1. starting (正在克隆)

    2. running (克隆完成,开始流复制)

    3. Lag in MB 会从 unknown 变成一个具体的数字,并逐渐减小到 0 或很小的值。

    4. TL (Timeline) 会变成 266,与 Leader 一致。

总结

您遇到的不是故障,而是备库数据太旧,正在艰难地进行跨 Timeline 恢复。由于落后太多(TL 8 vs TL 266),自行恢复效率极低。清空数据目录让 Patroni 重新克隆是解决此问题的最佳实践。


高效沟通(五):好老板要善于提问

2026-08-25 10:02:12

前面的几篇文章中,我分享了一些通用的沟通方法,如尊重、倾听和情绪控制等。接下来的几篇文章中,我将从如何与员工沟通、如何与客户沟通,以及如何与老板沟通这几个角度,和你聊聊这些沟通方法具体应该如何应用。

作为一名团队 Leader,你首先应该学会如何与团队成员进行有效沟通,因为它是实现管理效果的必要手段和有效途径。但如何拥有这个基本功呢?我来分享一下我的经验。

引导

我在汤森路透工作的时候,曾经参加过一个管理上的培训课程。这个培训课程的第一课就是教这些管理者如何在沟通中引导员工,而不是给员工灌输自己的想法。课程里强调,管理者要想尽一切办法让员工自己思考问题,想出答案;而不是灌输,什么事儿都是自己在想,自己讲给员工听。员工不想,你怎么说,他都很难把你的话理解到位,也就是说你一定要让他自己把事情想出来。

这有点儿像电影《盗梦空间》说的,你应该在思想里埋下一个种子。我们要干的就是在员工的思想里埋一个种子,让它生根发芽。但这要怎样实现呢?

答案就是管理者要学会问问题,问员工怎样做。假如员工给出了一个方案,但不巧,可能由于他考虑得不全面,或者由于他不知道某些情况,不是你想要的答案。这时,该怎么办呢?

你可以说,如果这么做的话,会有一个什么问题,而这个问题很重要,如何解决?然后,他会给出解决这个问题的方法。但这么做又会带来另一个问题,直到把他逼到你想要的答案上去。

如果每次遇到问题,都让他自己想答案,次数多了以后,他会觉得自己的参与感越来越多。最后,他会觉得是他用他的观点说服了你。尽管这就是你想要的答案,但你还是要假装被说服。这样他会很开心的,会有一种参与感。然后,在执行这件事儿的时候,也会更加卖力,更加有激情。他会觉得自己在实现自己的想法,而且自己的想法是对的。

作为 Leader,你要记住,永远不要给员工答案,要让员工给你答案,而且不要只给一个答案,一定要给多个答案。然后让他们比较这些答案,促使他们深入地进行思考。这不是在让员工做问答题,其实是在给员工成长机会,促进他们的成长。

永远不要跟员工说,我给你一个任务,这个任务两星期完成。要让他来说,这个任务需要多久能完成。并要求员工提供多种执行方案,不要只给一个时间。你快点做怎么做,慢点做怎么做,是否还有其他方案。一定要员工自己去做计划,去思考。反之,如果你什么都想了,只让员工去执行,那么他就不思考了,而且有时还会生出一些怨念。比如抱怨领导这样安排不合理,那个执行方案有问题等。带有情绪的执行,势必会产生不够好的执行结果。

但根据我的观察,喜欢给答案的管理者还是挺多的,他们总是习惯性地给员工答案,而不善于挖掘员工的实力和潜力。我觉得这是世界上最 Low 的管理模式了,是家长式、保姆式的管理。实际上,你的员工都是专业人才,你应该充分信任他们,并且想方设法激发他们的主观能动性,促使他们发挥自己的能力,积极地为你贡献答案,从而保持团队的活力和创造力。

倾听

倾听意味着在听他人讲话的时候,不让自己的想法扭曲别人传递的信息。你要做到毫无偏见,才能全面理解对方的信息。倾听不只是听或者听见,需要你用心聆听别人讲话,而不是只听自己想听到的内容。如我在《沟通方式及技巧》一文中提到的,倾听可以让员工感觉到自己被尊重,所以他们会乐意分享更多的信息。

学会倾听不仅可以帮你拉近和员工的距离,还可以让你更加了解员工。我在汤森路透工作的时候,团队里有两个刚毕业的小伙子。一个来自农村,一个来自城市。来自农村的小伙子是家里老大,家里条件不太好,不仅要挣钱还自己的助学贷款,还要帮家里还外债。而那个来自城市的小伙子是家里老五,上面是四个姐姐,家里条件也相对比较好。不用去想人物性格,从这个背景里,就能大致猜出这两个人的差距。果不其然,有四个姐姐的小伙子,抗压能力相当低,觉得什么活儿都有难度,什么都适应不了。

而要还外债的小伙子抗压能力相当高,没事儿就来跟我说,你把什么任务都给我,我什么都能搞定。经过几年的努力,他终于把家里的外债还干净了,然后特别高兴,请我吃饭。我说,你不用感谢我,要感谢你自己,是你自己做得多。通过这个例子,我想说明,通过倾听更多地了解员工,了解他们的生长环境和背景,可以帮你对每个员工建立更加合理的预期,从而更好地进行任务分配和人员管理。

所以,外企一般都会要求经理和员工有周期性的一对一交谈,就是为了及时了解员工的各种动态和想法。

共情

共情,又被称为同理心,或者换位思考,它指的是站在对方立场设身处地思考问题的一种方式。换句话说,在人际交往过程中,你需要能够体会他人的情绪和想法、理解他人的立场和感受,并站在他人的角度思考和处理问题。

比如,有团队成员要辞职了,你要怎样跟他谈呢?你肯定要找他谈感情。我们一起共事这么久,你要走了,我们一起回忆回忆过去。然后说,没关系,你看你要离开了,有没有什么我可以帮你的?不要强行让对方留下来,要多谈感情,多回忆一下,多听听对方的诉说。当他回想起过去一起同甘共苦的日子,难免会心生留恋,也许会回心转意的。当然,如果你并不能把他留下来时,不如大度一些,帮他看看他要去的另外一家公司是否是正确的选择,而且你还可以给他介绍更好的地方。既然留不下来,就索性为他介绍更好的地方。这样做至少还能引发他一些思考,“我都要离开了,我老板对我还这么好,我以后能不能找到这么好的老板?”

这里的关键是,当对方开始想离开你了,你千万不要指责和教育对方,而一定要站在对方的角度来思考问题,理解对方,真心对对方好。晓之以理,动之以情。

高维

员工来跟你聊的,通常都是细节问题。这时,你可以耐心地跟员工沟通,并共同来寻找解决问题的方案。但有的时候涉及到公司的一些问题时,你自己也解决不了,那么你该怎样跟员工聊呢?比如,公司因为战略方向调整,想要砍掉你负责的业务,你和你团队都需要转到新的业务线上。

你肯定不能跟自己的“弟兄们”说,公司混蛋,把我们这么好的业务给砍掉了。作为管理者,你应该知道,没有完美的公司,任何公司都存在这样那样的问题。你需要有更高的维度来看待这个问题,来给员工做出解释,让他们既能理解公司的决定,又能保持动力转到新的方向上。

对于这样的问题,你首先应该肯定员工过去的努力以及取得的成绩,明确说明虽然业务被砍,但是我们的技术积累还在,这是我们谋求未来发展的基石。同时,帮助员工看清公司新的战略方向会给全公司的人带来什么前景,新的业务方向如何更能发挥出大家积累的经验和能力。在成功安抚人心的同时,引发大家对新业务方向的兴趣,从而更有利于帮助团队后续过渡到新业务方向上。

当然,在讲这个事情的时候,千万不要太过了,还是要跟员工共情一下,也要表达出自己的不满,这样让员工觉得你是跟他们站在一起的,而不是跟公司站在一起的,后者无疑会引发你和大家的对立。这里的沟通思路是这样的:“公司的这个决定,我也有点难理解,我们这么辛苦做了这么多,没想会这样……但是我们做的事是很牛的,我们这个团队是强大的,强大到对于这样的打击都是没有问题的。这个世界就是这样的不完美,但是我们还是要去奋斗,不然就更不完美了……接下来,无论发生什么,我们都要一起杠!” 也许,这么说也没什么用,但至少,在困难到来时,你可以让大家的心更近了。

反馈

反馈是一种非常重要的沟通形式,对于确保团队的正常运转十分关键。但有时候员工没有反馈的意识,或者不愿意反馈,你应该怎么办?这时,你应该建立一些反馈机制。比如,在我目前的团队里面就在用“1-2-3 反馈机制”。

  1. 不管你遇到什么问题,如果自己在那儿憋一个小时找不到解决方案,或者说没有任何思路,就要反馈到高级工程师这边来。

  2. 如果跟高级工程师在一起两个小时内,找不到任何解决方案或者没有思路,那么就要反馈到一线 leader。

  3. 如果一线 leader、高级工程师,花了三个小时,依然找不到方案,那么这个事就可能是个大事了,要向上级反馈了。

这么做,就是为了确保一个大问题,在一天之内能够上升到管理层。然后管理层可能会寻求更牛的人或是从外界获取帮助,以使得问题尽快能够得到解决。

这个反馈机制不仅能确保问题及时被反应出来,并及时得到解决,而且能够帮团队节约大量的时间和精力,对团队来说是种很好的正向鼓励,属于正反馈。

之前我一直强调,正反馈的重要性。在这个场景下,无疑也是如此。试想一下,你和你的“兄弟们”逢山开路,遇水搭桥,一路凯歌的样子,是不是很酣畅?这便是反馈机制的威力了,它会潜移默化地在团队中形成一种“解决问题”的文化,让我们在发现问题的第一时间正视问题,拼尽全力来解决问题,并能从中享受到“搞定问题”的成就感,从而形成正向循环。

除了对工作中问题的反馈,反馈还可以存在与很多其他方面,你完全可以结合团队的实际需求拟定出各种合适的反馈机制。对于任何反馈机制的建立,你只需要记住两点:一是及时反馈;二是能够形成正向循环。

小结

总结一下今天的内容。我分享了我与员工沟通时经常用到的几大法宝:引导、倾听、共情、高维和反馈。

  • 引导,用提问的方式,“倒逼”员工找到答案,从而提高员工的参与感和成就感。

  • 倾听,心态平和,毫无偏见,全面接收和理解对方的信息,而不是只听自己想听的信息。

  • 共情,换位思考,站在对方立场设身处地思考和处理问题,动之以情,晓之以理。

  • 高维,提升自己的格局观,能从全局利益、长远利益思考问题,解决问题。

  • 反馈,建立反馈机制,及时发现问题、解决问题,形成正向循环。

下篇文章中,我将继续就如何与员工沟通这个话题进行讨论,主要探讨如何进行一对一会议、如何做绩效沟通、如何定位性格特殊的员工、如何挽留离职员工、如何辞退员工等问题。敬请期待。

来源:《左耳听风专栏:高效沟通》

Spring Cloud Gateway 和 Nginx 网关代理 WebSocket 路由配置

2026-08-16 17:39:13

最近有个小程序项目客户在并网时有个需求:需要在他们的Spring Cloud Gateway 公网网关开个入口,打到自建的Nginx代理转发到后端服务,需要同时能支持 http 和 ws 的请求。整体访问链路从公网 → Spring Cloud Gateway → Nginx → 应用服务主要的工作是Spring Cloud Gateway 和 Nginx 的路由配置和整个链路的联调,mark一下。

一、Spring Cloud Gateway 配置(第一层)

spring:
  cloud:
    gateway:
      routes:
        # 1. WebSocket 路由:处理 WebSocket 握手请求
        - id: websocket_route
          uri: wss://your-backend-service # 非加密的 WebSocket 协议使用 ws://
          predicates:
            # 匹配 /ws/ 路径及其子路径,并确保包含 Upgrade 头
            - Path=/ws/**
            - Header=Upgrade, websocket
          filters:
            # 不要用 StripPrefix,会破坏 WebSocket 握手头
            - name: SetResponseHeader
              args:
                name: Sec-WebSocket-Accept
                value: ".*"  # 让后端自行生成
            # 保持 Host 头不变
            - name: PreserveHostHeader

        # 2. HTTP 路由:处理普通的 HTTP 请求处理(处理非 WebSocket 请求)
        - id: http_route
          # 普通的 HTTP 负载均衡
          uri: lb://your-backend-service
          predicates:
            # 匹配相同的路径
            - Path=/ws/**
          filters:
            # 显式保证查询参数透传
            - StripPrefix=0

      # 全局 CORS 配置(可选)
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins: "*"
            allowed-methods: "*"
            allowed-headers: "*"
            allow-credentials: true

二、Nginx 配置(第二层)

http {
    # 定义 connection_upgrade 变量
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    upstream app_cluster {
        server 127.0.0.1:8080;
        # 可添加多个 app 实例
    }

        # ---------- HTTP 服务器(提供 ws://)----------
    server {
        listen 80;
        server_name test.com.cn;   # 内网域名或 IP

        location /ws/ {
            # 去掉 /ws/ 前缀,将请求转发到后端根路径
            proxy_pass http://app_cluster/;

            # 必须的 WebSocket 握手配置
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;

            # 传递原始 Host 和客户端 IP
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            # 超时设置(长连接)
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
            proxy_buffering off;
        }
        
        # 其他非 /ws/ 请求
        location / {
            proxy_pass http://app_cluster;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
    
    # ---------- HTTPS 服务器(提供 wss://)----------
    server {
        listen 443 ssl;
        server_name test.com.cn;

        # 网关自身的 SSL 证书(客户端信任的证书)
        ssl_certificate     /path/to/cert.pem;
        ssl_certificate_key /path/to/key.pem;
        ssl_protocols       TLSv1.2 TLSv1.3;
        ssl_ciphers         HIGH:!aNULL:!MD5;

        location /ws/ {
            proxy_pass http://app_cluster/; # 注意末尾斜杠,可去掉 /ws/ 前缀,不加末尾"/" 会保留 /ws/ 前缀 

            # 关键:WebSocket 握手依赖 HTTP/1.1
            proxy_http_version 1.1;

            # 传递 WebSocket 升级头
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;

            # 传递原始信息
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme; # 告诉后端客户端实际使用的是 HTTPS(当后端需要区分协议时有用)

            # 长连接超时(避免空闲断开)
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
            # 关闭缓冲,提升实时性
            proxy_buffering off;
        }

        # 其他非 /ws/ 请求
        location / {
            proxy_pass http://app_cluster;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

后端为 HTTPS(WSS,加密)的情况

如果后端 WebSocket 服务器也要求使用 WSS(例如 https://backend_server:8443),则需额外处理 SSL 验证:

location /ws/ {
    proxy_pass https://backend_server:8443/;   # 使用 HTTPS 协议

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # 如果后端使用自签名证书,关闭 SSL 验证(测试环境)
    proxy_ssl_verify off;
    # 或者指定 CA 证书链:proxy_ssl_trusted_certificate /path/to/ca.pem;

    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
    proxy_buffering off;
}

# 强制将 HTTP 重定向到 HTTPS(可选配置)
server {
    listen 80;
    server_name test.com.cn;
    return 301 https://$host$request_uri;
}

关键检查项

  • 确认 proxy_pass 的 URL 正确:如果你想去掉 /ws/ 前缀,proxy_pass 末尾要带 /,如 proxy_pass http://backend/;

  • 确认后端能接收未加密流量:如果网关 Nginx 处理了 HTTPS(WSS),那么它转发给下游 Nginx 的可以是 HTTP(WS)。请确保你的下游 Nginx 和后端服务能正确处理这种转发。

  • 启用会话保持 (Sticky Sessions):对于有状态的应用,应在目标组(Target Group)上启用会话保持,确保来自同一客户端的请求始终到达同一后端

  • 检查安全组:确保 LB 的安全组允许来自客户端和去往后端的目标端口(如 80/443)的流量

三、验证步骤

1. 检查 Gateway 和 Nginx 路由是否生效

curl -v -H "Host: test.com.cn" \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
  -H "Sec-WebSocket-Version: 13" \
  https://网关IP:网关端口/ws/channel/checkHost?id=xxx

预期返回 101 Switching Protocols

使用 websocat 工具测试:brew install websocat(mac安装)

  • 使用 websocat ws://test.com.cn/ws/ 测试 WS。

  • 使用 websocat -k wss://test.com.cn/ws/ 测试 WSS(自签名证书需加 -k 忽略验证)。

  • 在线验证:http://tool0.com/websocket/


测试普通 HTTP 请求

curl -v https://test.com.cn/ws/channel/checkHost?id=xxx

应正常返回业务数据。

2. 查看 Gateway 和 Nginx 日志

application.yml 中为 org.springframework.cloud.gateway 开启 DEBUG 级别日志,观察请求被匹配到了哪条路由。

logging:
  level:
    org.springframework.cloud.gateway: DEBUG    
    org.springframework.web: DEBUG

观察请求被哪个路由匹配。

查看 Nginx 错误日志

  • tail -f /var/log/nginx/error.log,可定位连接后端失败或 SSL 错误。

  • tail -f /var/log/nginx/access.log,可观察请求被哪个路由匹配

3. 分阶段验证

先验证后端服务是否正常,然后排查 Nginx 配置,再访问 Gateway 服务(绕过 Nginx),确认 Gateway 配置

  • 测试验证后端服务 ws 连接是否正常

  • 再通过 Nginx 访问:https://test.com.cn/ws/...

  • 访问 Gateway(绕过 Nginx):https://网关IP:端口/ws/...

四、常见错误及处理

在配置 WebSocket 代理或客户端时,最常见的错误往往源于反向代理(如 Spring Cloud GatewayNginx)的配置缺失网络环境干扰SSL/TLS 证书问题。下表汇总了典型错误现象、可能原因及对应解决方案,快速定位问题。

错误现象 可能原因 解决方案
1002 (PROTOCOL_ERROR)
协议错误
1. 反向代理未转发升级头:Nginx/网关未显式传递 UpgradeConnection 头。
2. HTTP 版本过低:代理使用 HTTP/1.0 与后端通信,而 WebSocket 要求 HTTP/1.1+。
3. 子协议协商失败:客户端请求的 Sec-WebSocket-Protocol 服务端不支持。
4. 数据帧格式错误:客户端发送了非掩码帧(仅服务端可发送非掩码帧)。
1. 配置反向代理
- Nginx:proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
- Spring Cloud Gateway:路由 uri 使用 ws://lb:ws://,避免使用 StripPrefix
2. 指定子协议:在客户端使用 --protocol 参数或设置 Sec-WebSocket-Protocol 头。
3. 检查客户端库:确认发送的数据符合 RFC 6455(如客户端帧必须掩码)。
400 (Bad Request)
错误请求
1. URL 路径/参数编码错误:包含非法字符(如未转义的 []、空格)。
2. 缺少必要请求头:服务端强制要求 OriginHost 等头部,但客户端未提供。
3. 路径匹配错误:代理转发时路径重写(StripPrefix)导致后端无法识别路由。
4. 请求体过大(非标准):某些实现会限制握手请求的头部大小。
1. 检查并正确编码 URL:对查询参数进行 urlencode
2. 手动添加头部:使用 -H "Origin: https://example.com"-H "Host: ..."
3. 调整路径重写策略:在网关中谨慎使用 StripPrefix,确保转发后的路径与后端路由匹配。
4. 查看后端日志:定位具体拒绝原因。
426 (Upgrade Required)
需要升级
1. 代理未传递 Upgrade:Nginx/网关默认不转发 UpgradeConnection 头。
2. 后端强制要求 TLS:服务端只接受 wss:// 连接,客户端却使用 ws://
3. 客户端协议与服务器期望不符:例如网关用 http:// 而非 ws:// 转发。
1. 确保代理配置正确(同上 1002 的 Nginx/网关配置)。
2. 使用正确的协议前缀:客户端使用 wss://,代理转发时用 ws://(若后端非加密)或 wss://(若后端加密)。
3. 检查 Spring Cloud Gateway 路由uri 必须以 ws://lb:ws:// 开头。
I/O failure
输入/输出错误
1. 网络不可达:目标 IP/端口被防火墙拦截,或服务未启动。
2. DNS 解析问题localhost 同时解析 IPv4 和 IPv6,旧版 websocat 只尝试第一个。
3. 连接超时:代理或服务端空闲超时断开连接。
4. 代理/负载均衡器主动断开:健康检查失败或会话粘性未配置。
1. 使用具体 IP 替代主机名(如 127.0.0.1)。
2. 升级 websocat 至 ≥v1.13(支持并发连接多 IP)。
3. 增加超时时间
- Nginx:proxy_read_timeout 3600s; proxy_connect_timeout 60s;
- 网关:配置 spring.cloud.gateway.httpclient.connect-timeout 等。
4. 启用会话保持:负载均衡器配置 Sticky Session。
SSL handshake failed
SSL 握手失败
1. 证书不受信任:服务端使用自签名证书或内部 CA 证书,客户端系统不信任。
2. 证书域名与请求域名不匹配:证书 CN/SAN 不包含访问的域名。
3. TLS 版本/加密套件不兼容:服务端强制 TLS 1.3,客户端仅支持 TLS 1.2。
4. 代理 SSL 验证问题:Nginx 向后端转发时启用 proxy_ssl_verify 但未配置 CA。
1. 跳过验证(仅测试)websocat -kcurl --insecure
2. 指定 CA 证书websocat --ca /path/to/ca.pem
3. 调整服务端 TLS 配置:放宽 ssl_protocolsssl_ciphers
4. Nginx 代理关闭后端验证proxy_ssl_verify off;(或配置正确的 CA)。
连接建立后立即断开
(无显式错误)
1. 空闲超时:代理/负载均衡器的空闲超时(默认 60s)太短。
2. 心跳缺失:未发送 Ping/Pong 保持连接。
3. 后端应用逻辑主动关闭(如认证过期)。
1. 增大代理超时(如上)。
2. 定期发送 Ping 帧:客户端设置 --ping-interval,或应用层心跳。
3. 检查后端日志,确认是否有业务层面关闭原因。


参考: