Redis群集实现Asp.net Mvc分布式Session
Session的缺点
众所周知Asp.net Session默认存储在IIS中,IIS的重启会导致Session丢失。
如果你的网站使用了session,当网站并发过大时可能引起溢出。
配置Redis 集群
安装Redis
创建一个文件,进入该文件夹
下载Redis 地址:http://download.redis.io/releases/redis-3.0.4.tar.gz
tar -xvf redis-3.0.4.tar.gz 解压文件
cd redis-3.0.4 进入解压的文件
make && make install 安装
创建三个文件夹
把redis.conf文件分别复制到这三个文件夹
然后分别进入这三个文件夹修改配置文件和指定不同的端口
1.daemonize yes (开启后台运行)
2.port 6379 (端口)
3.pidfile /var/run/redis-6379.pid (Redis 以守护进程的方式运行的时候,Redis 默认会把 pid 文件放在/var/run/redis.pid,你可以配置到其他地址。当运行多个 redis 服务时,需要指定不同的 pid 文件和端口)
4.cluster-enabled yes (是否启用集群)
5.cluster-config-file "nodes-6379.conf" (nodes 节点文件)
安装ruby环境
yum -y install zlib ruby rubygems
gem install redis
开启服务
分别开启这三个实例
查看是否开启
创建集群
首先,进入redis的安装包路径下:
cd /usr/local/src/redis/redis-3.0.1/src/
执行命令:
./redis-trib.rb create --replicas 0 ./redis-trib.rb create --replicas 0 192.168.1.108:6379 192.168.1.108:6380 192.168.1.108:6381
进入redis-cli -p 6379 -c
Cluster nodes 查看集群信息
配置完成
使用 StackExchange.Redis驱动连接到redis
代码是我从Asp.net vnext Caching 程序集中copy下来的
配置类型
public class RedisCacheOptions { /// <summary> /// 连接配置 /// </summary> public string Configuration { get; set; } /// <summary> /// 实例名字 /// </summary> public string InstanceName { get; set; } }
static void Main(string[] args) { string key = "myKey"; object state = null; string value1 = "yyyyyyyyyyyyyy"; byte[] value = Encoding.Default.GetBytes(value1); Stream valueStream; Console.WriteLine("Connecting to cache"); var cache = new RedisCache(new RedisCacheOptions { InstanceName = "sessionId", Configuration = "192.168.1.108:6379,192.168.1.108:6380,192.168.1.108:6381" }); Console.WriteLine("Connected"); Console.WriteLine("Setting"); valueStream = cache.Set(key, state, context => { context.Data.Write(value, 0, value.Length); });
RedisCache 集体实现
public class RedisCache : IDistributedCache { //Lua 脚本 private const string SetScript = (@" redis.call('HMSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4]) if ARGV[3] ~= '-1' then redis.call('EXPIRE', KEYS[1], ARGV[3]) end return 1"); //key private const string AbsoluteExpirationKey = "absexp"; //key private const string SlidingExpirationKey = "sldexp"; //key private const string DataKey = "data"; private const long NotPresent = -1; private ConnectionMultiplexer _connection; private IDatabase _cache; private readonly RedisCacheOptions _options; private readonly string _instance; /// <summary> /// 初始化配置 /// </summary> /// <param name="optionsAccessor"></param> public RedisCache(RedisCacheOptions optionsAccessor) { _options = optionsAccessor; _instance = optionsAccessor.InstanceName; } public void Connect() { if (_connection == null) { _connection = ConnectionMultiplexer.Connect(_options.Configuration); _cache = _connection.GetDatabase(); } } /// <summary> /// set到Redis中 /// </summary> /// <param name="key"></param> /// <param name="state"></param> /// <param name="create"></param> /// <returns></returns> public Stream Set(string key, object state, Action<ICacheContext> create) { Connect(); var context = new CacheContext(key) { State = state }; //设置绝对过期时间 //context.SetAbsoluteExpiration(DateTimeOffset.Now.AddMilliseconds(22222)); //设置滑动过期时间 context.SetSlidingExpiration(TimeSpan.FromSeconds(22222)); create(context); var value = context.GetBytes(); //Lua脚本赋值 var result = _cache.ScriptEvaluate(SetScript, new RedisKey[] { _instance + key }, new RedisValue[] { context.AbsoluteExpiration?.Ticks ?? NotPresent, context.SlidingExpiration?.Ticks ?? NotPresent, context.GetExpirationInSeconds() ?? NotPresent, value }); return new MemoryStream(value, writable: false); } public bool TryGetValue(string key, out Stream value) { value = GetAndRefresh(key, getData: true); return value != null; } public void Refresh(string key) { var ignored = GetAndRefresh(key, getData: false); } private Stream GetAndRefresh(string key, bool getData) { Connect(); RedisValue[] results; if (getData) { results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey); } else { results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey); } if (results.Length >= 2) { DateTimeOffset? absExpr; TimeSpan? sldExpr; MapMetadata(results, out absExpr, out sldExpr); Refresh(key, absExpr, sldExpr); } if (results.Length >= 3 && results[2].HasValue) { return new MemoryStream(results[2], writable: false); } return null; } private void MapMetadata(RedisValue[] results, out DateTimeOffset? absoluteExpiration, out TimeSpan? slidingExpiration) { absoluteExpiration = null; slidingExpiration = null; var absoluteExpirationTicks = (long?)results[0]; if (absoluteExpirationTicks.HasValue && absoluteExpirationTicks.Value != NotPresent) { absoluteExpiration = new DateTimeOffset(absoluteExpirationTicks.Value, TimeSpan.Zero); } var slidingExpirationTicks = (long?)results[1]; if (slidingExpirationTicks.HasValue && slidingExpirationTicks.Value != NotPresent) { slidingExpiration = new TimeSpan(slidingExpirationTicks.Value); } } /// <summary> /// 刷新缓存过期时间 /// </summary> /// <param name="key"></param> /// <param name="absExpr"></param> /// <param name="sldExpr"></param> private void Refresh(string key, DateTimeOffset? absExpr, TimeSpan? sldExpr) { TimeSpan? expr = null; if (sldExpr.HasValue) { if (absExpr.HasValue) { var relExpr = absExpr.Value - DateTimeOffset.Now; expr = relExpr <= sldExpr.Value ? relExpr : sldExpr; } else { expr = sldExpr; } _cache.KeyExpire(_instance + key, expr); // TODO: Error handling } } /// <summary> /// 移除指定key /// </summary> /// <param name="key"></param> public void Remove(string key) { Connect(); _cache.KeyDelete(_instance + key); } }
运行结果
源码:http://pan.baidu.com/s/1gdm8F9h