什么是心跳

news2025/1/30 5:15:36

心跳(Heartbeat) 是在计算机网络、分布式系统和嵌入式系统中常见的一种机制,用于检测系统或组件的可用性、存活状态以及维持连接。

1. 心跳的作用

检测存活状态

  • 确保服务器、客户端、微服务或设备仍然在线。
  • 适用于 分布式系统、集群、高可用(HA)系统

网络连接保活

  • 防止 TCP 连接超时、NAT 断连
  • 常见于 WebSocket、MQTT、数据库连接池

故障检测与恢复

  • 当某个节点不响应心跳,则触发 自动故障转移(Failover)
  • 适用于 主从复制(如 Redis Sentinel、MySQL Replication)

2. 心跳的工作原理

(1) 定期发送心跳包

  • 主动方(Client 或 Master) 定期发送“心跳”数据包。
  • 被动方(Server 或 Slave) 接收并返回确认包。

(2) 监测心跳超时

  • 设定 心跳超时时间(Timeout),如果在规定时间内没有响应,则认为对方掉线

(3) 触发故障处理

  • 重新连接、切换主节点、报警通知等。

3. 心跳的常见应用

🔹 网络协议

  • TCP Keepalive:维持长连接,防止 NAT 超时。
  • WebSocket Ping/Pong:检测客户端是否断开。
  • MQTT 心跳(Keep Alive):保证 IoT 设备在线。

🔹 分布式 & 高可用

  • ZooKeeper:Leader 监测 Follower 存活。
  • Redis Sentinel:监测主从服务器状态,故障转移。
  • Kubernetes Liveness Probe:检测容器是否存活。

🔹 设备监控

  • 物联网(IoT):智能设备定期汇报在线状态。
  • 服务器健康检查:Nginx、负载均衡(如 HAProxy)使用心跳检测后端服务器可用性。

4. 心跳机制的实现方式

类型方式特点
主动轮询客户端定期请求服务器实现简单,但开销大
被动监听服务器定期向客户端发送心跳适合长连接,如 WebSocket
TCP KeepaliveTCP 内置机制,自动探测断连适用于长连接,减少应用层处理
定时心跳包业务层实现,UDP/TCP 发送心跳灵活,适用于分布式系统

5. 设计心跳时的注意点

心跳间隔(Interval)

  • 过短:网络开销大,影响性能。
  • 过长:无法及时发现故障。

超时时间(Timeout)

  • 超时 = 3~5 次心跳间隔 比较合适。
  • 需要根据网络环境和业务需求调整。

重试机制(Retry)

  • 多次心跳丢失 后才认为对方掉线,避免误判。

负载优化

  • 使用 指数退避算法(Exponential Backoff) 逐步增加心跳间隔,减少无效流量。

💡 总结

  • 心跳 = 定期发送信号,检测系统/设备是否在线。
  • 用于网络保活、故障检测、负载均衡等场景。
  • 合理设置心跳间隔、超时和重试策略,优化系统稳定性。

在 Java 中实现心跳机制的方式有多种,通常使用 Socket(TCP/WebSocket)、Netty、ScheduledExecutorService 等方式实现。下面介绍几种常见的 Java 心跳机制实现方法:


方法 1:使用 TCP Socket 实现心跳(适用于长连接)

客户端 定期向 服务器 发送心跳包,服务器如果在超时时间内没有收到心跳,就认为客户端掉线。

🔹 服务器端(Server)

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class HeartbeatServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("服务器启动,等待客户端连接...");

        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("客户端连接成功:" + socket.getInetAddress());

            new Thread(() -> {
                try {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    while (true) {
                        String message = reader.readLine();
                        if (message == null) break;
                        System.out.println("收到心跳: " + message);
                    }
                } catch (IOException e) {
                    System.out.println("客户端断开连接!");
                }
            }).start();
        }
    }
}

🔹 客户端(Client)

import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

public class HeartbeatClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket("127.0.0.1", 8080);
             PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true)) {

            while (true) {
                writer.println("HEARTBEAT");
                System.out.println("发送心跳包...");
                Thread.sleep(5000); // 每 5 秒发送一次
            }
        } catch (Exception e) {
            System.out.println("服务器断开连接!");
        }
    }
}

📝 说明:

  • 服务器监听端口 8080,接收 心跳消息
  • 客户端每 5 秒 发送一次 "HEARTBEAT"
  • 服务器如果 超时未收到 客户端的心跳,就可以认为 客户端掉线

方法 2:使用 ScheduledExecutorService 定时发送心跳

如果你使用的是 Java NIO / Netty / WebSocket,可以使用 ScheduledExecutorService 定时发送心跳。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class HeartbeatScheduler {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        Runnable heartbeatTask = () -> {
            System.out.println("发送心跳包...");
            // 这里可以调用 TCP/WebSocket 发送心跳消息
        };

        // 每 5 秒执行一次心跳
        scheduler.scheduleAtFixedRate(heartbeatTask, 0, 5, TimeUnit.SECONDS);
    }
}

📝 说明:

  • 适用于 多线程环境,避免 Thread.sleep() 阻塞线程。
  • scheduleAtFixedRate() 定时执行心跳。

方法 3:使用 Netty 实现心跳(适用于高并发场景)

Netty 是高性能的异步网络通信框架,适用于 WebSocket、TCP 长连接 场景。

🔹 服务器(Server)

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;

public class NettyHeartbeatServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new IdleStateHandler(10, 0, 0)); // 10秒没收到消息触发事件
                            ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
                                @Override
                                public void channelRead0(ChannelHandlerContext ctx, String msg) {
                                    System.out.println("收到心跳:" + msg);
                                }

                                @Override
                                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
                                    if (evt instanceof IdleStateEvent) {
                                        IdleStateEvent event = (IdleStateEvent) evt;
                                        if (event.state() == IdleState.READER_IDLE) {
                                            System.out.println("客户端超时,关闭连接!");
                                            ctx.close();
                                        }
                                    }
                                }
                            });
                        }
                    });

            ChannelFuture future = bootstrap.bind(8080).sync();
            System.out.println("Netty 服务器启动...");
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

🔹 客户端(Client)

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;

public class NettyHeartbeatClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) {
                            ch.pipeline().addLast(new IdleStateHandler(0, 5, 0, TimeUnit.SECONDS)); // 5秒发送心跳
                            ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
                                @Override
                                public void channelRead0(ChannelHandlerContext ctx, String msg) {
                                    System.out.println("服务器响应:" + msg);
                                }

                                @Override
                                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
                                    if (evt instanceof IdleStateEvent) {
                                        System.out.println("发送心跳包...");
                                        ctx.writeAndFlush("HEARTBEAT");
                                    }
                                }
                            });
                        }
                    });

            ChannelFuture future = bootstrap.connect("127.0.0.1", 8080).sync();
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

📝 说明:

  • Netty 服务器:使用 IdleStateHandler 检测 10 秒 无消息自动关闭连接。
  • Netty 客户端:使用 IdleStateHandler 5 秒 发送一次心跳。
  • 适用于高并发系统,如 IM、WebSocket、分布式服务。

总结

方法适用场景优点缺点
TCP Socket普通长连接实现简单适用于小型应用
ScheduledExecutorService多线程任务线程管理灵活需手动实现超时检测
Netty高并发服务器高性能,自动检测超时学习成本较高

如果你是初学者,建议 使用 TCP Socket;如果需要高并发,推荐 Netty! 🚀

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2285271.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

有限元分析学习——Anasys Workbanch第一阶段笔记梳理

第一阶段笔记主要源自于哔哩哔哩《ANSYS-workbench 有限元分析应用基础教程》 张晔 主要内容导图&#xff1a; 笔记导航如下&#xff1a; Anasys Workbanch第一阶段笔记(1)基本信息与结果解读_有限元分析变形比例-CSDN博客 Anasys Workbanch第一阶段笔记(2)网格单元与应力奇…

目标跟踪之sort算法(3)

这里写目录标题 1 流程1 预处理2 跟踪 2 代码 参考&#xff1a;sort代码 https://github.com/abewley/sort 1 流程 1 预处理 1.1 获取离线检测数据。1.2 实例化跟踪器。2 跟踪 2.1 轨迹处理。根据上一帧的轨迹预测当前帧的轨迹&#xff0c;剔除到当前轨迹中为空的轨迹得到当前…

uniapp 地图添加,删除,编辑标记,在地图中根据屏幕范围中呈现标记

前言 小程序实现新功能&#xff0c;在地图中选取位置添加标记&#xff0c;并在地图中呈现添加的标记&#xff0c;&#xff08;呈现的是根据当前屏幕范围内的标记&#xff09;&#xff0c;并对标记进行分享&#xff0c;删除&#xff0c;编辑&#xff0c;导航&#xff0c;并从分…

SOME/IP服务接口

本系列文章将分享我在学习 SOME/IP 过程中积累的一些感悟&#xff0c;并结合 SOME/IP 的理论知识进行讲解。主要内容是对相关知识的梳理&#xff0c;并结合实际代码展示 SOME/IP 的使用&#xff0c;旨在自我复习并与大家交流。文中引用了一些例图&#xff0c;但由于未能找到原作…

NeetCode刷题第17天(2025.1.27)

文章目录 086 Course Schedule II 课程安排二087 Graph Valid Tree 图有效树088 Number of Connected Components in an Undirected Graph 无向图中的连接组件数量 086 Course Schedule II 课程安排二 您将获得一个数组 prerequisites &#xff0c;其中 prerequisites[i] [a,…

c++学习第十四天

提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考。 //力扣代码 class Solution {const char* numStrArr[10]{"","","abc","def","ghi","jkl","mno","pqrs","tuv&q…

遗传算法【Genetic Algorithm(GA)】求解函数最大值(MATLAB and Python实现)

一、遗传算法基础知识 来自B站视频的笔记&#xff1a; 【超容易理解】手把手逐句带你解读并实现遗传算法的MATLAB编程&#xff08;结合理论基础&#xff09;_哔哩哔哩_bilibili 1、遗传算法 使用“适者生存”的原则&#xff0c;在遗传算法的每一代中&#xff0c;…

【Rust自学】15.6. RefCell与内部可变性:“摆脱”安全性限制

题外话&#xff0c;这篇文章一共4050字&#xff0c;是截止到目前为止最长的文章&#xff0c;如果你能坚持读完并理解&#xff0c;那真的很强&#xff01; 喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以…

Luzmo 专为SaaS公司设计的嵌入式数据分析平台

Luzmo 是一款嵌入式数据分析平台&#xff0c;专为 SaaS 公司设计&#xff0c;旨在通过直观的可视化和快速开发流程简化数据驱动决策。以下是关于 Luzmo 的详细介绍&#xff1a; 1. 背景与定位 Luzmo 前身为 Cumul.io &#xff0c;专注于为 SaaS 公司提供嵌入式分析解决方案。…

HTML<label>标签

例子 三个带标签的单选按钮&#xff1a; <form action"/action_page.php"> <input type"radio" id"html" name"fav_language" value"HTML"> <label for"html">HTML</label><br&…

【技术洞察】2024科技绘卷:浪潮、突破、未来

涌动与突破 2024年&#xff0c;科技的浪潮汹涌澎湃&#xff0c;人工智能、量子计算、脑机接口等前沿技术如同璀璨星辰&#xff0c;方便了大家的日常生活&#xff0c;也照亮了人类未来的道路。这一年&#xff0c;科技的突破与创新不断刷新着人们对未来的想象。那么回顾2024年的科…

【Linux】gdb——Linux调试器

gdb使用背景 程序的发布方式有两种&#xff0c;debug模式和release模式 Linux gcc/g出来的二进制程序&#xff0c;默认是release模式 要使用gdb调试&#xff0c;必须在源代码生成二进制程序的时候, 加上 -g 选项 gdb使用方法 首先进入gdb gdb test_glist显示代码 断点 b 行…

fpga系列 HDL:XILINX Vivado Vitis 高层次综合(HLS) 实现 EBAZ板LED控制(上)

目录 创建工程创建源文件并编写C代码C仿真综合仿真导出RTL CG导出RTL错误处理&#xff1a; 创建工程 创建源文件并编写C代码 创建源文件(Souces下的hlsv.h和hlsv.cpp&#xff0c;Test Bench下的test_hlsv1.cpp)&#xff1a; hlsv1.h #ifndef HLSV1 #define HLSV1 #include &l…

卡特兰数学习

1&#xff0c;概念 卡特兰数&#xff08;英语&#xff1a;Catalan number&#xff09;&#xff0c;又称卡塔兰数&#xff0c;明安图数。是组合数学中一种常出现于各种计数问题中的数列。它在不同的计数问题中频繁出现。 2&#xff0c;公式 卡特兰数的递推公式为&#xff1a;f(…

Python-基于PyQt5,json和playsound的通用闹钟

前言&#xff1a;刚刚结束2024年秋季学期的学习&#xff0c;接下来我们继续来学习PyQt5。由于之前我们已经学习了PyQt5以及PyUIC,Pyrcc和QtDesigner的安装&#xff0c;配置。所以接下来我们一起深入PyQt5&#xff0c;学习如何利用PyQt5进行实际开发-基于PyQt5&#xff0c;json和…

关于数字地DGND和模拟地AGND隔离

文章目录 前言一、1、为什么要进行数字地和模拟地隔离二、隔离元件1.①0Ω电阻&#xff1a;2.②磁珠&#xff1a;3.电容&#xff1a;4.④电感&#xff1a; 三、隔离方法①单点接地②数字地与模拟地分开布线&#xff0c;最后再PCB板上一点接到电源。③电源隔离④、其他隔离方法 …

DeepSeek R1学习

0.回顾&#xff1a; https://blog.csdn.net/Together_CZ/article/details/144431432?ops_request_misc%257B%2522request%255Fid%2522%253A%25226574a586f0850d0329fbb720e5b8d5a9%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id…

12 款开源OCR发 PDF 识别框架

2024 年 12 款开源文档解析框架的选型对比评测&#xff1a;PDF解析、OCR识别功能解读、应用场景分析及优缺点比较 这是该系列的第二篇文章&#xff0c;聚焦于智能文档处理&#xff08;特别是 PDF 解析&#xff09;。无论是在模型预训练的数据收集阶段&#xff0c;还是基于 RAG…

【反悔堆】【hard】力扣871. 最低加油次数

汽车从起点出发驶向目的地&#xff0c;该目的地位于出发位置东面 target 英里处。 沿途有加油站&#xff0c;用数组 stations 表示。其中 stations[i] [positioni, fueli] 表示第 i 个加油站位于出发位置东面 positioni 英里处&#xff0c;并且有 fueli 升汽油。 假设汽车油…

为什么应用程序是特定于操作系统的?[计算机原理]

你把WINDOWS程序复制到MAC上使用&#xff0c;会发现无法运行。你可能会说&#xff0c;MAC是arm处理器&#xff0c;而WINDWOS是X86 处理器。但是在2019年&#xff0c;那时候MAC电脑还全是Intel处理器&#xff0c;在同样的X86芯片上&#xff0c;运行MAC和WINDOWS 程序还是无法互相…