1. 消息推送系统的核心价值与应用场景
消息推送系统是现代互联网应用中不可或缺的基础设施。想象一下,当你打开手机时看到的微信新消息提醒、电商平台的促销通知、或是服务器异常告警邮件——这些场景背后都依赖着消息推送系统的支撑。
从技术角度看,一个完整的消息推送系统需要解决三个核心问题:
- 实时性:消息产生后如何在最短时间内触达用户
- 可靠性:确保消息不丢失、不重复
- 扩展性:能够支撑海量设备的同时在线
我曾在多个项目中实现过不同规模的消息推送方案,从简单的Web站内信到日活千万级的App推送系统。本文将分享如何从零构建一个轻量级但具备生产可用性的推送系统,特别适合中小型项目快速集成。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计与技术选型
2.1 基础架构模型
一个最小可用的推送系统通常包含以下组件:
code复制客户端SDK → 推送服务 → 消息队列 → 连接管理 → 设备终端
在实际编码前,我们需要明确几个关键设计决策:
- 采用长连接还是短轮询?
- 长连接(WebSocket)适合高实时性场景
- 短轮询实现简单但资源消耗大
- 如何维护设备连接状态?
- 本地内存存储适合小规模部署
- Redis集群更适合分布式环境
- 消息存储策略:
- 内存队列速度快但易丢失
- 持久化到数据库更可靠
2.2 技术栈推荐
基于SpringBoot的典型技术组合:
java复制// 核心依赖
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.kafka:spring-kafka'
implementation 'redis.clients:jedis'
对于连接管理,我强烈建议使用Netty而非原生WebSocket实现。在日活10万+的项目中,Netty的性能优势非常明显——相同配置下连接数提升3-5倍,CPU负载降低40%。
3. 核心实现步骤详解
3.1 建立WebSocket连接
首先配置Spring的WebSocket支持:
java复制@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new PushHandler(), "/push")
.setAllowedOrigins("*");
}
}
客户端连接示例(JavaScript):
javascript复制const socket = new WebSocket('ws://yourdomain.com/push');
socket.onmessage = (event) => {
console.log('收到推送:', event.data);
};
关键细节:务必实现心跳机制(建议30秒间隔)防止运营商NAT超时断开连接
3.2 消息分发逻辑
使用Redis的Pub/Sub实现跨服务器消息广播:
java复制@Component
public class RedisMessageListener {
@Autowired
private JedisConnectionFactory connectionFactory;
@PostConstruct
public void init() {
new Thread(() -> {
Jedis jedis = connectionFactory.getConnection().getNativeConnection();
jedis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// 处理集群间广播消息
}
}, "push_channel");
}).start();
}
}
3.3 离线消息处理
当用户离线时,消息需要暂存到MySQL:
sql复制CREATE TABLE offline_messages (
id BIGINT AUTO_INCREMENT,
user_id VARCHAR(64) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
INDEX idx_user (user_id)
);
重连时的消息补发逻辑:
java复制public void resendOfflineMessages(String userId, WebSocketSession session) {
List<Message> messages = messageRepository.findByUserId(userId);
messages.forEach(msg -> {
try {
session.sendMessage(new TextMessage(msg.getContent()));
messageRepository.delete(msg);
} catch (IOException e) {
log.error("消息补发失败", e);
}
});
}
4. 生产环境关键优化点
4.1 连接保活策略
在移动网络环境下,需要实现三重保活机制:
- TCP层keepalive(系统默认2小时太长了)
- 应用层心跳(30秒一次)
- 断线自动重连(带指数退避)
Netty配置示例:
java复制ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(30, 0, 0));
pipeline.addLast(new HeartbeatHandler());
4.2 海量连接管理
当连接数超过单机上限(通常1-2万)时,需要:
- 使用负载均衡器(如Nginx)分散连接
- 采用一致性哈希确保用户始终连接到同一台服务器
- Redis存储全局连接路由表
Nginx配置片段:
nginx复制upstream push_servers {
hash $remote_addr consistent;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
location /push {
proxy_pass http://push_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
4.3 消息可靠性保障
必须实现的可靠性机制:
- 服务端消息确认(ACK)
- 客户端消息去重(基于messageId)
- 失败重试队列(使用RabbitMQ的死信队列)
消息确认协议示例:
json复制{
"msgId": "uuidv4",
"content": "hello",
"needAck": true
}
5. 监控与运维实践
5.1 关键指标监控
使用Prometheus采集的核心指标:
yaml复制- name: push_connections
help: Current active connections
type: gauge
- name: push_messages_total
help: Total messages processed
type: counter
- name: push_latency_seconds
help: Message delivery latency
type: histogram
5.2 日志排查技巧
通过日志染色快速定位问题:
java复制MDC.put("traceId", UUID.randomUUID().toString());
log.info("用户{}连接建立", userId);
// 所有相关日志都会自动带上traceId
5.3 压力测试数据
在4核8G的云服务器上实测结果:
- 维持5万长连接时内存占用约3.2GB
- 消息吞吐量:12,000 msg/sec(1KB大小)
- P99延迟:<200ms
JMeter测试建议配置:
code复制线程组:500并发
ramp-up时间:60秒
循环次数:无限
6. 常见问题解决方案
6.1 消息积压处理
当消费速度跟不上生产速度时:
- 增加消费者实例
- 实施消息分级(重要消息优先处理)
- 临时启用消息采样(如只处理50%消息)
Kafka分区扩容命令:
bash复制kafka-topics.sh --alter --topic push_msg \
--partitions 8 \
--bootstrap-server localhost:9092
6.2 安卓推送保活
针对不同厂商的特殊处理:
java复制// 华为设备
if (deviceBrand.equals("HUAWEI")) {
useHmsPush();
}
// 小米设备
else if (deviceBrand.equals("XIAOMI")) {
useMiPush();
}
6.3 Web推送权限问题
解决浏览器通知权限被拒绝:
javascript复制// 先检查权限状态
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
showNotification();
} else {
// 降级到站内消息提示
showFallbackAlert();
}
});
在实际项目中,消息推送系统往往需要根据业务特点进行定制化开发。比如电商平台需要支持商品链接的富文本推送,而IoT设备可能需要二进制协议传输。建议先实现基础版本,再逐步扩展高级功能。
