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 ...
随机推荐
- NHibernate学习教程(6)--事务Transactions
本节内容 事务概述 1.新建对象 [测试成功提交] [测试失败回滚] 2.删除对象 3.更新对象 4.保存更新对象 结语 上一篇我们介绍了NHibernate中的Insert, Update, De ...
- 【Alpha】阶段 第六次 Scrum Meeting
每日任务 1.本次会议为第 六次 Meeting会议: 2.本次会议在上午09:35,大课间休息时间在陆大召开,召开本次会议为20分钟,讨论统一下时间安排的问题以及一些程序上的该进: 一.今日站立式会 ...
- 团队作业8——第二次项目冲刺(Beta阶段)--第四天
一.Daily Scrum Meeting照片 二.燃尽图 三.项目进展 学号 成员 贡献比 201421123001 廖婷婷 17% 201421123002 翁珊 18% 201421123004 ...
- (Alpha)个人总结
(Alpha)个人总结 一. 总结自己的alpha 过程 1.团队的整体情况 alpha阶段,整个团队很团结状态很好.在计划制定出来后,大家都服从安排,将该完成的工作都一一完成.虽然在课堂演示的时候又 ...
- 团队作业8——第二次项目冲刺(Beta阶段)(冲刺计划)
Beta阶段冲刺计划 Alpha冲刺暂时告一段落,项目现在也有个了大体框架,当然还是有很多漏洞,在接下来的Beta冲刺中尽量完善,希望最后能有一个好的结果. 新成员介绍 何跃斌:掌握java.c的基本 ...
- 201521123025《java程序设计》第8周学习总结
1. 本周学习总结 2.书面作业 Q1.List中指定元素的删除(题目4-1) public static List<String> convertStringToList(String ...
- 201521123031 《Java程序设计》第8周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 1.List中指定元素的删除(题目4-1) 1.1 实验总结 答:实现con ...
- 201521123081《java程序设计》 第7周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 参考资料:XMind 2. 书面作业 Q1. ArrayList代码分析 1.1 解释ArrayList的 contains ...
- 201521123031《Java程序设计》 第2周学习总结
1. 本周学习总结 (1)能够更加熟练地使用码云 (2)学习了Arrys和String的用法和一些运用 (3)懂得如何查询函数的源代码,通过查看源代码,能够更深入的了解函数适用情况以及利弊 2. 书面 ...
- 201521123079《java程序设计》第11周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) ...