Discuz!NT千万级数据量上的两驾马车 TokyoCabinet,MongoDB


当前第2页 返回上一页

      http://www.cnblogs.com/daizhj/archive/2010/06/08/tokyotyrantclient.html


      Mongodb使用的是:http://github.com/samus/mongodb-csharp
    
      这里还有个小插曲,之前园子里有朋友介绍了这个客户端NoRM ,不过在我写了一个LINQ示例并进行压力测试后,发现速度不快,比samus的那个客户端慢了不少,在苦找原因无果的情况下,最终选择了samus,不过在samus中目前也支持LINQ的写法(也算是扩展和尝试吧),如下面的写法(更多具体示例还是参见其官方源码包中的相应内容): 

代码如下:

Mongo db = new Mongo("Servers=10.0.4.5:27017;ConnectTimeout=30000;ConnectionLifetime=300000;MinimumPoolSize=64;MaximumPoolSize=256;Pooled=true");
db.Connect();
var topicColl = db.GetDatabase("dnt_mongodb").GetCollection<Discuz.EntLib.MongoDB.Entity.TopicInfo>("topics");
var topicInfoList = topicColl.Linq().Where(t => t.Fid == 2 && t.Displayorder == 0).Skip(skip).OrderByDescending(t=>t.Lastpostid).Take(16).ToList();
Discuz.Common.Generic.List<TopicInfo> topicList = new List<TopicInfo>();
foreach (var topic in topicInfoList)
{
topicList.Add(LoadTopicInfo(topic));
}
db.Disconnect();
return topicList;

不过在使用上述代码进行1500万主题分页时,发现LR的测试周期延长(前者(document方式)从2:10秒延长到后者(linq)2:30秒)和吞吐量降低。
所以这里还是最终延用了samus的document访问方式,参照上面的LINQ写法,下面是document写法,形如:

代码如下:

public Discuz.Common.Generic.List<TopicInfo> GetTopicList(int fid, int pageSize, int pageIndex, int startNumber)
{
int skip = 0;
if (pageIndex <= 1)
pageSize = pageSize - startNumber;
else
skip = (pageIndex - 1) * pageSize - startNumber;
Discuz.Common.Generic.List<TopicInfo> topicInfoList = new Common.Generic.List<TopicInfo>();
System.Collections.Generic.List<Document> docList = MongoDbHelper.Find(mongoDB, "topics",
new Document().Add("fid", fid).Add("displayorder", 0), "lastpostid", IndexOrder.Descending, pageSize, skip);
return docList;
}

如果在你的项目中非要使用LINQ方式的话,那在这里再要介绍的一个samus的属性绑定功能,这个功能对于那些数据库字段与代码中的属性存在 “大小写”差异的情况下,非常有用,即对相应实体类进行‘别名’的绑定,比如对于主题表(需引入MongoDB.Attributes名空间):

代码如下:

/// <summary>
/// 主题信息描述类
/// </summary>
public class TopicInfo : Discuz.Entity.TopicInfo
{
[MongoAlias("attention")]
public new int Attention { get; set; }
///<summary>
///主题tid
///</summary>
[MongoAlias("tid")]
public new int Tid { get; set; }
/// <summary>
/// 板块名称
/// </summary>
[MongoAlias("forumname")]
public new string Forumname { get; set; }
///<summary>
///版块fid
///</summary>
[MongoAlias("fid")]
public new int Fid { get; set; }
///<summary>
///主题图标id
///</summary>
[MongoAlias("iconid")]
public new int Iconid { get; set; }
......

上面的MongoAlias属性就是属性别名,它就是MONGODB中所存储的数据字段名称。

介绍到这里,再回到正文。
因为这两个工具都是在数据库层面进行缓存的,所以它对于原有的DISCUZ!NT中的缓存系统而言,与数据库帖的更近,所以对原有的业务逻辑改造,
就停留在了数据访问层"DISCUZ.DATA.dll"中了,其实到这里,就看出了当初为什么要分层,以及分层带来的好处了。
比如在Discuz.Data.Topics这个类中添加了这两个静态变量:

代码如下:

/// <summary>
/// 是否启用TokyoTyrantCache缓存用户表
/// </summary>
public static bool appDBCache = (EntLibConfigs.GetConfig() != null && EntLibConfigs.GetConfig().Cachetopics.Enable);
public static ICacheTopics ITopicService = appDBCache ? DBCacheService.GetTopicsService() : null;

前者用户判断是否启用主题缓存,后者则获取相应的缓存服务实例(前面配置文件中已做相应说明)。
这样,在已有的数据访问代码中加入相应的缓存逻辑,比如获取主题信息:

代码如下:

/// <summary>
/// 获得主题信息
/// </summary>
/// <param name="tid">要获得的主题ID</param>
/// <param name="fid">版块ID</param>
/// <param name="mode">模式选择, 0=当前主题, 1=上一主题, 2=下一主题</param>
public static TopicInfo GetTopicInfo(int tid, int fid, byte mode)
{
TopicInfo topicInfo = null;
if (appDBCache)//新增代码
topicInfo = ITopicService.GetTopicInfo(tid, fid, mode);
if(topicInfo == null)
{
//原代码
IDataReader reader = DatabaseProvider.GetInstance().GetTopicInfo(tid, fid, mode);
if (reader.Read())
topicInfo = LoadSingleTopicInfo(reader);
reader.Close();
if (appDBCache && topicInfo != null)
ITopicService.CreateTopic(topicInfo);
}
return topicInfo;
}

当然,因为使用了缓存方式,所以就牵扯到缓存中的数据与数据库中数据的一致性问题,所以对于主题的CUD操作,也要对应有相应的对缓存的操作,这基本上就是一个工作量的问题了。因为无论是TTCACHED,还是MONGODB,都支持更新操作。
比如同样是更新主题附件类型的操作,下面是TTCACHED的写法:

代码如下:

/// <summary>
/// 更新主题附件类型
/// </summary>
/// <param name="tid">主题Id</param>
/// <param name="attType">附件类型,1普通附件,2为图片附件</param>
/// <returns></returns>
public int UpdateTopicAttachmentType(int tid, int attType)
{
var qrecords = TokyoTyrantService.QueryRecords(pool, new Query().NumberEquals("tid", tid));
foreach (string key in qrecords.Keys)
{
var column = qrecords[key];
column["attachment"] = attType.ToString();
TokyoTyrantService.PutColumns(pool, column["tid"], column, true);
break;
}
return 1;
}

下面是MongoDB的写法

代码如下:

/// <summary>
/// 更新主题附件类型
/// </summary>
/// <param name="tid">主题Id</param>
/// <param name="attType">附件类型,1普通附件,2为图片附件</param>
/// <returns></returns>
public int UpdateTopicAttachmentType(int tid, int attType)
{
MongoDbHelper.Update(mongoDB, "topics",
new Document() { { "$set", new Document() { { "attachment", attType } } } },
new Document().Add("_id", tid));
return 1;
}

通过对比可以看出,MONGODB可以对某一字段进行操作,而TTCACEHD则只能通过查询先获取整条记录,然后修改某一‘字段’,之后再整条提交更新,所以单从这一角度讲,MONGDOB要比TTCACHED更新性能要高许多(之后的测试结果也说明了这一点)。
  
      正如之前所说的那样,如用户对于这两个接口实现方案均不满意,那么他可以使用其它类型的NOSQL数据库,只要实现了相应的接口:
     public interface ICacheTopics
     public interface ICacheUsers
     public interface ICacheOnlineUser
     public interface ICachePosts     
       并在配置文件中进行相应的配置就可以了,当然本文中代码因为时间问题还是有待考量的,但主要的架构设计思想基本被确定下来了。
 
 
      当然对于原有的数据库中的记录,如果要使用本方案,我提供了转换工具,用于把数据转到TTCACHED或MONGODB中的任一服务端上。如下:
 
     TTCACEHD:
    
    
     MongoDB(目前比TTACEHD多了帖子分表转换功能):
   
 
 
      最后在压力测试过程中,还出现了一些小问题,好在对着官方文档,逐步优化解决了,这里要特别说一下MONGDOB,其文件的详细程度要好于TTCACHED,基本上主要的功能都有详细的介绍说明页面,呵呵。当然TTCACHED的诞生时间要比MONGODB早,所以在生产环境下的成功案例也相对多一些。
    
    
     下面列了一下使用过程中的小问题,仅作记录:           
     
      TokyoTyrant的使用问题:尽量不要在查询的列表中使用排序操作,因为它的排序效率还不如数据库高。尽量使用索引进行查询
                   键值操作。2000w记录以下查询效率很高,但更高的数据量上目前没做过压力测试(包括CRUD操作)
     
      Mongodb:尽量使用_ID做为查询键值操作,包括排序等,对索引进行优化(单列或多列进行索引)。
原文链接:http://www.cnblogs.com/daizhj/archive/2010/07/20/1781140.html

更多关于Discuz论坛内容来自木庄网络博客


标签:Discuz论坛

返回前面的内容

相关阅读 >>

discuz!7.0标签聚合功能详解

微信公众平台开发接口php sdk完整版

从零开始 教你如何搭建discuz!4.1论坛(图)

destoon各类调用汇总

discuz! 论坛如何添加配置选项

discuz 公告效果(自动换行,无间隙滚动)

复杂背景的验证码识别破解 以discuz的动画验证码为例。

利用php自动生成印有用户信息的名片

discuz批量替换帖子内容的方法(使用sql更新数据库)

discuz nt的退出实现代码

更多相关阅读请进入《Discuz论坛》频道 >>



打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...