V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
unt
V2EX  ›  程序员

如何一次性获取海量设备(以 5W 为例)的最新当前状态(在线离线打开关闭)

  •  
  •   unt · 296 天前 · 4444 次点击
    这是一个创建于 296 天前的主题,其中的信息可能已经有所发展或是发生改变。

    仅有且已有 redis+mysql+mongo 。

    目前已采用两种方式:

    1. 用户刚进入页面时直接用 HMGET 。缺点:害怕 redis 存在性能瓶颈,需要专门做优化处理,比如拆分成小批量或者使用管道;
    2. 每次 iot 数据上报时,改变 mysql 的状态字段,那样每次调用业务接口,直接就能拿到状态;

    请问最合理、性能开销最小的方案是什么。请教大家一些经验。

    第 1 条附言  ·  296 天前
    心跳是 30s ,工况主动上报是几分钟一次;用户不操作的话无其他数据,用户每次操作后会有一次 ack 数据。

    能否结合用户的登录态再进行一次筛选,因为用户不用你软件的话,大部分时间的状态更新其实都是无意义的。但是如果等到用户登陆后/操作后再去下发设备命令获取最新数据(标题说的最新是指数据库数据,物联网应用没有真正最新的概念),又会有几秒的延迟。
    39 条回复    2024-01-18 09:10:33 +08:00
    LsLsLsLsLs
        1
    LsLsLsLsLs  
       296 天前   ❤️ 1
    redis bitmap
    yannxia
        2
    yannxia  
       296 天前
    最快的肯定是 IOT 数据上传的时候就通过 Redis 的 Sub/Pub 一类的机制让服务端缓存在内存里,那 5w 就立等可取(从内存里拿)缺点嘛,不一致就想办法补偿(去数据库同步啥的)
    retanoj
        3
    retanoj  
       296 天前
    需求是想统计出各种状态的计数和吗?
    opengps
        4
    opengps  
       296 天前
    内存表
    des
        5
    des  
       296 天前
    不说说一次性获取这么多是做什么用,列表也显示不下吧
    感觉像是在线/离线统计
    ytmsdy
        6
    ytmsdy  
       296 天前
    在 iot 的固件里加入心跳请求吧,三秒或者 10 秒一次心跳。
    把设备 ID 号丢到 redis 里面,设置 60 秒的过期时间,有心跳包上来了,就把值刷新一下。
    需要统计有多少设备的话,直接统计一下 redis 里面有多少个 key 就可以了。
    gongquanlin
        7
    gongquanlin  
       296 天前
    iot 上报时改变 mysql 状态很容易就把 mysql 干炸,之前因为这个吃过很多次亏了;就缓存到 redis 里,一般不存在性能瓶颈,定期根据 key 取模分批的同步到数据库里; model 封装一下,先读缓存,缓存没有再读库(这时候基本上就是掉线了)

    维护好 redis 就行了
    wanwaneryide
        8
    wanwaneryide  
       296 天前
    根据对设备状态重要程度的重视程度吧,比如说比较一般般的话,设置设备 1 分钟上报一次状态,连续多久都是非正常的状态就提醒管理者啥的。如果对时效性比较重视,30s 或者 15s 或更短上报一次状态。何必每次查看的时候再去获取状态。如果每次查看都获取一次的话,假如这批设备多人多账号管理,极端的情况,几个人间隔几秒或者几十秒刷新一下页面,服务器得炸
    me1onsoda
        9
    me1onsoda  
       296 天前
    这一看就是列数据库的长项。redis mysql mongo 都不太符合。
    如果状态只有 01 的话用 redis 的位图吧
    nothingistrue
        10
    nothingistrue  
       296 天前   ❤️ 1
    在线离线的变更,你要用连接打开、关闭事件,以及心跳事件来触发,不要依赖业务数据上报。

    要是追求强一致性,那就让 IOT 服务器先自行管理在线设备集合(一般你要有下行数据需求的话,这个是必须的功能),每次查询时,基于 IOT 服务器汇报的在线设备集合,来计算待查询设备集合的在线状态。

    要是不需要强一致性,那就是你的方式 2 。

    至于性能开销,实际上没法直接评估,这个是随环境变更的。比如上面俩方案,第一个性能耗费在数据读方向上面,第二个性能耗费在数据写方向上面。如果平均每设备每天上下线 1000 次,则第一个方案性能好。如果设备一两年才上下线一次,则第二个方案性能好。
    unt
        11
    unt  
    OP
       296 天前
    @yannxia #2 方法 1 就是这么搞的呀,已经实现了
    unt
        12
    unt  
    OP
       296 天前
    @retanoj #3 不是
    @des #5 不是。是 GIS 地图展示,传统做法是根据缩放等级是视口经纬度分批展示,我们不想这么做,要一次性展示全。
    unt
        13
    unt  
    OP
       296 天前
    @nothingistrue #10 谢谢,你提供了另一种思路,通过服务器来管理,我考虑下可行性。
    unt
        14
    unt  
    OP
       296 天前
    @2677672 #1
    @me1onsoda #9 我去了解下
    unt
        15
    unt  
    OP
       296 天前
    @ytmsdy #6 你说的这个已经实现了,问的不是这个,依然感谢🙏
    retanoj
        16
    retanoj  
       296 天前
    感觉 10 楼回答了服务端记录在离线状态的问题。
    对于“GIS 展示”这个事情,我咋觉得不用考虑“一次获取”,而分批获取+有过程的展示在前端效果上可能更有好一些
    unt
        17
    unt  
    OP
       296 天前
    @retanoj #16 一般确实是这么搞的,但是我们不决定这么操作
    unt
        18
    unt  
    OP
       296 天前
    @gongquanlin #7 单次操作确实没问题,并发操作就炸了
    Mithril
        19
    Mithril  
       296 天前
    你 5W 个设备要是时钟很准,卡在同一秒上报状态的话,那就是极限状态下一秒 5W 的写操作,你那数据库分分钟就干爆了。而且你要的是“状态转换”而不是“当前状态”。
    要是我的话,会做一个用来缓冲和过滤的组件,来维护这个状态,并当数据改变时再分批写入数据库。
    直接做个简单的 webserver ,内存里维护一个数组。收到数据时对比一下已有数据,如果有变更就塞队列里,然后 mysql 定期从队列同步状态写回数据库。

    这程序甚至都不用做持久化,每次启动的时候从数据库里读一次全局状态,或者等 30 秒后设备给你发过来就行了。

    但还是同样的问题,如果你 IoT 设备时钟很准,你这 webserver 也要好好设计才行。50K 的 IOPS 写操作对于单个 server 也不是个随随便便就能搞起来的。但好在这简单的 server 你可以横向拆分,根据请求来源的设备做个路由,分到几个实例里就行了,非常容易。
    kirory
        20
    kirory  
       296 天前
    5W 也不多啊,真有性能问题直接放应用里放个 map 不就行了
    allenby
        21
    allenby  
       296 天前 via Android
    redis bitmap 不错
    xmumiffy
        22
    xmumiffy  
       296 天前
    5w 用 redis 有啥好怕的
    totoro52
        23
    totoro52  
       296 天前
    我之前阅读 thingsboard 源码他是两种都做的, 数据库也更新面板统计也存在缓存里
    cutchop
        24
    cutchop  
       296 天前
    5w 又不多,直接读 mysql 不就好了吗
    unt
        25
    unt  
    OP
       296 天前
    @Mithril #19 嗯,现在想来在服务端内存中再做一层维护比较好。 可是那块业务不是我做的,哎,以后再说吧。
    iv8d
        26
    iv8d  
       296 天前
    都是临时状态,一般存内存或 redis 里,没必要存 rdbms
    unt
        27
    unt  
    OP
       296 天前
    @iv8d #26 主要是 mysql 读没有性能压力,随便玩
    cnsdytedison
        28
    cnsdytedison  
       296 天前 via Android
    你说的这个 aws 家有现成的方案,可以参考。不过我也没用过,只是看过宣传。推广期价格也不贵
    yannxia
        29
    yannxia  
       296 天前
    @unt 看内容是更新的是 mysql ,每次都要读 5w 的 mysql 数据啊,应该更前置到后端服务的内存里面
    LiaoMatt
        30
    LiaoMatt  
       296 天前
    设备离线上线更新数据库, 在线状态的维持靠心跳或者消息更新缓存, 然后定时读缓存刷新状态, redis 读 5W 应该还是很轻松的, 如果对实时要求不高直接通过数据库做聚合, 几万条数据应该也很轻松
    LiaoMatt
        31
    LiaoMatt  
       296 天前
    放数据库一半是因为要用设备在线状态做业务, 筛选排序, 或者一些关联数据查询需要指定某种状态的设备
    ITdream
        32
    ITdream  
       296 天前
    上 IoTDB
    koloonps
        33
    koloonps  
       296 天前
    在集群内部广播一下,让每个服务发送下当前在线客户端列表.
    hamsterbase
        34
    hamsterbase  
       296 天前
    如果是实时上报的话,是不是直接在内存里维护一个 map 就行了。


    1. 在内存里维护一个 map
    2. 定时将 map 的数据备份储存。
    unt
        35
    unt  
    OP
       296 天前 via iPhone
    @hamsterbase 嗯,因为这种方法最简单粗暴,性能也强,但是现在一般都默认不会这么做。

    其实想想这样做也挺好
    a67793581
        36
    a67793581  
       296 天前 via Android
    op 记得更新一下最终方案 回馈社区
    F7TsdQL45E0jmoiG
        37
    F7TsdQL45E0jmoiG  
       295 天前
    这种是典型是时序数据,用时序数据库最好,最简单的实现方式是 Prometheus
    yc8332
        38
    yc8332  
       295 天前
    不知道你存的数据多少?。。不大的话,redis 主从,查询从从库查,直接一次性 hgetall 出来,几十万一点问题都没有。
    所以你说的海量到底是多少?真是海量也不可能会一次性需要把所有数据都展示。顶多做个多少在线这种,这样只要取计数就好了。具体要查看某些目标的值的时候再取详细的
    unt
        39
    unt  
    OP
       295 天前
    @a67793581 #36 那块业务不是我做的,而且已经上线运行,暂时改不动,数据库端不动话目前的解决方法应该是在服务器内存中再维护一层设备 map ,但是需要综合考虑一下性能消耗并完善下逻辑
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   996 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 19:49 · PVG 03:49 · LAX 11:49 · JFK 14:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.