Поддержание открытого соединения Redis с помощью BookSleeve
У кого-нибудь есть солидный шаблон, извлекающий Redis через библиотеку BookSleeve?
Я имею в виду:
Автор BookSleeve @MarcGravell рекомендует не открывать и не закрывать соединение каждый раз, а поддерживать одно соединение во всем приложении. Но как вы можете справиться с перебоями в сети? т.е. соединение может быть успешно открыто в первую очередь, но когда некоторый код пытается прочитать / записать в Redis, есть вероятность, что соединение было разорвано, и вы должны снова открыть его (и изящно потерпеть неудачу, если оно не откроется - но это зависит от ваших потребностей дизайна.)
Я ищу фрагменты кода, которые охватывают общее открытие соединения Redis, и общую проверку "вживую" (+ дополнительный пробуждение, если не активен), которая будет использоваться перед каждым чтением / записью.
Этот вопрос предполагает хорошее отношение к проблеме, но он только частичный (например, он не восстанавливает потерянное соединение), и принятый ответ на этот вопрос рисует правильный путь, но не демонстрирует конкретный код.
Я надеюсь, что эта ветка получит исчерпывающие ответы и в конечном итоге станет своего рода Wiki в отношении использования BookSleeve в приложениях.Net.
-----------------------------
ВАЖНОЕ ОБНОВЛЕНИЕ (21/3/2014):
-----------------------------
Марк Грэвелл (@MarcGravell) / Stack Exchange недавно выпустил библиотеку StackExchange.Redis, которая в конечном итоге заменит Booksleeve. Эта новая библиотека, помимо прочего, внутренне обрабатывает переподключения и делает мой вопрос излишним (то есть, это не излишне ни для Booksleeve, ни для моего ответа ниже, но я думаю, что лучший способ продвинуться вперед - это начать использовать новую библиотеку StackExchange.Redis).
3 ответа
Поскольку у меня нет хороших ответов, я пришел к этому решению (кстати, спасибо @Simon и @Alex за ваши ответы!).
Я хочу поделиться этим со всем сообществом в качестве ссылки. Конечно, любые исправления будут высоко оценены.
using System;
using System.Net.Sockets;
using BookSleeve;
namespace Redis
{
public sealed class RedisConnectionGateway
{
private const string RedisConnectionFailed = "Redis connection failed.";
private RedisConnection _connection;
private static volatile RedisConnectionGateway _instance;
private static object syncLock = new object();
private static object syncConnectionLock = new object();
public static RedisConnectionGateway Current
{
get
{
if (_instance == null)
{
lock (syncLock)
{
if (_instance == null)
{
_instance = new RedisConnectionGateway();
}
}
}
return _instance;
}
}
private RedisConnectionGateway()
{
_connection = getNewConnection();
}
private static RedisConnection getNewConnection()
{
return new RedisConnection("127.0.0.1" /* change with config value of course */, syncTimeout: 5000, ioTimeout: 5000);
}
public RedisConnection GetConnection()
{
lock (syncConnectionLock)
{
if (_connection == null)
_connection = getNewConnection();
if (_connection.State == RedisConnectionBase.ConnectionState.Opening)
return _connection;
if (_connection.State == RedisConnectionBase.ConnectionState.Closing || _connection.State == RedisConnectionBase.ConnectionState.Closed)
{
try
{
_connection = getNewConnection();
}
catch (Exception ex)
{
throw new Exception(RedisConnectionFailed, ex);
}
}
if (_connection.State == RedisConnectionBase.ConnectionState.Shiny)
{
try
{
var openAsync = _connection.Open();
_connection.Wait(openAsync);
}
catch (SocketException ex)
{
throw new Exception(RedisConnectionFailed, ex);
}
}
return _connection;
}
}
}
}
Я не программист на C#, но я смотрю на проблему следующим образом:
Я бы написал обобщенную функцию, которая бы принимала в качестве параметров соединение redis и лямбда-выражение, представляющее команду Redis
если попытка выполнить команду Redis приведет к исключению, указывающему на проблему с подключением, я повторно инициализирую подключение и повторю операцию
если исключение не возникло, просто верните результат
Вот какой-то псевдокод:
function execute(redis_con, lambda_func) {
try {
return lambda_func(redis_con)
}
catch(connection_exception) {
redis_con = reconnect()
return lambda_func(redis_con)
}
}
В других системах (таких как ADO.NET) это достигается с помощью пула соединений. Вы никогда не получите новый объект Connection, но на самом деле получите его из пула.
Сам пул управляет новыми и мертвыми соединениями независимо от кода вызывающего. Идея здесь в том, чтобы иметь лучшую производительность (установка нового соединения обходится дорого) и пережить проблемы с сетью (код вызывающего абонента потерпит неудачу, когда сервер не работает, но возобновит работу, когда он снова подключится). На самом деле существует один пул на AppDomain на "тип" соединения.
Такое поведение проявляется, когда вы смотрите на строки подключения ADO.NET. Например, строка подключения SQL Server ( свойство ConnectionString) имеет понятия "Пул", "Максимальный размер пула", "Минимальный размер пула" и т. Д. Это также метод ClearAllPools, который используется для программного сброса текущих пулов AppDomain при необходимости. например.
Я не вижу ничего похожего на такую функцию, которая просматривает код BookSleeve, но, похоже, планируется следующий выпуск: BookSleeve RoadMap.
В то же время, я полагаю, вы можете написать свой собственный пул соединений, так как RedisConnection имеет событие ошибки, которое вы можете использовать для этого, чтобы определить, когда он мертв.