您的位置:首页 > 数据库 > Redis

Discuz!NT中的Redis架构设计

2011-02-21 11:01 417 查看
在之前的Discuz!NT缓存的架构方案中,曾说过Discuz!NT采用了两级缓存方式,即本地缓存+memcached
方式。在近半年多的实际
运行环境下,该方案经受住了检验。现在为了提供多样式的解决方案,我在企业版里引入了Redis这个目前炙手可热的缓存架构产品,即将memcached
与Redis作为可选插件方式来提供了最终用户,尽管目前测试的结果两者的差异不是很大(毫秒级),但我想多一种选择对用户来说也是好的。


闲话不多说了,开始今天的正文吧。




熟悉我们产品的开发者都知道,我们的产品在缓存设计上使用了策略模式(Stratety
Pattern),之前在系统中就已实现了DefaultCacheStrategy和MemCachedStrategy,前者用于本地缓存方式,后者
顾名思义,就是memcached缓存方式。所以只要我们实现一个RedisStrategy策略,并适当修改加载缓存策略的代码,就可以将
memcached缓存方式替换成Redis,如下图:








下面我先将RedisStrategy的部分代码放上来,大家一看便知:







///

<summary>

///
企业级Redis缓存策略


///

</summary>

public

class
RedisStrategy : DefaultCacheStrategy

{


///

<summary>


///
添加指
定ID的对象


///

</summary>


///

<param name="objId"></param>


///

<param name="o"></param>


public

override

void
AddObject(
string
objId,
object
o)

{


if
(
!
objId.StartsWith(
"
/Forum/ShowTopic/
"
))


base
.AddObject(objId, o, LocalCacheTime);


using
(IRedisClient Redis
=
RedisManager.GetClient())

{

Redis.Set
<
byte
[]
>
(objId,
new
ObjectSerializer().Serialize(o));

}

}


///

<summary>


///
加入当
前对象到缓存中


///

</summary>


///

<param name="objId">
对象的键值
</param>


///

<param name="o">
缓存的对象
</param>


///

<param name="o">
到期时间,单位:秒
</param>


public

override

void
AddObject(
string
objId,
object
o,
int
expire)

{


//
凡是以"/Forum/ShowTopic/"为前缀不添加到本地缓存中,现类似键值包括: "/Forum/ShowTopic/Tag
/{topicid}/" , "/Forum/ShowTopic/TopList/{fid}"


if
(
!
objId.StartsWith(
"
/Forum/ShowTopic/
"
))


base
.AddObject(objId, o, expire);


using
(IRedisClient Redis
=
RedisManager.GetClient())

{


//
永不过期


if
(expire
==

0
)

Redis.Set
<
byte
[]
>
(objId,
new
ObjectSerializer().Serialize(o));


else

Redis.Set
<
byte
[]
>
(objId,
new
ObjectSerializer().Serialize(o), DateTime.Now.AddSeconds(expire));

}

}


///

<summary>


///
移除指
定ID的对象


///

</summary>


///

<param name="objId"></param>


public

override

void
RemoveObject(
string
objId)

{


//
先移除本地cached,然后再移除memcached中的相应数据


base
.RemoveObject(objId);


using
(IRedisClient Redis
=
RedisManager.GetClient())

{

Redis.Remove(objId);

}

Discuz.EntLib.SyncCache.SyncRemoteCache(objId);

}


public

override

object
RetrieveObject(
string
objId)

{


object
obj
=

base
.RetrieveObject(objId);


if
(obj
==

null
)

{


using
(IRedisClient Redis
=
RedisManager.GetClient())

{

obj
=

new
ObjectSerializer().Deserialize(Redis.Get
<
byte
[]
>
(objId));


if
(obj
!=

null

&&

!
objId.StartsWith(
"
/Forum/ShowTopic/
"
))
//
对ShowTopic页面缓存数据不放到本地缓存

{


if
(objId.StartsWith(
"
/Forum/ShowTopicGuestCachePage/
"
))
//
对游客缓存页面ShowTopic数据缓存设置有效时间


base
.TimeOut
=
GeneralConfigs.GetConfig().Guestcachepagetimeout
*

60
;


if
(objId.StartsWith(
"
/Forum/ShowForumGuestCachePage/
"
))
//
对游客缓存页面ShowTopic数据缓存设置有效时间


base
.TimeOut
=
RedisConfigs.GetConfig().CacheShowForumCacheTime
*

60
;


else


base
.TimeOut
=
LocalCacheTime;


base
.AddObject(objId, obj, TimeOut);

}

}

}


return
obj;

}


///

<summary>


///
到期时
间,单位:秒


///

</summary>


public

override

int
TimeOut

{


get

{


return

3600
;

}

}


///

<summary>


///
本地缓
存到期时间,单位:秒


///

</summary>


public

int
LocalCacheTime

{


get

{


return
RedisConfigs.GetConfig().LocalCacheTime;

}

}


///

<summary>


///
清空的
有缓存数据


///

</summary>


public

override

void
FlushAll()

{


base
.FlushAll();


using
(IRedisClient Redis
=
RedisManager.GetClient())

{

Redis.FlushAll();

}

}

}



可以看出RedisStrategy类继承自DefaultCacheStrategy,这一点与MemCachedStrategy实现如出一辙,唯一
不同是其缓存数据加载和设置的方式有所不同,而具体的用法可以参见我之前写的这篇文章
中的“object序列化方式存储
” 。




当然上面代码中有两个类未说明,一个是RedisConfigs,一个是RedisManager,前者是配置文件信息类,我们产品因为使用了统一的序列
化接口实现方式(参见该文
),所以其实现方式比较清晰,其序列化类的结构如
下:





///

<summary>

///
Redis配置信息类文件

///

</summary>

public

class
RedisConfigInfo : IConfigInfo

{


private

bool
_applyRedis;


///

<summary>


///
是否应
用Redis


///

</summary>


public

bool
ApplyRedis

{


get

{


return
_applyRedis;

}


set

{

_applyRedis
=
value;

}

}


private

string
_writeServerList;


///

<summary>


///
可写的
Redis链接地址


///

</summary>


public

string
WriteServerList

{


get

{


return
_writeServerList;

}


set

{

_writeServerList
=
value;

}

}


private

string
_readServerList;


///

<summary>


///
可读的
Redis链接地址


///

</summary>


public

string
ReadServerList

{


get

{


return
_readServerList;

}


set

{

_readServerList
=
value;

}

}


private

int
_maxWritePoolSize;


///

<summary>


///
最大写
链接数


///

</summary>


public

int
MaxWritePoolSize

{


get

{


return
_maxWritePoolSize
>

0

?
_maxWritePoolSize :
5
;

}


set

{

_maxWritePoolSize
=
value;

}

}


private

int
_maxReadPoolSize;


///

<summary>


///
最大读
链接数


///

</summary>


public

int
MaxReadPoolSize

{


get

{


return
_maxReadPoolSize
>

0

?
_maxReadPoolSize :
5
;

}


set

{

_maxReadPoolSize
=
value;

}

}


private

bool
_autoStart;


///

<summary>


///
自动重



///

</summary>


public

bool
AutoStart

{


get

{


return
_autoStart;

}


set

{

_autoStart
=
value;

}

}




private

int
_localCacheTime
=

30000
;


///

<summary>


///
本地缓存到期时间,该设置会与memcached搭配使用,单位:秒


///

</summary>


public

int
LocalCacheTime

{


get

{


return
_localCacheTime;

}


set

{

_localCacheTime
=
value;

}

}


private

bool
_recordeLog
=

false
;


///

<summary>


///
是否记录日志,该设置仅用于排查redis运行时出现的问题,如redis工作正常,请关闭该项


///

</summary>


public

bool
RecordeLog

{


get

{


return
_recordeLog;

}


set

{

_recordeLog
=
value;

}

}


private

int
_cacheShowTopicPageNumber
=

5
;


///

<summary>


///
缓存帖子列表分页数(showtopic页数使用缓存前N页的帖子列表信息)


///

</summary>


public

int
CacheShowTopicPageNumber

{


get

{


return
_cacheShowTopicPageNumber;

}


set

{

_cacheShowTopicPageNumber
=
value;

}

}


///

<summary>


///
缓存
showforum页面分页数


///

</summary>


public

int
CacheShowForumPageNumber{
set
;
get
;}


///

<summary>


///
缓存
showforum页面时间(单位:分钟)


///

</summary>


public

int
CacheShowForumCacheTime{
set
;
get
;}

}




其序列化出来的xml文件格式形如:







<?
xml version
=
"
1.0
"
?>

<
RedisConfigInfo xmlns:xsi
=
" http://www.w3.org/2001/XMLSchema-instance "
>


<
ApplyRedis
>
true
</
ApplyRedis
>


<
WriteServerList
>
10.0
.
4.210
:
6379
</
WriteServerList
>


<
ReadServerList
>
10.0
.
4.210
:
6379
</
ReadServerList
>


<
MaxWritePoolSize
>
60
</
MaxWritePoolSize
>


<
MaxReadPoolSize
>
60
</
MaxReadPoolSize
>


<
AutoStart
>
true
</
AutoStart
>


<
LocalCacheTime
>
180
</
LocalCacheTime
>


<!--
单位:秒
-->


<
RecordeLog
>
false
</
RecordeLog
>


<!--
缓存帖子列表分页数
(showtopic页数使用缓存前N页的帖子列表信息)
-->


<
CacheShowTopicPageNumber
>
2
</
CacheShowTopicPageNumber
>


<!--
缓存showforum页面分页数
-->


<
CacheShowForumPageNumber
>
2
</
CacheShowForumPageNumber
>


<!--
缓存showforum页面时间(单位:分钟)
-->


<
CacheShowForumCacheTime
>
10
</
CacheShowForumCacheTime
>

</
RedisConfigInfo
>


之前所说的RedisManager类则是一个RedisClient客户端实现的简单封装,主要为了简化基于链接池的Redis链接方式的使用。其结构
如下:





using
System.Collections;

using
Discuz.Config;

using
Discuz.Common;

using
ServiceStack.Redis;

using
ServiceStack.Redis.Generic;

using
ServiceStack.Redis.Support;

namespace
Discuz.EntLib

{


///

<summary>


///
MemCache
管理操作类


///

</summary>


public

sealed

class
RedisManager

{


///

<summary>


///
redis
配置文件信息


///

</summary>


private

static
RedisConfigInfo redisConfigInfo
=
RedisConfigs.GetConfig();


private

static
PooledRedisClientManager prcm;


///

<summary>


///
静态构
造方法,初始化链接池管理对象


///

</summary>


static
RedisManager()

{

CreateManager();

}


///

<summary>


///
创建链
接池管理对象


///

</summary>


private

static

void
CreateManager()

{


string
[] writeServerList
=
Utils.SplitString(redisConfigInfo.WriteServerList,
"
,
"
);


string
[] readServerList
=
Utils.SplitString(redisConfigInfo.ReadServerList,
"
,
"
);

prcm
=

new
PooledRedisClientManager(readServerList, writeServerList,


new
RedisClientManagerConfig

{

MaxWritePoolSize
=
redisConfigInfo.MaxWritePoolSize,

MaxReadPoolSize
=
redisConfigInfo.MaxReadPoolSize,

AutoStart
=
redisConfigInfo.AutoStart,

});

}


///

<summary>


///
客户端
缓存操作对象


///

</summary>


public

static
IRedisClient GetClient()

{


if
(prcm
==

null
)

CreateManager();


return
prcm.GetClient();

}

}

}



上面的代码主要将redis.config的配置文件文件信息加载到程序里并实始化PooledRedisClientManager对象,该对象用于池
化redis的客户端链接,具体方式参见这篇文章




好了,到这里主要的内容就介绍完了。



注:本文的部分代码位于企业版产品中,目前暂未开源所以大家可能
没有拿到,我们计划今年开源企业版1.0的代码,包括本文中代码部分,以便从社区中获取更多经验和反馈,同时希望大家支持和关注我们的产品。





原文链接:http://www.cnblogs.com/daizhj/archive/2011/02/21/1959511.html


作者: daizhj, 代震军





Tags: discuz!nt,redis
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: