新能源充电桩
面试官问:介绍下你最近做的项目,主要负责的什么内容,遇到过哪些问题,怎么解决的?
你答:
好的,我这个项目叫xxx, 有个口号的, “好桩 , 好平台,好服务”,规模还是比较大的,全国在四百多个城市都有布局,充电终端有100W 个以上,应用场景主要包括:重卡充电站,公共停车场,工业园区,小区等场合,还有核心城市的公交公司也有业务合作,加盟商有20W+.
当时我负责的主要有:
运营平台
- 核心有个数据大屏:运营数据统计概况,用户的注册数量,设备的总数,交易金额,总订单,用户城市分布,收入统计柱状图表这些 用的是阿里那个dataV
- 还有一些场站管理,充电设备管理,还有像视频中心的视频设备管理
- 还有视频中心监控,告警中心等等这些都是我负责的
客户端
另外,像c端的充电流程也是我主要负责的,我觉得这个项目最核心的就是 充电桩 + 运营平台 + 小程序端的通信的架构,这个和正常的web请求不太一样,需要去设计一下这个通信架构。要用到这个mqtt协议的
面试官问:你这个通信架构是怎么设计的?
你答:

这个项目核心其实就是通信:
充电桩和云服务器的通信 以及 小程序和云服务器的通信
1.充电桩包括其他的一些硬件设备,和云服务器通信,首先,都得有个路由器,路由器通过mqtt broker 相当于一个中转站吧和云服务器进行通信。
充电桩通过TCP连接MQTTBroker
充电桩的路由器 向 mqtt broker 上报充电状态
mqtt broker 向 充电桩的路由器 推送充电指令
mqtt的话,他必须要有一个topic, 充电桩这个场景分为 充电状态,控制指令 这两个主题
充电桩的路由器向mqtt broker上传充电状态相关数据,就必须上传到 充电状态 这个主题,EMQX设置连接器,定义规则,通过动作的设置将数据同步到IotDb时序数据库
mqtt broker 向充电桩路由器推送控制指令的话,就得发送到控制指令这个主题,这样路由器订阅了这个主题才能接收到
云平台和mqtt broker之间的连接是通过websocket进行连接,通信协议也是mqtt协议
2.小程序和云服务器的通信
首先肯定是要登录,进行身份验证,通过后才能发送充电指令
云服务器会给小程序推送充电桩的状态,小程序才能看到充电桩的相关状态信息,包括电量啊,计费策略这些,这个是要不断推送的,所以通信协议用的是websocket.
面试官问:你能讲下为什么用mqtt协议吗?
你答:
mqtt是物联网最核心,使用率最高的通信协议
- 物联网行业,硬件设备一般都带有一个路由器,他的这个网络很不稳定,就容易丢数据,所有用mqtt borker [ emqx ] 做个过渡,防止丢数据,有个QOS指标,可以设置保证消息不丢失
而且还具备离线机制啊 心跳机制啊 等等 都是用来保证数据不丢失的
物联网特点,消息吞吐量比较大,mqtt就比较适合,支持百万级的设备连接,单节点就能抗住10W+的并发,当然数据不能太大,我们需要做一些机制转换,可以转成十六进制
mqtt协议进行数据传输,电耗很小的,共享单车,胎压检测啥的都是很小一块电池,就可以使用很多年
面试官问:http , websocket区别?
你答:
http 是短连接的
websocket 是长连接的
- 短连接,就是客户端给服务器一次请求,然后服务器响应, websocket 一直保持连接
- 短连接只能客户端给服务器发消息,长连接服务器也可以给客户端发消息,他是双向的,所有经常可以用来实现后台推送,IM[即时通讯]等实时性要求高的需求
- 数据量肯定是websocket小,http 包含很多头信息
spring cloud , spring cloud alibaba 所包含的组件,底层都是基于http协议的,他的一些心跳机制啥的都是不断发送http请求,看有没有响应
dubbo框架的底层就是基于websocket长连接的,长连接性能是要优于短连接的
面试官问:TCP 协议为什么要设计三次握手?
你答:
三次握手是tcp建立连接的过程,确保连接的可靠性,主要是发同步序列号SYN,和ACK确认
我的理解很简单,其实就相当于一个有效沟通,
比如:领导通知我开会,我回复收到,领导扣1【表示领导知道我收到了】,相当于三次握手,那这次通知才算是通知到位了。

面试官问:四次挥手如何理解?
你答:
四次挥手是tcp断开连接的过程,tcp是全双工的,俩边都可以传输数据,所以俩端都需要关闭。
- 客户端 给 服务器 发 FIN " 请求释放连接 "
- 服务器 给 客户端 发 ACK 表示" 收到释放请求 ",此刻客户端处于半关闭状态
- 服务器 给 客户端 " 已经准备好释放 "
- 客户端 接收到消息 从半关闭状态 变更为 等待关闭状态,
并给服务器发送 " 已收到准备释放信号 "
然后客户端进入close状态
服务器收到确认后也进入close状态

充电桩大量接入,如何保证EMQX抗住高并发?
答:
主要是从 接入 到 分发 再到 存储 最后呢就是 弹性扩容
我记得我上个项目的话是这样做的 当时我们充电桩规模从几千增长到了几万甚至十几万,单实例EMQX 没有办法承载所有的连接、心跳与消息吞吐。当时我们做的就是 高可用 和 高并发框架设计 来保证平台稳定性,
EMQX这边的话就是集群水平扩展分摊连接数和消息吞吐,提升系统上限。然后呢就是做了一个消息削峰和控流的操作,就是我们有一个Qos按业务区分 心跳为0,电表读数为1,命令下发为2;配置了MQTT客户端最大消息速率,
因为高并发吗 所以我们使用的就是Kafak作为消息缓冲层。还有就是 心跳优化千万充电桩每几秒发一次心跳,瞬间压力就很大,所以我们用的就是心跳错峰,避免同时上报造成波峰。最后呢我们还做了监控和性能压测。
十万级MQTT长连接 心跳如何减压?
答:
我们上个项目的话在线充电规模接近8-10万台(还有一部分是双抢的)MQTT长连接带来的心跳量非常大,如果都按固定周期发心跳的话,Broker压力瞬间拉满。所以我们对心跳做了四项优化,让平台稳定跑在高并发规模下
首先就是
心跳错峰和随机抖动
我记得有一次上线初期的时候,我们遇到过晚上批量连接造成心跳洪峰的问题,瞬间把EMQX CPU打爆。我们是这样做的首次连接上Broker 延迟随机 0到 heartbeatInterval(心跳间隔时间)才发送首个心跳之后按偏移时间持续心跳
优化后呢变成了分散流式,峰值流量直接下降到了原来的20%左右
心跳周期按业务动态调节
我们发现空间桩发10s心跳完全浪费,因此我们做了一个分级的处理
充电中和挂单计费的我们设置心跳周期就是 6-10s,空闲的呢就是 30-45s,离线/故障尝试重连就是设置的指数退避 60-180s。这样整体心跳吞吐下降65%
服务端采用时间批量检测,不单扫
早期我们每秒遍历全量连接做判断,10万连接扫下来CPU明显抖,最后呢我们改成了 基于HashedWheelTimer(一个分段的时间轮) 分桶管理 lastSeen(设备最近一次活动时间)每秒只检查一个bucket(时间轮桶),而不是扫全部。资源占用下降明显,心跳超时检查延迟更稳定
心跳不是唯一在线依据 ->LWT 和 线上事件替代,我们项目把心跳作为兜底,不作为唯一判断依据
4.1 桩断开会发布LWT遗嘱 -> Broker直接推offline(设备离线状态)
4.2 桩重连SessionResumed(电桩重连不是重新开一个会话,而是继续之前的会话) -> 触发pending(未执行完成/未确认的待处理指令存储队列)指令恢复
4.3 心跳只负责保活 和 最后兜底
最后在线判断不在依赖大量心跳轮询 进一步节省了资源
实时通信
实时通信模块基于 Netty+WebSocket+IoTDB 时序数据库构建,是充电桩智慧能源服务系统的核心通信与数据存储组件,承担充电桩终端与云端平台的实时双向通信、前端可视化层的即时数据交互,以及海量时序化充电数据的高效存储与检索,为充电桩设备管控、运营监控、用户服务提供低延迟、高可靠、高吞吐的技术支撑。
1.Netty:设备端-云端高可靠通信基座
Netty作为高性能异步事件驱动的Java网络通信框架,为充电桩终端与云端平台搭建稳定的TCP/UDP通信链路,
核心价值体现在:
高并发连接管理:支持数千至数万台分布式充电桩终端的长连接管理,适配充电桩户外分散部署、网络环境复杂的场景,满足大规模设备接入的并发需求;
低延迟数据传输:通过Reactor线程模型、零拷贝、内置编解码组件,解决数据粘包/拆包、网络抖动导 致的传输异常问题,保障充电启停指令、设备状态(电流/电压/故障码)、计量数据等关键信息的低延迟(毫秒级)、无丢失传输;
设备通信标准化:基于Netty封装充电桩通信协议(如国标GB/T27930)的编解码逻辑,实现不同品牌、型号充电桩终端的统一接入与指令交互,降低设备适配成本。
2.WebSocket:云端-前端实时交互通道
WebSocket作为基于TCP的全双工通信协议,构建云端平台与前端应用(运营管理后台、车主APP/小程序)的实时交互链路,
核心价值体现在:
双向实时数据交互:突破HTTP协议单向请求-响应的局限,实现云端主动向前端推送充电桩实时状态(空闲/充电中/故障)、充电进度、告警信息等,同时支持前端向云端下发远程运维指令(如远程重启、参数配置),保障前端可视化层数据秒级更新;
轻量化交互适配:适配多终端前端应用的通信需求,通过统一的WebSocket通信接口,实现跨端(PC端管理后台、移动端APP)的实时数据同步,降低多端适配的开发与维护成本;
带宽资源优化:替代传统HTTP轮询方式,减少无效请求频次,降低云端与前端的带宽消耗,提升系统整体交互效率。
3.loTDB:时序化充电数据专属存储引擎
IoTDB作为面向物联网场景的高性能时序数据库,专为充电桩产生的海量时序数据提供存储与检索能力,核心价值体现在:
高效时序数据存储:针对充电桩按时间维度持续产生的电流、电压、功率、充电量、计费金额等时序数据,采用列式存储、数据分区与压缩策略,大幅降低存储成本(相比关系型数据库存储效率提升70%以上),支持亿级时序数据的高效写入;
精准快速检索:基于“设备-测点-时间”的原生数据模型,贴合充电桩设备数据结构,支持按设备维度、时间范围、测点类型的多维度快速查询(如单桩历史充电曲线、区域桩群空载率统计、故障码时序分析),查询响应时延控制在毫秒级;
可扩展集群部署:支持水平扩容的集群架构,适配充电桩数量从千级到万级的规模化扩展,满足业务增长过程中数据存储与检索的性能需求。
充电桩的告警和监控
充电桩的告警和监控。简单说,就是我这块专门负责“盯着充电桩”,看它有没有出问题,一旦有问题就马上报警,同时把实时数据和历史数据都记录下来,方便后面查。
其中有一些简单的crud,还有线程池加 CompletableFuture 做异步编排,一个是 Netty 加 WebSocket 做长连接推送,还有一块是 MQTT 加 IoTDB
现在充电站大家都见得挺多的,一般一个站里面有好多台充电桩,每台桩下面还挂着一两把充电枪。车一插上去开始充电,这些充电枪就会不停往后台发数据,比如当前电压、电流、功率、已经充了多少度电、充了多长时间,还有它现在是什么状态,是空闲、正在充电,还是故障、还是说车停在那儿但是没充电。运维的人其实很需要有一块地方,能随时看到这些数据,并且当设备出问题的时候,能立刻知道是哪一台出问题了,不然就只能靠用户投诉或者人工巡检,那就会比较被动。
所以我这个模块的目标有三件事。第一,把设备端发来的所有监控数据都接进来,不能丢。第二,根据配置好的规则,自动判断这些数据是不是异常,是不是要报警。第三,把实时数据和告警结果,清楚地展示给管理人员看,并且把历史数据完整存下来,方便后面查问题、做报表。
比如,点开监控大屏幕, 我们能够看到场站的信息 场站对应着电桩, 电桩对应着电枪, 我们在设计表的时候, 也是这么设计的
那我先简单说一下这些基础功能,证明一下我不是只写了个 demo,是真正有一整套管理后台的。
1、设备监控管理
这个功能主要是用来查看和管理每一台充电桩的实时运行数据。在后台页面上,我们可以按站点、按充电桩编号去查询,可以看到每一台桩当前的电压、电流、功率、温度这些关键参数,还有它的在线状态、设备状态这些。后台上可以新增监控记录、修改记录、删除记录,也可以按各种条件去筛选和查询。这些数据都是从 MQTT 消息里解析出来,然后存到数据库里的。这样做的好处是,运维人员可以随时翻看历史数据,比如某台桩在某个时间点的电压是多少、温度是多少,方便后面排查问题。
2、告警记录管理
这个是比较贴近日常运维的场景。每一次真正触发了告警,我这边都会落一条告警记录到数据库里。这条记录里面会包含很多信息,比如:是哪个站点、哪一台充电桩、哪一把枪触发的;告警类型是什么,是过压、欠压、过温、离线,还是长时间未充电之类的;告警是什么时间开始的,目前持续了多久;后来是谁去处理的,处理结果是什么,是简单重启了一下就好了,还是更换了设备;最后这个告警的状态是“未处理”、“处理中”,还是已经“处理完成”。在后台页面上,运维可以按时间、站点、告警类型、告警级别、处理状态去筛选这些记录,也可以导出一段时间的告警数据做报表。这样就形成了一个完整的闭环:从告警被系统发现,到后来谁在什么时候处理的,整条链路都有记录,以后要追溯问题,或者做绩效考核,都有依据。
3、告警规则
目前我们这边的告警判断逻辑是写死在代码里的,比如电压超过多少、温度超过多少就算异常。后续我们规划把这一块做成可配置的,让运维人员可以在页面上自己配规则,比如把阈值调高一点、调低一点,或者调整持续多长时间才算告警,这样会更灵活一些。目前这个功能还在规划中
4、监控看板
这一块以“查”为主。按站点的角度看,可以看到这个站目前总共有多少台桩在线,多少台桩离线,多少台桩处于故障状态,以及当前正在进行的充电次数、最近一段时间内触发的告警数量。如果点进某一台具体的桩或者某一把具体的枪,就可以看到这把枪当前的运行状态,比如说正在充电,它的实时电压、电流、功率是多少,还有最近一段时间内的告警记录、开始时间和处理情况。我们还会做一些简单的统计,比方说这个站最近一个月一共完成了多少次充电,其中有多少次是正常充完的,有多少次是异常中断的,比如中途跳闸、用户强制断电之类的。
5、这些基础的增删改查其实都是比较常规的
设备端通过 MQTT 协议,把充电数据发到 MQTT 的 Broker 上;我这个 warning 服务作为 MQTT 的客户端,订阅对应的 Topic,把这些消息收进来;收进来之后,在服务内部做一些校验、解析和业务判断,然后分别把数据写到 MySQL 和 IoTDB 里;与此同时,我这边还跑了一个基于 Netty 的 WebSocket 服务端,负责跟前端的监控页面保持长连接,一旦有新的告警或者关键状态变化,就通过 WebSocket 实时推到前端去。所以整条链路可以简单概括成:设备 → MQTT → 后端服务 → 数据库 → WebSocket → 前端页面。在后端服务里,除了普通的接口以外,我还重点做了一块,是用自定义的线程池加 CompletableFuture 来给一些大接口做异步编排,提高接口性能。这个也是我面试的时候比较想讲的一个亮点。那我就从线程池这一块开始讲。
在这个系统里,有一个接口叫“站点详情”之类的,前端在点开一个站点的时候会调用这个接口。这个接口要干的事情挺多的:它要查站点的基本信息,比如站点名、地址、运营商之类的;要查这个站下面所有的充电桩列表,每个桩下面又有几个充电枪;还要查每一把枪现在的实时监控数据;有的时候还要做一些统计,比如在线的桩数量、故障的桩数量,最近一段时间总共充了多少次电、平均充电时长等等。如果这些东西都用最原始的写法,一个一个顺序去查,那大概就是:先查站点,再查桩列表,再查枪列表,再算统计。这样做的问题就是,一旦其中有一步稍微慢一点,比如某个查询碰上数据库锁或者网络抖了一下,整个接口就会被拉长,前端打开这个页面就会明显感觉到卡顿。
所以在做这个接口的时候,这里面其实很多东西是互相没有依赖关系的,完全可以并行起来做。比如,查站点基本信息、查桩列表、查历史统计,其实都是可以同时开始的,不需要一个等一个。只有少数几个地方有依赖关系,比方说你要先有桩列表,才能根据桩的 ID 去查这几个桩下面的枪,这种地方我们再做一下顺序的编排就行。
一开始也考虑过直接用 Spring 提供的 @Async,但是 @Async 更适合那种简单的场景,比如发个异步短信,或者异步写一条日志之类的,它是那种“丢一个任务出去,不管了”的感觉。如果我要同时起一堆任务,然后等它们都跑完,再把结果合在一起返回,这种编排用 @Async 写起来就有点绕,而且默认线程池的参数也没那么好调。所以最后我选的方案是:自己定义几个线程池,然后配合 CompletableFuture 来做这种多任务的并行和汇总。
线程池这块,我大概分了两类。第一类是专门用来跑查询类任务的,比如查站点信息、查桩列表、查告警列表等等。这类任务的特点是:单个任务通常比较快,但是数量会比较多。第二类是用来跑稍微重一点的任务的,比如做一些统计、聚合运算,这些任务数量没那么多,但是单个任务执行时间会稍长一点。不同的线程池我会配不同的核心线程数、最大线程数和队列长度,比如查询型的线程池线程数可以稍微大一点,但队列长度不要太夸张,避免短时间瞬间堆太多;计算型的线程池线程数相对适中,保证不会有太多重任务并行把 CPU 打满。
使用自定义线程池 & 参数配置,配置类:
在拒绝策略这块,我更偏向一种“出问题就大声说”的做法。具体来说,就是当队列满了以后,不是悄悄地丢任务,而是直接抛异常,然后记录详细日志。这样我们在压测和线上监控的时候,一旦线程池有被打满的趋势,就可以很快感知到,及时调整阈值或者做限流,而不是让系统慢慢拖成一个大死锁。上层接口如果真的感知到线程池拒绝了任务,我们也可以做一些降级处理,比如只返回核心数据,暂时不查次要的统计信息,让页面至少能打开。
CompletableFuture 异步编排
场站详情/监控接口,用 completableFuture.supplyAsync 并行查站点名、桩列。表,thenApplyAsync 衔接枪列表查询,最后,sompletakleutus.a110f(...)join0汇总结果
在具体的编程上,我用得比较多的是 CompletableFuture,也就是经常用它的 supplyAsync(s铺蓝A信)、thenApplyAsync(then饿铺来A信) 和 allOf 这些方法。拿“站点详情”这个接口举例,我的做法大概是这样的:先用 supplyAsync 起三四个并行任务,一个任务去查站点基本信息,另一个去查这个站下面所有的桩,还有一个去查一段时间内的统计数据。这几个任务之间基本上互不影响,可以同时跑。对于有依赖关系的地方,比如说“根据桩列表再查每个桩下面的枪”,我就会在拿到桩列表的结果以后,再启动一个新的异步任务,去查这些枪的监控数据,并且把结果按照桩 ID 做好分组。最后我会用 (康姆普雷包扶有qie)CompletableFuture.allOf 把这些 Future 合在一起,等待它们全部完成,然后从每个 Future 里面拿出结果,组合成一个完整的 DTO 返回给前端。
用完这一套之后,实际效果还是挺明显的。原来串行查询的时候,这个接口的响应时间有时候会比较飘,少的时候可能两三百毫秒,多的时候上到五六百甚至一秒多都有可能。并行改造之后,在正常压力下,大部分请求可以稳定在一两百毫秒,偶尔有波动也不会太夸张。特别是在并发稍微大一点的时候,这种把大任务拆开并行执行的方式,会比单纯靠堆硬件要更划算一点。
线程池这块有一个坑也可以顺便说一下。一开始我线程池队列设得有点大,结果在数据库出现短暂问题的时候,大量的任务会堆在队列里,慢慢把内存顶上去。后来我就调小了队列长度,并且在业务上做了一些超时控制,也就是说,一个任务在一定时间内没执行完,我宁可让它失败,也不让它一直在那儿无限等。这种做法的好处是,系统宁可在局部失败,也不要整体被拖死。
说完线程池,我再讲讲 Netty 加 WebSocket 做长连接这一部分。
用这个是希望尽可能实时地看到当前的充电状态。如果我们用传统的 HTTP 轮询,比如每隔三秒钟、五秒钟前端发一个请求问一句“有新数据吗”,有几个问题。第一个是开销大,很多请求其实是白问的,因为很多时候数据没变,但请求还是发了,对服务器和网络都是负担。第二个是实时性不太好,总会有一个轮询间隔,你可能三秒钟之后才知道刚刚发生了一条告警。第三个是,如果用户开的监控页面特别多,所有页面都在轮询,接口压力会非常恐怖。
所以在这个项目里,我是用 WebSocket 来做长连接。简单说,就是前端跟后端先通过 HTTP 做一次握手,升级成 WebSocket 协议之后,就变成了一条长连接。以后不管是后端给前端推消息,还是前端给后端发一些指令,比如订阅或者取消订阅某个站点,都是走这条长连接。
WebSocket 底层我用的是 Netty。Netty 的好处是性能比较好,适合处理高并发连接,而且对各种编解码、心跳管理、线程模型这些都有比较成熟的抽象。我这边起了一个 Netty 的 WebSocket 服务端,它里面是经典的 Boss/Worker 模型:Boss 线程组专门负责接新的连接请求,Worker 线程组负责处理已经建立连接之后的读写事件。这样可以在连接数比较多的时候,比较稳定地扛住压力。
在 Netty 的 Pipeline(拍普兰) 里面,我会先挂一些 HTTP 和 WebSocket 协议相关的 handler,负责协议层面的解析和升级,然后在后面挂一个自己的业务 handler。这个业务 handler 主要做两件事情:一是管理连接,二是处理业务数据的发送。
连接管理方面,前端连上来时会带一些信息,比如用户 ID、站点 ID。服务端拿到后,给这个连接生成一个唯一标识,然后把“标识”和“连接通道(Channel欠no)”的对应关系存到一个 Map 里,就像“姓名 → 电话号码”的通讯录。
要推送数据时,根据站点 ID 或用户 ID,从 Map 里找到对应的连接,然后发送。比如要给站点 001 的所有用户推送告警,就从 Map 里找出所有关注站点 001 的连接,挨个推送。(就像快递员送包裹,先看地址(站点 ID),再查通讯录(Map)找到对应的门牌号(Channel),然后敲门(推送消息)。
心跳和超时清理
为了避免出现一堆“僵尸连接”,我还做了心跳和超时清理。
问题场景:浏览器标签页关了或网络断了,但服务端可能还认为连接存在,这些“死连接”会占用资源。
做法:前端每隔一段时间(比如 30 秒)发一个很小的“心跳包”,后端收到后立刻回一个“心跳响应”。如果后端连续几次没收到心跳,就认为连接已断,主动关闭并从 Map 里移除。
效果:避免连接数一直增长,防止内存被占满
(打个比方:
就像定期打电话确认对方还在,如果连续几次打不通,就认为对方不在,从通讯录里删除。
)
在推送策略上,
可以理解为“重要消息立刻发,普通消息攒一攒再发”。
“重要消息立刻发,普通消息攒一攒再发”。
重要消息(告警、状态变化):一有变化就立刻推送,保证第一时间看到。
普通消息(电压、电流等高频数据):几秒就变一次,每次都推会浪费资源。做法是“攒一攒再发”,比如每隔 1 秒或 5 秒,把这段时间的数据打包一起发,前端收到后画成平滑曲线。这样既能看趋势,又不会把网络和服务器打爆。
(打个比方:就像发微信:紧急消息立刻发;日常数据像“步数统计”,可以每小时汇总一次再发。)
再说说 MQTT 和 IoTDB 这一块。
最后说一下 IoTDB。为什么要引入时序数据库,而不是全部用 MySQL 呢?主要原因是我们这边的监控数据有一个特点:它是高频的、按时间往前走的。一把充电枪可能每隔几秒钟就上报一次电压、电流、功率,如果全部丢在 MySQL,一段时间之后,表会变得非常大,每天插入的行数会很恐怖,而且你一旦要按时间范围查一条电压曲线,比如“这把枪过去 24 小时的电压变化情况”,或者你要在一个时间区间内算平均值、最大值,传统关系型数据库就会比较吃力了
IoTDB 就是专门针对这种“时间序列”场景做优化的数据库。简单说,它在写入速度、存储空间和按时间查询方面都有明显优势。建模的时候,我是这样设计的:最上层是一个根节点,比如 root.charging,下面一层是站点 ID,再下面一层是设备 ID,再下面一层是具体的指标名,比如 voltage、current、power 等。这样一条路径看起来就像 root.charging.s001.d001.voltage 这样的形式。每次写入的时候,其实就是“时间戳 + 一批指标的值”,比如在某一个时间点,这个设备的电压、电流、功率是多少。
在实际使用场景里,运维打开某个设备的历史趋势页面时,我就会从 IoTDB 里查询最近一段时间,比如最近 24 小时或者最近一周,这个设备的电压曲线、电流曲线、功率曲线,然后前端画出来。有的时候,还会做一些统计,比如在某个时间段内,这把枪的最大功率是多少、平均电压是多少,这些 IoTDB 都有比较方便的查询方式,而且响应速度也不错。相比之下,如果这些数据都存在 MySQL 里,查询起来就会很笨重,甚至需要加很多索引、做分表分库,整体复杂度会更高。
最后,我简单说一下我在这个项目中的一些收获和踩过的坑。线程池这一块,一开始参数配置得不是特别合理,队列设得太大,结果在某次数据库出问题的时候,大量任务被堆在队列里,内存占用一度上去得比较快。后来通过压测和线上观察,我把队列长度收紧了一些,同时给任务加上了超时和快速失败的机制,这样宁可短时间内部分请求失败,也不要让整个服务挂掉。WebSocket 那边,一开始前端没有做心跳,很多浏览器标签页关掉了,但是连接在服务端还保持着,时间长了连接数会越来越多,这个后来也是通过加心跳检测和超时清理解决的。MQTT 和 IoTDB 那边,最开始 Topic 命名不太规范,导致排查问题的时候有点乱,后面统一了一套命名规则,并且把 IoTDB 写入从一条一条写改成了小批量写,性能有一个比较明显的提升。
整体来说,这个充电桩告警监控模块,一方面在业务上帮运维做到了“实时看状态、实时收告警、有完整处理记录”,另一方面在技术上让我把 MQTT、Netty、WebSocket、线程池异步编排、IoTDB 这些东西串在了一起,形成了一条比较完整的物联网数据链路。对我个人来说,这个项目最大的收获就是:第一,对物联网这种“设备端到服务端再到前端”的全流程有了更直观的理解;第二,亲自调了一遍线程池参数、用 CompletableFuture 做了不少多任务编排,真切感受到合理设计并发模型对性能的影响;第三,第一次在正式项目里使用时序数据库,知道了什么时候应该用这种专门的时序库,什么时候继续用传统的关系型数据库。
小程序基于websocket长连接与云服务通信
本模块描述 微信小程序 与 云端平台 之间的通信机制,涵盖用户身份验证、充电控制指令下发、实时状态同步及计费信息推送等核心功能。通信采用 WebSocket 长连接 的混合模式,确保安全性与实时性。
1.WebSocket 连接频繁断开
原因:网络不稳定, 实现心跳机制(每 30 秒 ping/pong),小程序端自动重连
2.消息推送给错误用户
原因:userId ↔ Channel 映射错误 使用线程安全 Map(ConcurrentHashMap),连接关闭时及时 remove
3.Token 过期后仍能通信
原理:未验证 Token 有效期 每次消息处理前校验 Token,过期则关闭连接
4.小程序收不到推送
原因:微信限制后台运行 提示用户“保持小程序前台运行”,或结合订阅消息兜底
5.高并发下 Netty 内存溢出
原因:Channel 未释放、对象堆积 合理配置 EventLoopGroup,监控 GC,及时 close 异常连接
什么是粘包/拆包:TCP会把多条消息粘一起或拆开传,所以要自己设计协议做消息边界,比如加长度字段,分隔符,定义协议 当然最标准解决办法是加长度字段
netty websocket为什么要实现心跳机制
Netty 的 WebSocket 服务必须实现心跳机制,主要原因在于 维持长连接有效性、检测连接存活状态、防止资源泄漏和提升系统稳定性
为什么选择使用netty来实现webSocket?
netty网络通信框架的三个核心,net线程模型、net事件驱动机制、net零拷贝
因为net他能够高并发以及他传输速度快,高并发就是net它能够在同一时间处理大量的请求。传输速度快就是,net能够在极短的时间内去处理这些请求。nt之所以能够实现高并发,主要在于两点,第一点就是nt的线程模型,第二点就是net的时间驱动机制,而net它之所以能够实现网络请求处理的速度很快,它主要的原因就是在于零拷贝。
什么是线程模型?(能够使net在同一时间去处理大量的并发请求)
我们可以把net这个网络通信框架,理解为一个物流公司,那么在这个net物流公司里面,他的员工能够在同一时间,一次性的处理多个包裹。
什么是时间驱动机制?(net提供了专门的线程去处理所对应的事件)
同样的也是net这个物流公司,它是派了一个专门的员工去处理专门的事件,也就是说,这个单独的事件,就由这个员工单独的去处理。
什么是零拷贝?(数据的快速传输)
我们可以理解为net这个物流公司,它能够将包裹通过一个传送门,直接的把包裹运输到目的地,而不需要经过长途的运输,这个就是netty的零拷贝。