设为首页 收藏本站
查看: 1659|回复: 0

[经验分享] 对ServiceStack.Redis的连接池进行故障转移改造

[复制链接]

尚未签到

发表于 2015-7-19 13:54:20 | 显示全部楼层 |阅读模式
  使用ServiceStack.Redis的连接池在操作多台Redis的时候并不会对出现故障的Redis进行排除切换,这样就会导致应用会还是会分配到故障的Redis服务中导致应用处理错误.这次对ServiceStack.Redis连接池的改造主要实现两个功能:1)对故障的Redis服务在轮循的时候排除,2)定期检测故障的Redis服务,如果服务正常则恢复到轮盾环节中.(ServiceStack.Redis的代码结构还是很不错修改起来也很方便)

增加基于Host的连接池功能
  ServiceStack.Redis连接池的连接存储结构相对简单,只是用一些简单的数组进行处理也没有明确按Host划分,所以修改起来比较麻烦.通过查看代码决定在RedisEndPoint添加一系列的功能.包括:连接获取,回收,有效性验测等功能.详细代码如下:



    public class RedisEndPoint : EndPoint
{
public RedisEndPoint(string host, int port) : base(host, port)
{
}
public RedisEndPoint(string host, int port, string password) : this(host,port)
{
this.Password = password;
}
public string Password { get; set; }
public bool RequiresAuth { get { return !string.IsNullOrEmpty(Password); } }
private System.Collections.Generic.Stack mStack = new System.Collections.Generic.Stack();
private bool mAvailable = true;
private int mLastDetectTime = 0;
private void PingHost(object state)
{
try
{
RedisClient client = Redis.RedisClientFactory.Instance.CreateRedisClient(Host, Port);
client.Password = Password;
client.EndPoint = this;
client.Ping();
Push(client);
mAvailable = true;
}
catch
{
}
mIsDetecting = false;
}
private bool mIsDetecting = false;
public bool Detect()
{
if (!mAvailable)
{
if (System.Math.Abs(System.Environment.TickCount - mLastDetectTime) >= 10000)
{
mLastDetectTime = System.Environment.TickCount;
if (!mIsDetecting)
{
mIsDetecting = true;
System.Threading.ThreadPool.QueueUserWorkItem(PingHost);
}

}
}
return mAvailable;
}
public RedisClient Pop()
{
lock (mStack)
{
if (mStack.Count > 0)
return mStack.Pop();
}
RedisClient client = Redis.RedisClientFactory.Instance.CreateRedisClient(Host, Port);
client.EndPoint = this;
client.Password = Password;
return client;
}
public void Push(RedisClient client)
{
lock (mStack)
{
if (!client.HadExceptions)
{
mStack.Push(client);
}
else
{
client.ClientManager = null;
client.Dispose();
while (mStack.Count > 0)
{
client = mStack.Pop();
client.ClientManager = null;
client.Dispose();
}
mAvailable = false;
}
}
}
}
  比较重要的功能主要是回收和检测,在连接回收的时候判断连接是否存在异常(从代码来看HadExceptions的设置是由SocketError引发的,因此可以判断当这个值为True的时候存在网络异常),如果是则把当前节点标识为不可用,并把池中的所有连接进行清除关闭.检测方法主要是每隔10秒对redis服务进行一个连接和ping操作,如果成功该节点恢复到有效状态.

修改PooledRedisClientManager
  为了让新连接池的代码生效,必须修改PooledRedisClientManager几个地方,主要是连接获了和连接回收几个方法的代码.
  GetInActiveWriteClient



///
/// Called within a lock
///
///
private RedisClient GetInActiveWriteClient()
{
var desiredIndex = WritePoolIndex % writeClients.Length;
//this will loop through all hosts in readClients once even though there are 2 for loops
//both loops are used to try to get the prefered host according to the round robin algorithm
for (int x = 0; x < ReadWriteHosts.Count; x++)
{
var nextHostIndex = (desiredIndex + x) % ReadWriteHosts.Count;
var nextHost = ReadWriteHosts[nextHostIndex];
if (nextHost.Detect())
{
RedisClient client = nextHost.Pop();
if (client != null)
{
if (nextHost.RequiresAuth)
client.Password = nextHost.Password;
client.Id = RedisClientCounter++;
client.ClientManager = this;
client.NamespacePrefix = NamespacePrefix;
client.ConnectionFilter = ConnectionFilter;
return client;
}
}
//for (var i = nextHostIndex; i < writeClients.Length; i += ReadWriteHosts.Count)
//{                    
//    if (writeClients != null && !writeClients.Active && !writeClients.HadExceptions)
//        return writeClients;
//    else if (writeClients == null || writeClients.HadExceptions)
//    {
//        if (writeClients != null)
//            writeClients.DisposeConnection();
//        var client = RedisClientFactory.CreateRedisClient(nextHost.Host, nextHost.Port);
//        if (nextHost.RequiresAuth)
//            client.Password = nextHost.Password;
//        client.Id = RedisClientCounter++;
//        client.ClientManager = this;
//        client.NamespacePrefix = NamespacePrefix;
//        client.ConnectionFilter = ConnectionFilter;
//        writeClients = client;
//        return client;
//    }
//}
            }
return null;
}
  把代码改成直接检测当明的Host是否有效,如果是则获取连接并返回,这里只修改的writerclient,类里面还有readclient的方法也相对应用进行修改.
   GetClient方法代码



        ///
/// Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts
///
///
public IRedisClient GetClient()
{
lock (writeClients)
{
AssertValidReadWritePool();
RedisClient inActiveClient;
inActiveClient = GetInActiveWriteClient();
if(inActiveClient == null)
throw new TimeoutException(PoolTimeoutError);
//while ((inActiveClient = GetInActiveWriteClient()) == null)
//{
//    if (PoolTimeOut.HasValue)
//    {
//        // wait for a connection, cry out if made to wait too long
//        if (!Monitor.Wait(writeClients, PoolTimeOut.Value))
//            throw new TimeoutException(PoolTimeoutError);
//    }
//    else
//        Monitor.Wait(writeClients);
//}

WritePoolIndex++;
inActiveClient.Active = true;
if (this.ConnectTimeout != null)
{
inActiveClient.ConnectTimeout = this.ConnectTimeout.Value;
}
if( this.SocketSendTimeout.HasValue )
{
inActiveClient.SendTimeout = this.SocketSendTimeout.Value;
}
if( this.SocketReceiveTimeout.HasValue )
{
inActiveClient.ReceiveTimeout = this.SocketReceiveTimeout.Value;
}
inActiveClient.NamespacePrefix = NamespacePrefix;
//Reset database to default if changed
if (inActiveClient.Db != Db)
{
inActiveClient.ChangeDb(Db);
}
return inActiveClient;
}
}
  DisposeClient方法代码



        public void DisposeClient(RedisNativeClient client)
{
if (client.EndPoint != null)
{
client.EndPoint.Push((RedisClient)client);
return;
}
//lock (readClients)
//{
//    for (var i = 0; i < readClients.Length; i++)
//    {
//        var readClient = readClients;
//        if (client != readClient) continue;
//        client.Active = false;
//        Monitor.PulseAll(readClients);
//        return;
//    }
//}
  通过以上简单的代码修改后ServiceStack.Redis的连接池就具备了故意迁移和恢复的功能:)

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-88325-1-1.html 上篇帖子: serviceStack.Redis 在PooledRedisClientManager 中设置密码 下篇帖子: 使用redis构造优先级队列
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表