由于数据库负载过高,查看发现Oracle中保持连接的Session达到600+,为了降低数据库的负载,添加了Redis缓存处理。想着尽量不改变原有的代码结构,于是一直在想能不能写一个attribute,实现了该attribute的方法走Redis缓存路线。
- 实现思路:使用PostSharp获取方法的签名信息,扩展出针对方法的Attribute,添加此Attribute,方法调用时自动从缓存中读取,如果有值,直接返回,否则连接数据库获取数据。
- 具体方法包括两个class,一个用来获取方法的签名信息,并根据此信息读取Redis缓存,另一个是Redis缓存的处理逻辑。话不多说,上代码。
- MethodTraceAttribute 用来获取签名信息以及Entry和Exit处理。
[Serializable]
public sealed class MethodTraceAttribute : OnMethodBoundaryAspect
{
public MethodTraceAttribute()
{
}
public string SignInfo { get; set; }
public void SetMethodSign(MethodExecutionArgs args)
{
SignInfo = string.Format("Method:{0}.{1}",
args.Method.DeclaringType.Name,
args.Method.Name);
SignInfo += ",Paras={";
for (int x = 0; x < args.Arguments.Count; x++)
{
var para = args.Method.GetParameters()[x].Name + ":" +
args.Arguments.GetArgument(x);
SignInfo += para;
}
SignInfo += "}";
var type = (args.Method as MethodInfo).ReturnType.ToString();
SignInfo += ",Return=" + type;
}
public override void OnEntry(MethodExecutionArgs args)
{
base.OnEntry(args);
SetMethodSign(args);
var mthInfo = args.Method as MethodInfo;
var cacheValue = RedisCache.GetCacheValue<string>(SignInfo);
if (cacheValue != null)
{
if (mthInfo != null)
args.ReturnValue = JsonConvert.DeserializeObject(cacheValue, mthInfo.ReturnType);
else
args.ReturnValue = null;
args.FlowBehavior = FlowBehavior.Return;
}
}
public override void OnExit(MethodExecutionArgs args)
{
var cacheValue = RedisCache.GetCacheValue<string>(SignInfo);
if (cacheValue == null)
RedisCache.SetCacheValue<string>(SignInfo, JsonConvert.SerializeObject(args.ReturnValue),args.Method.Name);
}
}
SetMethodSign获取方法的命名空间+类名+参数声明+参数值+方法返回类型,作为方法的唯一签名。当参数值不同时,签名肯定不同,返回类型不同,签名也不同。
OnEntry 在方法进入时,先获取方法签名,以方法签名作为Key获取Redis中的缓存,如果存在,将缓存值设置为返回值ReturnValue,然后FlowBehavior = FlowBehavior.Return,直接返回,否则方法按照原来设定继续访问数据库。
OnExit 在方法退出时,根据签名信息获取Redis缓存,如果没有,将返回值添加至Redis缓存。
- RedisCache 类用来获取和设置Redis缓存信息,为了便于设置,添加了CacheSetUp.config配置文件。
RedisCache 类代码:
public class RedisCache
{
private static RedisCache curcache=null;
public static ConfigXmlDocument conf = null;
private readonly ConnectionMultiplexer redisConnections;
public static RedisCache GetCurCache()
{
if (curcache == null)
curcache = new RedisCache();
return curcache;
}
public ConfigXmlDocument GetCacheSetUpConfig()
{
if (conf == null)
{
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CacheSetUp.config");
conf = new ConfigXmlDocument();
conf.Load(filepath);
}
return conf;
}
public RedisCache()
{
var con = GetRedisConf();
if (con == null)
redisConnections = null;
else
redisConnections = ConnectionMultiplexer.Connect(con);
}
public string GetConfigValue(string key)
{
try
{
var ele = GetCacheSetUpConfig().SelectSingleNode("//appSettings").SelectSingleNode("//add[@key='" + key + "']") as XmlElement;
if (ele == null) return "";
return ele.GetAttribute("value");
}
catch(Exception ex)
{
throw ex;
}
}
public ConfigurationOptions GetRedisConf()
{
if (GetConfigValue("RedisCacheEnable") == "N")
return null;
var configstr = GetConfigValue("DefaultConnect");
return ConfigurationOptions.Parse(configstr);
}
public static void SetCacheValue<T>(object cachekey,T value,string configName="Dfault") where T : class
{
if (GetCurCache().redisConnections == null) return;
var key = JsonConvert.SerializeObject(cachekey);
GetCurCache().Set<T>(key,value, GetConfigTimeOut(configName));
}
public static TimeSpan GetConfigTimeOut(string configName)
{
var timespan = new TimeSpan();
string key = "DefaultTimeOut";
var speckey = configName + "TimeOut";
if (curcache.GetConfigValue(speckey) != "")
key = speckey;
var t = curcache.GetConfigValue(key).Split(',').ToList();
timespan = new TimeSpan(int.Parse(t[0]), int.Parse(t[1]), int.Parse(t[2]));
return timespan;
}
public static T GetCacheValue<T>(object cachekey) where T : class
{
var key = JsonConvert.SerializeObject(cachekey);
return GetCurCache().Get<T>(key);
}
public T Get<T>(string key) where T : class
{
if (curcache.redisConnections == null) return null;
var db = curcache.redisConnections.GetDatabase();
var redisObject = db.StringGet(key);
if (redisObject.HasValue)
{
return JsonConvert.DeserializeObject<T>(redisObject
, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
}
else
{
return (T)null;
}
}
public void Set<T>(string key, T objectToCache, TimeSpan? expiretime=null) where T : class
{
var db = curcache.redisConnections.GetDatabase();
db.StringSet(key, JsonConvert.SerializeObject(objectToCache
, Newtonsoft.Json.Formatting.Indented
, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
PreserveReferencesHandling = PreserveReferencesHandling.Objects
}), expiretime);
}
}
RedisCache 类没啥好讲的,从Redis获取缓存,反序列化为特定类型。同时附上CacheSetUp.config配置文件信息,配置文件可以设置缓存是否开启,同时也可以针对特定方法设置特定的缓存期限。默认30分钟。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="RedisCacheEnable" value="Y"/>
<add key="DefaultTimeOut" value="0,30,0"/>
<add key="DefaultConnect" value="127.0.0.1:6379,serviceName=25,connectTimeout=100000,abortConnect=False,connectRetry=3,responseTimeout=2800000"/>
</appSettings>
</configuration>