在使用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实例。

  1. # -*- coding:utf-8 -*-
  2. import abc
  3. import uuid
  4. import time
  5. import logging
  6. import traceback
  7. import threading
  8. import redis
  9. from Podis import Podis
  10.  
  11. logger = logging.getLogger(__name__)
  12.  
  13. class CodisPool(object):
  14.  
  15. def __init__(self, codis_config):
  16. self._pool_shards = []
  17. self._availables = []
  18. self._connection = None
  19. self._create_pool(codis_config)
  20. self._check_available_backgroud()
  21.  
  22. def _check_available_backgroud(self):
  23. bg = Background(self._check_pool_shards)
  24. bg.start()
  25.  
  26. def _create_pool(self, codis_config):
  27. address_list = codis_config.get('addrs').split(',')
  28. for address in address_list:
  29. host = address.split(':')[0]
  30. port = address.split(':')[1]
  31. self._pool_shards.append(
  32. Podis(
  33. redis.ConnectionPool(
  34. host=host, port=port, db=0,
  35. password=codis_config.get('password'),
  36. max_connections=codis_config.get('max_connections')
  37. )
  38. )
  39. )
  40. self._availables.append(True)
  41. if len(self._pool_shards) == 0:
  42. logger.error('创建codis链接池失败')
  43. raise Exception('创建codis链接池失败')
  44.  
  45. def _check_pool_shards(self):
  46. while True:
  47. self._pool_shards_is_available()
  48.  
  49. def _pool_shards_is_available(self, retry_num=3):
  50. i = 0
  51. for pool in self._pool_shards:
  52. try:
  53. retry = retry_num
  54. go_on = True
  55. while go_on and retry > 0:
  56. try:
  57. pong = pool.ping()
  58. if not pong:
  59. retry -= 1
  60. else:
  61. go_on = False
  62. except Exception, ex:
  63. retry -= 1
  64. raise
  65. finally:
  66. time.sleep(1)
  67. if retry <= 0:
  68. self._availables[i] = False
  69. else:
  70. self._availables[i] = True
  71. except Exception, ex:
  72. logger.error(traceback.format_exc())
  73. finally:
  74. i += 1
  75.  
  76. def _get_available_shards(self):
  77. i = 0
  78. available_shards = []
  79. for shard in self._pool_shards:
  80. if self._availables[i]:
  81. available_shards.append(shard)
  82. i += 1
  83. return available_shards
  84.  
  85. def get_connection(self, pick_up=None):
  86. if isinstance(pick_up, PickUp):
  87. codisPool = pick_up.pick_up(self._get_available_shards())
  88. else:
  89. pick_up = RandomPickUp()
  90. codisPool = pick_up.pick_up(self._get_available_shards())
  91. return codisPool
  92.  
  93. def get_pool_shards(self):
  94. return self._pool_shards
  95.  
  96. def get_availables(self):
  97. return self._get_available_shards()
  98.  
  99. class Background(object):
  100. def __init__(self, target, daemon=True):
  101. self.daemon = daemon
  102. self.thread = threading.Thread(target=target)
  103.  
  104. def start(self):
  105. self.thread.setDaemon(self.daemon)
  106. self.thread.start()
  107.  
  108. class PickUp(object):
  109.  
  110. __metaclass__ = abc.ABCMeta
  111.  
  112. @abc.abstractmethod
  113. def __init__(self):
  114. pass
  115.  
  116. @abc.abstractmethod
  117. def pick_up(self, pool_list):
  118. return
  119.  
  120. class RandomPickUp(PickUp):
  121. def __init__(self):
  122. PickUp.__init__(self)
  123.  
  124. def pick_up(self, pool_list):
  125. pool_size = len(pool_list)
  126. index = abs(hash(uuid.uuid4())) % pool_size
  127. pool = pool_list[index]
  128. print "RandomPickUp, 拿到第", index, "个pool"
  129. return pool
  130.  
  131. class RoundRobinPickUp(PickUp):
  132.  
  133. def __init__(self):
  134. PickUp.__init__(self)
  135. self.index = 0
  136. self.round_robin_lock = threading.Lock()
  137.  
  138. def pick_up(self, pool_list):
  139. with self.round_robin_lock:
  140. pool_size = len(pool_list)
  141. self.index += 1
  142. index = abs(self.index) % pool_size
  143. pool = pool_list[index]
  144. print "RoundRobinPickUp, 拿到第", index, "个pool"
  145. return pool

2、Podis,实际的句柄资源

在CodisPool获得的句柄就是本类的一个实例。

  1. # -*- coding:utf-8 -*-
  2. import redis
  3. import logging
  4. import traceback
  5.  
  6. logger = logging.getLogger(__name__)
  7.  
  8. def redis_getter(func):
  9. def wrapper(*args, **kwargs):
  10. try:
  11. result = func(*args, **kwargs)
  12. return result or None
  13. except Exception, ex:
  14. logger.error(traceback.format_exc())
  15. raise
  16. return wrapper
  17.  
  18. def redis_setter(func):
  19. def wrapper(*args, **kwargs):
  20. try:
  21. func(*args, **kwargs)
  22. return True
  23. except Exception, ex:
  24. logger.error(traceback.format_exc())
  25. raise
  26. return wrapper
  27.  
  28. class Podis(object):
  29.  
  30. def __init__(self, pool):
  31. self._connection = redis.StrictRedis(connection_pool=pool)
  32.  
  33. @redis_getter
  34. def ping(self):
  35. return self._connection.ping()
  36.  
  37. @redis_getter
  38. def get(self, key):
  39. return self._connection.get(key)
  40.  
  41. @redis_setter
  42. def set(self, key, value):
  43. self._connection.set(key, value)
  44.  
  45. @redis_setter
  46. def lpush(self, key, *value):
  47. self._connection.lpush(key, *value)
  48.  
  49. @redis_getter
  50. def lpop(self, key):
  51. return self._connection.lpop(key)
  52.  
  53. @redis_getter
  54. def lrange(self, key, start, end):
  55. return self._connection.lrange(key, start, end)
  56.  
  57. @redis_setter
  58. def sadd(self, key, *value):
  59. self._connection.sadd(key, *value)
  60.  
  61. @redis_setter
  62. def srem(self, key, *value):
  63. self._connection.srem(key, *value)
  64.  
  65. @redis_getter
  66. def zrange(self,key,start,end):
  67. return self._connection.zrange(key,start,end)
  68.  
  69. @redis_getter
  70. def zrevrange(self,key,start,end):
  71. return self._connection.zrevrange(key,start,end,withscores=True)
  72.  
  73. @redis_getter
  74. def zscore(self,key,*value):
  75. return self._connection.zscore(key,value)
  76.  
  77. @redis_setter
  78. def zadd(self,key,score,*value):
  79. self._connection.zadd(key,score,value)
  80.  
  81. @redis_getter
  82. def smembers(self, key):
  83. return self._connection.smembers(key)
  84.  
  85. @redis_getter
  86. def hgetall(self, key):
  87. return self._connection.hgetall(key)
  88.  
  89. @redis_getter
  90. def hget(self, key, name):
  91. return self._connection.hget(key, name)
  92.  
  93. @redis_getter
  94. def hkeys(self, key):
  95. return self._connection.hkeys(key)
  96.  
  97. @redis_setter
  98. def hset(self, key, name, value):
  99. self._connection.hset(key, name, value)
  100.  
  101. @redis_setter
  102. def hmset(self, name, mapping):
  103. self._connection.hmset(name, mapping)
  104.  
  105. @redis_setter
  106. def hdel(self, key, name):
  107. self._connection.hdel(key, name)
  108.  
  109. @redis_setter
  110. def delete(self, *key):
  111. self._connection.delete(*key)
  112.  
  113. # codis不支持
  114. @redis_getter
  115. def keys(self, pattern):
  116. return self._connection.keys(pattern)
  117.  
  118. @redis_setter
  119. def expire(self, key, time):
  120. return self._connection.expire(key, time)
  121.  
  122. @redis_getter
  123. def ttl(self, key):
  124. return self._connection.ttl(key)

3、配置文件CodisConfig.py

这里我没有对最大连接数,超时时间等等做配置,你可以根据你的场景自行添加。

  1. codis_config = {
  2. 'addrs': '100.90.186.47:3000,100.90.187.33:3000'
  3. }

4、单测和使用

我们模拟在并发场景下,对资源的获得和释放情况。

  1. import time
  2. import threading
  3. from pycodis.CodisConfig import codis_config
  4. from pycodis.CodisPool import CodisPool, RoundRobinPickUp
  5.  
  6. codis_pool1 = CodisPool(codis_config)
  7. print '------1-------'
  8. pick_up1 = RoundRobinPickUp()
  9. print '------2-------'
  10. codis_pool2 = CodisPool(codis_config)
  11. print '------3-------'
  12. pick_up2 = RoundRobinPickUp()
  13. print '------4-------'
  14.  
  15. def func(i):
  16. for i in range(10):
  17. podis1 = codis_pool1.get_connection(pick_up=pick_up1)
  18. podis2 = codis_pool2.get_connection(pick_up=pick_up2)
  19. podis1.delete(i)
  20. podis2.delete(i)
  21. time.sleep(1)
  22.  
  23. thread_list = []
  24. for i in range(100):
  25. thread_list.append(threading.Thread(target=func, args=[i]))
  26.  
  27. for thread in thread_list:
  28. thread.setDaemon(True)
  29. thread.start()
  30.  
  31. time.sleep(10)

可以看到打印的信息,每次都不一样

  1. -------------
  2. -------------
  3. -------------
  4. -------------
  5. RoundRobinPickUp, 拿到第 pool
  6. RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 pool
  7. pool
  8. RoundRobinPickUp, 拿到第 poolRoundRobinPickUp, 拿到第
  9. pool
  10. RoundRobinPickUp, 拿到第 1RoundRobinPickUp, 拿到第 pool
  11. pool
  12. RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第0 pool
  13. pool
  14. RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 pool
  15. RoundRobinPickUp, 拿到第个pool
  16. 1RoundRobinPickUp, 拿到第 pool
  17. RoundRobinPickUp, 拿到第 poolpool
  18.  
  19. RoundRobinPickUp, 拿到第 1RoundRobinPickUp, 拿到第 pool
  20. poolRoundRobinPickUp, 拿到第
  21. 0RoundRobinPickUp, 拿到第 pool
  22. RoundRobinPickUp, 拿到第 pool
  23. pool
  24. RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 pool
  25. pool
  26. RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 poolpool
  27.  
  28. RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 pool
  29. RoundRobinPickUp, 拿到第 pool
  30. RoundRobinPickUp, 拿到第 pool1
  31. RoundRobinPickUp, 拿到第个pool
  32. poolRoundRobinPickUp, 拿到第
  33. RoundRobinPickUp, 拿到第 0pool
  34. RoundRobinPickUp, 拿到第 pool1
  35. poolRoundRobinPickUp, 拿到第
  36. RoundRobinPickUp, 拿到第 pool pool
  37.  
  38. RoundRobinPickUp, 拿到第 0RoundRobinPickUp, 拿到第 pool
  39. poolRoundRobinPickUp, 拿到第 pool
  40.  
  41. RoundRobinPickUp, 拿到第 RoundRobinPickUp, 拿到第 pool
  42. pool
  43. RoundRobinPickUp, 拿到第RoundRobinPickUp, 拿到第 pool
  44. poolRoundRobinPickUp, 拿到第
  45. pool
  46. RoundRobinPickUp, 拿到第RoundRobinPickUp, 拿到第 pool
  47. RoundRobinPickUp, 拿到第 poolpool

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. NHibernate学习教程(6)--事务Transactions

    本节内容 事务概述 1.新建对象 [测试成功提交] [测试失败回滚] 2.删除对象 3.更新对象 4.保存更新对象 结语 上一篇我们介绍了NHibernate中的Insert, Update,  De ...

  2. 【Alpha】阶段 第六次 Scrum Meeting

    每日任务 1.本次会议为第 六次 Meeting会议: 2.本次会议在上午09:35,大课间休息时间在陆大召开,召开本次会议为20分钟,讨论统一下时间安排的问题以及一些程序上的该进: 一.今日站立式会 ...

  3. 团队作业8——第二次项目冲刺(Beta阶段)--第四天

    一.Daily Scrum Meeting照片 二.燃尽图 三.项目进展 学号 成员 贡献比 201421123001 廖婷婷 17% 201421123002 翁珊 18% 201421123004 ...

  4. (Alpha)个人总结

    (Alpha)个人总结 一. 总结自己的alpha 过程 1.团队的整体情况 alpha阶段,整个团队很团结状态很好.在计划制定出来后,大家都服从安排,将该完成的工作都一一完成.虽然在课堂演示的时候又 ...

  5. 团队作业8——第二次项目冲刺(Beta阶段)(冲刺计划)

    Beta阶段冲刺计划 Alpha冲刺暂时告一段落,项目现在也有个了大体框架,当然还是有很多漏洞,在接下来的Beta冲刺中尽量完善,希望最后能有一个好的结果. 新成员介绍 何跃斌:掌握java.c的基本 ...

  6. 201521123025《java程序设计》第8周学习总结

    1. 本周学习总结 2.书面作业 Q1.List中指定元素的删除(题目4-1) public static List<String> convertStringToList(String ...

  7. 201521123031 《Java程序设计》第8周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 1.List中指定元素的删除(题目4-1) 1.1 实验总结 答:实现con ...

  8. 201521123081《java程序设计》 第7周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 参考资料:XMind 2. 书面作业 Q1. ArrayList代码分析 1.1 解释ArrayList的 contains ...

  9. 201521123031《Java程序设计》 第2周学习总结

    1. 本周学习总结 (1)能够更加熟练地使用码云 (2)学习了Arrys和String的用法和一些运用 (3)懂得如何查询函数的源代码,通过查看源代码,能够更深入的了解函数适用情况以及利弊 2. 书面 ...

  10. 201521123079《java程序设计》第11周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) ...