尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
PostgreSQL 流复制与高可用切换:主从延迟、同步提交与 Spring Boot 读写分离的边界
PostgreSQL 流复制与高可用切换主从延迟、同步提交与 Spring Boot 读写分离的边界1. 从一个线上困惑说起刚下单为什么查不到假设你在做一个订单系统。用户在 App 上点了“提交订单”接口返回成功页面跳转到订单详情结果提示“订单不存在”。用户再点一次又出现了两条订单。日志里写库明明是成功的查库却查不到。这个现象背后最常见的原因不是事务没提交而是写请求和读请求被路由到了两台不同的数据库实例写到了主库读走到了还没有追上主库的备库。这就是 PostgreSQL 流复制和高可用方案要解决的核心矛盾我们希望用多台数据库提升吞吐和容灾能力但多台数据库之间的数据不是瞬时一致的。理解这套体系不是先记住wal_level、synchronous_commit这些参数而是先想清楚“一次写入从产生到能被别的节点读到中间经历了哪些环节每个环节会带来什么代价”。本文面向正在使用或准备使用 PostgreSQL 的 Java 后端开发者、数据库工程师和架构师。我们会先建立整体框架再逐步展开流复制、同步提交、主从延迟、故障切换和读写分离最后用 Spring Boot 示例把边界落到实处。2. 一句话模型与全局框架先记住一个最小模型PostgreSQL 的主库先把变化写进自己的预写日志WALWrite-Ahead Log再通过一条常驻连接把日志流式发给备库备库不断重放日志来保持数据接近主从延迟就是备库“重放到哪”和主库“写到哪”之间的时间差或字节差。这句话里有四个关键角色主库primary、备库standby、WAL 日志、复制连接。一次写入的完整流转顺序可以用下面的流程图表示客户端写入请求 | v [主库 backend 进程] | 1. 写 WAL 到本地 wal buffer v [WAL writer / commit] | 2. WAL 落盘到 pg_wal v [WAL sender 进程] -- 3. 通过网络流式传输 WAL -- [备库 WAL receiver 进程] | | | v | 4. 写入备库 pg_wal | | | v | [备库 startup 进程] | 5. 重放 WAL 到数据文件 v [主库返回提交成功]看这条链路时要抓住两个“进度点”主库的 WAL 写入位置通常看pg_current_wal_lsn()和备库的重放位置通常看pg_last_wal_replay_lsn()。它们之间的差值就是延迟的量化来源。后面讨论同步提交时我们关心的是“主库在哪个进度点才敢向客户端返回成功”讨论读写分离时我们关心的是“读请求走到备库时备库重放到了哪个进度点”。这两个问题的答案不同正是很多误解的根源。3. 流复制到底复制了什么3.1 WAL 是复制的最小单位PostgreSQL 不做逻辑上的“把这一行复制过去”而是把物理层面的 WAL 记录发过去。WAL 记录描述的是“在哪个数据页做了什么修改”例如在某个页面偏移处写入新元组、更新事务状态等。备库收到这些记录后像做一次重放一样把相同的数据页修改应用到自己的数据目录。这个设计的好处是复制链路简单、对主库业务代码透明无论是INSERT、UPDATE、CREATE INDEX还是VACUUM最终都会产生 WAL都会被复制。代价是备库必须是主库的物理副本数据目录结构一致不能只选几张贴过去。下面的命令配置就是一个最小可用的流复制环境。# 主库 postgresql.conf 关键配置 wal_level replica max_wal_senders 10 wal_keep_size 512MB listen_addresses * # 主库 pg_hba.conf 允许备库使用复制连接 host replication repl_user 0.0.0.0/0 scram-sha-256在主库执行创建复制用户和查看复制状态CREATEROLE repl_userWITHREPLICATIONLOGIN PASSWORDrepl_pass;-- 在主库查看当前 WAL 写入位置和备库接收位置SELECTclient_addr,state,sent_lsn,write_lsn,flush_lsn,replay_lsn,pg_wal_lsn_diff(pg_current_wal_lsn(),replay_lsn)ASreplay_lag_bytesFROMpg_stat_replication;预期结果是每个已连接的备库一行state通常是streamingreplay_lag_bytes表示备库重放落后主库的字节数。这个查询是后面所有延迟讨论的起点没有测量就没有延迟判断。3.2 备库的两种启动方式备库第一次建立时通常用基础备份base backup把主库的数据目录复制过去然后进入持续重放 WAL 的状态。备库的standby.signal文件告诉 PostgreSQL“我是一个备库请持续恢复 WAL 而不是结束恢复”。备库启动后会通过primary_conninfo连接主库并请求从指定位置开始发送 WAL。在实际部署里我们常常还会在中间加一层 WAL 归档或复制槽replication slot。复制槽能保证主库不会在备库确认接收前删除备库还需要的 WAL避免因为网络抖动导致备库追不上、需要重新做基础备份。代价是如果备库长期离线主库的pg_wal会持续增长所以必须配合磁盘监控。3.3 级联复制与多备库当主库连接数或网络出口有限时可以让一个备库再作为其他备库的上游这叫级联复制。它的数据流是主库 → 中间备库 → 下游备库。级联复制减轻了主库的发送压力但增加了一级延迟而且中间备库一旦故障下游备库也会中断。选择级联还是让所有备库直连主库本质上是在主库发送压力、延迟层级和故障域之间做取舍。4. 同步提交主库什么时候敢返回成功4.1 异步提交与同步提交的差别默认情况下PostgreSQL 是异步提交主库把 WAL 写到本地磁盘后就可以向客户端返回提交成功不需要等备库确认。如果此时主库突然宕机且无法恢复那些还没来得及发送到备库的 WAL 就丢失了切换后备库会丢掉最新的一小段数据。同步提交则要求主库在返回成功前至少等待指定数量的备库确认收到更严格地说确认已经写入或刷盘WAL。配置方式是在主库设置# 主库 postgresql.conf synchronous_commit on # 默认级别等待本地 WAL 刷盘 synchronous_standby_names FIRST 1 (standby1, standby2)这样主库会等待standby1或standby2中任意一台确认才向客户端返回提交成功。这带来了一个直接结论同步提交不是在优化延迟而是在用延迟换数据可靠性。4.2 几个容易混淆的同步级别synchronous_commit有几个取值它们影响的是“本地 WAL 刷盘”和“等待备库”两个维度取值本地 WAL 刷盘等待备库典型代价off不保证立即刷盘不等待最快但崩溃可能丢最近若干事务local等待本地刷盘不等待单机安全主库故障可能丢未同步数据on等待本地刷盘等待同步备库确认最常用延迟受备库和网络影响remote_write不等待本地刷盘等待备库写入操作系统缓存备库崩溃可能丢主库崩溃也可能丢remote_apply等待本地刷盘等待备库完成重放读备库一致性最好延迟最大最容易误解的是remote_write和remote_apply。前者只要求备库把 WAL 写进它的操作系统缓存后者要求备库已经应用到数据文件也就是读备库已经能看到这笔数据。如果你的业务需要“写成功后立刻从备库读到”remote_apply更接近目标但它对延迟更敏感通常要配合本地网络和低负载备库。4.3 同步提交的失败模式同步提交最大的风险不是慢而是“同步备库全挂”时主库会阻塞。synchronous_standby_names指定必须等待某个备库如果那个备库失联已提交事务会一直等待直到超时或配置被调整。这在高可用设计里非常危险你为了数据可靠把主库变成了必须依赖备库才能服务。工程上的常见做法是配置多个候选备库并用FIRST 1 (...)或ANY 1 (...)指定“任意一个确认即可”。这样单台备库故障不会阻塞主库。更激进的做法是使用synchronous_commit on配合外部仲裁组件或者接受异步提交、用备份恢复应对极小概率的数据丢失。没有绝对正确的答案关键是明确业务能承受哪种失败。5. 主从延迟的量、因、测5.1 延迟有两种不要混着看第一种是传输延迟主库发送了 WAL但备库还没收到或还没写入。第二种是重放延迟备库已经收到了 WAL但还没把它应用到数据文件。前者影响的是“数据有没有离开主库”后者影响的是“读备库能不能看见”。pg_stat_replication里的write_lsn、flush_lsn、replay_lsn正好对应这条链路上的三个进度点。第二个重要概念是“延迟是变化的”。网络抖动、备库磁盘慢、备库上长查询与 WAL 重放抢资源、主库批量写入产生大事务都会让延迟在几毫秒到几分钟之间波动。所以判断延迟不能只看某一瞬间而是看持续时间、峰值和恢复速度。5.2 如何测量延迟最常用的方法是在主库查询 LSN 差值-- 在主库执行查看每个备库的字节延迟SELECTapplication_name,pg_wal_lsn_diff(pg_current_wal_lsn(),replay_lsn)ASreplay_lag_bytes,replay_lagFROMpg_stat_replication;replay_lag返回的是一个时间间隔表示备库重放落后主库多久。很多团队会把它作为告警指标。但要注意replay_lag在当前没有新 WAL 时可能显示为空或零并不代表备库一定完全一致更稳妥的方式是结合字节延迟和应用层时间戳探针。应用层探针的做法是在主库写入一条带时间戳的心跳记录在备库读取最新记录用两者时间差衡量端到端延迟。它更贴近业务真实感受但需要额外的表和清理逻辑。5.3 延迟太大的常见原因现象可能原因排查方向replay_lag 持续升高备库重放速度跟不上写入速度备库 CPU、磁盘 I/O、是否有长查询sent_lsn 落后 current_wal_lsn网络带宽不足或 WAL sender 阻塞网络吞吐、主库发送进程状态延迟突然飙升后恢复主库大事务或批量写入是否执行了批量 UPDATE/DDL只有某个备库延迟大该备库资源不足或配置落后单机监控、级联复制层级这里最容易误解的是“延迟大一定是网络问题”。实际排查中备库磁盘写入能力、max_wal_size、检查点频率、以及备库上并行执行的分析查询都可能成为瓶颈。尤其是备库上有长事务或长查询时PostgreSQL 需要保留旧版本数据这会拖慢清理并间接影响重放效率。6. 故障切换从主库不可用到恢复服务6.1 切换的判定与步骤故障切换要回答三个问题怎么判断主库真的不可用、谁来决定切换、切换后如何让应用和旧主库接受新状态。一个典型的手动切换流程如下1. 监控发现主库连接失败或心跳超时 2. 确认不是网络分区导致的误判 3. 选择一个 replay_lsn 最靠前的备库 4. 在该备库执行 pg_ctl promote使其成为新主库 5. 将其他备库指向新主库继续复制 6. 应用侧切换写连接串到新主库 7. 旧主库恢复后作为备库重新加入必要时先做基础备份这里最关键的是第 4 步之前的“确认”和第 6 步的“应用切换”。如果监控误判把还活着的旧主库切走就可能出现双主写入数据分叉几乎无法自动修复。所以生产上通常不会让单个监控指标直接触发切换而是结合多数派仲裁、人工确认或专门的 HA 组件。6.2 切换后应用连接怎么变PostgreSQL 客户端连接一旦建立不会因为服务端角色变化而自动重定向。也就是说即使备库被提升为主库应用里指向旧主库的 JDBC 连接池仍然会失效或继续报错必须让应用感知新的写入地址。常见方式有三种把主库地址放在负载均衡或虚拟 IP 后面使用支持拓扑感知的连接池或者在应用里配合配置中心做动态切换。这三种方式都要求应用能处理“写连接短暂失败并重试”的情况。重试必须是幂等安全的否则一次提交成功但网络超时重试可能生成两条订单。这个问题在读写分离场景下更突出我们下一节展开。6.3 切换不等于零丢失异步复制下主库宕机时尚未发送到备库的 WAL 会丢失切换后这部分事务就没了。同步复制可以把丢失窗口缩小到零对已确认的事务而言但会引入阻塞风险。还有一点常被忽略切换后原主库可能带着“未来”的数据重新加入如果它曾经接受过未同步的提交就必须先处理这部分分歧数据通常通过重新做基础备份解决。7. Spring Boot 读写分离的边界7.1 读分离能解决什么不能解决什么读写分离把写请求发到主库把读请求发到备库从而把报表、列表、搜索这类读负载从主库上分走。它不能解决的是“写后立刻读”的一致性问题因为备库可能还没重放完。它也不能自动处理备库故障如果读请求路由到已经挂掉的备库应用需要降级到主库或返回可接受的错误。因此读写分离的设计重点不是“怎么配两个数据源”而是“哪些读能容忍延迟哪些不能”。可以先把业务读分成三类强一致读刚写就要读例如订单详情跳转、单调读同一个用户至少不会读到更旧的数据、最终一致读列表、统计、报表。前两类通常应走主库或采用粘性策略第三类才适合走备库。7.2 三种路由策略对比策略实现方式一致性复杂度适用场景强制主库读写后一段时间内读主库强低订单详情、支付结果会话粘性同一会话内读写都走主库较强中用户中心、后台管理延迟感知路由根据延迟指标决定是否走备库最终一致高报表、搜索、列表延迟感知路由听起来很美但实现时要小心延迟指标本身有采集延迟而且备库可能在路由决策后瞬间变慢。更稳妥的工程做法是“默认走主库明确标注可容忍延迟的读走备库”而不是“默认走备库出问题再回主库”。7.3 写后读的一个具体修复回到开头的订单问题。一个直接修复是订单创建成功后把订单 ID 或用户 ID 写入一个短期的“强制主库读”标记后续该用户的订单详情查询在标记有效期内走主库。这个标记可以放在本地缓存或 Redis 中过期时间略大于正常复制延迟。它不能替代正确的一致性设计但能把用户可见的异常窗口压到很小。8. 完整示例一用 Docker 搭一套可观察的主从复制8.1 目标与前置环境目标在一台机器上用 Docker 启动一个主库和一个备库配置流复制并能观察到pg_stat_replication的延迟字段。前置环境已安装 Docker 和 Docker Compose主机有至少 2GB 空闲内存。下面是最小配置。它省略了生产需要的持久化目录和监控但足以验证复制链路。# docker-compose.ymlservices:pg-primary:image:postgres:16container_name:pg-primaryenvironment:POSTGRES_PASSWORD:postgresPOSTGRES_USER:postgresports:-5432:5432command:-postgres--c-wal_levelreplica--c-max_wal_senders10--c-wal_keep_size256MB--c-hot_standbyonpg-standby:image:postgres:16container_name:pg-standbydepends_on:-pg-primaryports:-5433:5432environment:POSTGRES_PASSWORD:postgresPOSTGRES_USER:postgrescommand:-bash--c-|if [ ! -f /var/lib/postgresql/data/PG_VERSION ]; then PGPASSWORDpostgres pg_basebackup -h pg-primary -U postgres -D /var/lib/postgresql/data -Fp -Xs -R fi exec docker-entrypoint.sh postgres启动并验证docker compose up -d docker exec -it pg-primary psql -U postgres -c SELECT client_addr, state, replay_lsn FROM pg_stat_replication;8.2 关键步骤与预期结果第一步pg_basebackup会在备库数据目录生成一个基础备份并写入primary_conninfo和standby.signal让备库以流复制方式启动。第二步主库的pg_stat_replication应该出现一行state streaming。第三步在主库插入数据后备库应能在很短时间内查到。容易改错的地方有三个pg_hba.conf没有允许复制连接时备库会一直重试wal_level不是replica或更高时复制无法建立备库数据目录不为空时pg_basebackup会失败。这个示例不能替代生产部署因为它没有持久化、没有 TLS、没有监控也没有处理主库重启后备库重连的细节。9. 完整示例二用 Java 测量复制延迟并做读路由判断9.1 目标与前置环境目标写一个可独立运行的 Java 程序连接主库和备库读取主库的当前 WAL 位置和备库的重放位置计算出延迟并据此决定一次读请求是否允许走备库。前置环境JDK 17 及以上、Maven、PostgreSQL JDBC 驱动、前面启动的主备库。这个示例不依赖 Spring Boot先把机制看清楚。importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.ResultSet;importjava.sql.Statement;publicclassReplicationLagProbe{privatestaticfinalStringPRIMARY_URLjdbc:postgresql://localhost:5432/postgres;privatestaticfinalStringSTANDBY_URLjdbc:postgresql://localhost:5433/postgres;privatestaticfinalStringUSERpostgres;privatestaticfinalStringPASSWORDpostgres;publicstaticvoidmain(String[]args)throwsException{longprimaryLsncurrentWalLsn(PRIMARY_URL);longstandbyLsnlastReplayLsn(STANDBY_URL);longlagBytesprimaryLsn-standbyLsn;System.out.println(primary lsn primaryLsn);System.out.println(standby lsn standbyLsn);System.out.println(lag bytes lagBytes);booleanallowStandbyReadlagBytes0lagBytes1024*1024;System.out.println(standby read allowed allowStandbyRead);}privatestaticlongcurrentWalLsn(Stringurl)throwsException{try(ConnectionconnDriverManager.getConnection(url,USER,PASSWORD);Statementstmtconn.createStatement();ResultSetrsstmt.executeQuery(SELECT pg_current_wal_lsn())){rs.next();returnparseLsn(rs.getString(1));}}privatestaticlonglastReplayLsn(Stringurl)throwsException{try(ConnectionconnDriverManager.getConnection(url,USER,PASSWORD);Statementstmtconn.createStatement();ResultSetrsstmt.executeQuery(SELECT pg_last_wal_replay_lsn())){rs.next();Stringvaluers.getString(1);returnvaluenull?0:parseLsn(value);}}privatestaticlongparseLsn(Stringlsn){String[]partslsn.split(/);longhighLong.parseLong(parts[0],16);longlowLong.parseLong(parts[1],16);return(high32)|low;}}9.2 关键步骤与边界程序先连主库拿pg_current_wal_lsn()再连备库拿pg_last_wal_replay_lsn()把 LSN 字符串按十六进制解析成可比较的数值后相减。预期输出是两次 LSN 和一个字节差值。当差值小于 1MB 时认为可以走备库读。这里有几个容易改错的地方。第一pg_last_wal_replay_lsn()在备库尚未重放任何 WAL 时可能返回空必须处理 null。第二LSN 是 64 位值用两个 32 位十六进制段拼接时要避免符号扩展代码里用long和左移 32 位是合理做法但在其他语言里要小心。第三这个阈值是拍脑袋的真实系统应该结合业务允许的延迟分布来设定而不是照抄 1MB。最重要的是边界这个探测本身也会消耗连接和查询时间不能在高频请求里每次调用。生产上应该做成后台定时采样把结果暴露给读路由决策器而不是在用户请求链路上同步探测。10. 完整示例三Spring Boot 中的读写路由与降级10.1 目标与前置环境目标在 Spring Boot 应用里实现一个简单的读写路由写操作走主库标记为可延迟的读走备库并且当备库延迟过大时自动降级到主库。前置环境Spring Boot 3.x、Spring JDBC、两个数据源分别指向主库和备库。下面的示例使用AbstractRoutingDataSource做动态数据源切换。它是简化实现但足以说明边界。importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;publicclassReadWriteRoutingDataSourceextendsAbstractRoutingDataSource{privatestaticfinalThreadLocalBooleanREAD_ONLYThreadLocal.withInitial(()-false);publicstaticvoidmarkReadOnly(){READ_ONLY.set(true);}publicstaticvoidclear(){READ_ONLY.remove();}OverrideprotectedObjectdetermineCurrentLookupKey(){if(READ_ONLY.get()LagMonitor.standbyHealthy()){returnstandby;}returnprimary;}}用一个服务类演示写后读和可延迟读的差别importorg.springframework.jdbc.core.JdbcTemplate;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;ServicepublicclassOrderService{privatefinalJdbcTemplatejdbcTemplate;publicOrderService(JdbcTemplatejdbcTemplate){this.jdbcTemplatejdbcTemplate;}TransactionalpubliclongcreateOrder(StringuserId,Stringsku){jdbcTemplate.update(INSERT INTO orders(user_id, sku, status) VALUES (?, ?, ?),userId,sku,CREATED);LongidjdbcTemplate.queryForObject(SELECT currval(pg_get_serial_sequence(orders, id)),Long.class);// 明确告知后续该用户的读要走主库ReadWriteRoutingDataSource.clear();returnidnull?-1:id;}publicStringqueryOrderStatus(longorderId){ReadWriteRoutingDataSource.markReadOnly();try{returnjdbcTemplate.queryForObject(SELECT status FROM orders WHERE id ?,String.class,orderId);}finally{ReadWriteRoutingDataSource.clear();}}publiclongcountOrders(){ReadWriteRoutingDataSource.markReadOnly();try{LongcountjdbcTemplate.queryForObject(SELECT count(*) FROM orders,Long.class);returncountnull?0:count;}finally{ReadWriteRoutingDataSource.clear();}}}10.2 关键步骤、结果与边界关键步骤创建订单时走主库并清除只读标记查询订单状态时显式标记为可延迟读走备库统计类查询同样走备库。finally里清理 ThreadLocal避免线程池复用导致标记泄漏。LagMonitor.standbyHealthy()是前面 Java 探测器的定时封装延迟过大或备库不可用时返回 false让路由回退到主库。预期结果在高延迟时查询订单状态会透明地回到主库用户不会看到“订单不存在”。容易改错的地方包括忘记清理 ThreadLocal导致后续请求被误判在事务中切换数据源因为事务一旦在主库开启后续读不会自动切到备库反而可能因为路由不一致报错以及把Transactional(readOnly true)当成自动读分离实际上它只是提示不会帮你选数据源。这个示例不能替代成熟的读写分离中间件。它没有处理多数据源事务、没有连接池隔离、没有动态权重但它清楚地展示了读写分离的边界路由决策必须建立在对延迟和业务容忍度的明确判断上。11. 常见误区第一个误区是“配了流复制就等于高可用”。流复制只解决数据副本问题故障检测、切换决策、应用重连、旧主库回归都是高可用的一部分。没有这些复制只是多了一台会读旧数据的机器。第二个误区是“同步提交一定比异步好”。同步提交提供更强的一致性保证但它会让主库在同步备库故障时阻塞。选择同步还是异步取决于业务能承受的是“丢一点数据”还是“停一会儿服务”。第三个误区是“replay_lag为零就代表数据一致”。在备库空闲、主库没有新写入时这个值可能为零或为空但这并不证明某条特定业务数据已经可见。要判断具体业务的一致性需要用业务探针或直接读主库。第四个误区是“读写分离只改配置就行”。真正的难点在应用侧的一致性策略、重试幂等和降级路径。数据库层面只提供复制路由和一致性判断必须由应用或中间件负责。12. 生产实践建议第一把延迟测量做成常态化指标至少采集字节延迟和重放时间延迟并对持续升高的趋势告警而不是只对瞬时阈值告警。第二同步提交只用于确实不能丢的数据并配置多个候选同步备库避免单点阻塞主库。对日志、行为埋点这类数据异步提交通常更合适。第三切换流程要演练。切换脚本、应用重连、旧主库回归这三个环节都要在预发环境完整跑通否则真实故障时第一次执行的脚本大概率会失败。第四读写分离默认保守。让“可延迟读”成为显式选择而不是默认行为这样新代码不会无意中引入不一致。第五监控要覆盖复制槽积压、pg_wal磁盘增长、备库重放冲突和长查询。这几项是流复制环境里最常见的慢性问题。13. 排障清单当出现“写后读不到”时按下面顺序检查备库是否在重放pg_last_wal_replay_lsn()是否推进pg_stat_replication里replay_lsn与主库当前 LSN 的差值是否很大应用是否把读请求路由到了备库该请求是否处于本应走主库的强一致路径是否存在主库大事务导致延迟突然升高。当出现“切换后写入失败”时检查应用连接串是否更新、连接池是否重建、新主库是否可写、旧主库是否还被某个连接池持有。当出现“同步提交阻塞”时检查synchronous_standby_names指向的备库是否在线、复制连接是否正常、是否有网络分区以及是否有多个候选备库可以接管。当出现“备库磁盘持续增长”时检查复制槽是否被长期离线的备库占用、wal_keep_size是否过大、归档是否失败。14. 面试/复盘问题流复制复制的是逻辑行还是物理 WAL这对备库的使用有什么限制synchronous_commit的几个取值分别保证什么代价是什么pg_stat_replication中write_lsn、flush_lsn、replay_lsn的区别是什么同步提交下同步备库全部故障主库会发生什么读写分离下如何解决写后立刻读的一致性问题故障切换时如何避免双主写入旧主库恢复后如何处理主从延迟突然升高你会按什么顺序排查15. 总结回到开头的问题用户提交订单后查不到本质是读请求走到了尚未重放完成的备库。要解决它需要同时理解三层复制层负责把 WAL 从主库传到备库提交层决定主库何时向客户端确认成功应用层决定读请求走哪台库、在什么条件下走。可以用一张选择清单收束全文需要强一致读时走主库可以容忍延迟的读走备库不能丢数据时考虑同步提交并配置多个候选备库主库故障时要有经过演练的切换和应用重连流程任何读写分离策略都必须先明确业务能接受多长的延迟。把这些问题回答清楚技术选型就不再是参数堆砌而是可验证的工程判断。16. 参考资料PostgreSQL 官方文档High Availability, Load Balancing, and ReplicationPostgreSQL 官方文档Write-Ahead Logging (WAL) 与synchronous_commitPostgreSQL 官方文档Monitoring Database Activitypg_stat_replication视图PostgreSQL 官方文档pg_basebackup、复制槽与热备Spring Framework 官方文档JDBC 与AbstractRoutingDataSourcePostgreSQL JDBC Driver 官方文档
RELATED

相关推荐

PHP跨域调试全攻略:从CORS原理到实战排查与修复

PHP跨域调试全攻略:从CORS原理到实战排查与修复

搞跨域调试这事,十个人里有九个第一反应是“后端接口有问题”,后端那边拿 curl 一打又能通,两边一对质就僵住了。我见过太多次这种场面:前端在 localhost:8080 跑项目,接口在 api.example.com,控制台一片红…

📅 2026/10/18 5:37:15
AI辅助论文写作全流程实测:7款工具从选题到初稿的提效指南

AI辅助论文写作全流程实测:7款工具从选题到初稿的提效指南

每年到论文季,朋友圈里都是一片哀嚎。选题没方向、文献看不完、引言憋三天、综述写到怀疑人生。今年情况有点不一样了,越来越多人在讨论用AI辅助写初稿。有人用它两天搞定过去两周的活,有人用它痛痛快快踩了一堆坑,比如参考文献全…

📅 2026/10/18 5:37:15
汽车月销量预测实战:ARIMA、SARIMAX与Auto ARIMA对比

汽车月销量预测实战:ARIMA、SARIMAX与Auto ARIMA对比

1. 项目缘起与整体思路拆解1. 1 为什么选汽车月销量这个场景汽车月销量这个数据,做时间序列预测算是相当经典的一个练手场景。它有几个天然优势:数据颗粒度是月度的,频率适中,既不像日度数据那样噪声大到让人头疼,也不…

📅 2026/10/18 5:37:15
MORE NEWS

更多资讯

📰

前端二进制数据处理:ArrayBuffer、TypedArray与DataView实战解析

做前端时间久了,特别是接触过文件上传、音视频处理、WebSocket 二进制帧或者 WebAssembly 之后,你会发现有一个坎绕不开:ArrayBuffer、TypedArray 和 DataView。这三兄弟表面上看起来都是处理二进制的,但它们的定位、底层逻辑和适…

📰

P1008 [NOIP 1998 普及组] 三连击

题目链接&#xff1a; P1008 [NOIP 1998 普及组] 三连击 - 洛谷 最佳思路&#xff1a; 数据量小 直接枚举&#xff1a; #include<bits/stdc.h> using namespace std; bool f(int x,int y, int z) {int c[10] {0};int num[3] {x,y,z};for(int i 0 ; i < 3;i){whi…

📰

TPAMI 2026|湖南大学:PAN × 马赛克图像融合,实现等变贝叶斯高光谱成像

TPAMI 2026&#xff5c;等变贝叶斯高光谱融合&#xff1a;无真值训练&#xff0c;兼顾空间细节与光谱保真 一句话总结 论文用无监督等变贝叶斯框架融合马赛克与全色图像&#xff08;Panchromatic Image&#xff0c;PAN&#xff09;&#xff0c;改善高光谱图像&#xff08;Hype…

📰

CVPR 2026|CHAL:面向运动红外小目标检测的因果引导分层异常感知学习

CVPR 2026&#xff5c;CHAL&#xff1a;先学背景再找异常&#xff0c;让运动红外小目标更容易被发现 1. 一句话总结 红外小目标太小、太暗&#xff0c;容易与背景干扰混淆。CHAL先学习背景&#xff0c;再通过外观异常、运动验证和因果引导定位目标&#xff0c;在三个红外数据…

📰

从流量算法到价值算法:基于系统演化公理的下一代内容分发范式思考

从流量算法到价值算法&#xff1a;基于系统演化公理的下一代内容分发范式思考原创确权&#xff1a;尹玉玺&#xff5c;84321天道归一理论体系安全声明&#xff1a;本文为互联网内容生态、算法演化、用户心智系统的纯学术范式研究&#xff0c;无平台批判、无对立导向&#xff0c…

📰

BERT微调实战:微博舆情分析系统的数据治理与模型训练全流程

做了好几年NLP相关的东西&#xff0c;工具链换了一批又一批&#xff0c;但真正能在业务里稳定跑起来的系统&#xff0c;从来不是哪个模型最花哨&#xff0c;而是能把数据、模型和应用串成一条完整链路的那套工程方案。去年我接了一个社交平台舆论监测的需求&#xff0c;一开始以…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬