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

求评判/提醒下 “N 条未读提醒” 提醒(用户提醒消息)的最佳操作。分享点经验吧各种牛们,谢谢!

  •  1
     
  •   raincious · 2014-06-26 21:22:06 +08:00 · 5223 次点击
    这是一个创建于 3830 天前的主题,其中的信息可能已经有所发展或是发生改变。
    背景:

    数据库:MySQL
    语言:PHP

    (因为架构复杂度的问题,暂时不考虑引入其他的基础设施依赖,所以只能用MySQL作为数据库,而且PHP没有队列支持。一切都从最简单出发。)

    (好在我真的非常聪明的让框架透明的支持了按照表名称进行分库查询,所以这些表可以放在其他数据库服务器上不会而影响其他服务器的性能。当然,虽然这也导致了不能支持任何事务……)

    ===============================

    正文:

    正在给自己的网站写提醒服务,通过一个Ajax的Post请求不停的给Web发送带有参数的查询来获得数据并检查是否有给当前用户的提醒。然后按照这样显示出来:



    功能上类似于V2EX的“N 条未读提醒”,但是需要查询两张表,一张提醒,一张消息。

    查询是由Ajax发出并控制频率的,正常用户不会干预(非正常用户可以直接攻击接口,但是能根据会话频率限制来阻止)。不能是Websocket,也不能进行频繁的查询操作(MySQL大家都懂的)。所以大约60秒查询一次这样。

    ===============================

    设想的方案:

    现在已经有两个设想的方案,想知道那个好一些,请大家帮忙评价下,或者给个其他方案:


    1、代码上最简单的方案:

    当收到Ajax调用时,分别查询两张表,列出至多N条记录,然后将N返回,呈现给客户端。且不做缓存处理。

    查询语句类似于

    SELECT * FROM messages WHERE userID = 1 AND readed = 0 LIMIT 10;
    SELECT * FROM notifications WHERE userID = 1 AND readed = 0 LIMIT 10;

    优点:
    A:复杂度低,容易实现;
    B:可扩展性好。未来如果需要,可以不用仅显示“收到了 1 条新消息”,而是列出这最多10条消息/通知。

    缺点:
    A:列出这些数据相对耗时,尤其是直接读取了操作表,可能会导致性能下降。



    2、复杂度高一点的方案:

    建立一张数据表(如pushs),分别记录未读消息的数量,然后每次有新消息/通知写入和被阅读时,增减记录的数据值。这些数据直接从数据库取得,不做缓存。

    数据表类似于
    pushID(主健), countMessages, countNotifications, userID(唯一索引), time;

    查询语句类似于

    SELECT countMessages, countNotifications WHERE userID = 1;

    优点:
    A:唯一索引查询速度更快,而且它是唯一的查询条件;
    B:单独的数据表可以作为内存表,获得更快的查询速度,只需要在用户登录时重新统计数据即可(这意味着方案1里那两条查询会在用户登录时执行一次)。

    缺点:
    A:复杂度高。需要变更一系列已有代码(好在我可以用预留的钩子实现),同时还需要维护表中的这些数据以确保其准确。
    B:扩展性差,基本上就只能那样了,如果未来需要显示其他数据,则需要变更大量代码。


    // 啥时候支持Markdown?
    第 1 条附言  ·  2014-07-01 16:18:34 +08:00


    看见各种浏览器收到消息后不停蹦跶,真是很有喜感。
    7 条回复    2016-02-02 16:52:55 +08:00
    Seita
        1
    Seita  
       2014-06-26 21:23:27 +08:00 via iPhone   ❤️ 1
    楼主的想法适合几百人的小网站。
    ovear
        2
    ovear  
       2014-06-26 21:41:31 +08:00   ❤️ 1
    @Seita 这实现并没有什么问题。而且适当缓存负载可以到很高。
    建议lz做好索引,然后做一个内存表就可以实现了
    raincious
        3
    raincious  
    OP
       2014-06-26 22:05:09 +08:00
    @ovear 谢谢。

    刚发贴之后我就在测试,只是比对了下查询取出的数据量(用来一定程度上代表服务器的负载),感觉方案2是唯一选择。那么我就继续了。

    另外虽然上面说是没有缓存,只是因为现在我选择困难了,也还是两种方案作选择,一是Memcache的古典搭配,二是让ORM支持Redis。

    目前选择了二,虽然SQL ORM上本来也就只是支持WHERE条件,但是考虑到索引和Redis里Set的限制,太难了,所以一直停滞不前,于是为了不影响主要的项目进度,选择先暂时这样,等真的能上百并发的时候估计我也把Redis或Memcache这两个方案研究的差不多了。
    piglei
        4
    piglei  
       2014-06-26 22:38:05 +08:00
    仔细看了看,楼主应该花了不少心思分析这个东西。我的经验是不用过于纠结,选一个 **不是那么差劲但是很好扩展** 的实现就好,等瓶颈来的那天,再花时间优化吧。

    如果现在过于追求这个问题,把性能优化到非常好,很有可能之后 设计/需求 变动,整个又需要推倒重来,不划算。

    个人意见。
    ovear
        5
    ovear  
       2014-06-27 00:38:32 +08:00   ❤️ 1
    @raincious 其实不用考虑这么多的,mysql本身就有缓存。
    缓存效果还是不错的,尤其是主键unique。
    还有就是建议楼主在资源允许的情况下,使用固定line长度以及字段长度
    这样mysql就不用扫描每行的大小了,对速度有非常大的提升。
    同时其实第一种并没有什么 问题,还是挺不错的。
    关于count方面,lz完全可以在user表中新增个notify字段,来模拟kvdb过期的效果。当然坏处也有。。就是跟v2ex一样,通知会有延迟。在我看来,其实v2ex的通知系统做的并不是很好。延迟非常的大,还经常莫名其妙的丢信息。
    总之这种事情最好上cache或者nosql(也就是kvdb)。lz如果实在要用mysql,那么我推荐使用hash索引,可以在一定程度的情况下模拟出nosql的hashdb效果
    -0- 希望lz以后有更好的方法来交流下哈~
    pythoner
        6
    pythoner  
       2014-06-27 15:08:10 +08:00
    如果让我做,我首先想到的方案是用redis来作计数器(inc/dec操作是原子的)
    有新消息的时候计数器+1,读过之后-1
    jarlyyn
        7
    jarlyyn  
       2016-02-02 16:52:55 +08:00
    数量缓存?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2894 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 00:07 · PVG 08:07 · LAX 16:07 · JFK 19:07
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.