2026-09-22 08:31:12
有了AI以后,这个世界更加抽象了~
找研发看问题,全用AI给你搞,让AI出方案,也不自己验证,直接AI出个文档方案就让去生产操作

没有AI不会干活了吗?
还有更狠的,让我用AI改他应用初始化SQL去适配达梦......

AI很强大,确实让工作提效了很多,但是也让很多人产生了 AI是万能的 幻觉。
有了AI以后,活越干越多,也越干越累了,AI在这里好像并没有解放生产力,反而快成为了奴役禁锢人的工具,真是抽象啊!
2026-09-18 14:28:41
JDBC(Java Database Connectivity)是 Java 应用程序与数据库的接口规范,旨在让各数据库开发商为 Java 程序员提供标准的数据库应用程序编程接口(API)。JDBC 定义了一个跨数据库、跨平台的通用 SQL 数据库 API。
DM JDBC 驱动程序是 DM 数据库的 JDBC 驱动程序,它是一个能够支持基本 SQL 功能的通用应用程序编程接口,支持一般的 SQL 数据库访问。
通过 JDBC 驱动程序,用户可以在应用程序中实现对 DM 数据库的连接与访问,JDBC 驱动程序的主要功能包括:
建立与 DM 数据库的连接
发送 SQL 语句到数据库
处理并返回语句执行结果
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
host、port 作为连接属性,此时必须按照JDBC连接串属性表中说明进行设置,且属性名称大小写敏感。
格式:
jdbc:dm://[/schemaName][?propName1=propValue1][&propName2=propValue2][&…]…
示例:
jdbc:dm://?host=192.168.0.96&port=5236
使用自定义服务名,可指定多个数据库节点。
格式:
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 连接串优先。
所有时间参数可使用(h:小时,m:分钟,s:秒,ms:毫秒),不带单位时默认 ms,有效值范围 0~2147483647。缺省值 5 分钟。
| 属性 | 说明 | 必须 |
|---|---|---|
| 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 | 否 |
| 属性 | 说明 | 必须 |
|---|---|---|
| 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) | 否 |
| 属性 | 说明 | 必须 |
|---|---|---|
| rwSeparate | 是否启用读写分离(0~5) | 否 |
| rwPercent | 分发到主库的事务占比(%) | 否 |
| rwAutoDistribute | 事务分发是否由 JDBC 自动管理 | 否 |
| rwHA | 是否开启读写分离高可用 | 否 |
| rwStandbyRecoverTime | 备库故障恢复检测间隔(ms) | 否 |
| allowRange | 允许动态负载均衡误差范围(%) | 否 |
| 属性 | 说明 | 必须 |
|---|---|---|
| sslFilesPath | SSL 加密文件路径 | 否 |
| sslKeystorePass | SSL 加密文件口令 | 否 |
| uKeyName | Ukey 文件路径 | 否 |
| uKeyPin | Ukey 口令 | 否 |
| OsAuthType | 操作系统认证类型(0~4) | 否 |
| loginCertificate | 登录加密公钥路径 | 否 |
| localEncrypt | 是否启用用户名密码本地加密 | 否 |
| userEncrypt | 消息通信是否加密用户名 | 否 |
| 属性 | 说明 | 必须 |
|---|---|---|
| logDir | 日志文件生成目录 | 否 |
| logLevel | 日志级别:off/assert/error/warn/sql/info/DEBUG/all | 否 |
| logFlushFreq | 日志刷盘频率(s) | 否 |
| statEnable | 是否启用状态监控 | 否 |
| statDir | 状态监控信息输出目录 | 否 |
| statFlushFreq | 状态监控写文件刷盘频率(s) | 否 |
| statSlowSqlCount | 统计慢 SQL top 行数 | 否 |
| statHighFreqSqlCount | 统计高频 SQL top 行数 | 否 |
| 属性 | 说明 | 必须 |
|---|---|---|
| mppLocal | 是否 MPP 本地连接 | 否 |
| mppOpt | MPP 集群批量插入优化 | 否 |
| keyWords | 用户关键字标识 | 否 |
| dmsvcconf | URL 属性配置文件路径 | 否 |
| dbAliveCheckTimeout | 检测数据库存活的连接超时(ms) | 否 |
| prepareOptimize | 是否对预编译 SQL 做优化 | 否 |
| serverOption | 附加信息,格式:{key=value,...} | 否 |
| reconnectErrors | 需要重连的异常错误码 | 否 |
| errMap | DM 错误码和 Oracle 错误码映射 | 否 |
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 修改路径。
| 配置项 | 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 | √ | × | × | √ |
dm_svc.conf 配置文件的内容分为全局配置区和服务配置区。全局配置区在前;服务配置区在后,以“[服务名]”开头。其中,服务名配置项可以在全局配置区,也可以在此服务名对应的服务配置区之前,即在其对应的“[服务名]”之前。
dm_svc.conf 格式如下:
|
# 全局配置区
参数配置……
# 服务配置区
[服务名1]
参数配置……
# 服务配置区
[服务名2]
参数配置…… |
服务配置区中的配置优先级高于全局配置区。全局配置区的配置项影响所有会话,除非会话所属服务配置区中单独进行了配置,因此对于全局配置区配置项的设置修改需要慎重。
如果对 dm_svc.conf 的配置项进行了修改,需要重启客户端工具,修改的配置才能生效。另外,如果 dm_svc.conf 配置文件中包含中文,则必须保证该配置文件的编码与客户端编码一致。
场景一
配置常规环境的 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) |
| 值 | 说明 |
|---|---|
| 0 | 优先 PRIMARY → NORMAL → STANDBY |
| 1 | 只连接主库 |
| 2 | 只连接备库 |
| 3 | 优先 STANDBY → PRIMARY → NORMAL |
| 4 | 优先 NORMAL → PRIMARY → STANDBY(缺省) |
启用读写分离时(rwSeparate ≠ 0),loginMode 不生效
| 值 | 说明 |
|---|---|
| 0 | 关闭连接 |
| 1 | 异常时自动切换到其他库,抛 SQLException 通知上层 |
| 2 | 配合 epSelector=1,节点恢复后切回原节点 |
| 4 | 保持各节点会话动态均衡 |
可组合使用,如 3 表示 1+2
| 值 | 说明 |
|---|---|
| DW | 主备 |
| RW | 读写分离 |
| MPP | 大规模并行处理集群 |
| DPC | 分布计算集群 |
| DSC | 数据共享集群(需配合 autoReconnect=2、epSelector=1) |
| NORMAL | 单机库(缺省) |
| 值 | 说明 |
|---|---|
| 0 | 不启用 |
| 1 | 启用,备库由系统自动决定 |
| 2 | 启用,备库由客户端选择 |
| 3 | 启用,备库由服务器通知 |
| 4 | 启用,只连接事务一致性备库 |
| 5 | 启用,为 GATEWAY 准备 |
| 值 | 说明 |
|---|---|
| 0 | 依次选取不同节点,连接均匀分布 |
| >0 |
2026-09-07 19:36:36
问题现象:3节点的Patroni + PostgreSQL集群 postgresql-0 节点异常,一直处于starting状态,Lag in MB 显示 unknown,postgresql-0使用的pv、etcd集群排查都没有问题,集群状态及日志报错如下图所示,输出排查步骤及恢复方案



基于您提供的截图和描述,这是一个典型的 Patroni + PostgreSQL 集群中备库(Replica)启动失败/卡住 的问题。
状态异常:postgresql-0 节点在 patronictl list 中显示为 starting,且 Lag in MB 为 unknown。这意味着 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 connections 和 Lag in MB: unknown 的根本原因。
进程状态 (ps -ef):
PID 61 是主进程 postgres -D ...。
关键进程 PID 64:postgresql: startup recovering 00000008E000000000000077。
结论:您的 postgresql-0 节点当前正处于 WAL 日志回放(Crash Recovery / Standby Recovery)阶段。它正在努力应用 WAL 日志以达到一致性状态。在 startup 进程完成之前,数据库无法接受连接,Patroni 也就只能一直显示 starting。
结合您第一张图的信息:
集群整体延迟大:健康的备库 postgresql-1 也有 752 MB 的延迟。这说明主库写入量很大,或者网络/磁盘 IO 存在瓶颈。
Timeline (TL) 差异巨大:Leader (postgresql-2) 的 TL 是 266,而 postgresql-1 的 TL 是 135。这说明集群历史上发生过非常多次的故障切换(Failover)。
当前恢复位置:postgresql-0 正在恢复的 WAL 段是 00000008E000000000000077(注意前缀 00000008 代表 Timeline 8)。
核心矛盾:postgresql-0 本地的数据还停留在非常古老的 Timeline 8,而当前的 Leader 已经在 Timeline 266 上了。
PostgreSQL 备库在启动时,必须先从本地 WAL 或归档中把历史日志回放完,直到追上当前的 Timeline。由于跨度太大(从 8 到 266),它可能需要回放海量的 WAL 日志,或者正在尝试寻找跨越这些 Timeline 的历史记录,导致启动过程极其漫长,看起来像是“卡住”了。
请按顺序在 postgresql-0 (172.24.0.107) 节点上执行以下操作:
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)。
FATAL 或 PANIC:任何致命错误。
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 进程已经完全不存在。
尝试直接用 pg_isready 或 psql 连接本地 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。
根据上述排查结果,选择对应的方案:
如果 PG 日志显示正在 redo 且没有报错,只是速度慢(特别是看到 pg-1 也有 700多MB 延迟,说明集群整体写入压力大或网络带宽受限):
耐心等待:不要强制重启。让 PG 完成恢复。
监控进度:观察日志中 LSN 的变化。
优化:如果长期如此,考虑增加 max_wal_senders,调整 wal_receiver_status_interval,或检查磁盘 IO 性能。
如果日志显示 PANIC,或者长时间(超过 30 分钟)无任何进展,且 psql 始终报 starting up,则需要重建该副本。由于是 Patroni 集群,重建非常安全。
针对这种情况,有两种处理策略。强烈建议直接采用方案 B,因为等待一个跨越 200 多个 Timeline 的备库自行追平几乎是不现实的,且极易出错。
如果您刚刚重启该节点不到 10-20 分钟,可以观察一下 PostgreSQL 的原生日志,看 LSN 是否在跳动。
# 查看 PG 日志,确认是否在持续回放 tail -f /home/postgres/pgdata/pgroot/data/log/*.log # (路径根据您的实际 pg_log 位置调整)
如果日志里不断有 redo at ... 且数字在变大,说明它在干活。但考虑到 TL 差距,不建议死等。
既然 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 leader、running pg_basebackup 的日志。
查看集群状态:
patronictl list
状态变化预期:
starting (正在克隆)
running (克隆完成,开始流复制)
Lag in MB 会从 unknown 变成一个具体的数字,并逐渐减小到 0 或很小的值。
TL (Timeline) 会变成 266,与 Leader 一致。
您遇到的不是故障,而是备库数据太旧,正在艰难地进行跨 Timeline 恢复。由于落后太多(TL 8 vs TL 266),自行恢复效率极低。清空数据目录让 Patroni 重新克隆是解决此问题的最佳实践。
2026-08-25 10:02:12
前面的几篇文章中,我分享了一些通用的沟通方法,如尊重、倾听和情绪控制等。接下来的几篇文章中,我将从如何与员工沟通、如何与客户沟通,以及如何与老板沟通这几个角度,和你聊聊这些沟通方法具体应该如何应用。
作为一名团队 Leader,你首先应该学会如何与团队成员进行有效沟通,因为它是实现管理效果的必要手段和有效途径。但如何拥有这个基本功呢?我来分享一下我的经验。
我在汤森路透工作的时候,曾经参加过一个管理上的培训课程。这个培训课程的第一课就是教这些管理者如何在沟通中引导员工,而不是给员工灌输自己的想法。课程里强调,管理者要想尽一切办法让员工自己思考问题,想出答案;而不是灌输,什么事儿都是自己在想,自己讲给员工听。员工不想,你怎么说,他都很难把你的话理解到位,也就是说你一定要让他自己把事情想出来。
这有点儿像电影《盗梦空间》说的,你应该在思想里埋下一个种子。我们要干的就是在员工的思想里埋一个种子,让它生根发芽。但这要怎样实现呢?
答案就是管理者要学会问问题,问员工怎样做。假如员工给出了一个方案,但不巧,可能由于他考虑得不全面,或者由于他不知道某些情况,不是你想要的答案。这时,该怎么办呢?
你可以说,如果这么做的话,会有一个什么问题,而这个问题很重要,如何解决?然后,他会给出解决这个问题的方法。但这么做又会带来另一个问题,直到把他逼到你想要的答案上去。
如果每次遇到问题,都让他自己想答案,次数多了以后,他会觉得自己的参与感越来越多。最后,他会觉得是他用他的观点说服了你。尽管这就是你想要的答案,但你还是要假装被说服。这样他会很开心的,会有一种参与感。然后,在执行这件事儿的时候,也会更加卖力,更加有激情。他会觉得自己在实现自己的想法,而且自己的想法是对的。
作为 Leader,你要记住,永远不要给员工答案,要让员工给你答案,而且不要只给一个答案,一定要给多个答案。然后让他们比较这些答案,促使他们深入地进行思考。这不是在让员工做问答题,其实是在给员工成长机会,促进他们的成长。
永远不要跟员工说,我给你一个任务,这个任务两星期完成。要让他来说,这个任务需要多久能完成。并要求员工提供多种执行方案,不要只给一个时间。你快点做怎么做,慢点做怎么做,是否还有其他方案。一定要员工自己去做计划,去思考。反之,如果你什么都想了,只让员工去执行,那么他就不思考了,而且有时还会生出一些怨念。比如抱怨领导这样安排不合理,那个执行方案有问题等。带有情绪的执行,势必会产生不够好的执行结果。
但根据我的观察,喜欢给答案的管理者还是挺多的,他们总是习惯性地给员工答案,而不善于挖掘员工的实力和潜力。我觉得这是世界上最 Low 的管理模式了,是家长式、保姆式的管理。实际上,你的员工都是专业人才,你应该充分信任他们,并且想方设法激发他们的主观能动性,促使他们发挥自己的能力,积极地为你贡献答案,从而保持团队的活力和创造力。
倾听意味着在听他人讲话的时候,不让自己的想法扭曲别人传递的信息。你要做到毫无偏见,才能全面理解对方的信息。倾听不只是听或者听见,需要你用心聆听别人讲话,而不是只听自己想听到的内容。如我在《沟通方式及技巧》一文中提到的,倾听可以让员工感觉到自己被尊重,所以他们会乐意分享更多的信息。
学会倾听不仅可以帮你拉近和员工的距离,还可以让你更加了解员工。我在汤森路透工作的时候,团队里有两个刚毕业的小伙子。一个来自农村,一个来自城市。来自农村的小伙子是家里老大,家里条件不太好,不仅要挣钱还自己的助学贷款,还要帮家里还外债。而那个来自城市的小伙子是家里老五,上面是四个姐姐,家里条件也相对比较好。不用去想人物性格,从这个背景里,就能大致猜出这两个人的差距。果不其然,有四个姐姐的小伙子,抗压能力相当低,觉得什么活儿都有难度,什么都适应不了。
而要还外债的小伙子抗压能力相当高,没事儿就来跟我说,你把什么任务都给我,我什么都能搞定。经过几年的努力,他终于把家里的外债还干净了,然后特别高兴,请我吃饭。我说,你不用感谢我,要感谢你自己,是你自己做得多。通过这个例子,我想说明,通过倾听更多地了解员工,了解他们的生长环境和背景,可以帮你对每个员工建立更加合理的预期,从而更好地进行任务分配和人员管理。
所以,外企一般都会要求经理和员工有周期性的一对一交谈,就是为了及时了解员工的各种动态和想法。
共情,又被称为同理心,或者换位思考,它指的是站在对方立场设身处地思考问题的一种方式。换句话说,在人际交往过程中,你需要能够体会他人的情绪和想法、理解他人的立场和感受,并站在他人的角度思考和处理问题。
比如,有团队成员要辞职了,你要怎样跟他谈呢?你肯定要找他谈感情。我们一起共事这么久,你要走了,我们一起回忆回忆过去。然后说,没关系,你看你要离开了,有没有什么我可以帮你的?不要强行让对方留下来,要多谈感情,多回忆一下,多听听对方的诉说。当他回想起过去一起同甘共苦的日子,难免会心生留恋,也许会回心转意的。当然,如果你并不能把他留下来时,不如大度一些,帮他看看他要去的另外一家公司是否是正确的选择,而且你还可以给他介绍更好的地方。既然留不下来,就索性为他介绍更好的地方。这样做至少还能引发他一些思考,“我都要离开了,我老板对我还这么好,我以后能不能找到这么好的老板?”
这里的关键是,当对方开始想离开你了,你千万不要指责和教育对方,而一定要站在对方的角度来思考问题,理解对方,真心对对方好。晓之以理,动之以情。
员工来跟你聊的,通常都是细节问题。这时,你可以耐心地跟员工沟通,并共同来寻找解决问题的方案。但有的时候涉及到公司的一些问题时,你自己也解决不了,那么你该怎样跟员工聊呢?比如,公司因为战略方向调整,想要砍掉你负责的业务,你和你团队都需要转到新的业务线上。
你肯定不能跟自己的“弟兄们”说,公司混蛋,把我们这么好的业务给砍掉了。作为管理者,你应该知道,没有完美的公司,任何公司都存在这样那样的问题。你需要有更高的维度来看待这个问题,来给员工做出解释,让他们既能理解公司的决定,又能保持动力转到新的方向上。
对于这样的问题,你首先应该肯定员工过去的努力以及取得的成绩,明确说明虽然业务被砍,但是我们的技术积累还在,这是我们谋求未来发展的基石。同时,帮助员工看清公司新的战略方向会给全公司的人带来什么前景,新的业务方向如何更能发挥出大家积累的经验和能力。在成功安抚人心的同时,引发大家对新业务方向的兴趣,从而更有利于帮助团队后续过渡到新业务方向上。
当然,在讲这个事情的时候,千万不要太过了,还是要跟员工共情一下,也要表达出自己的不满,这样让员工觉得你是跟他们站在一起的,而不是跟公司站在一起的,后者无疑会引发你和大家的对立。这里的沟通思路是这样的:“公司的这个决定,我也有点难理解,我们这么辛苦做了这么多,没想会这样……但是我们做的事是很牛的,我们这个团队是强大的,强大到对于这样的打击都是没有问题的。这个世界就是这样的不完美,但是我们还是要去奋斗,不然就更不完美了……接下来,无论发生什么,我们都要一起杠!” 也许,这么说也没什么用,但至少,在困难到来时,你可以让大家的心更近了。
反馈是一种非常重要的沟通形式,对于确保团队的正常运转十分关键。但有时候员工没有反馈的意识,或者不愿意反馈,你应该怎么办?这时,你应该建立一些反馈机制。比如,在我目前的团队里面就在用“1-2-3 反馈机制”。
不管你遇到什么问题,如果自己在那儿憋一个小时找不到解决方案,或者说没有任何思路,就要反馈到高级工程师这边来。
如果跟高级工程师在一起两个小时内,找不到任何解决方案或者没有思路,那么就要反馈到一线 leader。
如果一线 leader、高级工程师,花了三个小时,依然找不到方案,那么这个事就可能是个大事了,要向上级反馈了。
这么做,就是为了确保一个大问题,在一天之内能够上升到管理层。然后管理层可能会寻求更牛的人或是从外界获取帮助,以使得问题尽快能够得到解决。
这个反馈机制不仅能确保问题及时被反应出来,并及时得到解决,而且能够帮团队节约大量的时间和精力,对团队来说是种很好的正向鼓励,属于正反馈。
之前我一直强调,正反馈的重要性。在这个场景下,无疑也是如此。试想一下,你和你的“兄弟们”逢山开路,遇水搭桥,一路凯歌的样子,是不是很酣畅?这便是反馈机制的威力了,它会潜移默化地在团队中形成一种“解决问题”的文化,让我们在发现问题的第一时间正视问题,拼尽全力来解决问题,并能从中享受到“搞定问题”的成就感,从而形成正向循环。
除了对工作中问题的反馈,反馈还可以存在与很多其他方面,你完全可以结合团队的实际需求拟定出各种合适的反馈机制。对于任何反馈机制的建立,你只需要记住两点:一是及时反馈;二是能够形成正向循环。
总结一下今天的内容。我分享了我与员工沟通时经常用到的几大法宝:引导、倾听、共情、高维和反馈。
引导,用提问的方式,“倒逼”员工找到答案,从而提高员工的参与感和成就感。
倾听,心态平和,毫无偏见,全面接收和理解对方的信息,而不是只听自己想听的信息。
共情,换位思考,站在对方立场设身处地思考和处理问题,动之以情,晓之以理。
高维,提升自己的格局观,能从全局利益、长远利益思考问题,解决问题。
反馈,建立反馈机制,及时发现问题、解决问题,形成正向循环。
下篇文章中,我将继续就如何与员工沟通这个话题进行讨论,主要探讨如何进行一对一会议、如何做绩效沟通、如何定位性格特殊的员工、如何挽留离职员工、如何辞退员工等问题。敬请期待。
来源:《左耳听风专栏:高效沟通》
2026-08-16 17:39:13
最近有个小程序项目客户在并网时有个需求:需要在他们的Spring Cloud Gateway 公网网关开个入口,打到自建的Nginx代理转发到后端服务,需要同时能支持 http 和 ws 的请求。整体访问链路从公网 → Spring Cloud Gateway → Nginx → 应用服务。主要的工作是Spring Cloud Gateway 和 Nginx 的路由配置和整个链路的联调,mark一下。
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
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;
}
}
}
如果后端 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)的流量。
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
应正常返回业务数据。
在 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,可观察请求被哪个路由匹配。
先验证后端服务是否正常,然后排查 Nginx 配置,再访问 Gateway 服务(绕过 Nginx),确认 Gateway 配置。
测试验证后端服务 ws 连接是否正常
再通过 Nginx 访问:https://test.com.cn/ws/...
访问 Gateway(绕过 Nginx):https://网关IP:端口/ws/...
在配置 WebSocket 代理或客户端时,最常见的错误往往源于反向代理(如 Spring Cloud Gateway、Nginx)的配置缺失、网络环境干扰或 SSL/TLS 证书问题。下表汇总了典型错误现象、可能原因及对应解决方案,快速定位问题。
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
|
1002 (PROTOCOL_ERROR) 协议错误 |
1. 反向代理未转发升级头:Nginx/网关未显式传递 Upgrade 和 Connection 头。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. 缺少必要请求头:服务端强制要求 Origin、Host 等头部,但客户端未提供。3. 路径匹配错误:代理转发时路径重写( StripPrefix)导致后端无法识别路由。4. 请求体过大(非标准):某些实现会限制握手请求的头部大小。 |
1. 检查并正确编码 URL:对查询参数进行 urlencode。2. 手动添加头部:使用 -H "Origin: https://example.com" 或 -H "Host: ..."。3. 调整路径重写策略:在网关中谨慎使用 StripPrefix,确保转发后的路径与后端路由匹配。4. 查看后端日志:定位具体拒绝原因。 |
|
426 (Upgrade Required) 需要升级 |
1. 代理未传递 Upgrade 头:Nginx/网关默认不转发 Upgrade 和 Connection 头。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 -k 或 curl --insecure。2. 指定 CA 证书: websocat --ca /path/to/ca.pem。3. 调整服务端 TLS 配置:放宽 ssl_protocols 和 ssl_ciphers。4. Nginx 代理关闭后端验证: proxy_ssl_verify off;(或配置正确的 CA)。
|
|
连接建立后立即断开 (无显式错误) |
1. 空闲超时:代理/负载均衡器的空闲超时(默认 60s)太短。 2. 心跳缺失:未发送 Ping/Pong 保持连接。 3. 后端应用逻辑主动关闭(如认证过期)。 |
1. 增大代理超时(如上)。 2. 定期发送 Ping 帧:客户端设置 --ping-interval,或应用层心跳。3. 检查后端日志,确认是否有业务层面关闭原因。 |
参考: