在之前的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