0x01.About
最近在处理系统消息模块,查阅了很多实践案例,各有针对性。
首先站内消息主要包括:个人消息(评论,点赞),系统消息,订阅消息,私信。
其中,订阅区分用户群,即系统消息是一个特殊的所有人订阅的订阅消息,特点是一对多。
前三个实时性比较低,最后一个实时性高,离线状态下是私信,如果双方在线要转为聊天室,特点是一对一。
那么,接下来,该选个方案了,SQL or NoSQL?
0x02.Mysql实现
首先,对于个人消息、私信("UserMessage"),一条消息插一句,Mysql跑跑没问题。
对于系统消息或订阅消息,必然不可以,假如有10万用户,一次性那么要插入10万条消息,Mysql必死。
那么就是说,要设立一个系统库("SystemMessage"),每当用户登录,就去跑跑系统库("SystemMessage"),把未读的系统库跑到个人库。
关于订阅消息就比较麻烦了,对用户分组?对消息分组?
关系型数据库处理集合问题是比较麻烦的,目前想到的结论是建立一个表("RssMessage")存储消息类型,消息索引。
下面列了大致的数据库模型:
看完这个数据库设计,我也觉得好难受,吐槽前先来想想为什么吧。
UserSystemRelation表用于记录用户读取到哪个位置的标记。
可以看到,UserMessage与SystemMessage表中,title、tid、ctime、type字段冗余了,好像也没必要,
但是从用户功能上看,当用户登陆后,查找自己站内消息,必然要用到的有:status,必然要显示的有:title、ctime,type作为用户进入消息面板后,要筛选的方式之一,这样的话,Mysql就只要跑一个表就可以完成显示给用户的最新站内消息了。
由于MessageText可能是一个大信息通知,用户查看个人消息时候,并未查看MessageText内容,所以单独放一张表。
相应处理流程
- 用户登录后,先通过"UserSystemRelation"表查询是否有新的系统消息
- 如果"UserSystemRelation",查找到自身uid,同步系统消息到个人消息;如果"UserSystemRelation"未查找到自身uid,直接插入"UserSystemRelation",并读取最近50条系统消息。
- 用户点击未读消息,获取"MessageText"",并更新状态(status)为已读。
- 用户通过"status"、"type",可以筛选系统消息。
0x03.Mysql+MongoDB实现
由于Mongodb是一种文档型的数据结构,所以,可以考虑把所有数据转成json直接塞给Mongodb。
基于用户的习惯,读多写少,大部分时候都是看到消息,删除、更新比较少,如果数据没更新直接读Mongodb,如果数据更新,直接删除Mongodb
的索引。
这个考虑是在,用户数量很大的时候,要在"UserSystem"表里查找到用户消息比较慢的时候用,类似于吧Mongodb当缓存。
0x04.Redis实现
看了Mysql下站内消息的数据库设计,我也觉得很蛋疼,临时过渡没事,但是还是NoSQL合适。
Redis自带订阅与发布系统,http://redisbook.readthedocs.org/en/latest/feature/pubsub.html
在下图展示的这个 pubsub_channels 示例中, client2 、 client5 和 client1 就订阅了 channel1 , 而其他频道也分别被别的客户端所订阅:
只要是订阅了相应地频道,就会收到频道的消息。
把用户ID作为频道,私信就是反向的频道订阅,系统消息就是所有用户的订阅,那么离线的消息呢?
1、线上用户
还是存在系统或个人的哈希表里,等上线后再去读取。
在Python中,订阅发布消息(Publish)如下:
import redis,time
queue = redis.StrictRedis(host='localhost', port=6379, db=0)
channel = queue.pubsub()
for i in range(100):
queue.publish("test", i)
time.sleep(0.1)
Python中,订阅监听消息(Subcribe)如下:
import redis,time
r = redis.StrictRedis(host='localhost', port=6379, db=0)
p = r.pubsub()
p.subscribe('test')
while True:
message = p.get_message()
if message:
print "Subscriber: %s" % message['data']
Redis-py的API可以看GitHub:https://github.com/andymccurdy/redis-py
这是线上用户做法。
2、线下用户
看过一种做法是建立一个Redis链表,存储登陆用户,当用户登陆就直接发送,没登陆就暂存起来。
这里的话,可以用WebSocket实时监听,定期发送心跳包,如果在线直接返回Redis自带的订阅系统。
系统消息建立一个集合:
SADD system:2015-08-03 7 8 9 10 11
第一段标示系统信息,第二段标示日期,后面的数字标示message id。
个人消息建立一个集合:
SADD user:12345:read 1 2 3 4
第一段标示用户信息集合,第二段标示用户id,下一段标示消息类型为已读,后面的数字标示message id。
关于订阅消息如下:
SADD rss:xiaocao 12 13 14 15
那么你就收到小草的订阅消息,消息ID分别是 12, 13, 14, 15
还有很重要的消息数据存储,
HMSET message:12 title 标题 content 内容 date 2015-08-03
Python创建数据库的例子就是:
import redis,time,threading,random
pool = redis.ConnectionPool(host='localhost', port=6379, db=1)
rs = redis.Redis(connection_pool=pool)
rs.sadd("user:123:read", "1", "2")
rs.sadd("user:123:unread", "4", "5", "6")
rs.sadd("system:2015-08-03", "7", "8", "9", "10", "11")
rs.sadd("rss:xiaocao", "12", "13", "14", "15", "11")
for i in range(15):
rs.hset("message:"+str(i), "title", "title=>"+str(random.uniform(1, 99999)))
rs.hset("message:"+str(i), "content","content=>"+str(time.time()))
rs.hset("message:"+str(i), "date", str(time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())))
参考:
- http://redisbook.readthedocs.org/en/latest/feature/pubsub.html
- http://huoding.com/2012/02/29/146
- http://yijiebuyi.com/blog/faa9e68cb296d88bbc65b24dbc7b8de1.html
- http://www.cnblogs.com/grenet/archive/2010/03/08/1680655.html
本文出自 夏日小草,转载请注明出处:http://homeway.me/2015/08/03/website-system-message/
-by小草
2015-08-03 01:35:10
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。