WebIM系列文章

一步一步打造WebIM(1)一文中,已经介绍了如何实现一个简单的WebIM,但是,这个WebIM有一个问题,就是每一次添加消息监听器时,都必须访问一次数据库去查询是否有消息,显然,如果用户比较多时,必然对数据库的压力比较大。解决这个问题的一个方法就是先将消息缓存在内存中,不立即写入数据库,等到缓存满了才写入数据库。本文将介绍如何实现消息缓存。

基本思路

实现一个消息缓存管理类,以用户为单位缓存所有消息,每一个用户对应着一个List<Message>,保存着该用户新收到的消息,消息缓存管理用一个Hashtable保存着所有用户对应的List<Message>。

具体实现代码如下:

  1. public class MessageCacheManagement
  2. {
  3. static MessageCacheManagement m_Instance = new MessageCacheManagement();
  4.  
  5. static public MessageCacheManagement Instance
  6. {
  7. get { return m_Instance; }
  8. }
  9.  
  10. private MessageCacheManagement()
  11. {
  12. }
  13.  
  14. Int32 m_Count = 0;
  15. Hashtable m_Cache = new Hashtable();
  16.  
  17. List<Message> GetUserMessageCache(String user)
  18. {
  19. if (!m_Cache.ContainsKey(user))
  20. {
  21. m_Cache.Add(user, new List<Message>());
  22. }
  23.  
  24. return m_Cache[user] as List<Message>;
  25. }
  26.  
  27. /// <summary>
  28. /// 清除缓存
  29. /// </summary>
  30. public void Clear()
  31. {
  32. lock (m_Cache)
  33. {
  34. List<Message> msgs = new List<Message>();
  35. foreach (DictionaryEntry ent in m_Cache)
  36. {
  37. (ent.Value as List<Message>).Clear();
  38. }
  39. m_Count = 0;
  40. }
  41. }
  42.  
  43. /// <summary>
  44. /// 获取所有缓存的消息
  45. /// </summary>
  46. /// <returns></returns>
  47. public List<Message> GetAll()
  48. {
  49. lock (m_Cache)
  50. {
  51. List<Message> msgs = new List<Message>();
  52. foreach (DictionaryEntry ent in m_Cache)
  53. {
  54. foreach (Message msg in ent.Value as List<Message>)
  55. {
  56. msgs.Add(msg);
  57. }
  58. }
  59. return msgs;
  60. }
  61. }
  62.  
  63. /// <summary>
  64. /// 获取某一用户缓存的消息的最小时间
  65. /// </summary>
  66. public Nullable<DateTime> GetMinCreatedTime(string user)
  67. {
  68. lock (m_Cache)
  69. {
  70. List<Message> userMsgs = GetUserMessageCache(user);
  71. return userMsgs.Count == 0 ? null : new Nullable<DateTime>(userMsgs[0].CreatedTime);
  72. }
  73. }
  74.  
  75. /// <summary>
  76. /// 在缓存中插入一条消息
  77. /// </summary>
  78. /// <param name="user"></param>
  79. /// <param name="msg"></param>
  80. public void Insert(String user, Message msg)
  81. {
  82. List<Message> userMsgs = null;
  83.  
  84. lock (m_Cache)
  85. {
  86. userMsgs = GetUserMessageCache(user);
  87. }
  88.  
  89. lock (userMsgs)
  90. {
  91. userMsgs.Add(msg);
  92. m_Count++;
  93. }
  94. }
  95.  
  96. /// <summary>
  97. /// 查找缓存中接受者为user,发送时间大于from的消息
  98. /// </summary>
  99. public List<Message> Find(String user, DateTime from)
  100. {
  101. List<Message> userMsgs = null;
  102.  
  103. lock (m_Cache)
  104. {
  105. userMsgs = GetUserMessageCache(user);
  106. }
  107.  
  108. lock (userMsgs)
  109. {
  110. List<Message> msgs = new List<Message>();
  111.  
  112. int i = 0;
  113. while (i < userMsgs.Count && userMsgs[i].CreatedTime <= from) i++;
  114.  
  115. while (i < userMsgs.Count) { msgs.Add(userMsgs[i]); i++; }
  116.  
  117. return msgs;
  118. }
  119. }
  120.  
  121. /// <summary>
  122. /// 获取消息总量
  123. /// </summary>
  124. public Int32 Count
  125. {
  126. get { return m_Count; }
  127. }
  128. }

添加消息监听器

增加消息缓存后,添加消息监听器的流程也要修改,具体思路是先获取消息接收者在缓存中发送时间最早的消息的发送时间,显然,如果监听器的From大于或等于这个最小发送时间时,无需访问数据库,可以直接访问缓存。具体代码修改为:

  1. /// <summary>
  2. /// 添加消息监听器,如果查找到符合监听器条件的消息,返回false,此时不会添加监听器
  3. /// 如果没有查找到符合监听器条件的消息,返回true,此时监听器将被添加到m_Listeners中
  4. /// </summary>
  5. public bool AddListener(String receiver, String sender, Nullable<DateTime> from, WebIM_AsyncResult asynResult)
  6. {
  7. MessageListener listener = new MessageListener(receiver, sender, from, asynResult);
  8. lock (m_Lock)
  9. {
  10. if (!m_Listeners.ContainsKey(receiver))
  11. {
  12. m_Listeners.Add(receiver, new List<MessageListener>());
  13. }
  14. List<MessageListener> listeners = m_Listeners[receiver] as List<MessageListener>;
  15.  
  16. //获取用户receiver缓存的消息的最小发送时间
  17. Nullable<DateTime> min = MessageCacheManagement.Instance.GetMinCreatedTime(receiver);
  18.  
  19. List<Message> messages = new List<Message>();
  20.  
  21. //当from >= 缓存在内存中的消息的最小时间时,不必查询数据库
  22. if (min == null || from == null || from.Value < min.Value)
  23. {
  24. //查询数据库
  25. messages.AddRange(Find(receiver, sender, from));
  26. }
  27.  
  28. //在缓存中查询
  29. messages.AddRange(MessageCacheManagement.Instance.Find(receiver, from.Value));
  30.  
  31. if (messages.Count == 0)
  32. {
  33. //插入监听器
  34. listeners.Add(listener);
  35. }
  36. else
  37. {
  38. //发送消息
  39. listener.Send(messages);
  40. }
  41. return messages.Count == 0;
  42. }
  43. }

发送消息

增加消息缓存后,发送消息的流程也要修改,具体思路是:先将消息保存到缓存中,之后判断缓存的消息的总数,如果超过设定的上限,就将消息写入数据库。具体代码修改为(您可以通过修改MAX_CACHE_COUNT修改缓存消息数的上限):

  1. /// <summary>
  2. /// 插入新的消息,插入消息后将查询m_Listeners中是否有符合条件的监听器,如存在,同时将消息发送出去
  3. /// </summary>
  4. public Message NewMessage(String receiver, String sender, DateTime createdTime, String content)
  5. {
  6. lock (m_Lock)
  7. {
  8. Message message = new Message(sender, receiver, content, createdTime, ++m_MaxKey);
  9.  
  10. List<Message> messages = new List<Message>();
  11. messages.Add(message);
  12.  
  13. if (m_Listeners.ContainsKey(receiver))
  14. {
  15. List<MessageListener> listeners = m_Listeners[receiver] as List<MessageListener>;
  16. List<MessageListener> removeListeners = new List<MessageListener>();
  17. foreach (MessageListener listener in listeners)
  18. {
  19. if ((listener.Sender == "*" || String.Compare(listener.Sender, sender, true) == 0) &&
  20. (listener.From == null || message.CreatedTime > listener.From))
  21. {
  22. listener.Send(messages);
  23. removeListeners.Add(listener);
  24.  
  25. System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(listener.Complete));
  26. }
  27. }
  28.  
  29. foreach (MessageListener listener in removeListeners)
  30. {
  31. //移除监听器
  32. listeners.Remove(listener);
  33. }
  34. }
  35.  
  36. MessageCacheManagement.Instance.Insert(receiver, message);
  37.  
  38. if (MessageCacheManagement.Instance.Count >= MAX_CACHE_COUNT)
  39. {//超过缓存的最大值,将缓存中的消息全部写入数据库
  40. //启动事务
  41. SQLiteTransaction trans = m_Conn.BeginTransaction();
  42.  
  43. try
  44. {
  45. List<Message> cacheMsgs = MessageCacheManagement.Instance.GetAll();
  46.  
  47. foreach (Message msg in cacheMsgs)
  48. {SQLiteCommand cmd = new SQLiteCommand(
  49. "insert into Message (Receiver,Sender,Content,CreatedTime,Key) values (?,?,?,?,?)",
  50. m_Conn
  51. );
  52. cmd.Parameters.Add("Receiver", DbType.String).Value = msg.Receiver;
  53. cmd.Parameters.Add("Sender", DbType.String).Value = msg.Sender;
  54. cmd.Parameters.Add("Content", DbType.String).Value = msg.Content;
  55. cmd.Parameters.Add("CreatedTime", DbType.DateTime).Value = msg.CreatedTime;
  56. cmd.Parameters.Add("Key", DbType.Int64).Value = msg.Key;
  57.  
  58. cmd.ExecuteNonQuery();
  59. }
  60.  
  61. trans.Commit();
  62. }
  63. catch
  64. {
  65. trans.Rollback();
  66. }
  67.  
  68. MessageCacheManagement.Instance.Clear();
  69. }
  70.  
  71. return message;
  72. }
  73. }

本文来自:http://www.cnblogs.com/lucc/archive/2010/04/27/1722470.html

WebIM(2)---消息缓存的更多相关文章

  1. nordic mesh中的消息缓存实现

    nordic mesh中的消息缓存实现 代码文件msg_cache.h.msg_cache.c. 接口定义 头文件中定义了四个接口,供mesh协议栈调用,四个接口如下所示,接口的实现代码在msg_ca ...

  2. 6张图为你分析Kafka Producer 消息缓存模型

    摘要:发送消息的时候, 当Broker挂掉了,消息体还能写入到消息缓存中吗? 本文分享自华为云社区<图解Kafka Producer 消息缓存模型>,作者:石臻臻的杂货铺. 在阅读本文之前 ...

  3. [Storm] 内部消息缓存

    这篇文件翻译自 http://www.michael-noll.com/blog/2013/06/21/understanding-storm-internal-message-buffers/ 当进 ...

  4. WebIM(3)----性能测试

    WebIM系列文章 在一步一步打造WebIM(1)和(2)中,已经讨论了如何开发一个WebIM,并且使用缓存来提高WebIM的性能,本文将编写一个程序模拟大量用户登录来对WebIM进行性能测试. 1. ...

  5. Netty构建分布式消息队列实现原理浅析

    在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...

  6. C#分布式消息队列 EQueue 2.0 发布啦

    前言 最近花了我几个月的业余时间,对EQueue做了一个重大的改造,消息持久化采用本地写文件的方式.到现在为止,总算完成了,所以第一时间写文章分享给大家这段时间我所积累的一些成果. EQueue开源地 ...

  7. Net分布式系统之四:RabbitMQ消息队列应用

    消息通信组件Net分布式系统的核心中间件之一,应用与系统高并发,各个组件之间解耦的依赖的场景.本框架采用消息队列中间件主要应用于两方面:一是解决部分高并发的业务处理:二是通过消息队列传输系统日志.目前 ...

  8. 消息队列与RabbitMQ

    1 什么是消息队列 消息指进程或应用间通信的数据:队列是保存数据的结构:消息队列是指进程或应用间通信时,保存消息的容器.消息队列独特的机制和结构保证了消息发送者和接收者之间良好的异步通信. 2 为什么 ...

  9. AMQ学习笔记 - 06. 可靠消息传送

    概述 本文介绍JMS中可能发生消息故障的3个隐患阶段,以及确保消息安全的3种保障机制. 故障分析 在介绍可靠传送的确保机制之前,先分析消息在传送的过程中可能在哪个阶段出现问题. 1.两个跃点 跃点的含 ...

随机推荐

  1. JAVA —— 文件输入输出

    import java.io.*; public class FileIO { public static void main(String[] args) { //1.相对路径 File testF ...

  2. UVA 193 Graph Coloring 图染色 DFS 数据

    题意:图上的点染色,给出的边的两个点不能都染成黑色,问最多可以染多少黑色. 很水的一题,用dfs回溯即可.先判断和当前点相连的点是否染成黑色,看这一点是否能染黑色,能染色就分染成黑色和白色两种情况递归 ...

  3. 熟人UML

    UML,全名Unified Modeling Language.模语言.它是软件和系统开发的标准建模语言.主要是以图形的方式对系统进行分析.设计. 同一时候,UML不是一个程序设计语言,也不是一个形式 ...

  4. SSH深度历险(三) EJB Session Bean有状态和无状态的差别与联系

    刚開始对两种sessionbean存在误解.觉得有状态是实例一直存在,保存每次调用后的状态,并对下一次调用起作用.而觉得无状态是每次调用实例化一次,不保留用户信息.细致分析并用实践检验后,会发现,事实 ...

  5. 安装Docker

    安装Docker 1. 增加Repository配置文件 cat >/etc/yum.repos.d/docker.repo <<-EOF [dockerrepo]name=Dock ...

  6. MySQL 架构

    原文:MySQL 架构 MySQL架构和结构分析 官方架构图: MySQL DB 各模块架构图如下: MySQL安装方式 MySQL初始化 简介:什么是事务: 事务: ACID :  事务确保了银行不 ...

  7. 《Java并发编程实战》第十四章 构建自己的同步工具定义 札记

    一.状态依赖性的管理 有界缓存实现的基类 @ ThreadSafe public abstract class BaseBoundedBuffer<E> { @GuardeBy( &quo ...

  8. R语言数据分析系列六

    R语言数据分析系列六 -- by comaple.zhang 上一节讲了R语言作图,本节来讲讲当你拿到一个数据集的时候怎样下手分析,数据分析的第一步.探索性数据分析. 统计量,即统计学里面关注的数据集 ...

  9. 微信公众平台企业号验证接口、回调 PHP版

    微信公众平台企业号验证接口.回调 PHP版,本人为了解决这个企业号的验证和发送消息的问题,整整研究了几天时间,由于微信企业号刚推出来,网上资料太少了!后来在一些朋友的帮助下和本人重复调试完好下,最终整 ...

  10. crawler_爬虫分布式设计图收集_01