python codis集群客户端(一) - 基于客户端daemon探活与服务列表维护
在使用codis时候,我们遇到的场景是,公司提供了HA的Proxy(例如N个),但是不暴露zookeeper(也就是说没有codis后端服务列表)。
如果暴露zk的话,可以看这一篇,http://www.cnblogs.com/kangoroo/p/7485760.html
要求在开发客户端api的过程中,自己进行探活&故障摘除&负载均衡。
我这里做了一个简单的实现,提供给大家参考。本实例支持使用在server或者daemon中。
我们的实现叫做pycodis。

1、核心文件CodisPool.py
在这个文件里,实现了多服务端实例链接,探活与故障摘除,负载均衡策略(随机&roundrobin)。
1)探活
由于公司不暴露CodisProxy的zookeeper,我们在编写客户端程序的时候无法获取活跃的codisProxy服务列表。我们不得不自己去探活并且保存活跃的服务列表。
我们通过在初始化CodisPool实例时候,启动了后台线程_check_available_backgroud,去轮询配置的codisProxy列表,我们的探活与摘除满足以下3个要求:
a)如果在一定的阈值时间段内(默认3s),某个codisProxy始终无法提供服务,就暂时将它从codisProxy列表中摘除;
b)被摘除的codisProxy实例无法通过get_connection得到,但是在codisProxy列表中保留它,让它可以在满足条件的情况下复起;
c)当被摘除的codisProxy恢复了,就把它放到可用codisProxy列表中,这样通过get_connetion又能拿到它。
注:这里通过get_connection拿到的proxy,其实现方式是redis链接池。
2)负载均衡
我们通过get_connection这个函数在proxy列表中得到一个可用链接,那么获取可用链接的负载均衡算法是怎样的呢?
PickUp抽象类,定义了负载均衡类需要实现的方法,pick_up()。
我们实现了两种负载均衡算法:
a)RandomPickUp,随机负载均衡
b)RoundRobinPickUp,轮询负载均衡
3)单例模式的保证
为了不至于在程序运行时创建很多的链接池实例,这是违反设计初衷的,链接池就没有意义了,我们需要实现为线程安全的单例模式。
在CodisPool实现中只保证了不会额外建立太多的链接池--我们在__init__中一次性创建好了Podis实例,并且放入到proxy列表中。单例模式留待使用中去实现,最简单的方式是,在python的module中先把CodisPool的实例建立好。然后其他的线程在去访问。可以参考我们的example实例。
# -*- coding:utf-8 -*-
import abc
import uuid
import time
import logging
import traceback
import threading
import redis
from Podis import Podis logger = logging.getLogger(__name__) class CodisPool(object): def __init__(self, codis_config):
self._pool_shards = []
self._availables = []
self._connection = None
self._create_pool(codis_config)
self._check_available_backgroud() def _check_available_backgroud(self):
bg = Background(self._check_pool_shards)
bg.start() def _create_pool(self, codis_config):
address_list = codis_config.get('addrs').split(',')
for address in address_list:
host = address.split(':')[0]
port = address.split(':')[1]
self._pool_shards.append(
Podis(
redis.ConnectionPool(
host=host, port=port, db=0,
password=codis_config.get('password'),
max_connections=codis_config.get('max_connections')
)
)
)
self._availables.append(True)
if len(self._pool_shards) == 0:
logger.error('创建codis链接池失败')
raise Exception('创建codis链接池失败') def _check_pool_shards(self):
while True:
self._pool_shards_is_available() def _pool_shards_is_available(self, retry_num=3):
i = 0
for pool in self._pool_shards:
try:
retry = retry_num
go_on = True
while go_on and retry > 0:
try:
pong = pool.ping()
if not pong:
retry -= 1
else:
go_on = False
except Exception, ex:
retry -= 1
raise
finally:
time.sleep(1)
if retry <= 0:
self._availables[i] = False
else:
self._availables[i] = True
except Exception, ex:
logger.error(traceback.format_exc())
finally:
i += 1 def _get_available_shards(self):
i = 0
available_shards = []
for shard in self._pool_shards:
if self._availables[i]:
available_shards.append(shard)
i += 1
return available_shards def get_connection(self, pick_up=None):
if isinstance(pick_up, PickUp):
codisPool = pick_up.pick_up(self._get_available_shards())
else:
pick_up = RandomPickUp()
codisPool = pick_up.pick_up(self._get_available_shards())
return codisPool def get_pool_shards(self):
return self._pool_shards def get_availables(self):
return self._get_available_shards() class Background(object):
def __init__(self, target, daemon=True):
self.daemon = daemon
self.thread = threading.Thread(target=target) def start(self):
self.thread.setDaemon(self.daemon)
self.thread.start() class PickUp(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod
def __init__(self):
pass @abc.abstractmethod
def pick_up(self, pool_list):
return class RandomPickUp(PickUp):
def __init__(self):
PickUp.__init__(self) def pick_up(self, pool_list):
pool_size = len(pool_list)
index = abs(hash(uuid.uuid4())) % pool_size
pool = pool_list[index]
print "RandomPickUp, 拿到第", index, "个pool"
return pool class RoundRobinPickUp(PickUp): def __init__(self):
PickUp.__init__(self)
self.index = 0
self.round_robin_lock = threading.Lock() def pick_up(self, pool_list):
with self.round_robin_lock:
pool_size = len(pool_list)
self.index += 1
index = abs(self.index) % pool_size
pool = pool_list[index]
print "RoundRobinPickUp, 拿到第", index, "个pool"
return pool
2、Podis,实际的句柄资源
在CodisPool获得的句柄就是本类的一个实例。
# -*- coding:utf-8 -*-
import redis
import logging
import traceback logger = logging.getLogger(__name__) def redis_getter(func):
def wrapper(*args, **kwargs):
try:
result = func(*args, **kwargs)
return result or None
except Exception, ex:
logger.error(traceback.format_exc())
raise
return wrapper def redis_setter(func):
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
return True
except Exception, ex:
logger.error(traceback.format_exc())
raise
return wrapper class Podis(object): def __init__(self, pool):
self._connection = redis.StrictRedis(connection_pool=pool) @redis_getter
def ping(self):
return self._connection.ping() @redis_getter
def get(self, key):
return self._connection.get(key) @redis_setter
def set(self, key, value):
self._connection.set(key, value) @redis_setter
def lpush(self, key, *value):
self._connection.lpush(key, *value) @redis_getter
def lpop(self, key):
return self._connection.lpop(key) @redis_getter
def lrange(self, key, start, end):
return self._connection.lrange(key, start, end) @redis_setter
def sadd(self, key, *value):
self._connection.sadd(key, *value) @redis_setter
def srem(self, key, *value):
self._connection.srem(key, *value) @redis_getter
def zrange(self,key,start,end):
return self._connection.zrange(key,start,end) @redis_getter
def zrevrange(self,key,start,end):
return self._connection.zrevrange(key,start,end,withscores=True) @redis_getter
def zscore(self,key,*value):
return self._connection.zscore(key,value) @redis_setter
def zadd(self,key,score,*value):
self._connection.zadd(key,score,value) @redis_getter
def smembers(self, key):
return self._connection.smembers(key) @redis_getter
def hgetall(self, key):
return self._connection.hgetall(key) @redis_getter
def hget(self, key, name):
return self._connection.hget(key, name) @redis_getter
def hkeys(self, key):
return self._connection.hkeys(key) @redis_setter
def hset(self, key, name, value):
self._connection.hset(key, name, value) @redis_setter
def hmset(self, name, mapping):
self._connection.hmset(name, mapping) @redis_setter
def hdel(self, key, name):
self._connection.hdel(key, name) @redis_setter
def delete(self, *key):
self._connection.delete(*key) # codis不支持
@redis_getter
def keys(self, pattern):
return self._connection.keys(pattern) @redis_setter
def expire(self, key, time):
return self._connection.expire(key, time) @redis_getter
def ttl(self, key):
return self._connection.ttl(key)
3、配置文件CodisConfig.py
这里我没有对最大连接数,超时时间等等做配置,你可以根据你的场景自行添加。
codis_config = {
'addrs': '100.90.186.47:3000,100.90.187.33:3000'
}
4、单测和使用
我们模拟在并发场景下,对资源的获得和释放情况。
import time
import threading
from pycodis.CodisConfig import codis_config
from pycodis.CodisPool import CodisPool, RoundRobinPickUp codis_pool1 = CodisPool(codis_config)
print '------1-------'
pick_up1 = RoundRobinPickUp()
print '------2-------'
codis_pool2 = CodisPool(codis_config)
print '------3-------'
pick_up2 = RoundRobinPickUp()
print '------4-------' def func(i):
for i in range(10):
podis1 = codis_pool1.get_connection(pick_up=pick_up1)
podis2 = codis_pool2.get_connection(pick_up=pick_up2)
podis1.delete(i)
podis2.delete(i)
time.sleep(1) thread_list = []
for i in range(100):
thread_list.append(threading.Thread(target=func, args=[i])) for thread in thread_list:
thread.setDaemon(True)
thread.start() time.sleep(10)
可以看到打印的信息,每次都不一样
-------------
-------------
-------------
-------------
RoundRobinPickUp, 拿到第 个pool
RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 个pool
个pool
RoundRobinPickUp, 拿到第 个poolRoundRobinPickUp, 拿到第
个pool
RoundRobinPickUp, 拿到第 1RoundRobinPickUp, 拿到第 个pool
个pool
RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第0 个pool
个pool
RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 个pool
RoundRobinPickUp, 拿到第个pool
1RoundRobinPickUp, 拿到第 个pool
RoundRobinPickUp, 拿到第 个pool个pool RoundRobinPickUp, 拿到第 1RoundRobinPickUp, 拿到第 个pool
个poolRoundRobinPickUp, 拿到第
0RoundRobinPickUp, 拿到第 个pool
RoundRobinPickUp, 拿到第 个pool
个pool
RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 个pool
个pool
RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 个pool个pool RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 个pool
RoundRobinPickUp, 拿到第 个pool
RoundRobinPickUp, 拿到第 个pool1
RoundRobinPickUp, 拿到第个pool
个poolRoundRobinPickUp, 拿到第
RoundRobinPickUp, 拿到第 0个pool
RoundRobinPickUp, 拿到第 个pool1
个poolRoundRobinPickUp, 拿到第
RoundRobinPickUp, 拿到第 个pool 个pool RoundRobinPickUp, 拿到第 0RoundRobinPickUp, 拿到第 个pool
个poolRoundRobinPickUp, 拿到第 个pool RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 个pool
个pool
RoundRobinPickUp, 拿到第RoundRobinPickUp, 拿到第 个pool
个poolRoundRobinPickUp, 拿到第
个pool
RoundRobinPickUp, 拿到第RoundRobinPickUp, 拿到第 个pool
RoundRobinPickUp, 拿到第 个pool个pool
python codis集群客户端(一) - 基于客户端daemon探活与服务列表维护的更多相关文章
- python codis集群客户端(二) - 基于zookeeper对实例创建与摘除
在这一篇中我们实现了不通过zk来编写codis集群proxys的api,http://www.cnblogs.com/kangoroo/p/7481567.html 如果codis集群暴露zk给你的话 ...
- 『集群』004 Slithice 集群分布式(多个客户端,基于中央服务器的集群服务)
Slithice 集群分布式(多个客户端,基于中央服务器的多个集群服务端) 案例Demo展示: 集群架构图 如下: 如上图,上图 展示了 这个集群 的 结构: >一个中央服务器(可以有多个),负 ...
- Redis Cluster集群搭建后,客户端的连接研究(Spring/Jedis)(待实践)
说明:无论是否已经搭建好集群,还是使用什么样的客户端去连接,都是必须把全部IP列表集成进去,然后随机往其中一个IP写. 这样做的好处: 1.随机IP写入之后,Redis Cluster代理层会自动根据 ...
- 本文介绍如何使用 Docker Swarm 来部署 Nebula Graph 集群,并部署客户端负载均衡和高可用
本文作者系:视野金服工程师 | 吴海胜 首发于 Nebula Graph 论坛:https://discuss.nebula-graph.com.cn/t/topic/1388 一.前言 本文介绍如何 ...
- 实战Centos系统部署Codis集群服务
导读 Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别 (不支持的命令列表), 上层应用可 ...
- Codis集群的搭建与使用
一.简介 Codis是一个分布式的Redis解决方案,对于上层的应用来说,连接Codis Proxy和连接原生的Redis Server没有明显的区别(不支持的命令列表),上层应用可以像使用单机的Re ...
- Codis集群的搭建
Codis集群的搭建与使用 一.简介 Codis是一个分布式的Redis解决方案,对于上层的应用来说,连接Codis Proxy和连接原生的Redis Server没有明显的区别(不支持的命令列表 ...
- [Big Data - Codis] Codis集群的搭建与使用
一.简介 Codis是一个分布式的Redis解决方案,对于上层的应用来说,连接Codis Proxy和连接原生的Redis Server没有明显的区别(不支持的命令列表),上层应用可以像使用单机的Re ...
- Codis 集群搭建
Codis 集群搭建 1 安装go1.3.1 CentOS 7.0 安装go 1.3.1 1.1 下载go安装包 golang中国上下载 下载到Downloads下 1.2 解压 tar -zxf g ...
随机推荐
- Web云笔记--CSS
CSS CSS CSS Web自学第二阶段之CSS 参考资料:<Head First HTML&CSS>(中文第二版)(美国)弗里昂ISBN:9787508356464 中国电力出 ...
- 个人作业3——个人总结(Alphe)
小结: 1.软件工程的第一阶段终于结束了,说实话,每个人的课程都很紧张,在这么紧张的时期我们都每周抽出一些时间来开个小会总结或者计划软件工程的相关任何非常难得,大家的态度都诚恳认真,我亦是如此,只是我 ...
- 201521123107 《Java程序设计》第1周学习总结
第1周学习总结 1.本周学习总结 本周我们正式开始了对一门新的编程语言java的学习.本周的主要内容是初步了解了java的发展过程,java具有简约且简单 .平台无关性等优点.java的3个关键的工具 ...
- 201521123093 java 第二周学习总结
201521123093 <java程序设计> 第二周学习总结 一.第二周学习总结 答:(1)关于进一步使用码云管理代码,本周才真正学会了如何将Eclipse里的代码上传到码云中,并且能够 ...
- 201521123042《Java程序设计》第13周学习总结
本次作业参考文件 正则表达式参考资料 1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.bai ...
- java :equals()和hashcode()方法的结合使用
哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: 1 public native int hashCode(); 根据 ...
- 更自然的解决字典数组插入nil而导致crash
最近在优化项目虽说小优化一直在持续,大版本的优化也进行了两个版本了但是bug列表依旧血淋淋的摆在那里.有的看一眼也能找到问题所在但是有的就是想破头也不知道问题在哪里,毕竟整个项目经过了N个人的手代码风 ...
- JDBC操作数据库之批处理
JDBC开发中,操作数据库需要和数据库建立连接,然后将要执行的SQL语句发送到数据库服务器,最后关闭数据库连接,都是按照这样的操做的,如果按照此流程要执行多条SQL语句,那么就要建立多个数据库连接,将 ...
- CSDN博客新手使用方案
CSDN博客简易使用 在CSDN上写博客,总是遇到很多问题,虽然这些问题很简单,但是对于新手来说,缺经常遇到,因此写篇博客记载. 一.CSDN的博客如何上传图片 如果有现成 ...
- Matlab入门学习(文件读写)
一.save,load >> a=[ ]; >> b=[ ] b = >> save('b.mat','a','b');%a file named b.mat wi ...