我正在编写一个ejabberd模块,用户可以控制何时将消息发送给收件人而不是立即发送(如提前发送的生日祝福)。这是通过向消息节添加自定义xml元素来完成的,如下所示
<message xmlns="jabber:client" from="test2@ubuntu" to="test1@ubuntu/32375806281445450055240436" type="chat">
<schedule xmlns="ank" year="2015" month="10" day="19" hour="22" minute="36" second="13"/>
<body>hi</body>
</message>
现在,这些预定的消息必须存储在mnesia数据库中,并在时间到达时发送给收件人。
方法1: 一种方法是为每个用户创建一个表,当收到消息时,将消息存储到users表并设置一个计时器来处理 完成后的消息和删除,如下面的示例代码
timer:apply_after(SecondsDelay, ?MODULE, post_message_delete, [TableName, RecordUniqueKeyHash, From, To, Packet]).
post_message_delete 方法将在计时器到期后使用路由方法调用时发送消息,如下所示,并从mnesia数据库中删除记录。
ejabberd_router:route(From, To, Packet)
由于mnesia中表的数量有限,因此无法为每个用户创建表。
方法2: 另一种方法是将所有用户消息存储在一个表中,并在消息到达时为每条消息设置定时器(与上面相同),并在处理消息后将其删除。
使用mnesia数据库的整个想法是在ejabberd服务器崩溃的情况下可靠地处理消息。
为实现此目的,我们在每条消息的记录中使用pid字段。每个消息记录都有一个pid字段,其中包含正在处理此消息的进程的pid。最初它是未定义的(当消息到达filter_packet挂钩时)但是在生成消息处理方法之后它会更新mnesia数据库中记录中的pid。
因此,如果服务器在模块启动方法中重新启动时崩溃,则迭代所有消息并检查pid是否处于活动状态(is_process_alive),如果不是活动的话,则会在消息上生成处理方法,该消息将使用新进程pid进行更新,处理消息并在完成后退出。
缺点 这种方法的缺点是,即使将来(下个月或明年)必须传递一条消息,仍然会为此消息运行一个进程,并且运行的进程数与那些消息一样多。
方法3:
为了克服方法2的缺点,每小时扫描一次数据库并累积仅在下一个小时内交付的消息并进行处理。
这种方法的缺点是每小时扫描一次可能影响性能的数据库。
方法4:
为了完成方法3的性能,我们可以为每年_month创建表,并仅在当前月表上生成消息处理函数。
哪种方法最适合使用mnesia数据库的这个用例?
答案 0 :(得分:1)
即使这是一个老问题,但有一天可能会成为其他人的问题。
我认为mnesia是这种数据存储用例的错误选择。 版本2.8.0中的Redis在执行某些操作时具有键空间事件通知功能,包括由 EXPIRE , EXPIREAT 和其他设置的密钥到期命令变种。此信息可通过 PUBSUB 功能访问您的代码。有关如何开始的信息,请参见 Redis Keyspace Notifications 。
为每个生日讯息生成一个唯一的密钥( K ),可能是 UUID 。 将消息(整个XML)存储在给定的生成的 K 下发送。
使用 SET 命令将此消息密钥存储为名为 K:计时器的键下的值,并将 TTL 设置为现在与生日时间戳之间的时差(以秒为单位)或使用 EXPIREAT 进行设置消息到达生日本身的Unix时间戳的到期时间。当 TTL 过期时,pubsub客户端会收到有关该信息的通知 K:timer 。提取 K 并使用它获取消息。发送消息并在之后删除。
需要考虑的问题:
1:多个pubsub客户端可能会收到相同的到期事件通知。这可能导致同一消息被多次发送。实现某种锁定以防止这种情况。
2: Redis PUBSUB 是一个消防和遗忘消息传递构造。因此,如果客户端出现故障并再次启动,则可能在此时间窗口内错过了事件通知。确保可靠性的一种方法是将密钥 K 存储在 K:计时器,K:计时器:1,K:计时器:2,K:计时器:3的不同键变量下...... 增加TTL偏移量(1,2,3,介于两者之间)以定位不可用客户端可用的最差时间窗口。
3: Redis在内存中。存储大量的大消息会花费你的RAM。解决此问题的一种方法是仅在redis中存储消息密钥 K ,并在任何磁盘基础键值中使用相同的密钥 K 存储消息(XML)像Riak,Cassandra等商店。