技术探索与实践:从需求到上线的思考之路

向量宇航员
2025-06-13 13:33
阅读 441

作为一名iOS工程师,5年的工作时间让我经历了大大小小的技术挑战和项目演变。从最初单纯地按照产品文档写代码,到现在能够主动思考技术选型、架构设计甚至推动团队的技术演进,这个过程里有很多值得记录和反思的经验。

今天我想分享一个真实的项目案例,它不仅仅是一个功能开发的故事,更是一次关于如何进行有效的技术探索与实践的实战经历。这篇文章不会讲太多空洞的理论,我会尽量用贴近实际开发的语言,还原当时的场景,让你能真切感受到那种“一边掉坑一边填坑”的过程。

背景介绍:我们到底要做什么?

背景介绍:我们到底要做什么?

事情发生在去年年底,公司决定优化用户消息系统的体验。我们的App中有一个“系统通知”功能,原本是通过本地推送实现的,但随着业务增长,用户反馈越来越多:“为什么有的通知收不到?”、“通知内容显示不对”。最严重的是,在夜间活跃度低时,部分通知压根没送达。

这个问题的背后其实涉及到了多个层面:

  1. 本地推送存在局限性:比如无法保证准时送达,也无法在应用未启动时触发;
  2. 推送平台分散:Android 用 Firebase,iOS 用 APNs,维护成本高;
  3. 后端推送逻辑不统一:不同服务模块各自发送消息,难以集中管理;
  4. 个性化推送需求强烈:需要根据用户行为动态调整推送时机和内容。

于是,我们决定做一次全面改造:引入统一的推送服务平台,并整合前后端推送流程。这是一个典型的“技术升级+业务重构”的场景,也是我第一次主导这样一个中大型技术方案的设计和落地。


遇到的问题:不只是技术问题那么简单

遇到的问题:不只是技术问题那么简单

第一个问题:跨平台推送的一致性难题

我们需要支持 iOS 和 Android,同时希望前端保持最小改动,后端也期望减少重复开发。当时我们考虑了几个技术选型:

  • 继续使用 Firebase(仅用于 Android) + 自建 APNs 系统
  • 引入第三方推送服务,比如极光、个推、OneSignal
  • 使用 AWS SNS 或者阿里云 MNS 这类云服务作为底层推送通道

这几种方案各有优劣。自建 APNs 成本高、维护复杂;而使用第三方推送虽然省事,但灵活性差,尤其是在处理个性化推送逻辑的时候容易受限。

我们最终选择了一个折中方案:搭建统一的推送网关服务,封装对下层推送通道(如 APNs、FCM)的调用,上层由业务服务对接该网关。这样既保持了一定的灵活性,又减少了直接依赖特定厂商 SDK 的耦合。

第二个问题:推送失败率居高不下

接入统一网关后,初期测试阶段就出现了大量失败的情况。iOS 设备的通知 token 变化频繁,如果缓存策略不合理,很容易出现无效设备仍然尝试推送的情况,造成无意义的压力和资源浪费。

为了缓解这个问题,我们在客户端做了几件事:

  • 每次 App 启动时重新获取设备 token 并上报给服务端
  • 在服务端记录 token 最近一次更新时间,结合设备状态(是否活跃)来判断是否继续推送
  • 定期清理长时间未登录用户的 token 记录

但在实际运行过程中,由于网络波动或服务不可用,某些 token 更新失败,导致后续推送依然失败。这个时候就需要引入“异步重试机制”。

我们采用了一个基于 Kafka 的消息队列,将推送任务异步解耦出来。失败的任务会被重新入队并在一定时间内进行多次重试,直到成功或者达到最大尝试次数为止。这种方式显著提高了推送成功率。

第三个问题:个性化推送怎么搞?

用户画像已经建立起来,我们也拿到了用户的行为数据,如何把这些信息有效地利用到推送策略中去?这是当时困扰我们比较久的问题。

简单粗暴的做法是写死规则,比如“用户7天未登录则发提醒通知”,但这显然不够灵活,也不利于运营人员自主配置。

我们最后决定引入一个“规则引擎”模块。整个流程大概是这样的:

  1. 推送服务监听 Kafka 中的事件流(如用户注册、订单创建等)
  2. 每个事件进入规则引擎,根据预设的条件筛选目标用户
  3. 条件匹配成功后生成一条推送任务并加入推送队列
  4. 用户点击通知后跳转到对应的页面,并上报效果数据供分析

规则引擎我们是基于开源项目 Drools 做二次开发的,它本身支持规则热加载和版本控制,非常适合我们这种需要不断迭代推送策略的场景。


实践中的关键代码片段

实践中的关键代码片段

虽然完整的工程代码不能公开,但我可以分享一些核心逻辑,帮助你理解整个流程是如何串联起来的。

推送任务封装

// Swift 示例:构建一条推送任务结构体
struct PushTask {
    let userId: String
    let deviceToken: String
    let payload: [String: Any]
    let priority: Int
}

// 将任务投递到 Kafka(伪代码)
func sendToKafka(task: PushTask) {
    guard let data = try? JSONSerialization.data(withJSONObject: task.payload) else {
        return
    }
    
    kafkaProducer.send(topic: "push_tasks", value: data, key: task.userId)
}

推送服务消费队列

# Python 示例:消费 Kafka 中的任务并调用 APNs 发送
def consumer_loop():
    consumer = KafkaConsumer('push_tasks')
    for message in consumer:
        task_data = json.loads(message.value)
        
        if is_ios_device(task_data['device_token']):
            apns_response = apns_client.send_notification(
                token=task_data['device_token'],
                payload=task_data['payload']
            )
            
            # 记录日志或失败重试
            if apns_response.status != 'success':
                retry_queue.push(task_data)

Token 失效检测逻辑(伪代码)

// 检测当前设备 token 是否已过期或无效
func shouldRetryPush(for token: String) -> Bool {
    let lastUpdated = getTokenLastUpdateTime(fromServer: token)
    let now = Date()
    
    if now.timeIntervalSince(lastUpdated) > 7 * 24 * 60 * 60 {
        // 如果超过一周未更新,则拒绝推送
        return false
    }

    if !isDeviceActive(token) {
        // 设备状态为非活跃,则延迟推送或跳过
        return false
    }

    return true
}

踩过的那些坑

踩过的那些坑

坑一:APNs 的证书失效

刚上线的时候,我们的 APNs 证书配置有误,而且没有及时监控证书过期时间。导致连续两天 iOS 设备无法收到通知。后来我们专门写了个自动化检测脚本,部署在 CI/CD 流程中,每次构建前自动检查证书有效期,到期提前两周预警。

坑二:Kafka 分区分配不均

刚开始我们只用了默认的一个分区,后来随着推送任务量增加,消费速度跟不上,导致积压。后来我们增加了分区数量,并且根据 userId 做哈希分配,确保同一个用户的消息被同一个消费者组处理,避免状态混乱。

坑三:Drools 规则冲突

规则引擎上线后,有一次营销部门不小心配置了两条互相覆盖的规则,结果导致一部分用户收到了两条相同内容的通知。我们在规则保存时加了冲突检测逻辑,现在每次新增规则都会自动校验是否有语义冲突。


最终效果与收益

经过大约两个月的改造与持续优化,我们得到了以下成果:

  • 整体推送成功率提升到了98%以上
  • 推送失败后的平均恢复时间缩短至1小时内
  • 运营同学可以通过 UI 自定义推送规则,效率大大提升
  • 推送系统具备良好的扩展性,未来接入新渠道只需少量适配工作

更重要的是,这次项目的完成让我们团队的技术能力上了一个台阶,大家开始更愿意主动去思考“我们能不能做得更好”而不是“客户要什么我们就照做”。


我的一些经验总结

1. 技术选型要有前瞻性,但也得务实

不要看到新技术就盲目跟风。像我们一开始差点引入一个轻量级的推送 SDK,后面才发现它并不支持我们复杂的规则体系,差点返工。技术选型时一定要结合业务场景,权衡利弊。

2. 工具链必须完善

包括日志、监控、报警、追踪这些都非常重要。我们后期接入了 Prometheus + Grafana 做推送成功率、延迟趋势图,一旦异常立马告警。这对系统的稳定运行至关重要。

3. 小步快跑,持续迭代

不要幻想一口气解决所有问题。我们最初的计划是做一个完美系统,后来发现根本来不及上线。最终采用分阶段推进的方式,先打通基础推送链路,再逐步加上重试、规则引擎等功能。

4. 别忽视测试的重要性

特别是集成测试和灰度上线环节。我们在正式发布前进行了两轮大规模内测,还写了压力测试脚本模拟万级并发推送。这一步帮我们提前发现了不少性能瓶颈。

5. 沟通比代码更重要

有时候你会发现问题不是出在代码上,而是出在“人”身上。比如后端以为前端会处理 token 更新,前端以为服务端已经托管。这些问题靠技术手段解决不了,必须依靠清晰的需求同步和文档保障。


写在最后:关于成长的一点感悟

回顾这段经历,我深刻体会到:

真正的技术成长不是学会了多少框架和语言,而是懂得在复杂环境中做出合理的取舍,找到合适的路径把事情做成。

每一个技术决策背后都是无数次的讨论、验证、推翻和重构。作为开发者,我们要做的不仅仅是写代码,更要学会理解业务、沟通协作、推动变革。

如果你也正在面对类似的技术难题,不妨先静下心来理清思路,多问问自己:“我们到底要解决什么问题?现有的方案有哪些不足?有没有更好的方式?”

很多时候答案就在不断的追问中浮现。

希望这篇文章能给你带来一些启发,也欢迎你在评论区分享你的实践经验,我们一起交流、一起进步 🙌


本文作者是一名拥有5年工作经验的iOS工程师,目前专注于App性能优化与基础设施建设,热爱技术写作与团队赋能。如果你喜欢这类内容,欢迎关注我的公众号或博客,我们将持续输出一线开发者的实战经验。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝