[Geek工具箱] 后端程序员福音 – TellMe 类Server酱的 推送助手
为什么做这个
身为后端程序员,需要管理很多服务器,再加上自己折腾Nas,树莓派,智能家居等等,有很多消息需要推送,比如:
- 系统运行状态定时推送
- 异常报告推送
- 在线、离线状态
- 程序运行完成状态等等
我尝试了很多办法,但都没有完全满足我的需求:
- QQ \ 微信 机器人, 失败。 原因是这些都已经关闭了,且认证麻烦,使用不便。
- 邮箱推送, 失败。 邮箱推送的延迟不可控,且容易受手机杀后台的影响。
- Server酱, 目前在使用。 在这里还是要感谢Server酱为大家免费很多优质的服务,但在我目前使用过程中有很多不太方便的地方,比如: 内容自定义程度不高
既然没有,那就只能自己撸一个工具了,我对其定位是: 简单( 只做推送), 可扩展(方便开发新功能), 开源(人多力量大)
功能设计
目前我设想的几个特性:
- 无需登录。 打开APP即获取ID,往该ID发GET/POST请求即可推送到APP
- 发送普通消息。
GET /push/{id}?title=标题&content=内容
- 宕机监测。 重复发送
GET /downCheck/{id}?timeout=10&title=宕机了&content=设备1宕机了
,timeout没收到,则向APP推送宕机消息
核心功能解析
长连接
- Websocket长连接。 APP通过websocket与服务器保持长连接,避免轮训消耗。
- 心跳监测。定时检查连接状态,离线自动重连。
消息的可靠推送
ACK + ID, 对消息编号,通过ACK确保消息传输的可靠性。
其中,最关键的ACK机制流程如下:
- GET \ POST收到 发送给device1的消息msg
- 对消息编号,并存储到消息缓存:message_buffer
ConcurrentHashMap<String:device_id, TreeMap<Integer:message_id, Message>>
这样的结构中。 device_id -> 消息map。外层使用ConcurrentHashMap是为了提高并发访问消息缓存的效率,内层使用TreeMap是为了实现消息的有序访问(message_id是自增的)。
同时,向device1推送该消息 - device1收到消息之后,返回message_id的ACK,并存储到本地数据库
- 服务器收到ACK,从message_buffer中清除该消息。
- 设备重新上线,获取所有消息(TreeMap有序的),并推送到设备
public class MessageBuffer {
private static ConcurrentHashMap<String, TreeMap<Integer, Msg>> buffer = new ConcurrentHashMap<>(); // 待优化
/**
* 添加消息
* @param dst
* @param mId
* @param msg
*/
public void addMessage(String dst, Integer mId, Msg msg) {
synchronized (this) {
TreeMap<Integer, Msg> temp;
if (!buffer.containsKey(dst)) { // 新设备,新TreeMap
temp = new TreeMap<>();
} else {
temp = buffer.get(dst);
}
temp.put(mId, msg);
buffer.put(dst, temp);
}
}
/**
* 获取所有
* @param dst
* @return
*/
public List<MessageBean> getMessages(String dst) {
if (!buffer.containsKey(dst)) return null;
synchronized (this) {
TreeMap<Integer, Msg> temp = buffer.get(dst);
return temp.entrySet().stream().map((entry) -> { // 获取所有消息
MessageBean m = new MessageBean();
m.setId(entry.getKey());
m.setTitle(entry.getValue().getTitle());
m.setContent(entry.getValue().getContent());
return m;
}).collect(Collectors.toList());
}
}
/**
* 删除消息
* @param dst
* @param mId
*/
public void deleteMessage(String dst, Integer mId) {
if (!buffer.containsKey(dst)) return;
synchronized (this) {
TreeMap<Integer, Msg> temp = buffer.get(dst); // 移除
temp.remove(mId);
}
}
}
进度及安排
目前已完成
- 核心功能的初步实现
- 可以推送消息到APP端
第二阶段
- 完善核心功能,如自动获取ID,宕机检测
- 项目部署上线,供大家免费使用
第三阶段
- 代码整理开源
- 邀请IOS程序员开发IOS端(目前只有Android客户端)