V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
xx19941215
V2EX  ›  问与答

Web 消息中心怎么做

  •  
  •   xx19941215 · 2018-06-30 09:40:46 +08:00 · 5143 次点击
    这是一个创建于 2364 天前的主题,其中的信息可能已经有所发展或是发生改变。

    公司一个新任务 消息中心,有没有大佬指点小弟下。具体需求就是比如运营在后台发一篇通知,现在的几百万用户的消息中心会出现一条未读消息,然后可以标记已读还有删除功能。还有各种其他的消息,比如支付提醒、收藏的店铺上新提醒。这个要怎么设计,感觉 mysql 顶不住啊?

    14 条回复    2019-02-24 17:52:45 +08:00
    xiaoxinshiwo
        1
    xiaoxinshiwo  
       2018-06-30 09:47:01 +08:00
    mq,发布 /订阅
    mysql 顶得住,主要是你的提醒查询 sql 要快
    最简单就是 ajax 轮询(次不推荐)
    其次是 comet 长连接(不推荐)
    最后就是 webSocket(推荐)
    iac
        2
    iac  
       2018-06-30 09:47:16 +08:00
    一个信息表,一个信息查看记录表
    misaka19000
        3
    misaka19000  
       2018-06-30 09:49:01 +08:00
    想用 mysql 的话直接用一张表存用户与消息的关系,之后查询的时候根据用户来查询与其相关的消息就行了,消息的已读未读状态用一个字段来表示,几千万条数据 mysql 还是能顶住的,关键是你这个业务场景的并发量大概是怎么样的?
    misaka19000
        4
    misaka19000  
       2018-06-30 09:50:00 +08:00
    @xiaoxinshiwo #1 我觉得对实时性要求没那么高的话完全可以在用户刷新页面的时候再显示消息内容,没必要实时显示
    wuxi317
        5
    wuxi317  
       2018-06-30 10:07:22 +08:00 via Android   ❤️ 1
    消息主表,用户自己的消息表,用户最后一次查询或者登录的时间。定向给用户的消息就直接落表,全局消息在用户登录或查询接口里按照 msg 创建时间>最后查询/登录时间的,查到了,就 copy 到用户自己的消息表中,标注为系统消息。这样可以标注已读未读了。
    vebuqi
        6
    vebuqi  
       2018-06-30 10:08:55 +08:00   ❤️ 3
    从场景上来看,建议把消息分成两种,全站消息和个人消息。
    全站消息一张表,所有用户个人消息一张表
    消息操作记录如已读,删除等用另一张表

    运营推新文章,往全站表里塞一条
    支付提醒,店铺上新提醒属个人消息,记到个人表

    塞完后
    在线用户通过 WebSocket 推送提醒,用户侧出红点
    离线用户,在登录时候,去查一次返回就可以了

    用户收到提醒,点开消息中心时候去查消息(一定不能收到 WebSocket 推送就去查消息)
    用户操作消息,加入到消息操作记录表里
    abcbuzhiming
        7
    abcbuzhiming  
       2018-06-30 10:10:58 +08:00
    @misaka19000 它这个场景上 MySQL 还真有些问题,如果它说的几百万用户是真的。那么只要群发 10 次,用户与信息的关系表的记录会达到几千万,群发 100 次的时候这张表的数据会达到几亿这个级别。到这个级别的时候 MySQL 的查询速度也会大大的变慢,即使加了索引改善也不会很大,换别的关系数据库也不会有啥改善的。要么系统设计成按时间分割表,比如每个月(群发特别频繁的时候估计每周都得新建)新建一张用户与信息关系表,查询的时候 union 操作。要么就只有上大数据了。
    另外这个场景还可能存在的一个问题就是几百万用的消息到达时间的差别拉的太长,单库群发一次,毕竟要一口气插入几百万条用户与信息关系数据,也是要时间的
    abcbuzhiming
        8
    abcbuzhiming  
       2018-06-30 10:19:34 +08:00
    @wuxi317 你这个想法补充,我一直以来没想到怎么弄的问题就是在群发的时候需要插入很多条用户-信息关系记录的问题。你这个解决办法把插入过程延后,并分散到每个客户端登录的时候由他们来做。
    但是你这个方法有个弱点,如果这条群发消息是用户在线的时候发的,那用户如果没有退出重新登录行为,就永远收不到这条消息了,所以这个方法似乎有点问题,我建议还是把“ copy 到用户自己的消息表中”这个行为放在客户端轮询时都有新消息的过程中做
    wuxi317
        9
    wuxi317  
       2018-06-30 10:36:21 +08:00 via Android
    @abcbuzhiming 有提到了一个消息查询接口,看实时性需求。如果有实效要求,那么是可以使用消息查询接口的。但是在消息查询接口中,时间查询条件就需要很好的去处理,如果使用 mysql 来记录每一个用户最后一次查询时间,那这个量级就非常巨大了。
    xx19941215
        10
    xx19941215  
    OP
       2018-06-30 10:43:40 +08:00
    @abcbuzhiming @iac @misaka19000 @vebuqi @wuxi317 @xiaoxinshiwo 小弟先消化一下各位的回答 非常感谢🙏
    zpf124
        11
    zpf124  
       2018-06-30 11:22:57 +08:00   ❤️ 1
    我们之前的做法是:

    消息组表,消息表,用户消息已读表。

    消息组:id 0 表示全站消息,所有人都加入这个组,其他组 根据当时的消息分类,代码后台自动创建。
    消息表: 存储消息内容,关联消息组表,
    已读表: 存储已读记录 关联消息 id 与用户 id。

    通过这样,我们把 读取压力改成了写入压力... 不过我们用户量不是非常大,而且信息重要性还不高,不是每个人都有兴趣去标记已读的.... 如果后来真有这个压力了,到时候再用 redis 做写入缓存。

    另外 因为我们需求的原因,我们没有给单个用户发消息的情况,都是群发:“你好你关注的 xx 有了 xx 的新动态”(当然如果关注这个订阅的只有一个人那就是单发了),
    用户也没有删除消息的选项,不过如果做删除的话我们直接在已读表上改造一下就可以逻辑删除了。
    XYxe
        12
    XYxe  
       2018-06-30 12:15:38 +08:00 via Android
    mingyun
        13
    mingyun  
       2018-06-30 18:03:07 +08:00
    @XYxe 这篇文章很详细
    xx19941215
        14
    xx19941215  
    OP
       2019-02-24 17:52:45 +08:00
    @wuxi317 我是用老哥这个方法弄的 😂
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5378 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 08:55 · PVG 16:55 · LAX 00:55 · JFK 03:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.