• 售前

  • 售后

热门帖子
入门百科

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

[复制链接]
快乐人L 显示全部楼层 发表于 2021-8-14 16:53:49 |阅读模式 打印 上一主题 下一主题
特殊是像主题表(topic),用户表(user)等,因为对于一个流量和发帖量都很大的论坛而言,在运行几年之后,这两个表的数据量大概会破万万(注:因为帖子表采取分表机制,以是这里暂未涉及,但出于性能思量,也提供了本文中雷同的办理方案)。其时思量的架构计划中有两种思路来办理这种问题:
      一种是采取雷同MYSPACE的方式,即按一定记载KEY值(好比用户表的UID)来对大数据表中的记载进行分割,好比前200万用户(即:UID<200w)放入一个表,200-400万的用户放入另一个表,以此类推。固然可以把几个表都放到一个数据库中,也可以放到别的MSSQL数据库上或实例上。但这种方案有一些问题,例如当用户表必要被联表(如LEFT JION)查询时使用,好比我们的帖子表进行分页查询时就必要左联user表,这时如采取分表或分布式布署就大概面临这样的问题,不光业务逻辑要变化,就连存储过程中也要产生不小的变化,这里还不思量服从上的问题。固然有人发起可以使用数据冗余的方式,好比在帖子表中冗余用户信息相应字段,但这种方案同样要大幅度的修改即有代码,同时如果用户信息发生变化时,不光要更新用户表,还要更新帖子表中的相应冗余字段,如果这两者差异步,就会造成数据表现非常,固然在数据库层面增长存储资源也是不得不付出的。
      第二种就是使用能处理惩罚大数据量表格的第三方工具,好比本文所说的TokyoTyrant,Mongodb等,这类NOSQL软件从一问世就是面向海量数据存储访问的,而且这类软件通常都是开源的,别的通过与打算布署企业版的用户打仗,发现虽然他们的服务器设置很高,但数目即不多,以是就要思量怎样最大限度的复用已有的机器资源,而这类NOSQL软件通常都是‘性价比’很高的,即用不多的资源(内存,CPU等)就能到达意想不到的效果。固然我目前对其照旧很谨慎的使用,即不会立刻把它当做主力数据存储工具,而是辅助MSSQL数据库工具,以是各人在看完本文后会发现,这两个工具在企业版中的脚色顶多就是一个高级的MEMCACEHD。不外我的想法很简朴,就是任何工具和技能,如果不是很了解它或者它很新,那么肯定要有一个“稽核期”,如果在‘任间’内它通过稽核,才委以重任,如未通过稽核,也不会让体系平台负担过多的技能层面上的‘风险’。     综上所述,终极我把方向放到了TokyoTyrant,Mongodb上,之以是选择了这两个工具,重要基于下面因素:
   
    1.海量数据的办理方案应该可以跑在LINUX和WINDOW平台上。固然有人会说Mongodb完全可以跑这两个平台,那还为什么要引入TokyoTyrant呢?实在这里有一些产物的特殊情况要思量,好比我们的用户中绝大多数对于数据的读写比在 4:1,即5条SQL访问中有4条是SELECT操纵,1条是CUD操纵,这就造成了读写比例的失衡。虽然Mongodb在读写性能上非常优秀和稳定,但在并发读上相对于TokyoTyrant+cabinet照旧有一些差距(注:更多内容拜见该链接,然后这只限于在我们产物中压力测试环境下的结果,不具备广泛性,以是希望各人详细问题详细分析)
    2.思量到有些用户公司是有相应技能储备的,两种方案也便于用户公司进行的技能选型(固然因为采取接口方式,用户完全可以引入其它第三方的NOSQL工具来实现)。
    好了,说了这么多,开始本日的正文吧。
   
    前面说过,该方案使用了接口方式,这里就先看一下相应的接口声明:
   
      
   
     可以看到,目前在企业版中,对主题表(dnt_topics),用户表(dnt_users),在线表(dnt_online)以及帖子表(dnt_posts)进行了NOSQL数据支持,以是定义了如下的几个接口(图中):

复制代码代码如下:
public interface ICacheTopics
public interface ICacheUsers
public interface ICacheOnlineUser
public interface ICachePosts

因为目前只是把这类NOSQL工具看成高级的‘缓存’来用,以是接口命名上都带着‘Cache’的字样。
然后我使用了一个叫做DBCacheService的类,提供获取这几个接口实例的方法,好比ICacheTopics的实例代码如下:

复制代码代码如下:
/// <summary>
/// 该类用于获取NoSqlDb声明的缓存服务
/// </summary>
public class DBCacheService
{
static ICacheTopics iCacheTopics = null;
public static ICacheTopics GetTopicsService()
{
if (iCacheTopics == null)
{
lock (lockHelper)
{
if (iCacheTopics == null)
{
try
{
if (EntLibConfigs.GetConfig().Cachetopics.Enable)
{
iCacheTopics = (ICacheTopics)Activator.CreateInstance(Type.GetType(
EntLibConfigs.GetConfig().Cachetopics.CacheType == 2 ?
"Discuz.EntLib.TokyoTyrant.Data.Topics, Discuz.EntLib.TokyoTyrant" :
"Discuz.EntLib.MongoDB.Data.Topics, Discuz.EntLib.MongoDB", false, true));
}
}
catch
{
throw new Exception("请检查" + (EntLibConfigs.GetConfig().Cachetopics.CacheType == 2 ?
"Discuz.EntLib.TokyoTyrant.dll" :
"Discuz.EntLib.MongoDB.dll") + "文件是否被放置到了bin目录下!");
}
}
}
}
return iCacheTopics;
}
}

从上面代码可以看出,使用反射方式获取相应DLL文件(分别是Discuz.EntLib.TokyoTyrant.dll和Discuz.EntLib.MongoDB.dll)中的 类信息并初始化该实例。固然,这里还定义了一个设置文件,也就是EntLibConfigs.GetConfig()这个方法所获取的设置文件信息, 相应 设置文件内容包罗:

复制代码代码如下:
/// <summary>
/// 提供数据库缓存服务,将在线表主题表这类大表放入缓存之中
/// </summary>
public class DBCache
{
/// <summary>
/// 是否有用
/// </summary>
public bool Enable = false;
/// <summary>
/// 服务地点
/// </summary>
public string Host = "";
/// <summary>
/// 服务地点
/// </summary>
public int Port = 0;
/// <summary>
/// 链接池名称
/// </summary>
public string PoolName = "dnt";
/// <summary>
/// 初始化链接数
/// </summary>
public int IntConnections = 4;
/// <summary>
/// 最少链接数
/// </summary>
public int MinConnections = 4;
/// <summary>
/// 最大连接数
/// </summary>
public int MaxConnections = 4;
/// <summary>
/// avaiable pool池中线程的最大空闲时间
/// </summary>
public int MaxIdle = 30000;
/// <summary>
/// busy pool中线程的最大繁忙时间
/// </summary>
public int MaxBusy = 50000;
/// <summary>
/// 维护线程休息时间
/// </summary>
public int MaintenanceSleep = 300000;
/// <summary>
/// TcpClient读操纵超时时间
/// </summary>
public int TcpClientTimeout = 3000;
/// <summary>
/// TcpClient链接超时时间
/// </summary>
public int TcpClientConnectTimeout = 30000;
/// <summary>
/// 缓存范例1为mongodb,2为tokyotyrnat
/// </summary>
public int CacheType = 1;
}

上面是设置文件中‘可复用信息’的基类,下面是详细的设置类实例声明:

复制代码代码如下:
/// <summary>
/// 企业版设置信息类文件
/// </summary>
public class EntLibConfigInfo : IConfigInfo
{
/// <summary>
/// 提供数据库缓存服务,将在线表(dnt_online)放入CACHE中
/// </summary>
public DBCache Cacheonlineuser = new DBCache();
/// <summary>
/// 提供数据库缓存服务,将用户表(dnt_users)放入CACHE中
/// </summary>
public DBCache Cacheusers = new DBCache();
/// <summary>
/// 提供数据库缓存服务,将主题表(dnt_topic)放入CACHE中
/// </summary>
public DBCache Cachetopics = new DBCache();
/// <summary>
/// 提供数据库缓存服务,将主题表(dnt_topic)放入CACHE中
/// </summary>
public DBCache Cacheposts = new DBCache();
}

通过该类,就可以用如下设置文件内容初始化相应的实例了:

复制代码代码如下:
<EntLibConfigInfo>
<Cacheonlineuser>
<!--在开启该功能之前,请确保相干服务已设置完毕-->
<Host>10.0.4.119</Host>
<Port>27017</Port>
<Enable>false</Enable>
<PoolName>dnt_online</PoolName>
<IntConnections>4</IntConnections>
<MinConnections>4</MinConnections>
<MaxConnections>4</MaxConnections>
<MaxIdle>30000</MaxIdle>
<MaxBusy>50000</MaxBusy>
<MaintenanceSleep>300000</MaintenanceSleep>
<TcpClientTimeout>3000</TcpClientTimeout>
<TcpClientConnectTimeout>30000</TcpClientConnectTimeout>
<CacheType>1</CacheType>
</Cacheonlineuser>
<Cacheusers>
<!--在开启该功能之前,请确保相干服务已设置完毕-->
<Host>10.0.4.66</Host>
<Port>112121</Port>
<Enable>false</Enable>
<PoolName>dnt_users</PoolName>
<IntConnections>4</IntConnections>
<MinConnections>4</MinConnections>
<MaxConnections>4</MaxConnections>
<MaxIdle>30000</MaxIdle>
<MaxBusy>50000</MaxBusy>
<MaintenanceSleep>300000</MaintenanceSleep>
<TcpClientTimeout>3000</TcpClientTimeout>
<TcpClientConnectTimeout>30000</TcpClientConnectTimeout>
<CacheType>1</CacheType>
</Cacheusers>
<Cachetopics>
<!--在开启该功能之前,请确保相干服务已设置完毕-->
<Host>10.0.4.5</Host>
<Port>27017</Port>
<Enable>false</Enable>
<PoolName>dnt_topics</PoolName>
<IntConnections>25</IntConnections>
<MinConnections>25</MinConnections>
<MaxConnections>25</MaxConnections>
<MaxIdle>30000</MaxIdle>
<MaxBusy>5000</MaxBusy>
<MaintenanceSleep>300000</MaintenanceSleep>
<TcpClientTimeout>300000</TcpClientTimeout>
<TcpClientConnectTimeout>30000</TcpClientConnectTimeout>
<CacheType>1</CacheType>
</Cachetopics>
<Cacheposts>
<!--在开启该功能之前,请确保相干服务已设置完毕-->
<Host>10.0.4.5</Host>
<Port>27017</Port>
<Enable>false</Enable>
<PoolName>dnt_posts</PoolName>
<IntConnections>25</IntConnections>
<MinConnections>25</MinConnections>
<MaxConnections>25</MaxConnections>
<MaxIdle>30000</MaxIdle>
<MaxBusy>5000</MaxBusy>
<MaintenanceSleep>300000</MaintenanceSleep>
<TcpClientTimeout>300000</TcpClientTimeout>
<TcpClientConnectTimeout>30000</TcpClientConnectTimeout>
<CacheType>1</CacheType>
</Cacheposts>
</EntLibConfigInfo>

固然,因为使用的开源的客户源工具在设置上有一定的的差异性(好比命名上等),以是这里有些参数可以对TTCACHE有用,却对MONGODB无效, 不外这并不影响对这两种工具的使用。

      这里要阐明的是,对于TokyoTrant而言,这里使用的是我开发的这款客户端软件:
      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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作