在使用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探活与服务列表维护的更多相关文章

  1. python codis集群客户端(二) - 基于zookeeper对实例创建与摘除

    在这一篇中我们实现了不通过zk来编写codis集群proxys的api,http://www.cnblogs.com/kangoroo/p/7481567.html 如果codis集群暴露zk给你的话 ...

  2. 『集群』004 Slithice 集群分布式(多个客户端,基于中央服务器的集群服务)

    Slithice 集群分布式(多个客户端,基于中央服务器的多个集群服务端) 案例Demo展示: 集群架构图 如下: 如上图,上图 展示了 这个集群 的 结构: >一个中央服务器(可以有多个),负 ...

  3. Redis Cluster集群搭建后,客户端的连接研究(Spring/Jedis)(待实践)

    说明:无论是否已经搭建好集群,还是使用什么样的客户端去连接,都是必须把全部IP列表集成进去,然后随机往其中一个IP写. 这样做的好处: 1.随机IP写入之后,Redis Cluster代理层会自动根据 ...

  4. 本文介绍如何使用 Docker Swarm 来部署 Nebula Graph 集群,并部署客户端负载均衡和高可用

    本文作者系:视野金服工程师 | 吴海胜 首发于 Nebula Graph 论坛:https://discuss.nebula-graph.com.cn/t/topic/1388 一.前言 本文介绍如何 ...

  5. 实战Centos系统部署Codis集群服务

    导读 Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别 (不支持的命令列表), 上层应用可 ...

  6. Codis集群的搭建与使用

    一.简介 Codis是一个分布式的Redis解决方案,对于上层的应用来说,连接Codis Proxy和连接原生的Redis Server没有明显的区别(不支持的命令列表),上层应用可以像使用单机的Re ...

  7. Codis集群的搭建

    Codis集群的搭建与使用   一.简介 Codis是一个分布式的Redis解决方案,对于上层的应用来说,连接Codis Proxy和连接原生的Redis Server没有明显的区别(不支持的命令列表 ...

  8. [Big Data - Codis] Codis集群的搭建与使用

    一.简介 Codis是一个分布式的Redis解决方案,对于上层的应用来说,连接Codis Proxy和连接原生的Redis Server没有明显的区别(不支持的命令列表),上层应用可以像使用单机的Re ...

  9. Codis 集群搭建

    Codis 集群搭建 1 安装go1.3.1 CentOS 7.0 安装go 1.3.1 1.1 下载go安装包 golang中国上下载 下载到Downloads下 1.2 解压 tar -zxf g ...

随机推荐

  1. [Vue安装教程]十分钟学会vue 安装

    Vue的安装主要有一下几个步骤: 1.安装npm淘宝镜像 npm install -g cnpm --registry=https://registry.npm.taobao.org 2.安装脚手架工 ...

  2. 附录:MySQL忘记root密码

    中小型规模网站集群架构:MySQL忘记root密码 : 矮哥linux运维群:93324526 前言 你忘记系统root密码的时候,你怎么解决的? 不就是single用户进行修改密码吗?这里原理是类似 ...

  3. CentOS 6.5安装部署Zabbix监控系统

    CentOS 6.5安装部署Zabbix监控系统 先说一点废话,我没有用centos7做实验,讲真,centos 7我也不常用,喜欢新版本的同学其实可以尝试下,注意一点的就是centos 6.5只支持 ...

  4. 个人作业3——个人总结(Alpha阶段)

    个人总结 Alpha阶段总结: 起初关于手机app的开发真的一无所知,选了一条较远的路走(使用 Android Studio 来开发 Android 应用更加方便,而我们选用 Eclipse 开发 A ...

  5. 【Beta阶段】测试与发布

    [Beta阶段]测试报告 一.Bug记录 1. 已经修复的BUG:文件查重的小组的空指针处理了 . 2.未能修复的bug: (1).在进行查重的时候必要要有10个文件,不然会报错:        (2 ...

  6. 201521123121 《Java程序设计》第4周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 对象的封装:将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现 ...

  7. Java中的基本数据类型和基本数据类型之间的转换

    在Java中有8中基本数据类型,分别为: 整型: byte.short.int.long 浮点型:float.double 布尔型:boolean 字符型:char. byte:    8位,  封装 ...

  8. Java-错误处理机制学习(一)异常处理

    注意:本文介绍Java中的异常处理理论知识及相关语法结构,对于实际应用来说是万万不够的.关于如何高效地使用异常,请查看Java-高效地使用Exception-实践. 异常处理的思想是,当应用程序处于异 ...

  9. 《JAVA程序设计》第9周学习总结

    1. 本章学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2. 书面作业 1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写的代码中经常出现 ...

  10. 201521123015 《Java程序设计》第10周学习总结

    1. 本章学习总结 你对于本章知识的学习总结 2. 书面作业 一.inally 题目4-2 1.1 截图你的提交结果(出现学号) 1.2 4-2中finally中捕获异常需要注意什么? 答: 4-2中 ...