设计心跳机制维护用户在线状态
业务场景
- 需要维护用户的在线状态,在线状态类型:在线、离线、在直播间、在上麦等
- 可以允许非实时更新状态,但误差需要在几分钟内
- 需要考虑用户强杀App,或者强杀后又上线,用户弱网情况下用户状态的切换问题
- 需要考虑大DAU下用户状态频繁切换与维护带来的性能开销,需要做性能优化
为什么要用心跳实现?
- 提高系统的可靠性:心跳机制可以检测系统是否正常运行,如果系统出现故障,可以及时发现并采取相应的措施,提高系统的可靠性。
- 降低系统的维护成本:通过心跳机制,可以实时监控系统的运行情况,及时发现故障并解决问题,可以降低系统的维护成本。
- 提高系统的可扩展性:心跳机制可以实现系统的动态扩展,当系统负载过高时,可以自动增加节点,提高系统的可扩展性。
- 提高系统的可用性:心跳机制可以实现系统的高可用性,当某个节点出现故障时,可以自动切换到其他节点,保证系统的正常运行。
- 提高系统的性能:心跳机制可以实现负载均衡,将请求分配到不同的节点上,提高系统的性能。
心跳机制使用案例
在 Java 生态中,很多组件使用了心跳机制
Nacos:心跳机制可以用来检测服务的健康状况,如果服务没有正常的心跳,则认为该服务不可用。还可以用来自动摘除故障节点,当服务节点出现故障或者网络异常时,Nacos会通过心跳机制检测到这些异常,并自动将这些故障节点从服务列表中摘除,以避免服务调用时出现错误。
XXL-JOB:XXL-JOB使用了心跳机制解决任务执行器宕机或网络异常的问题:在任务执行器与调度中心建立心跳连接后,任务执行器会定时向调度中心发送心跳包,以保持连接。如果调度中心在一定时间内没有收到任务执行器的心跳包,就会认为该任务执行器已宕机或网络异常,将该任务执行器标记为失效状态,避免调度任务给失效的任务执行器执行。
ZooKeeper:ZooKeeper 是一个分布式协调服务,它使用心跳机制来检测服务节点的健康状态,并在节点故障时触发故障转移。
Kafka:Kafka 是一个分布式消息队列,它使用心跳机制来检测消费者和生产者的健康状态,并在节点故障时触发重新平衡。
Netty:Netty 是一个高性能的网络通信框架,它使用心跳机制来检测连接的健康状态,并在连接断开时触发重连。
Dubbo:Dubbo 是一个分布式服务框架,它使用心跳机制来检测服务提供者和消费者的健康状态,并在节点故障时触发切换到备用节点。
Elasticsearch:Elasticsearch 是一个分布式搜索和分析引擎,它使用心跳机制来检测节点的健康状态,并在节点故障时触发数据的重新分片和复制。
心跳机器设计要点
- 心跳间隔:心跳间隔是指客户端向服务器发送心跳包的时间间隔。心跳间隔应该根据系统的实际情况来设置,通常建议在几分钟到十几分钟之间。
- 心跳包内容:心跳包的内容应该尽可能地简单,只包含必要的信息,例如用户ID、时间戳等。避免发送过多的数据,以减少网络带宽的消耗。
- 心跳超时时间:心跳超时时间是指服务器在多长时间内没有收到客户端的心跳包就认为客户端已经离线。心跳超时时间应该设置得足够长,以避免网络延迟等原因导致误判客户端状态。
- 心跳重试机制:当服务器发送心跳包后,如果客户端没有响应,服务器应该进行重试。重试次数和时间间隔应该根据系统的实际情况来设置,通常建议进行3-5次重试。
- 心跳机制的稳定性:心跳机制是维护用户在线状态的重要机制,必须保证其稳定性。在心跳机制的实现过程中,应该考虑到网络波动、服务器负载等因素,尽可能地保证心跳机制的稳定性和可靠性。
- 心跳机制的性能优化:心跳机制会占用一定的网络带宽和服务器资源,因此在实现过程中需要进行性能优化,例如采用压缩算法、合并心跳包等措施,以减少网络带宽的消耗和服务器资源的占用。
心跳传输方式
- HTTP 心跳:通过发送 HTTP 请求来检测服务是否可用。通常情况下,客户端会定时发送一个 HTTP 请求到服务端,如果服务端能够正常响应请求,则说明服务可用,否则就认为服务不可用。
- TCP 心跳:通过发送 TCP 报文来检测服务是否可用。客户端会定时向服务端发送一个 TCP 报文,如果服务端能够正常响应,则说明服务可用,否则就认为服务不可用。
- UDP 心跳:通过发送 UDP 报文来检测服务是否可用。UDP 心跳相比于 TCP 心跳更加轻量级,但是可靠性较低,因为 UDP 报文不保证可靠传输。
- ICMP 心跳:通过发送 ICMP 报文来检测服务是否可用。通常情况下,客户端会发送一个 ICMP Echo 请求到服务端,如果服务端能够正常响应 ICMP Echo 请求,则说明服务可用,否则就认为服务不可用。
我们这边用得比较多的是TCP心跳,实现方式有很多组件已经实现,netty,websocket、gRpc等,不需要自己再造轮子,当然如果你坚决想造轮子,那你设计时需要考虑以下几个问题:
TCP心跳实现考虑要点
- 客户端定时发送数据包:客户端定时向服务器发送一些特定的数据包,以告知服务器自己的存活状态。如果服务器长时间未收到客户端的数据包,则可以判定客户端已经下线。这种方式需要客户端和服务器双方都支持,且需要占用一定的网络带宽。
- 服务器定时发送数据包:服务器定时向客户端发送一些特定的数据包,以告知客户端自己的存活状态。如果客户端长时间未收到服务器的数据包,则可以判定服务器已经下线。这种方式需要客户端和服务器双方都支持,且需要占用一定的网络带宽。
- TCP keepalive机制:TCP协议本身提供了一种心跳机制,称为TCP keepalive机制。在这种机制下,TCP协议会定期向对端发送一个空的ACK数据包,以告知对端自己的存活状态。如果一段时间内未收到对端的ACK数据包,则可以判定对端已经下线。TCP keepalive机制需要在操作系统层面进行配置,且不同操作系统的配置方法可能有所不同。
- 应用层心跳:应用层可以自己实现心跳机制,比如通过定时发送一些特定的数据包来维持TCP连接的存活状态。这种方式需要应用程序进行特定的实现,且需要占用一定的网络带宽。
使用心跳实现用户在线状态的刷新
心跳包设计
{ userId:xxx,//用户ID type:xxx, //1:在线心跳 2:在房间心跳 3:上麦心跳 ... }
心跳传输方式
选择TCP方式,客户端使用Websocket定时向服务端发送心跳包,为了能控制不同类型心跳包间隔发送时间,客户端会从服务端拉取一个心跳类型对应心跳时间的配置表
{ type:xxx, //心跳类型 1:在线心跳 2:在房间心跳 3:上麦心跳... intervalSecond:xxx //不同类型心跳间隔时间不同 }
用户心跳维护
服务端接收到用户心跳后,使用MQ异步去做一个心跳状态、时间的维护
心跳数据存储结构
使用Redis Hash实现,大DAU与并发场景下,为了避免大Key,需要对大Key再进行拆分,通过用户ID Hash取余进行拆分即可,也可以用一致性Hash算法实现,有兴趣的朋友可以翻翻我以前写过的一篇分库分表,里面有提到一致性Hash算法。
分Key存储
Long userId = xxx; String key = "{heartbeat-type}"+Math.abs(userId%n); // n=你需要拆分的key个数 redis.hPut(key,userId,System.currentTimeMillis()) //存进Hash,记录当前最新心跳时间戳 redis.set(userOnlineKey,state) //收到心跳的同时设置在线状态 online,in room,in mic
有心跳之后我们的在线状态中的在线、在直播间、在麦上都很好维护,那么心跳过期或者没了心跳之后怎么剔除用户在线状态呢?
剔除用户在线状态
1.一种是用户主动触发离线,比如用户主动退出登录/上报离线的行为,这种情况操作起来很简单,直接收到请求进行用户状态刷新即可, 但这种情况往往不能被信任,用户强杀App、弱网下服务端无法收到客户端的请求 2.通过心跳Key轮询去判断用户心跳存活状态 //allKeys:所有拆分的Key集合 for(String key:allKeys){ HashMap<userId,time> heartBeatMap = redis.hGetAll(key); for(Map<userId,Time> entry:heartBeatMap.entrySet){ Long userId = entry.getKey(); long time = entry.getValue(); if(System.currentTimeMillis()-time > 1000*60*3){ //用户最近一次上报的心跳已经超过3分钟了,执行你的逻辑,由于是轮询, 需要考虑下你的轮询间隔时间和你所允许用户心跳存活时间的误差 你可以主动给该用户再发一次心跳,以验证它是否存活,没有你就干掉它,又或者你可以直接干掉它 redis.remove(key,userId); //设置在线状态为离线 redis.set(userOnlineKey,offline) } } }
总结
- 心跳间隔设置合理:心跳间隔过短会增加系统负担,过长会导致节点宕机时延迟发现。
- 心跳超时时间设置合理:心跳超时时间过短会导致误判节点宕机,过长会导致节点宕机后延迟发现。
- 心跳消息内容简洁明了:心跳消息应该只包含必要的信息,不宜过于复杂。
- 心跳机制应用范围明确:心跳机制应该针对需要保证高可用性的节点或服务,不应该应用于所有节点或服务。
- 心跳机制应该与其他机制相结合:心跳机制可以配合其他机制,如负载均衡、故障转移等,以实现更高的可用性。
- 心跳机制应该考虑网络延迟和抖动:网络延迟和抖动可能导致心跳消息的发送和接收出现问题,应该考虑这些因素并进行相应的优化。
- 心跳机制应该有相应的监控和报警机制:当节点宕机或心跳消息异常时,应该及时报警并采取相应的措施。
- 心跳机制应该有相应的容错机制:当出现节点宕机或心跳消息异常时,应该有相应的容错机制,以确保系统的可用性。
文章内容源自本人所在互联网社交企业实战项目,分享、记录从0-1做一个千万级直播项目,内容包括高并发场景下技术选型、架构设计、业务解决方案等。