经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
Redis解决网络抖动问题
来源:cnblogs  作者:huxiaofeng  时间:2023/7/24 8:53:42  对本文有异议

Redis解决网络抖动问题

所谓网络抖动问题, 简单来说就是防止用户短暂的时间内对同一个接口多次点击访问

这里利用的是redis锁的原子性和with Statement上下文管理器实现, 另外该类还支持协程, 可使用async with 调用

1. 源码

FuncDefine.py

  1. def clear_all_lock(PREFIX='lock'):
  2. keys = redis_operator.get_redis_keys_pattern(PREFIX + '*')
  3. for key in keys:
  4. if isinstance(key, bytes):
  5. kwl_py_write_log(key.decode(encoding='utf-8'), msgid='del_redis_key')
  6. redis_operator.delete_redis_key(key.decode(encoding='utf-8'), 1)
  7. def unlock(key):
  8. redis_operator.delete_redis_key(key, 1)
  9. class RedisLock:
  10. DEFAULT_VALUE = 1
  11. PREFIX = 'lock'
  12. def __init__(self, key, lock_time=300):
  13. """
  14. 初始化redis锁
  15. :param key: 关键字
  16. :param lock_time: 上锁时间 5min
  17. """
  18. self._key = RedisLock.PREFIX + key
  19. self.lock_time = lock_time
  20. self.hold_lock = False
  21. @property
  22. def key(self):
  23. return self._key
  24. @key.setter
  25. def key(self, key):
  26. self._key = RedisLock.PREFIX + key
  27. def __enter__(self):
  28. self.hold_lock = self.acquire()
  29. return self
  30. def __exit__(self, exc_type, exc_val, exc_tb):
  31. if self.hold_lock:
  32. self.release()
  33. return False
  34. async def __aenter__(self):
  35. self.hold_lock = await self.acquire_cas_lock()
  36. return self
  37. async def __aexit__(self, exc_type, exc_val, exc_tb):
  38. if self.hold_lock:
  39. self.release()
  40. return False
  41. async def acquire_cas_lock(self, lock_time=60):
  42. try:
  43. return await asyncio.wait_for(self.acquire_lock_until_succ(), lock_time)
  44. except asyncio.TimeoutError as e:
  45. return False
  46. async def acquire_lock_until_succ(self):
  47. while redis_operator.set_redis_key_ex_nx(self.key, self.DEFAULT_VALUE, self.lock_time) is not True:
  48. # redis return is None or other
  49. await asyncio.sleep(0.01)
  50. return True
  51. def acquire(self):
  52. """
  53. 设置redis锁
  54. :param key:
  55. :param lock_time:
  56. :return:
  57. """
  58. try:
  59. return redis_operator.set_redis_key_ex_nx(self.key, self.DEFAULT_VALUE, self.lock_time) is True
  60. except Exception:
  61. return False
  62. def release(self):
  63. redis_operator.delete_redis_key(self.key, 1)

redis_operator.py

  1. import redis
  2. from redisConfig import *
  3. # ------------------------------------------------------------------------------------------------------
  4. # 主从redis,个数一一对应
  5. g_main_redis_pool_list = []
  6. g_slave_redis_pool_list = []
  7. g_main_redis_is_ok = [] # 主redis是否可用True为主ok
  8. g_slave_redis_is_ok = [] # 从redis是否可用
  9. for each_redis in g_main_redis_server:
  10. redis_pool = redis.ConnectionPool(host=each_redis[0], port=each_redis[1], password=each_redis[2], socket_timeout=8,
  11. socket_connect_timeout=5)
  12. g_main_redis_pool_list.append(redis_pool)
  13. g_main_redis_is_ok.append(True)
  14. for each_redis in g_slave_redis_server:
  15. redis_pool = redis.ConnectionPool(host=each_redis[0], port=each_redis[1], password=each_redis[2], socket_timeout=8,
  16. socket_connect_timeout=5)
  17. g_slave_redis_pool_list.append(redis_pool)
  18. g_slave_redis_is_ok.append(True)
  19. def get_redis_by_key(strkey, nums):
  20. return (ord(strkey[0]) + ord(strkey[-1])) % nums
  21. # 从redis取
  22. def get_redis_key(key):
  23. # 根据key来分库
  24. index = get_redis_by_key(key, len(g_main_redis_pool_list))
  25. if g_main_redis_is_ok[index]:
  26. # 主ok
  27. try:
  28. return redis.Redis(connection_pool=g_main_redis_pool_list[index]).get(key)
  29. except Exception:
  30. # 主标记为挂
  31. g_main_redis_is_ok[index] = False
  32. # 主挂了试试从能不能用
  33. g_slave_redis_is_ok[index] = True
  34. if g_slave_redis_is_ok[index]:
  35. # 从ok
  36. try:
  37. return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).get(key)
  38. except Exception as e:
  39. # 从标记为挂
  40. g_slave_redis_is_ok[index] = False
  41. # 从也挂了下回只能尝试使用主
  42. g_main_redis_is_ok[index] = True
  43. # 抛出异常
  44. raise Exception(repr(e))
  45. # 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
  46. g_main_redis_is_ok[index] = Trueget_redis_by_key
  47. raise Exception('内部错误,get_redis_key运行异常')
  48. # redis存值且设置生命周期
  49. def set_redis_key_ex(key, value, expire):
  50. # 根据key来分库
  51. index = get_redis_by_key(key, len(g_main_redis_pool_list))
  52. if g_main_redis_is_ok[index]:
  53. # 主ok
  54. try:
  55. if expire == 0:
  56. return redis.Redis(connection_pool=g_main_redis_pool_list[index]).set(key, value)
  57. return redis.Redis(connection_pool=g_main_redis_pool_list[index]).setex(key, value, expire)
  58. except Exception:
  59. # 主标记为挂
  60. g_main_redis_is_ok[index] = False
  61. # 主挂了试试从能不能用
  62. g_slave_redis_is_ok[index] = True
  63. if g_slave_redis_is_ok[index]:
  64. # 从ok
  65. try:
  66. if expire == 0:
  67. return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).set(key, value)
  68. return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).setex(key, value, expire)
  69. except Exception as e:
  70. # 从标记为挂
  71. g_slave_redis_is_ok[index] = False
  72. # 从也挂了下回只能尝试使用主
  73. g_main_redis_is_ok[index] = True
  74. # 抛出异常
  75. raise Exception(repr(e))
  76. # 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
  77. g_main_redis_is_ok[index] = True
  78. raise Exception('内部错误,set_redis_key_ex运行异常')
  79. # redis存值且设置生命周期
  80. def expire_redis_key(key, expire):
  81. # 根据key来分库
  82. index = get_redis_by_key(key, len(g_main_redis_pool_list))
  83. if g_main_redis_is_ok[index]:
  84. # 主ok
  85. try:
  86. if expire == 0:
  87. return 0
  88. return redis.Redis(connection_pool=g_main_redis_pool_list[index]).expire(key, expire)
  89. except Exception:
  90. # 主标记为挂
  91. g_main_redis_is_ok[index] = False
  92. # 主挂了试试从能不能用
  93. g_slave_redis_is_ok[index] = True
  94. if g_slave_redis_is_ok[index]:
  95. # 从ok
  96. try:
  97. if expire == 0:
  98. return 0
  99. return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).expire(key, expire)
  100. except Exception as e:
  101. # 从标记为挂
  102. g_slave_redis_is_ok[index] = False
  103. # 从也挂了下回只能尝试使用主
  104. g_main_redis_is_ok[index] = True
  105. # 抛出异常
  106. raise Exception(repr(e))
  107. # 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
  108. g_main_redis_is_ok[index] = True
  109. raise Exception('内部错误,expire_redis_key运行异常')
  110. # redis删除key
  111. def delete_redis_key(key, expire):
  112. # 根据key来分库
  113. index = get_redis_by_key(key, len(g_main_redis_pool_list))
  114. if g_main_redis_is_ok[index]:
  115. # 主ok
  116. try:
  117. if expire == 0:
  118. return 0
  119. return redis.Redis(connection_pool=g_main_redis_pool_list[index]).delete(key)
  120. except Exception:
  121. # 主标记为挂
  122. g_main_redis_is_ok[index] = False
  123. # 主挂了试试从能不能用
  124. g_slave_redis_is_ok[index] = True
  125. if g_slave_redis_is_ok[index]:
  126. # 从ok
  127. try:
  128. if expire == 0:
  129. return 0
  130. return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).delete(key)
  131. except Exception as e:
  132. # 从标记为挂
  133. g_slave_redis_is_ok[index] = False
  134. # 从也挂了下回只能尝试使用主
  135. g_main_redis_is_ok[index] = True
  136. # 抛出异常
  137. raise Exception(repr(e))
  138. # 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
  139. g_main_redis_is_ok[index] = True
  140. raise Exception('内部错误,delete_redis_key运行异常')
  141. def set_redis_key_ex_nx(key, value, expire):
  142. """如果有键值则不设置"""
  143. # 根据key来分库
  144. index = get_redis_by_key(key, len(g_main_redis_pool_list))
  145. if g_main_redis_is_ok[index]:
  146. # 主ok
  147. try:
  148. if expire == 0:
  149. return 0
  150. return redis.Redis(connection_pool=g_main_redis_pool_list[index]).set(key, value, ex=expire, nx=True)
  151. except Exception:
  152. # 主标记为挂
  153. g_main_redis_is_ok[index] = False
  154. # 主挂了试试从能不能用
  155. g_slave_redis_is_ok[index] = True
  156. if g_slave_redis_is_ok[index]:
  157. # 从ok
  158. try:
  159. if expire == 0:
  160. return 0
  161. return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).set(key, value, ex=expire, nx=True)
  162. except Exception as e:
  163. # 从标记为挂
  164. g_slave_redis_is_ok[index] = False
  165. # 从也挂了下回只能尝试使用主
  166. g_main_redis_is_ok[index] = True
  167. # 抛出异常
  168. raise Exception(repr(e))
  169. # 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
  170. g_main_redis_is_ok[index] = True
  171. raise Exception('内部错误,set_redis_key_nx_运行异常')
  172. def get_redis_keys_pattern(key_pattern):
  173. from builtins import enumerate
  174. key_set = set()
  175. # 主库找
  176. for index, is_ok in enumerate(g_main_redis_is_ok):
  177. if is_ok:
  178. key_set.update(redis.Redis(connection_pool=g_main_redis_pool_list[index]).keys(key_pattern))
  179. # 从库找
  180. for index, is_ok in enumerate(g_slave_redis_is_ok):
  181. if is_ok:
  182. key_set.update(redis.Redis(connection_pool=g_slave_redis_pool_list[index]).keys(key_pattern))
  183. return key_set
  184. if __name__ == "__main__":
  185. # set_redis_key_ex('ab','a',10)
  186. print(get_redis_key('ab').decode())

2. 使用方法

  1. import FuncDefine
  2. with FuncDefine.RedisLock(rediskey) as lock:
  3. if not lock.hold_lock:
  4. return response(3, '商品添加中,请稍后~', '', [])

3. 源码分析

整体来看也就是接口访问过来的时候, 设置一个redis_key(nx=True, ex=300), 这样在五分钟之内就可以避免重复点击的情况

  1. 初始化redis, 上下文管理器会触发__enter__()方法, 从而调用self.acquire()

  1. 设置redis的键, 如果不加nx=True, redis的set会直接覆盖之前key的值, 这里还存在一个主从redis, 感兴趣可以看看源码

  1. 当执行完with中的代码块, 会触发__exit__()函数, 调用函数删除当前redis的key对应的值

  1. 剩下的一些函数都是封装的一些通用方法, 比如查看当前key值

原文链接:https://www.cnblogs.com/huxiaofeng1029/p/17573715.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号