首页 / 资讯中心 / 文章详情

Java游戏陪玩护航平台源码:订单状态机与Redis抢单结算实战

Java游戏陪玩护航平台源码:订单状态机与Redis抢单结算实战 ★ FEATURED ARTICLE
陪玩这个赛道我在两年前接过一个从零起步的项目客户手里有一批陪玩师资源想做一个能自己撮合、自己结算的平台预算不高但要能扛住晚上八点到十一点那波流量。我们前后用了四个月把一个可运营的版本推上线中间推翻过两次架构踩的坑比写的代码多。这篇就围绕这个 Java 源码项目——高效游戏陪玩护航服务平台把技术选型、领域模型、订单状态机、抢单派单、实时通信、支付结算、风控和排查手册全部摊开讲一遍。适合正在做类似撮合交易平台的后端同学也适合想看懂“一个平台型项目到底要处理多少非功能问题”的入门开发者。代码层面我会给到可直接抄改的结构和片段思路层面我会把每个决策背后的账算给你看。1. 先把业务想透陪玩平台到底在卖什么1.1 一笔订单里藏着四类角色和三条链路很多人以为陪玩平台就是个“聊天下单”的壳子真动手做才发现它本质上是一个带实时交互的撮合交易系统复杂度不低于小型本地生活平台。我在设计表结构之前先画了一张角色关系图把一笔订单从产生到完结涉及的实体全部列出来最后收敛成四类角色下单方普通用户、服务方陪玩师、平台方运营与客服、资金方支付渠道与结算通道。这四类角色各自有不同的数据视角同一个订单在他们眼里看到的字段完全不一样。三条链路必须同时成立缺一条业务就跑不通。第一条是撮合链用户发布需求系统找到匹配的陪玩师双方达成服务约定。第二条是履约链服务开始、进行中、结束、用户确认中间可能有中断、申诉、超时。第三条是资金链支付、冻结、分账、结算、提现、退款。我见过不少团队只把撮合链做完了就上线结果资金链用 Excel 手工对账一个月就乱套了。所以在这个项目里三条链路是一起设计的资金链甚至优先级更高——撮合错了顶多重来一次钱算错了是要出事的。还有一点容易被忽略陪玩师不是“商品”是“有状态的供给”。他同一时间只能服务一个订单服务期间要能拒绝新派单下线后不能被派到。这个“状态”必须集中管理不然会出现一个人被派了三个单、三个用户同时等他开局的尴尬场面。我在项目里给陪玩师定义了六种可派单状态空闲、待接单、服务中、休息中、审核中、封禁中只有“空闲”才进可派单池其余状态一律不派。这个约束看起来简单但它是后面所有并发控制的基础。提示先把“供给状态”想清楚再写代码。很多平台的超卖问题不是并发没锁住而是压根没有供给状态这个概念把陪玩师当成了无限库存。1.2 技术选型为什么最后落在 Spring Boot 这套组合上预算有限、团队只有三个人技术选型的核心不是“先进”而是“招得到人、查得到资料、出问题能兜住”。我们把候选方案缩到两个一是 Spring Boot 单体 MySQL Redis二是用某类响应式框架全异步实现。第二种我们在压测环境试了两周结论是维护成本太高团队里没人敢在凌晨两点改线上代码。最后定版是层次选型选择理由放弃的方案应用框架Spring Boot 2.7 Spring MVC生态成熟线程模型直观排查问题有完整链路全异步框架团队学习曲线陡持久层MyBatis-Plus 手写复杂 SQL简单 CRUD 不写 SQL复杂查询保留控制力全 ORM复杂查询生成的 SQL 不可控缓存Redis 6 单机 哨兵抢单、会话、限流、排行全靠它单机足够撑早期量级集群模式运维成本翻倍消息RocketMQ或任意支持延迟消息的队列超时关单、异步通知、削峰都用得上纯数据库轮询扫表压力大实时通信Netty 自建长连接网关需要精细控制心跳、鉴权、路由直接用现成推送服务可控性差定时任务分布式调度 Redis 锁对账、结算、清理任务单机 Scheduled多实例会重复执行这里我要重点说一个反直觉的结论游戏陪玩平台的第一瓶颈通常不是数据库而是长连接。晚上高峰期两万人在线就意味着两万个 WebSocket 连接每个连接还有心跳、消息推送、状态同步。数据库的读写 QPS 反而没那么夸张因为真正高频的是聊天消息和状态心跳这些都能被 Redis 和内存挡掉。所以选型时我把预算更多投在了连接层而不是一上来就分库分表。1.3 单体起步还是微服务起步这笔账要算清楚项目初期我强烈建议单体。理由很实在三个人维护六个微服务光是服务发现、配置中心、链路追踪的运维成本就能吃掉一半开发时间而这些能力在日订单几千的时候毫无收益。我们第一版就是单体按模块分包用户模块、陪玩师模块、订单模块、支付模块、通信模块、运营模块。模块之间只允许通过 Service 接口调用不允许跨模块直接操作对方的表这条纪律为后来的拆分留了口子。什么时候拆我给客户的判断标准是三条同时满足才拆单模块代码超过八万行、单模块 QPS 单独超过两千、该模块的发布频率明显高于其他模块。实际跑到第六个月只有订单模块和通信模块触发了第二条于是我们把通信网关单独拆出去因为长连接的发布节奏和业务代码完全不同——业务一天发三次网关一周发一次混在一起每次发布都要断连重连用户体验很差。踩坑记录第一版把长连接放在业务应用里每次发版所有在线用户掉线客服电话被打爆。后来把网关拆出来做了灰度重连才解决这个问题。2. 领域模型与订单状态机系统的地基2.1 核心表结构怎么切表设计的核心原则是状态字段和流水字段分离。订单表只存当前状态和快照所有状态变更都写到独立的流水表里。这样做的好处是排查问题时能完整还原一笔订单的生命周期而不是只看到一个“已完成”。我们最终落地的核心表大概是这样表名作用关键字段注意点t_user下单方账号id、手机号密文、实名状态、信用分手机号必须加密存储索引走哈希t_player陪玩师档案id、user_id、审核状态、可派单状态、评分可派单状态是热点字段放 Redist_player_skill技能标签player_id、游戏品类、段位、单价一品类一行便于按标签检索t_order订单主表order_no、user_id、player_id、金额、状态、版本号金额单位是分状态用枚举码t_order_log订单流水order_no、from_status、to_status、操作人、时间只增不改用于对账与追责t_wallet钱包owner_id、owner_type、可用余额、冻结余额双余额模型冻结和可用分开t_wallet_flow资金流水flow_no、业务单号、方向、金额、余额快照每条流水必须有唯一业务号t_settle_record结算单结算批次、陪玩师、应结金额、手续费按批次结算不按单结算t_appeal申诉单订单号、申诉方、证据、处理结果申诉期间资金必须冻结这里有个细节值得展开t_order里我加了一个version字段不是给乐观锁用的而是给状态机用的。每次状态变更必须带上前置状态和版本号做条件更新SQL 长这样UPDATE t_order SET status #{toStatus}, version version 1, update_time NOW() WHERE order_no #{orderNo} AND status #{fromStatus} AND version #{version};影响行数为 0 就说明状态已经被别人改过了直接抛业务异常。这一条 SQL 挡住了 90% 的并发状态错乱问题比在代码里加各种判断靠谱得多。很多团队喜欢在 Service 里先查再改中间只要有网络延迟就会出问题我实测下来条件更新是最省事也最稳的做法。2.2 订单状态机把 if-else 关进笼子订单状态是这类平台最容易写烂的地方。我接手过一个二手项目一个updateOrderStatus方法里塞了四百多行 if-else改一个状态要通读全文还经常漏改。所以这次我们从一开始就用状态机表驱动把“哪些状态能到哪些状态”固化成配置。我们定义的订单状态和流转关系如下当前状态允许流转到触发动作备注待支付待接单、已取消支付成功、超时未付15 分钟未付自动取消待接单服务中、已取消陪玩师接单、用户取消取消需判断是否已过免责期服务中待确认、申诉中服务结束、用户发起申诉服务时长由心跳累计待确认已完成、申诉中用户确认、超时自动确认24 小时未确认自动完成申诉中已完成、已退款客服判定判定期间资金冻结已完成已退款售后仲裁仅限规定时限内已取消终态—支付过的需走退款代码层面我用了枚举 静态映射表的方式实现核心结构大致是这样public enum OrderStatus { WAIT_PAY(10), WAIT_ACCEPT(20), IN_SERVICE(30), WAIT_CONFIRM(40), APPEALING(50), COMPLETED(60), CANCELED(70), REFUNDED(80); private final int code; OrderStatus(int code) { this.code code; } public int getCode() { return code; } } public final class OrderStateMachine { private static final MapOrderStatus, SetOrderStatus ALLOWED new HashMap(); static { ALLOWED.put(WAIT_PAY, EnumSet.of(WAIT_ACCEPT, CANCELED)); ALLOWED.put(WAIT_ACCEPT, EnumSet.of(IN_SERVICE, CANCELED)); ALLOWED.put(IN_SERVICE, EnumSet.of(WAIT_CONFIRM, APPEALING)); ALLOWED.put(WAIT_CONFIRM, EnumSet.of(COMPLETED, APPEALING)); ALLOWED.put(APPEALING, EnumSet.of(COMPLETED, REFUNDED)); ALLOWED.put(COMPLETED, EnumSet.of(REFUNDED)); ALLOWED.put(CANCELED, EnumSet.noneOf(OrderStatus.class)); ALLOWED.put(REFUNDED, EnumSet.noneOf(OrderStatus.class)); } public static void check(OrderStatus from, OrderStatus to) { if (!ALLOWED.getOrDefault(from, Collections.emptySet()).contains(to)) { throw new BizException(订单状态不允许从 from 流转到 to); } } }这个结构的价值在于新增状态的时候只需要改一处静态配置所有调用方自动生效。上线后我们加过两次状态一次是“部分退款”一次是“服务中断待重派”改动量都在半小时以内。心得状态机不要做成数据库配置表除非运营真的需要动态改。静态枚举 版本控制反而更容易 review也避免了配置改错导致线上混乱。2.3 金额与账本为什么所有钱都得按“分”存这一点没有商量余地所有金额字段用 bigint 存“分”。我见过用 decimal 的、用 double 的前者没问题但运算麻烦后者迟早出事。用 long 存分加减乘除全是整数运算不会有精度丢失展示层再除以 100。钱包模型我们用的是双余额可用余额和冻结余额。用户支付后钱先进平台的中间账户陪玩师侧记一笔“待结算”订单完成后才转入可提现余额。这样设计的原因是要处理申诉和退款——如果钱直接进了陪玩师钱包他当天提现走了用户再申诉退款平台就得垫钱。冻结模型把风险留在了平台侧虽然多了一次账务处理但账是清楚的。每条资金流水必须有唯一业务单号并且建唯一索引。这是防重复记账的最后一道防线比任何应用层判断都可靠。我们上线初期出过一次重复结算就是因为消息重投导致结算任务执行了两次后来加了唯一索引 幂等判断再没出现过。3. 抢单与派单把并发压在一个可控的漏斗里3.1 可派单池的构建与技能标签匹配陪玩平台的核心体验是“快”。用户下单后 30 秒内没人接流失率会明显上升。所以派单不能靠扫数据库得靠内存级的索引。我们的做法是在 Redis 里维护多个维度的可派单池pool:game:{gameId}按游戏品类分的 Set成员是陪玩师 IDpool:rank:{gameId}:{rankLevel}按段位分层避免低段位陪玩师接高段位单player:status:{playerId}Hash存可派单状态、当前负载、最近接单时间player:price:{playerId}单价用于匹配预算区间用户下单后系统按“品类 → 段位区间 → 预算区间 → 在线状态”四层过滤从 Redis 里拿到候选集再按评分、接单量、响应速度做加权排序取前 N 个推给用户。这里的排序权重我调过很多次最后定的是响应速度占 40%评分占 35%接单量占 15%新人有 10% 的曝光加权。新人加权很重要不然老陪玩师会形成马太效应新进来的永远接不到单。public ListLong matchCandidates(OrderCreateReq req) { String poolKey pool:game: req.getGameId(); SetString ids redis.opsForSet().members(poolKey); if (CollectionUtils.isEmpty(ids)) return Collections.emptyList(); ListPlayerScore scored new ArrayList(); for (String id : ids) { PlayerMeta meta loadMeta(Long.valueOf(id)); // 本地缓存 Redis if (!meta.isOnline()) continue; if (!meta.supportRank(req.getRankLevel())) continue; if (meta.getPrice() req.getBudget()) continue; if (meta.getLoad() meta.getMaxLoad()) continue; scored.add(new PlayerScore(meta.getId(), weight(meta))); } scored.sort(Comparator.comparingDouble(PlayerScore::getScore).reversed()); return scored.stream().limit(20).map(PlayerScore::getId).collect(Collectors.toList()); }这段代码有个隐患如果池子里有几万人一次循环遍历会拖慢接口。我们后来加了分段拉取和本地缓存把PlayerMeta放在 Caffeine 本地缓存里Redis 只存变化信号实测把匹配耗时从 180ms 压到了 25ms 左右。3.2 Redis Lua 原子抢单的完整实现抢单的本质是“一个人只能被一个订单占用”这是个典型的分布式互斥问题。如果只用SETNX需要考虑设置过期时间和释放逻辑我最后用的是“Redis 原子脚本 数据库条件更新”的双保险。第一道防线在 Redis接单请求进来执行一段 Lua 脚本判断陪玩师是否空闲、订单是否还在待接单两个条件都满足才写入占用标记。-- KEYS[1] player:lock:{playerId} -- KEYS[2] order:status:{orderNo} -- ARGV[1] orderNo -- ARGV[2] expireSeconds local playerKey KEYS[1] local orderKey KEYS[2] local orderStatus redis.call(GET, orderKey) if orderStatus ~ WAIT_ACCEPT then return -1 -- 订单已被接走或已取消 end local ok redis.call(SET, playerKey, ARGV[1], NX, EX, ARGV[2]) if not ok then return -2 -- 陪玩师正忙 end redis.call(SET, orderKey, ACCEPTED) return 1返回值区分得很细-1 是订单没了-2 是人被占了1 才是成功。前端拿到不同返回值给不同提示用户体验差别很大——手慢了换个人试试和这个陪玩师正在服务中是完全不同的引导。第二道防线在数据库拿到 Redis 锁之后执行 2.2 节那条带fromStatus的条件更新 SQL只有影响行数为 1 才算真正接单成功。如果数据库更新失败要立刻释放 Redis 锁这里我用try-finally保证释放并且释放时校验 value避免误删别人的锁。注意Redis 锁的过期时间不要设太短。我们一开始设了 3 秒结果遇到一次数据库慢查询锁过期了但事务还没提交出现了两个陪玩师同时接单的情况。后来改成 30 秒并且在数据库层加了唯一约束兜底。3.3 超时关单延迟消息和扫表到底选哪个超时关单有三种常见实现我全都试过结论是按场景混用方案优点缺点适用场景定时扫表实现简单不依赖中间件数据量大时扫表压力大精度受调度间隔限制数据量小、精度要求低的场景延迟消息精度高业务解耦依赖 MQ消息可能丢失需要补偿订单超时关单主方案Redis ZSet 延时队列轻量不额外引入组件需要自己处理可靠性和重启恢复中小规模、无 MQ 的场景我们最终用的是延迟消息为主、定时扫表为辅。支付超时 15 分钟用延迟消息同时每 5 分钟跑一次扫表把“状态还是待支付但创建时间超过 20 分钟”的订单兜底关掉。扫表的 SQL 走了(status, create_time)联合索引每次最多捞 500 条用游标分页避免深分页拖垮数据库。这里有个坑要提醒延迟消息可能重复投递所以消费端必须做幂等。我们的关单逻辑是“只有状态仍是待支付才关”这样重复消费第二次会直接返回成功不会误关已经支付的订单。4. 实时通信让用户“看得见人”4.1 长连接网关的分层设计前面说过我们把通信网关单独拆出来了。网关的职责很纯粹维护连接、鉴权、心跳、消息路由不碰业务逻辑。架构上分三层接入层Netty 监听握手时校验 token拿到 userId 后建立 Channel 与 userId 的映射路由层用 Redis 存userId → gatewayId的映射消息发送时查这个映射找到目标网关网关之间用内部 RPC 转发业务层网关只负责把消息丢进业务处理队列具体落库、推送、未读数更新都在业务侧做单机连接数我们压测到过 5 万内存占用约 500MBCPU 在心跳高峰期大概 30%。线上按 2 万连接一台机器配置留足余量。心跳间隔设的 30 秒超过 90 秒没收到心跳就断开这个参数调过太短会增加无效流量太长会让僵尸连接占着资源。4.2 消息可靠投递与未读数聊天消息最怕丢。我们的策略是“先落库再推送”消息进来后先写t_im_message写入成功才推给对端。推送失败不重试而是依赖客户端拉取补偿——客户端每次重连会带上本地最大消息 ID服务端返回该 ID 之后的消息。这样即使推送丢了用户重连也能补回来。未读数用了 Redis Hash 存储key 是unread:{userId}field 是会话 IDvalue 是数字。每次发消息给接收方HINCRBY一下用户进入会话时清零。这个设计的问题是不可靠——Redis 挂了会丢未读数但未读数本身就是可以容忍误差的数据丢了变成 0 比丢消息好得多。如果对准确性要求高可以定时从消息表重算。public void onMessage(Long fromUid, Long toUid, String content) { ImMessage msg new ImMessage(); msg.setFromUid(fromUid); msg.setToUid(toUid); msg.setContent(content); msg.setCreateTime(new Date()); imMessageMapper.insert(msg); // 先落库 String convKey buildConvKey(fromUid, toUid); redis.opsForHash().increment(unread: toUid, convKey, 1); pushService.pushAsync(toUid, msg); // 异步推送失败不影响主流程 }4.3 通话与房间别自己造轮子陪玩过程中通常需要语音这块我建议直接用成熟的实时音视频能力不要自研。自研音频通道的难点不在编码而在弱网对抗、回声消除、抖动缓冲这些坑不是小团队能填的。我们用的方案是接第三方音频房间能力自己只负责房间号分配、鉴权 token 下发和计费时长统计。时长统计这块有个细节房间计费时长不能只依赖客户端的开始和结束时间必须用服务端心跳来做。我们的做法是每 15 秒上报一次心跳服务端记录最后一次心跳时间订单结算时用“最后一次心跳 - 首次心跳”作为计费时长再和客户端上报的时长做比对偏差超过 10% 就走人工复核。这个机制上线后时长争议的工单量下降了一大半。5. 支付、结算与风控钱和安全的底线5.1 支付回调幂等三层防护支付回调是资金链的入口必须做到“不管回调多少次只处理一次”。我用了三层防护唯一索引t_wallet_flow上的biz_no唯一索引重复插入直接报错状态机校验回调进来先查订单状态只有“待支付”才处理分布式锁按订单号加锁防止并发回调同时进入Transactional(rollbackFor Exception.class) public void handlePayCallback(String orderNo, String channelTradeNo, long amount) { String lockKey lock:pay: orderNo; boolean locked redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS); if (!locked) throw new BizException(重复回调稍后重试); try { Order order orderMapper.selectByOrderNo(orderNo); if (order null) throw new BizException(订单不存在); if (order.getStatus() ! OrderStatus.WAIT_PAY.getCode()) { log.warn(重复回调订单已处理 orderNo{}, orderNo); return; // 幂等返回不抛异常 } if (order.getAmount() ! amount) { throw new BizException(金额不一致); // 必须校验金额 } orderService.transit(orderNo, WAIT_PAY, WAIT_ACCEPT); walletService.creditPending(order.getPlayerId(), orderNo, amount); } finally { redisLock.unlock(lockKey); } }这里最容易漏的是金额校验。有些实现只看回调成功就改状态结果被刷单。必须把回调金额和订单金额逐分比对不一致直接拒绝并告警。5.2 分账、冻结与提现结算模型我设计成“按批次结算”而不是按单结算。原因是对账成本一个陪玩师一天接十单按单提现要处理十次资金操作按批次一天一次就够了而且便于生成对账单。具体流程是订单完成 → 陪玩师钱包的“待结算”增加每日凌晨跑结算任务 → 汇总昨日所有完成订单扣除平台服务费生成结算单结算单审核通过 → 待结算转入可用余额用户发起提现 → 可用余额扣除进入提现审核队列提现成功 → 记流水更新状态平台服务费的比例做成可配置按陪玩师等级分档配置存在数据库里加本地缓存改了 5 分钟内生效。计算服务费时注意向上取整还是向下取整要提前定死我们踩过一次坑两个环境用了不同的取整方式对账差了十几块钱查了一整天才发现。5.3 合规与风控实名、年龄校验、内容审核这块既是底线也是难点。平台侧的必备能力包括账号实名信息校验、年龄分层管理、聊天内容审核、申诉举证、异常交易监控。实名校验我建议接第三方权威渠道不要自己做一是没有资质二是出了问题兜不住。内容审核分同步和异步两条路。同步审核用在用户发出的文本消息上接入内容安全能力做实时检测命中风险词直接拦截并记录异步审核用在图片、语音上先放行后抽检命中再回溯处理。这里有个尺度问题同步审核做得太严会误伤正常交流我们一开始把词库配得特别宽结果这局打得真菜都被拦截用户体验很差。后来改成三级策略——高危直接拦、中危替换、低危只记录效果好很多。年龄校验必须做在注册和下单两个节点。未完成实名校验的账号不允许下单也不允许成为陪玩师。这不是可选功能是必须有的前置门槛。6. 上线前后踩过的坑与排查手册6.1 五个真实故障的排查路径故障一订单状态卡在“服务中”不动。表现是用户反馈服务已结束但订单不结束。排查发现是心跳上报的时间戳没更新原因是网关重启后客户端没重连。解决方案是网关断开时主动清理该网关下所有连接的服务中订单标记并触发一次对账任务。教训任何依赖长连接的时长统计都要有服务端兜底逻辑。故障二抢单时提示“系统繁忙”。监控显示 Redis 的 Lua 脚本执行超时。原因是pool:game:{gameId}这个 Set 里混进了已下线陪玩师集合膨胀到十几万members命令一次拉全量。解决方案是改成SSCAN分批拉取并且下线时及时从池子里移除。故障三结算金额对不上。差异集中在有优惠券的订单上。查下来是优惠金额分摊时用了四舍五入多个订单累加后产生了分位误差。后来改成“最后一单承担余数”的规则误差归零。故障四消息重复推送。用户收到两条一样的消息。原因是 MQ 重复消费消费端没做幂等。加了消息唯一 ID 表和判重逻辑后解决。故障五高峰期接口大面积超时。定位到是订单查询接口没有走索引ORDER BY create_time DESC触发了文件排序。加了联合索引(user_id, status, create_time)后恢复。6.2 容量评估几个必须提前算的数上线前我做了三轮压测把几个关键数字算清楚避免拍脑袋扩容。算法是这样的订单 QPS 估算假设日均订单 5 万其中 60% 集中在 20:00 到 23:00 这三小时则平均 2.8 单/秒按峰值系数 10 倍算峰值约 28 QPS。下单接口还要乘以浏览转化比实际查询 QPS 会高出 20 到 30 倍所以订单查询接口要按 800 QPS 设计。连接数估算按日活 10 万、同时在线率 20% 算峰值 2 万连接。每连接内存按 10KB 估算含缓冲区单机 2 万连接约 200MB加上 JVM 堆和其他开销8G 内存的机器跑一台网关足够配两台做冗余。Redis 内存估算可派单池按 5 万陪玩师、每人 200 字节算约 10MB聊天未读按 10 万用户、每人 50 个会话算Hash 结构约 200MB加上其他缓存8G 内存的实例用不到一半。指标估算值设计容量扩容触发线下单峰值 QPS28200持续 5 分钟超 120订单查询峰值 QPS8002000持续 5 分钟超 1500在线连接数2 万4 万单机超 2.5 万Redis 内存约 1.5G8G使用率超 70%这几个数字不是一成不变的但有了它们扩容决策就有依据不至于半夜拍脑袋加机器。6.3 发布与回滚策略撮合类平台的发布风险集中在两处长连接断连和数据库变更。我们的做法是通信网关灰度发布先发一台观察 10 分钟连接数和错误率再逐台替换每台发布前先把新连接引导到其他机器数据库变更只做兼容性变更加字段不加非空约束、加索引用在线 DDL、删字段先停用一个版本再删业务代码用开关控制新增的派单策略、结算规则都挂配置开关出问题一键关闭回到旧逻辑回滚方案必须提前演练。我们有过一次发布后订单创建成功率掉到 60%原因是新加的校验规则太严从监控报警到确认回滚用了 8 分钟直接把晚上的高峰错过了。后来把“创建成功率”做成核心看板指标掉 5% 就自动告警到值班群。我个人在这个项目里最深的体会是平台型项目的难点从来不在某个单点技术而在“状态一致性”这四个字上。订单状态、钱包余额、可派单状态、未读数每一个都是会在高并发下出错的地方而它们之间还相互关联。我后来养成一个习惯每加一个新功能先问自己三个问题这个操作重复执行两次会怎样这个字段和哪个字段必须同时改改到一半失败了这个数据还能不能修把这三个问题回答清楚了代码写出来才敢上生产。
阅读完成 · 觉得有帮助?
咨询建站