PageRenderTime 628ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Guanima.Redis/Client/RedisClient.cs

http://github.com/ccollie/Guanima.Redis
C# | 432 lines | 316 code | 77 blank | 39 comment | 57 complexity | a42d5997551068d84e1813c48197add6 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Configuration;
  4. using System.Linq;
  5. using System.Text;
  6. using Guanima.Redis.Client;
  7. using Guanima.Redis.Configuration;
  8. using Guanima.Redis.Commands;
  9. using Guanima.Redis.Utils;
  10. namespace Guanima.Redis
  11. {
  12. public partial class RedisClient : Disposable
  13. {
  14. /// Represents a value which indicates that an item should never expire.
  15. /// </summary>
  16. public static readonly TimeSpan Infinite = TimeSpan.Zero;
  17. internal static RedisClientSection DefaultSettings = ConfigurationManager.GetSection("Guanima/Redis") as RedisClientSection;
  18. private static log4net.ILog log = log4net.LogManager.GetLogger(typeof(RedisClient));
  19. private IServerPool _serverPool;
  20. private Stack<IRedisNode> _onNodeStack;
  21. #region Constructor
  22. /// <summary>
  23. /// Initializes a new RedisClient instance using the default configuration section (Guanima/Redis).
  24. /// </summary>
  25. public RedisClient()
  26. {
  27. Initialize(DefaultSettings);
  28. }
  29. /// <summary>
  30. /// Initializes a new RedisClient instance using the specified configuration section.
  31. /// This overload allows to create multiple RedisClients with different pool configurations.
  32. /// </summary>
  33. /// <param name="sectionName">The name of the configuration section to be used for configuring the behavior of the client.</param>
  34. public RedisClient(string sectionName)
  35. {
  36. var section = (RedisClientSection)ConfigurationManager.GetSection(sectionName);
  37. if (section == null)
  38. throw new ConfigurationErrorsException("Section " + sectionName + " is not found.");
  39. Initialize(section);
  40. }
  41. /// <summary>
  42. /// Initializes a new instance of the <see cref="T:Guanima.Redis.RedisClient"/> using the specified configuration instance.
  43. /// </summary>
  44. /// <param name="configuration">The client configuration.</param>
  45. public RedisClient(IRedisClientConfiguration configuration)
  46. {
  47. if (configuration == null)
  48. throw new ArgumentNullException("configuration");
  49. Initialize(configuration);
  50. }
  51. private void Initialize(IRedisClientConfiguration configuration)
  52. {
  53. IServerPool pool = new DefaultServerPool(configuration);
  54. Initialize(pool);
  55. _serverPool = pool;
  56. _currentDb = configuration.DefaultDB; // ???
  57. _commandQueues = new Dictionary<IRedisNode, RedisCommandQueue>();
  58. }
  59. private static void Initialize(IServerPool pool)
  60. {
  61. // everything is initialized, start the pool
  62. pool.Start();
  63. }
  64. #endregion
  65. #region Commands
  66. #endregion
  67. #region Nodes/Servers
  68. internal IEnumerable<IRedisNode> GetNodes()
  69. {
  70. if (_onNodeStack != null && _onNodeStack.Count > 0)
  71. {
  72. return new IRedisNode[]{ _onNodeStack.Peek() };
  73. }
  74. return _serverPool.GetServers();
  75. }
  76. public IDisposable On(string alias)
  77. {
  78. var node = GetNodeByAlias(alias);
  79. if (node == null)
  80. throw new RedisClientException("No node found with the alias : '" + alias + "'");
  81. return On(node);
  82. }
  83. public IDisposable On(IRedisNode node)
  84. {
  85. if (node == null)
  86. throw new ArgumentNullException("node");
  87. if (!node.IsAlive)
  88. throw new RedisClientException("The node is not responding : " + node.Alias);
  89. if (_onNodeStack == null)
  90. _onNodeStack = new Stack<IRedisNode>();
  91. _onNodeStack.Push(node);
  92. return new DisposableAction(() =>
  93. {
  94. _onNodeStack.Pop();
  95. if (_onNodeStack.Count == 0)
  96. _onNodeStack = null;
  97. });
  98. }
  99. public String TransformKey(string key)
  100. {
  101. var kt = _serverPool.KeyTransformer;
  102. return (kt == null) ? key : kt.Transform(key);
  103. }
  104. public IEnumerable<string> TransformKeys(IEnumerable<String> keys)
  105. {
  106. var kt = _serverPool.KeyTransformer;
  107. if (kt == null)
  108. return keys;
  109. var transformed = new List<string>();
  110. foreach (var original in keys)
  111. {
  112. transformed.Add( kt.Transform(original) );
  113. }
  114. return transformed;
  115. }
  116. /// <summary>
  117. ///
  118. /// </summary>
  119. /// <param name="key">Cache item key</param>
  120. /// <returns></returns>
  121. protected IRedisNode GetNodeForTransformedKey(string key)
  122. {
  123. if (_onNodeStack != null && _onNodeStack.Count > 0)
  124. {
  125. return _onNodeStack.Peek();
  126. }
  127. var locator = _serverPool.NodeLocator;
  128. return locator.Locate(key);
  129. }
  130. public IRedisNode GetNodeForKey(string key)
  131. {
  132. return GetNodeForTransformedKey(TransformKey(key));
  133. }
  134. public IRedisNode GetNodeByAlias(string alias)
  135. {
  136. if (String.IsNullOrEmpty(alias))
  137. throw new ArgumentNullException(alias);
  138. return _serverPool.GetServers().Where(x => x.Alias == alias).FirstOrDefault();
  139. }
  140. /// <summary>
  141. /// Maps each key in the list to a RedisNode
  142. /// </summary>
  143. /// <param name="keys"></param>
  144. /// <returns></returns>
  145. protected Dictionary<IRedisNode, List<string>> SplitKeys(IEnumerable<string> keys)
  146. {
  147. var retval = new Dictionary<IRedisNode, List<string>>(RedisNode.Comparer.Instance);
  148. foreach (var key in keys)
  149. {
  150. var node = GetNodeForTransformedKey(key);
  151. if (node != null)
  152. {
  153. List<string> list;
  154. if (!retval.TryGetValue(node, out list))
  155. retval[node] = list = new List<string>();
  156. list.Add(key);
  157. }
  158. }
  159. return retval;
  160. }
  161. private int _workingServerCount = -1;
  162. protected bool Clustering
  163. {
  164. get
  165. {
  166. if (_workingServerCount == -1)
  167. {
  168. // this assumes the list doesnt change at runtime
  169. _workingServerCount = _serverPool.GetServers().Count();
  170. }
  171. return (_workingServerCount > 1) &&
  172. (_onNodeStack == null || _onNodeStack.Count == 0);
  173. }
  174. }
  175. protected void ForEachServer(Action<IRedisNode> action)
  176. {
  177. if (_onNodeStack != null && _onNodeStack.Count > 0)
  178. {
  179. action(_onNodeStack.Peek());
  180. return;
  181. }
  182. foreach (var server in _serverPool.GetServers())
  183. {
  184. if (!server.IsAlive)
  185. continue;
  186. action(server);
  187. }
  188. }
  189. protected void ForEachServer(RedisCommand command)
  190. {
  191. if (_onNodeStack != null && _onNodeStack.Count > 0)
  192. {
  193. Execute(_onNodeStack.Peek(), command);
  194. return;
  195. }
  196. ForEachServer(node => Execute(node, command));
  197. }
  198. #endregion
  199. #region Sockets
  200. internal void DisposeSocket(PooledSocket socket)
  201. {
  202. if (socket != null)
  203. ((IDisposable)socket).Dispose();
  204. }
  205. #endregion
  206. #region Command Execution
  207. protected void CannotCluster(string commandName)
  208. {
  209. string message = String.Format("{0} cannot executed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic.", commandName);
  210. throw new RedisClusterException(message);
  211. }
  212. private void EnsureNotClustered(string commandName, IEnumerable<String> transformedKeys)
  213. {
  214. var splitKeys = SplitKeys(transformedKeys);
  215. if (splitKeys.Count > 1)
  216. CannotCluster(commandName);
  217. }
  218. private void EnsureNotClustered(string commandName, string transformedDestKey, IEnumerable<string> transformedKeys)
  219. {
  220. // See if all keys reside on the same node
  221. var splitKeys = SplitKeys(transformedKeys);
  222. var node = GetNodeForTransformedKey(transformedDestKey);
  223. bool clusteredKeys = false;
  224. if (splitKeys.Count == 1)
  225. {
  226. var firstNode = splitKeys.First().Key;
  227. if (firstNode != node)
  228. {
  229. clusteredKeys = true;
  230. }
  231. }
  232. else
  233. {
  234. clusteredKeys = true;
  235. }
  236. if (clusteredKeys)
  237. CannotCluster(commandName);
  238. }
  239. //
  240. protected void HandleException(Exception ex)
  241. {
  242. // TODO generic catch-all does not seem to be a good idea now. Some errors (like command not supported by server) should be exposed
  243. // while retaining the fire-and-forget behavior
  244. log.Error(ex);
  245. }
  246. protected void Execute(String key, RedisCommand command)
  247. {
  248. Execute(GetNodeForTransformedKey(key), command);
  249. }
  250. internal void Execute(IRedisNode node, RedisCommand command)
  251. {
  252. EnqueueCommand(node, command);
  253. if (Pipelining || InTransaction)
  254. return;
  255. try
  256. {
  257. var temp = command.Value;
  258. }
  259. catch (Exception e)
  260. {
  261. // TODO generic catch-all does not seem to be a good idea now. Some errors (like command not supported by server) should be exposed
  262. // while retaining the fire-and-forget behavior
  263. HandleException(e);
  264. throw;
  265. }
  266. }
  267. protected RedisValue ExecValue(String key, RedisCommand command)
  268. {
  269. var node = GetNodeForTransformedKey(key);
  270. return ExecValue(node, command);
  271. }
  272. private RedisValue ExecValue(IRedisNode node, RedisCommand command)
  273. {
  274. EnqueueCommand(node, command);
  275. if (Pipelining || InTransaction)
  276. return RedisValue.Empty;
  277. try
  278. {
  279. return command.Value;
  280. }
  281. catch (Exception e)
  282. {
  283. // TODO generic catch-all does not seem to be a good idea now. Some errors (like command not supported by server) should be exposed
  284. // while retaining the fire-and-forget behavior
  285. HandleException(e);
  286. throw;
  287. }
  288. }
  289. protected int ExecuteInt(String key, RedisCommand command)
  290. {
  291. var val = ExecValue(key, command);
  292. return Pipelining ? 0 : (int) val;
  293. }
  294. protected int ExecuteInt(IRedisNode node, RedisCommand command)
  295. {
  296. var val = ExecValue(node, command);
  297. return Pipelining ? 0 : (int) val;
  298. }
  299. protected long ExecuteLong(IRedisNode node, RedisCommand command)
  300. {
  301. var val = ExecValue(node, command);
  302. if (Pipelining)
  303. return 0;
  304. return val;
  305. }
  306. protected bool ExecuteBool(IRedisNode node, RedisCommand command)
  307. {
  308. return ExecuteInt(node, command) > 0;
  309. }
  310. protected bool ExecuteBool(String key, RedisCommand command)
  311. {
  312. return ExecuteInt(key, command) > 0;
  313. }
  314. #endregion
  315. #region Miscellaneous // TODO: break out into helper class
  316. private static String ToString(byte[] value)
  317. {
  318. return (value == null) ? null : Encoding.UTF8.GetString(value);
  319. }
  320. #endregion
  321. #region [ Disposable ]
  322. protected bool CheckDisposed(bool throwOnError)
  323. {
  324. if (throwOnError && Disposed)
  325. throw new ObjectDisposedException("RedisClient");
  326. return Disposed;
  327. }
  328. /// <summary>
  329. /// Releases all resources allocated by this instance
  330. /// </summary>
  331. /// <remarks>Technically it's not really neccesary to call this, since the client does not create "really" disposable objects, so it's safe to assume that when
  332. /// the AppPool shuts down all resources will be released correctly and no handles or such will remain in the memory.</remarks>
  333. protected override void Release()
  334. {
  335. if (_serverPool == null)
  336. throw new ObjectDisposedException("RedisClient");
  337. try
  338. {
  339. FlushPipeline();
  340. }
  341. finally
  342. {
  343. try
  344. {
  345. _serverPool.Dispose();
  346. }
  347. finally
  348. {
  349. _serverPool = null;
  350. }
  351. }
  352. }
  353. #endregion
  354. }
  355. }