一个技术汪的开源梦 —— 公共组件缓存之分布式缓存 Redis 实现篇


Redis 安装 & 配置

本测试环境将在 CentOS 7 x64 上安装最新版本的 Redis。

1. 运行以下命令安装 Redis

$ wget http://download.redis.io/releases/redis-3.2.6.tar.gz
$ tar xzf redis-3.2.6.tar.gz
$ cd redis-3.2.6
$ make install

如果 CentOS 上提示 wget 命令未找到,则先安装 net-tools。

yum install net-tools

2. Redis 配置文件

1)开启守护程序

    修改 daemonize 节点为 yes。

2)运行外部IP访问

    配置 bind 节点为 0.0.0.0

 

本次示例只使用 Redis 的缓存 所以配置暂时只修改这两处即可。

3. 设置 Redis 开机自动启动

 1)在 Redis 的源码包中找到 utils 目录

 2) 将 redis_init_script 文件复制到 /etc/init.d 目录下并重命名为 redisd

cp redis_init_script /etc/init.d/redisd

 3) 打开 redisd 修改部分配置。

 1 #!/bin/sh
 2 # chkconfig:    2345 90 10
 3 # Simple Redis init.d script conceived to work on Linux systems
 4 # as it does use of the /proc filesystem.
 5 
 6 REDISPORT=6379
 7 EXEC=/usr/local/bin/redis-server
 8 CLIEXEC=/usr/local/bin/redis-cli
 9 
10 PIDFILE=/var/run/redis_${REDISPORT}.pid
11 CONF="/etc/redis/redis_${REDISPORT}.conf"

其中第二行 # chkconfig: 2345 90 10 需要另行添加。

- REDISPORT Redis 运行端口号;

- EXEC Redis 服务器命令文件路径(根据实际情况修改);

- CLIEXEC Redis 客户端命令文件路径(亦根据实际情况修改);

- CONF Redis 配置文件。

 

4)设置开机启动 & 启动、停止服务

#设置为开机自启动服务器
chkconfig redisd on
#打开服务
service redisd start
#关闭服务
service redisd stop

 

主:如果外部机器还访问不到 Redis 服务器,请将 6379 端口号加防火墙例外即可。

 

代码实现:

Common 包 接口定义 & 分布式缓存实例获取和配置

- IDistributedCache 接口
 1 using System;
 2 
 3 namespace Wlitsoft.Framework.Common.Core
 4 {
 5     /// <summary>
 6     /// 分布式缓存接口。
 7     /// </summary>
 8     public interface IDistributedCache
 9     {
10         /// <summary>
11         /// 获取缓存。
12         /// </summary>
13         /// <typeparam name="T">缓存类型。</typeparam>
14         /// <param name="key">缓存键值。</param>
15         /// <returns>获取到的缓存。</returns>
16         T Get<T>(string key);
17 
18         /// <summary>
19         /// 设置缓存。
20         /// </summary>
21         /// <typeparam name="T">缓存类型。</typeparam>
22         /// <param name="key">缓存键值。</param>
23         /// <param name="value">要缓存的对象。</param>
24         void Set<T>(string key, T value);
25 
26         /// <summary>
27         /// 设置缓存。
28         /// </summary>
29         /// <typeparam name="T">缓存类型。</typeparam>
30         /// <param name="key">缓存键值。</param>
31         /// <param name="value">要缓存的对象。</param>
32         /// <param name="expiredTime">过期时间。</param>
33         void Set<T>(string key, T value, TimeSpan expiredTime);
34 
35         /// <summary>
36         /// 判断指定键值的缓存是否存在。
37         /// </summary>
38         /// <param name="key">缓存键值。</param>
39         /// <returns>一个布尔值,表示缓存是否存在。</returns>
40         bool Exists(string key);
41 
42         /// <summary>
43         /// 移除指定键值的缓存。
44         /// </summary>
45         /// <param name="key">缓存键值。</param>
46         bool Remove(string key);
47 
48         /// <summary>
49         /// 获取 Hash 表中的缓存。
50         /// </summary>
51         /// <typeparam name="T">缓存类型。</typeparam>
52         /// <param name="key">缓存键值。</param>
53         /// <param name="hashField">要获取的 hash 字段。</param>
54         /// <returns>获取到的缓存。</returns>
55         T GetHash<T>(string key, string hashField);
56 
57         /// <summary>
58         /// 设置 缓存到 Hash 表。
59         /// </summary>
60         /// <typeparam name="T">缓存类型。</typeparam>
61         /// <param name="key">缓存键值。</param>
62         /// <param name="hashField">要设置的 hash 字段。</param>
63         /// <param name="hashValue">要设置的 hash 值。</param>
64         void SetHash<T>(string key, string hashField, T hashValue);
65 
66         /// <summary>
67         /// 判断指定键值的 Hash 缓存是否存在。
68         /// </summary>
69         /// <param name="key">缓存键值。</param>
70         /// <param name="hashField">hash 字段。</param>
71         /// <returns>一个布尔值,表示缓存是否存在。</returns>
72         bool ExistsHash(string key, string hashField);
73 
74         /// <summary>
75         /// 删除 hash 表中的指定字段的缓存。
76         /// </summary>
77         /// <param name="key">缓存键值。</param>
78         /// <param name="hashField">hash 字段。</param>
79         /// <returns>一个布尔值,表示缓存是否删除成功。</returns>
80         bool DeleteHash(string key, string hashField);
81     }
82 }

 - App 类

1 /// <summary>
2 /// 获取分布式缓存。
3 /// </summary>
4 public static IDistributedCache DistributedCache { get; internal set; }

 - AppBuilder 类

 1 namespace Wlitsoft.Framework.Common
 2 {
 3     /// <summary>
 4     /// 应用 构造。
 5     /// </summary>
 6     public class AppBuilder
 7     {
 8         #region 添加序列化者
 9 
10         /// <summary>
11         /// 添加序列化者。
12         /// </summary>
13         /// <param name="type">序列化类型。</param>
14         /// <param name="serializer">序列化者接口。</param>
15         public void AddSerializer(SerializeType type, ISerializer serializer)
16         {
17             #region 参数校验
18 
19             if (serializer == null)
20                 throw new ObjectNullException(nameof(serializer));
21 
22             #endregion
23 
24             App.SerializerService.SetSerializer(type, serializer);
25         }
26 
27         #endregion
28 
29         #region 添加日志记录者
30 
31         /// <summary>
32         /// 添加日志记录者。
33         /// </summary>
34         /// <param name="name">日志记录者名称。</param>
35         /// <param name="logger">日志接口。</param>
36         public void AddLogger(string name, ILog logger)
37         {
38             #region 参数校验
39 
40             if (string.IsNullOrEmpty(name))
41                 throw new StringNullOrEmptyException(nameof(name));
42 
43             if (logger == null)
44                 throw new ObjectNullException(nameof(logger));
45 
46             #endregion
47 
48             App.LoggerService.SetLogger(name, logger);
49         }
50 
51         #endregion
52 
53         #region 设置分布式缓存
54 
55         /// <summary>
56         /// 设置分布式缓存。
57         /// </summary>
58         /// <param name="cache">分布式缓存实例。</param>
59         /// <returns></returns>
60         public AppBuilder SetDistributedCache(IDistributedCache cache)
61         {
62             #region 参数校验
63 
64             if (cache == null)
65                 throw new ObjectNullException(nameof(cache));
66 
67             #endregion
68 
69             App.DistributedCache = cache;
70             return this;
71         }
72 
73         #endregion
74     }
75 }

 

分布式缓存 Redis 实现

 - RedisCache 类

  1 using System;
  2 using System.Linq;
  3 using System.Threading;
  4 using StackExchange.Redis;
  5 using Wlitsoft.Framework.Common.Core;
  6 using Wlitsoft.Framework.Common.Extension;
  7 using Wlitsoft.Framework.Common.Exception;
  8 
  9 namespace Wlitsoft.Framework.Caching.Redis
 10 {
 11     /// <summary>
 12     /// Redis 缓存。
 13     /// </summary>
 14     public class RedisCache : IDistributedCache
 15     {
 16         #region 私有属性
 17 
 18         //redis 连接实例。
 19         private volatile ConnectionMultiplexer _connection;
 20 
 21         //redis 缓存数据库实例。
 22         private IDatabase _database;
 23 
 24         //连接实例锁。
 25         private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(1, 1);
 26 
 27         //Redis 配置。
 28         internal static RedisCacheConfiguration RedisCacheConfiguration;
 29 
 30         #endregion
 31 
 32         #region IDistributedCache 成员
 33 
 34         /// <summary>
 35         /// 获取缓存。
 36         /// </summary>
 37         /// <typeparam name="T">缓存类型。</typeparam>
 38         /// <param name="key">缓存键值。</param>
 39         /// <returns>获取到的缓存。</returns>
 40         public T Get<T>(string key)
 41         {
 42             #region 参数校验
 43 
 44             if (string.IsNullOrEmpty(key))
 45                 throw new StringNullOrEmptyException(nameof(key));
 46 
 47             #endregion
 48 
 49             this.Connect();
 50             string result = this._database.StringGet(key);
 51             if (string.IsNullOrEmpty(result))
 52                 return default(T);
 53             return result.ToJsonObject<T>();
 54         }
 55 
 56         /// <summary>
 57         /// 设置缓存。
 58         /// </summary>
 59         /// <typeparam name="T">缓存类型。</typeparam>
 60         /// <param name="key">缓存键值。</param>
 61         /// <param name="value">要缓存的对象。</param>
 62         public void Set<T>(string key, T value)
 63         {
 64             #region 参数校验
 65 
 66             if (string.IsNullOrEmpty(key))
 67                 throw new StringNullOrEmptyException(nameof(key));
 68 
 69             if (value == null)
 70                 throw new ObjectNullException(nameof(value));
 71 
 72             #endregion
 73 
 74             this.Connect();
 75             this._database.StringSet(key, value.ToJsonString());
 76         }
 77 
 78         /// <summary>
 79         /// 设置缓存。
 80         /// </summary>
 81         /// <typeparam name="T">缓存类型。</typeparam>
 82         /// <param name="key">缓存键值。</param>
 83         /// <param name="value">要缓存的对象。</param>
 84         /// <param name="expiredTime">过期时间。</param>
 85         public void Set<T>(string key, T value, TimeSpan expiredTime)
 86         {
 87             #region 参数校验
 88 
 89             if (string.IsNullOrEmpty(key))
 90                 throw new StringNullOrEmptyException(nameof(key));
 91 
 92             if (value == null)
 93                 throw new ObjectNullException(nameof(value));
 94 
 95             #endregion
 96 
 97             this.Connect();
 98             this._database.StringSet(key, value.ToJsonString(), expiredTime);
 99         }
100 
101         /// <summary>
102         /// 判断指定键值的缓存是否存在。
103         /// </summary>
104         /// <param name="key">缓存键值。</param>
105         /// <returns>一个布尔值,表示缓存是否存在。</returns>
106         public bool Exists(string key)
107         {
108             #region 参数校验
109 
110             if (string.IsNullOrEmpty(key))
111                 throw new StringNullOrEmptyException(nameof(key));
112 
113             #endregion
114 
115             this.Connect();
116             return this._database.KeyExists(key);
117         }
118 
119         /// <summary>
120         /// 移除指定键值的缓存。
121         /// </summary>
122         /// <param name="key">缓存键值。</param>
123         public bool Remove(string key)
124         {
125             #region 参数校验
126 
127             if (string.IsNullOrEmpty(key))
128                 throw new StringNullOrEmptyException(nameof(key));
129 
130             #endregion
131 
132             this.Connect();
133             return this._database.KeyDelete(key);
134         }
135 
136         /// <summary>
137         /// 获取 Hash 表中的缓存。
138         /// </summary>
139         /// <typeparam name="T">缓存类型。</typeparam>
140         /// <param name="key">缓存键值。</param>
141         /// <param name="hashField">要获取的 hash 字段。</param>
142         /// <returns>获取到的缓存。</returns>
143         public T GetHash<T>(string key, string hashField)
144         {
145             #region 参数校验
146 
147             if (string.IsNullOrEmpty(key))
148                 throw new StringNullOrEmptyException(nameof(key));
149 
150             if (string.IsNullOrEmpty(hashField))
151                 throw new StringNullOrEmptyException(nameof(hashField));
152 
153             #endregion
154 
155             this.Connect();
156             string value = this._database.HashGet(key, hashField);
157             if (string.IsNullOrEmpty(value))
158                 return default(T);
159             return value.ToJsonObject<T>();
160         }
161 
162         /// <summary>
163         /// 设置 缓存到 Hash 表。
164         /// </summary>
165         /// <typeparam name="T">缓存类型。</typeparam>
166         /// <param name="key">缓存键值。</param>
167         /// <param name="hashField">要设置的 hash 字段。</param>
168         /// <param name="hashValue">要设置的 hash 值。</param>
169         public void SetHash<T>(string key, string hashField, T hashValue)
170         {
171             #region 参数校验
172 
173             if (string.IsNullOrEmpty(key))
174                 throw new StringNullOrEmptyException(nameof(key));
175 
176             if (string.IsNullOrEmpty(hashField))
177                 throw new StringNullOrEmptyException(nameof(hashField));
178 
179             if (hashValue == null)
180                 throw new ObjectNullException(nameof(hashValue));
181 
182             #endregion
183 
184             this.Connect();
185             this._database.HashSet(key, hashField, hashValue.ToJsonString());
186         }
187 
188         /// <summary>
189         /// 判断指定键值的 Hash 缓存是否存在。
190         /// </summary>
191         /// <param name="key">缓存键值。</param>
192         /// <param name="hashField">hash 字段。</param>
193         /// <returns>一个布尔值,表示缓存是否存在。</returns>
194         public bool ExistsHash(string key, string hashField)
195         {
196             #region 参数校验
197 
198             if (string.IsNullOrEmpty(key))
199                 throw new StringNullOrEmptyException(nameof(key));
200 
201             if (string.IsNullOrEmpty(hashField))
202                 throw new StringNullOrEmptyException(nameof(hashField));
203 
204             #endregion
205 
206             this.Connect();
207             return this._database.HashExists(key, hashField);
208         }
209 
210         /// <summary>
211         /// 删除 hash 表中的指定字段的缓存。
212         /// </summary>
213         /// <param name="key">缓存键值。</param>
214         /// <param name="hashField">hash 字段。</param>
215         /// <returns>一个布尔值,表示缓存是否删除成功。</returns>
216         public bool DeleteHash(string key, string hashField)
217         {
218             #region 参数校验
219 
220             if (string.IsNullOrEmpty(key))
221                 throw new StringNullOrEmptyException(nameof(key));
222 
223             if (string.IsNullOrEmpty(hashField))
224                 throw new StringNullOrEmptyException(nameof(hashField));
225 
226             #endregion
227 
228             this.Connect();
229             return this._database.HashDelete(key, hashField);
230         }
231 
232         #endregion
233 
234         #region 私有方法
235 
236         /// <summary>
237         /// 连接。
238         /// </summary>
239         private void Connect()
240         {
241             if (this._connection != null)
242                 return;
243 
244             this._connectionLock.Wait();
245             try
246             {
247                 if (this._connection == null)
248                 {
249                     this._connection = ConnectionMultiplexer.Connect(this.GetConfigurationOptions());
250                     this._database = this._connection.GetDatabase();
251                 }
252             }
253             finally
254             {
255                 this._connectionLock.Release();
256             }
257         }
258 
259         private ConfigurationOptions GetConfigurationOptions()
260         {
261             #region 校验
262 
263             if (RedisCacheConfiguration == null)
264                 throw new ObjectNullException(nameof(RedisCacheConfiguration));
265 
266             if (!RedisCacheConfiguration.HostAndPoints.Any())
267                 throw new Exception("RedisCahce 的 HostAndPoints 不能为空");
268 
269             #endregion
270 
271             ConfigurationOptions options = new ConfigurationOptions();
272 
273             foreach (string item in RedisCacheConfiguration.HostAndPoints)
274                 options.EndPoints.Add(item);
275 
276             options.ConnectRetry = RedisCacheConfiguration.ConnectRetry;
277             options.ConnectTimeout = RedisCacheConfiguration.ConnectTimeout;
278 
279             return options;
280         }
281 
282         #endregion
283 
284         #region 析构函数
285 
286         /// <summary>
287         /// 析构 <see cref="RedisCache"/> 类型的对象。
288         /// </summary>
289         ~RedisCache()
290         {
291             _connection?.Close();
292         }
293 
294         #endregion
295     }
296 }