StackExchange.Redis 客户端
一. Pipelining管道
许多redis客户允许您使用管道,是将多条消息通过管道发送的过程,而无需等待每个消息的回复,并且(通常)在稍后收到回复时对其进行处理。在.net中通过awit async来实现。
例如:要使用过程阻塞代码对这两个get进行管道传输
var aPending = db.StringGetAsync("a"); var bPending = db.StringGetAsync("b"); var a = db.Wait(aPending); var b = db.Wait(bPending);
使用管道使我们能够立即将两个请求都发送到网络上,从而消除了大部分延迟。管道还有助于减少数据包碎片:单独发送20个请求(等待每个响应)将至少需要20个数据包,但是在管道中发送的20个请求可以只需要更少的数据包(甚至可能只要一个)。
StackExchange.Redis有3种主要使用机制:
同步Sync ,同步模式会直接阻塞调用者,但是显然不会阻塞其他线程。
异步Async, 异步模式直接走的是Task模型,管道就是使用这种。
string value = "abcdefg"; await db.StringSetAsync("mykey", value);
即用即弃Fire-and-Forget, 就是发送命令,然后完全不关心最终什么时候完成命令操作。所有命令都会立即得到返回值,比如操作返回类型是bool将会立即得到false。Int64
将始终返回0
db.StringIncrement(pageKey, flags: CommandFlags.FireAndForget);
二.多路复用
管道虽很好,但是通常一个代码块都只需要一个值,这意味着我们仍然存在这样一个问题:我们大部分时间都在等待数据在客户机和服务器之间传输。如果您有20个并行应用程序请求都需要数据,则可以考虑拆分20个连接,或者可以同步对单个连接的访问(这意味着最后一个调用者将需要等待其他19个延迟甚至都没有开始)。通过多路复用单个连接来有效利用所有这些空闲时间,需要做很多工作。当由不同的调用者同时使用时,它会自动对单独的请求进行管道传输,因此无论请求使用阻塞访问还是异步访问,工作都将通过管道进行。
public static ConnectionMultiplexer Manager { get { if (_redis == null) { lock (_locker) { if (_redis != null) return _redis; _redis = GetManager(); return _redis; } } return _redis; } } private static ConnectionMultiplexer GetManager(string connectionString = null) { if (string.IsNullOrEmpty(connectionString)) { connectionString = GetDefaultConnectionString(); } return ConnectionMultiplexer.Connect(connectionString); }
IDatabase db = redis.GetDatabase();
参考资料:
https://stackexchange.github.io/StackExchange.Redis/PipelinesMultiplexers
三.发布订阅
当使用发布/订阅API,有二种消息订阅方式,一是顺序处理,二是并发处理。
顺序处理它们意味着您不必担心(非常多)线程安全性,并且意味着您保留了事件的顺序-事件的处理顺序与接收事件的顺序完全相同(通过队列) )-但其结果是,这意味着消息可能会彼此延迟。
multiplexer.GetSubscriber().SubScribe("messages", (channel, message) => { Console.WriteLine((string)message); });
另一个选择是并发处理。这不能保证工作的顺序,并且代码完全负责确保并发消息不会破坏您的内部状态,但是它可以更快,更可扩展。如果消息通常是不相关的,这特别好用。
var channelMessageQueue = multiplexer.GetSubscriber().SubScribe("messages"); channel.OnMessage(message => { Console.WriteLine((string)message.Message); });
1 #region Redis发布订阅 2 /// <summary> 3 /// Redis发布订阅 订阅 4 /// </summary> 5 /// <param name="subChannel"></param> 6 void RedisSub(string subChannel); 7 /// <summary> 8 /// Redis发布订阅 发布 9 /// </summary> 10 /// <typeparam name="T"></typeparam> 11 /// <param name="channel"></param> 12 /// <param name="msg"></param> 13 /// <returns></returns> 14 long RedisPub<T>(string channel, T msg); 15 /// <summary> 16 /// Redis发布订阅 取消订阅 17 /// </summary> 18 /// <param name="channel"></param> 19 void Unsubscribe(string channel); 20 /// <summary> 21 /// Redis发布订阅 取消全部订阅 22 /// </summary> 23 void UnsubscribeAll(); 24 25 #endregion
1 #region Redis发布订阅 2 /// <summary> 3 /// Redis发布订阅 订阅 4 /// </summary> 5 /// <param name="subChannel"></param> 6 public void RedisSub(string subChannel) 7 { 8 sub.Subscribe(subChannel, (channel, message) => 9 { 10 Console.WriteLine((string)message); 11 }); 12 } 13 /// <summary> 14 /// Redis发布订阅 发布 15 /// </summary> 16 /// <typeparam name="T"></typeparam> 17 /// <param name="channel"></param> 18 /// <param name="msg"></param> 19 /// <returns></returns> 20 public long RedisPub<T>(string channel, T msg) 21 { 22 23 return sub.Publish(channel, SerializeContent(msg)); 24 } 25 /// <summary> 26 /// Redis发布订阅 取消订阅 27 /// </summary> 28 /// <param name="channel"></param> 29 public void Unsubscribe(string channel) 30 { 31 sub.Unsubscribe(channel); 32 } 33 /// <summary> 34 /// Redis发布订阅 取消全部订阅 35 /// </summary> 36 public void UnsubscribeAll() 37 { 38 sub.UnsubscribeAll(); 39 } 40 #endregion
四.redis服务器维护
通过多路复用,可以获取到数据库IDatabase api ,能够进行基本数据库操作。但像keys或scan方法是没有的,由于StackExchange.Redis的目标是针对集群等场景,因此了解哪些命令针对数据库(可以分布在多个节点上的逻辑数据库)以及哪些命令针对服务器非常重要。以下命令均以单个服务器为目标:
KEYS
/SCAN
FLUSHDB
/FLUSHALL
RANDOMKEY
CLIENT
CLUSTER
CONFIG
/INFO
/TIME
SLAVEOF
SAVE
/BGSAVE
/LASTSAVE
SCRIPT
(不要与EVAL
/ 混淆EVALSHA
)SHUTDOWN
SLOWLOG
PUBSUB
(不是发布订阅,不要混淆PUBLISH
/SUBSCRIBE
)
那么如何使用它们呢?,因该是从服务器而不是数据库开始。像KEYS
/ SCAN会扫描整个服务器整个key,所以应该尽量避免在生产服务器上使用,因为会适成服务器阻塞,一定要用也是针对slave从库。
如何操作指定的服务器呢,因为生产环境一般是集群的。可以使用conn.GetEndPoints()
列出端点.
ConnectionMultiplexer redis = ConnectionMultiplexer("localhost:6379,localhost:6380"); foreach (var endpoint in redis.GetEndPoints(false)) { Console.WriteLine(endpoint.ToString()); } //将输出 $127.0.0.1:6379 Unspecified/localhost:6379 Unspecified/localhost:6380 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384
GetEndPoints(false)将返回DNS名称和解析的IP地址的行. 调用GetServer()
以查找所需的服务器(例如,选择一个从属服务器)。
在阿里云redis产品中,使用该组件时,好像不能指定数据库序号。