php站内信(站内消息)的设计
在很多⽹站系统(如CMS系统,SNS系统等),都有“站内信”的功能。
“站内信”不同于电⼦邮件,电⼦邮件通过专门的邮件服务器发送、保存。⽽“站内信”是系统内的消息,说⽩了,“站内信”的实现,就是通过数据库插⼊记录来实现的。
“站内信”有两个基本功能。⼀:点到点的消息传送。⽤户给⽤户发送站内信;管理员给⽤户发送站内信。⼆:点到⾯的消息传送。管理员给⽤户(指定满⾜某⼀条件的⽤户)发消息。点到点的消息传送很容易实现,本⽂不再详述。下⾯将根据不同的情况,来说说“站内信”的发是如何实现的。
第⼀种情况,站内的⽤户是少量级别的。(⼏⼗到上百)
这种情况,由于⽤户的数量⾮常少,因此,没有必要过多的考虑数据库的优化,采⽤简单的表格,对系统的设计也来的简单,后期也⽐较容易维护,是典型的⽤空间换时间的做法。
数据库的设计如下:表名:Message
ID:编号;SendID:发送者编号;RecID:接受者编号(如为0,则接受者为所有⼈);Message:站内信内容;Statue:站内信的查看状态;PDate:站内信发送时间;
如果,某⼀个管理员要给所有⼈发站内信,则先遍历⽤户表,再按照⽤户表中的所有⽤户依次将站内信插⼊到Message表中。这样,如果有56个⽤户,则发⼀条站内信要执⾏56个插⼊操作。这个理解上⽐较简单,⽐较耗损空间。
某⼀个⽤户登陆后,查看站内信的语句则为:
Select * FROM Message Where RecID=‘ID’ OR RecID=0
第⼆种情况,站内的⽤户中量级别的(上千到上万)。
如果还是按照第⼀种情况的思路。那发⼀条站内信的后果基本上就是后台崩溃了。因为,发⼀条站内信,得重复上千个插⼊记录,这还不是最主要的,关键是上千乃⾄上万条记录,Message字段的内容是⼀样的,⽽Message有⼤量的占⽤存储空间。⽐⽅说,Message字段有100个汉字,占⽤200个字节,那么5万条,就占⽤200×50000=10000000个字节=10M。简单的⼀份站内信,就占⽤10M,这还让不让⼈活了。
因此,将原先的表格拆分为两个表,将Message的主体放在⼀个表内,节省空间的占⽤
数据库的设计如下:
表名:Message
ID:编号;SendID:发送者编号;RecID:接受者编号(如为0,则接受者为所有⼈);MessageID:站内信编号;Statue:站内信的查看状态;
表名:MessageText
ID:编号;Message:站内信的内容;PDate:站内信发送时间;
在管理员发⼀封站内信的时候,执⾏两步操作。先在MessageText表中,插⼊站内信的内容。然后在Message表中给所有的⽤户插⼊⼀条记录,标识有⼀封站内信。
这样的设计,将重复的站内信的主体信息(站内信的内容,发送时间)放在⼀个表内,⼤量的节省存储空间。不过,在查询的时候,要⽐第⼀种情况来的复杂。
第三种情况,站内的⽤户是⼤量级的(上百万),并且活跃的⽤户只占其中的⼀部分。
⼤家都有这样的经历,某⽇看⼀个⽹站⽐较好,⼀时⼼情澎湃,就注册了⼀个⽤户。过了⼀段时间,由于种种原因,就忘记了注册时的⽤户名和密码,也就不再登陆了。那么这个⽤户就称为不活跃的。从实际来看,不活跃的⽤户占着不⼩的⽐例。
我们以注册⽤户2百万,其中活跃⽤户只占其中的10%。
就算是按照第⼆种的情况,发⼀封“站内信”,那得执⾏2百万个插⼊操作。但是其中的有效操作只有10%,因为另外的90%的⽤户可能永远都不会再登陆了。
在这种情况下,我们还得把思路换换。
数据库的设计和第⼆种情况⼀样:
表名:Message
ID:编号;SendID:发送者编号;RecID:接受者编号(如为0,则接受者为所有⼈);MessageID:站内信编号;Statue:站内信的查看状态;
表名:MessageText
ID:编号;Message:站内信的内容;PDate:站内信发送时间;
php修改数据库内容 管理员发站内信的时候,只在MessageText插⼊站内信的主体内容。Message⾥不插⼊记录。
那么,⽤户在登录以后,⾸先查询MessageText中的那些没有在Message中有记录的记录,表⽰是未读的站内信。在查阅站内信的内容时,再将相关的记录插⼊到Message中。
这个⽅法和第⼆种的⽐较起来。如果,活跃⽤户是100%。两者效率是⼀样的。⽽活跃⽤户的⽐例越低,越能体现第三种的优越来。只插⼊有效的记录,那些不活跃的,就不再占⽤空间了。
以上,是我对发“站内信”的实现的想法。
href:wwwblogs/grenet/archive/2010/03/08/1680655.html
百万级⽤户量的站内信发数据库设计
随着WEB2.0的发展,⽤户之间的信息交互也变得⼗分庞⼤,⽽且实时性要求越来越⾼。现在很多SNS⽹站和⼀部分CMS⽹站都⼴泛地应
⽤了站内信这⼀模块,这个看似简单的东西其实背后隐藏着很多需要设计师重视的设计细节,要做好这个“邮递员”是很不容易的。为什么这么说呢?下⾯我们就⼀步步来探索设计⼀个百万级⽤户量的站内信发数据库,看完以后你就会明⽩什么是真正可靠⾼效的“邮递员”。
1、⼏⼗——⼏百的⽤户量
这样的⽹站规模最⼩,可能是⼀个中⼩企业的CMS系统,⾯对这样的⽤户量,我们就不必要考虑短消息数据量太⼤的问题了,所以按照怎么⽅便怎么来的原则,发就每⼈复制⼀条消息数据,这样⽤户
可以⾃⼰管理⾃⼰的消息,可以⾮常⽅便进⾏“已读、未读、删除”等操作。按照这个思路,我们的数据库设计如下:
表T_Message
Id bigint --消息ID
SenderId bigint --发送者ID
ReceiverId bigint --接收者ID
SendTime datetime --发送时间
ReadFlag tinyint --已读标志
MessageText text --消息正⽂
这样,我们接受⾃⼰的消息时只要做如下查询:
SELECT * FROM T_Message WHERE ReceiverId=myid
查询⾃⼰的未读消息只要做如下查询:
SELECT * FROM T_Message WHERE ReceiverId=myid and ReadFlag=0
这种⽅法很简单,可能是我们第⼀个想到的,对于这样的⽤户量的情况这样的设计确实也⾜够了。
2、⼏千——⼏万的⽤户量
⽤户量到了这样的级哦别,这个⽹站应该算是⽐较⼤了,笔者估计,可能是⼀个地区性的SNS⽹站。那么⾯对这样的⽤户量,我们⼜该如何来设计站内信发呢?上⾯第⼀种思路还⾏得通吗?应该这样说,如果勉强要⽤上⾯那种设计,也是可以的,只不过T_Message可能要考虑分区。但是,⼤家会不会觉得消息正⽂复制那么多条对于这样的⽤户量来讲空间浪费太⼤,因为考虑到接收者⼀般是不修改消息正⽂的,所以我们可以让所有接收者共享⼀条消息正⽂。具体数据库设计⽅法和上⾯⼤同⼩异:
T_Message
Id bigint --消息ID
SenderId bigint --发送者ID
ReceiverId bigint --接收者ID
SendTime datetime --发送时间
ReadFlag tinyint --已读标志
MessageTextId bigint --这⾥把消息正⽂内容换成消息正⽂Id
T_MessageText
Id bigint --ID标识
SenderId bigint --发送者ID
MessageText text --消息正⽂
这样,我们就⼤⼤节省了消息的存储空间,但是查询的时候就稍微⿇烦⼀点,就需要进⾏联合查询了,查询⾃⼰的未读消息可以这样(意思⼀下,可能还有更⾼效的查询⽅式):
SELECT T_Message.*,T_MessageText.* FROM T_Message
INNER JOIN T_MessageText ON T_Message.MessageTextId=T_MessageText.Id
WHERE T_Message.ReceiverId=myid AND T_Message.ReadFlag=0
⽤这种⽅法除了正⽂我们不能随便删除外,⽤户还是可以⾃⼰管理⾃⼰的消息。
3、百万级⼤⽤户量
如果⼀个⽹站到了百万级的⽤户量了,那我不得不膜拜该⽹站和⽹站经营者了,因为经营这样的⽹站⼀直是笔者的梦想:)好了,回归正题,如果这样的系统放你⾯前,让你设计⼀个站内信发数据库,你该何去何从,总之,上⾯两种常规的办法肯定是⾏不通了的,因为庞⼤的数据量会让消息表撑爆,即使你分区也⽆济于事。这时候作为⼀个系统架构师的你,可能不仅仅要从技术的⾓度去考虑这个问题,更要从⽤户实际情况去着⼿寻解决问题的办法。这⾥,有⼀个概念叫“活跃⽤户”,即经常登录⽹站的⽤户,相对于那些⼀时冲动注册⽽接下来⼜从来不登录的⽤户来说,活跃⽤户对⽹站的忠诚度很⾼,从商业的⾓度来讲,忠诚的客户享受更⾼端的服务。
根据这个思路,我们来探索⼀种⽅法。假设⽹站有500万注册⽤户,其中活跃⽤户为60万(这个⽐例真很不错了),现在我们要对所有⽤户发⼀封致谢信。还是上⾯两张表,⾸先我们可以先往消息表中插⼊⼀条发标识为-1的消息,这⾥我们⽤字段SourceMessageId(原始消息)来标识(-1为原始发消息本⾝,其他则是原始消息id),这样其实发的⼯作已经完成了,⽤户可以看到这条公共的消息了。但是⽤户需要有消息的控制权,所以必须让每个⽤户拥有⼀条⾃⼰的消息。要达到这个⽬的,我们可以让⽤户登录时检查是否已经拷贝原始消息,如果没有拷贝,则拷贝⼀份原始消息并插⼊消息表,发标识为原始消息的id;如果已经存在原始消息的拷贝,则什么都不做。这样,我们就只要为这60万活跃⽤户消耗消息空间就可以了。具体数据库设计如下:
T_Message
Id bigint --消息ID
SenderId bigint --发送者ID
ReceiverId bigint --接收者ID,如果为原始发消息则为-1
SendTime datetime --发送时间
ReadFlag tinyint --已读标志,如果为原始发消息则统⼀为0未读
SourceMessageId bigint
⼀篇⽂章:
站内短信很常见,⽐如系统需要发消息给⽤户,⽤户登录之后可以看到这些消息。
Msg表,字段如下:
id int ⾃增长id
senderid int 外键关联发送者id
title varchar(128) 短信标题
content varchar(512) 短信内容
createTime datatime 发信时间
status tinyint 发件箱中的状态:0--普通;1--删除
⼀张user_has_msg表,字段如下:
id int
departmentid int 部门发的时候外键关联部门id,可以为空
receverid int 外键关联收信⼈,可以为空
msgid int 外键关联短信息
status tinyint 收件箱状态:0--普通;1--删除
readStatus tinyint 阅读状态:0--未读;1--已读
这样设计是基于如下考虑的:
⾸先,msg表包含了发件箱所需要的所有信息,程序的时候写发件箱的时候可以只考虑操作⼀张数据库表。
第⼆,user_has_msg中,departmentid主要考虑的是存在⼤量的按照部门发的可能,这样的话,发给⼀个部门的时候之需要在两张表上个记录⼀条数据,⽽不需要在user_has_msg中记录该部门员⼯数条记录。
但是,后来这个⽅案被我⾃⼰和同事讨论后否决了,原因如下:
⾸先,departmentid的存在使得没有⽤户可以删除收件箱中的站内信,因为删除了,其他⼈的收件箱⾥也看不到。
第⼆,msg表不能保证显⽰完所有的发件箱所需要的数据,因为只有着⼀张表的是后读不出来收件⼈信息。
修改后的版本是:
将msg修改为只保纯粹的信息的表:
id int ⾃增长id
title varchar(128) 短信标题
content varchar(512) 短信内容
createTime datatime 发信时间
将user_has_msg修改为保存各种关系和状态的表:
id int
senderid int 外键关联发送者id
receverid int 外键关联收信⼈
msgid int 外键关联短信息
sendStatus tinyint 发件箱中的状态:0--普通;1--删除
receveStatus tinyint 收件箱状态:0--普通;1--删除
readStatus tinyint 阅读状态:0--未读;1--已读
进⼀步:song/?p=1202
wamagic/librarys/veda/detail/431
wwwblogs/hejiaquan/archive/2012/04/07/2435817.html
baiyuxiong.iteye/blog/876211
huoding/2012/09/28/174
---------------------------------
⼀篇⽂章:
需要做⼀个站内信的功能,可是发不知道怎么弄,求帮助或者谁有例⼦可以给参考⼀下?
php
解决⽅案 »
1.
两种⽅法
NO1.给所有的⽤户‘真的’发送⼀个信息。
NO2.在⽤户登录时,获取程序的待办任务。
2.
建⼀个消息表。每个⽤户登录后到消息表中有没有给⾃⼰的。读取后更改标志位。
发布消息的并发不会太⼤,数据量⼤可以按时间分表,只关⼼近期的
也可以使⽤redis
3.
其实跟置顶贴道理⼀样设置由某系统管理员发出的信息为必读,每个⼈进⼊消息栏⽬都会见到那条信息(未读),阅读后就False未读状态
上⾯的的思路跟游戏中的邮件是⼀样的:
邮件需求:
根据需求,邮件共分两类,即个⼈和系统,个⼈类型邮件是⾯向个⼈的,⽽系统类型邮件向所有玩家投递的。个⼈类型的邮件信息单⼀,每⼀封邮件的接收者都需要做⼀条完整的数据记录,⽽系统类型的邮件信息的内容⼀致,如果像处理个⼈类型邮件⼀样,那就会出现⼤量数据冗余,从⽽造成数据库容量浪费,从这点⾓度出发,我准备对mysql数据库⾥的邮件做以下设计。
创建两个表,⼀个是个⼈类型表person_mail,另⼀个系统类型表system_mail,person_mail表⽤来存储所有玩家的个⼈邮件记
录,system_mail表⽤来存储系统邮件记录,然后在玩家基本信息表(⽐如取名user_baseinfo)⾥再加⼀个字段"sysmail_receiveidx",⽤来表⽰系统类型邮件的历史领取索引标记,该值初始为0,表⽰没有领取过系统类型邮件。
模拟个⽰例场景:
游戏刚开服,服务器的添加了10封新的系统邮件,玩家登⼊服务器,拉取邮件列表数据时,都会拿sysmail_receiveidx的值和system_mail表⾥的邮件索引(即id,id设定的是增量值)做⽐较,把⼤于该
值索引的系统类型邮件挑出来,然后把插⼊到person_mail表⾥,且将sysmail_receiveidx字段值设置为当前system_mail表⾥邮件索引的最⼤值。
考虑系统类型邮件数据的可共⽤性,所以执⾏这类插⼊操作时,从system_mail表筛选出来的每条邮件记录只做轻量拷贝,⽐如类型、创建时间和邮件发送者,然后把邮件接收者填充为本次操作的玩家⽤户ID,邮件索引⽤原始值与⽤户id通过算法⽣成⼀个新的唯⼀值。⾄于标题、内容和相关奖励数据因占⽤容量较⼤我们不进⾏拷贝,⽽是等客户端请求个⼈邮件列表数据时,如果是系统类型邮件,就需要去system_mail表⾥把对应的记录⾏取出来,根据记录,把邮件的标题,内容和相关奖励数据填充完整,这样就有效节约了不必要的冗余容量消耗。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论