Redis解决网络抖动问题

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

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

1. 源码

FuncDefine.py

def clear_all_lock(PREFIX='lock'):
keys = redis_operator.get_redis_keys_pattern(PREFIX + '*') for key in keys:
if isinstance(key, bytes):
kwl_py_write_log(key.decode(encoding='utf-8'), msgid='del_redis_key')
redis_operator.delete_redis_key(key.decode(encoding='utf-8'), 1) def unlock(key):
redis_operator.delete_redis_key(key, 1) class RedisLock:
DEFAULT_VALUE = 1
PREFIX = 'lock' def __init__(self, key, lock_time=300):
"""
初始化redis锁
:param key: 关键字
:param lock_time: 上锁时间 5min
"""
self._key = RedisLock.PREFIX + key
self.lock_time = lock_time
self.hold_lock = False @property
def key(self):
return self._key @key.setter
def key(self, key):
self._key = RedisLock.PREFIX + key def __enter__(self):
self.hold_lock = self.acquire()
return self def __exit__(self, exc_type, exc_val, exc_tb):
if self.hold_lock:
self.release()
return False async def __aenter__(self):
self.hold_lock = await self.acquire_cas_lock()
return self async def __aexit__(self, exc_type, exc_val, exc_tb):
if self.hold_lock:
self.release()
return False async def acquire_cas_lock(self, lock_time=60):
try:
return await asyncio.wait_for(self.acquire_lock_until_succ(), lock_time)
except asyncio.TimeoutError as e:
return False async def acquire_lock_until_succ(self): while redis_operator.set_redis_key_ex_nx(self.key, self.DEFAULT_VALUE, self.lock_time) is not True:
# redis return is None or other
await asyncio.sleep(0.01)
return True def acquire(self):
"""
设置redis锁
:param key:
:param lock_time:
:return:
"""
try:
return redis_operator.set_redis_key_ex_nx(self.key, self.DEFAULT_VALUE, self.lock_time) is True
except Exception:
return False def release(self):
redis_operator.delete_redis_key(self.key, 1)

redis_operator.py

import redis
from redisConfig import * # ------------------------------------------------------------------------------------------------------
# 主从redis,个数一一对应
g_main_redis_pool_list = []
g_slave_redis_pool_list = []
g_main_redis_is_ok = [] # 主redis是否可用True为主ok
g_slave_redis_is_ok = [] # 从redis是否可用 for each_redis in g_main_redis_server:
redis_pool = redis.ConnectionPool(host=each_redis[0], port=each_redis[1], password=each_redis[2], socket_timeout=8,
socket_connect_timeout=5)
g_main_redis_pool_list.append(redis_pool)
g_main_redis_is_ok.append(True) for each_redis in g_slave_redis_server:
redis_pool = redis.ConnectionPool(host=each_redis[0], port=each_redis[1], password=each_redis[2], socket_timeout=8,
socket_connect_timeout=5)
g_slave_redis_pool_list.append(redis_pool)
g_slave_redis_is_ok.append(True) def get_redis_by_key(strkey, nums):
return (ord(strkey[0]) + ord(strkey[-1])) % nums # 从redis取
def get_redis_key(key):
# 根据key来分库
index = get_redis_by_key(key, len(g_main_redis_pool_list))
if g_main_redis_is_ok[index]:
# 主ok
try:
return redis.Redis(connection_pool=g_main_redis_pool_list[index]).get(key)
except Exception:
# 主标记为挂
g_main_redis_is_ok[index] = False
# 主挂了试试从能不能用
g_slave_redis_is_ok[index] = True
if g_slave_redis_is_ok[index]:
# 从ok
try:
return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).get(key)
except Exception as e:
# 从标记为挂
g_slave_redis_is_ok[index] = False
# 从也挂了下回只能尝试使用主
g_main_redis_is_ok[index] = True
# 抛出异常
raise Exception(repr(e))
# 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
g_main_redis_is_ok[index] = Trueget_redis_by_key
raise Exception('内部错误,get_redis_key运行异常') # redis存值且设置生命周期
def set_redis_key_ex(key, value, expire):
# 根据key来分库
index = get_redis_by_key(key, len(g_main_redis_pool_list))
if g_main_redis_is_ok[index]:
# 主ok
try:
if expire == 0:
return redis.Redis(connection_pool=g_main_redis_pool_list[index]).set(key, value)
return redis.Redis(connection_pool=g_main_redis_pool_list[index]).setex(key, value, expire)
except Exception:
# 主标记为挂
g_main_redis_is_ok[index] = False
# 主挂了试试从能不能用
g_slave_redis_is_ok[index] = True
if g_slave_redis_is_ok[index]:
# 从ok
try:
if expire == 0:
return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).set(key, value)
return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).setex(key, value, expire)
except Exception as e:
# 从标记为挂
g_slave_redis_is_ok[index] = False
# 从也挂了下回只能尝试使用主
g_main_redis_is_ok[index] = True
# 抛出异常
raise Exception(repr(e))
# 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
g_main_redis_is_ok[index] = True
raise Exception('内部错误,set_redis_key_ex运行异常') # redis存值且设置生命周期
def expire_redis_key(key, expire):
# 根据key来分库
index = get_redis_by_key(key, len(g_main_redis_pool_list))
if g_main_redis_is_ok[index]:
# 主ok
try:
if expire == 0:
return 0
return redis.Redis(connection_pool=g_main_redis_pool_list[index]).expire(key, expire)
except Exception:
# 主标记为挂
g_main_redis_is_ok[index] = False
# 主挂了试试从能不能用
g_slave_redis_is_ok[index] = True
if g_slave_redis_is_ok[index]:
# 从ok
try:
if expire == 0:
return 0
return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).expire(key, expire)
except Exception as e:
# 从标记为挂
g_slave_redis_is_ok[index] = False
# 从也挂了下回只能尝试使用主
g_main_redis_is_ok[index] = True
# 抛出异常
raise Exception(repr(e))
# 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
g_main_redis_is_ok[index] = True
raise Exception('内部错误,expire_redis_key运行异常') # redis删除key
def delete_redis_key(key, expire):
# 根据key来分库
index = get_redis_by_key(key, len(g_main_redis_pool_list))
if g_main_redis_is_ok[index]:
# 主ok
try:
if expire == 0:
return 0
return redis.Redis(connection_pool=g_main_redis_pool_list[index]).delete(key)
except Exception:
# 主标记为挂
g_main_redis_is_ok[index] = False
# 主挂了试试从能不能用
g_slave_redis_is_ok[index] = True
if g_slave_redis_is_ok[index]:
# 从ok
try:
if expire == 0:
return 0
return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).delete(key)
except Exception as e:
# 从标记为挂
g_slave_redis_is_ok[index] = False
# 从也挂了下回只能尝试使用主
g_main_redis_is_ok[index] = True
# 抛出异常
raise Exception(repr(e))
# 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
g_main_redis_is_ok[index] = True
raise Exception('内部错误,delete_redis_key运行异常') def set_redis_key_ex_nx(key, value, expire):
"""如果有键值则不设置"""
# 根据key来分库
index = get_redis_by_key(key, len(g_main_redis_pool_list))
if g_main_redis_is_ok[index]:
# 主ok
try:
if expire == 0:
return 0
return redis.Redis(connection_pool=g_main_redis_pool_list[index]).set(key, value, ex=expire, nx=True)
except Exception:
# 主标记为挂
g_main_redis_is_ok[index] = False
# 主挂了试试从能不能用
g_slave_redis_is_ok[index] = True if g_slave_redis_is_ok[index]:
# 从ok
try:
if expire == 0:
return 0
return redis.Redis(connection_pool=g_slave_redis_pool_list[index]).set(key, value, ex=expire, nx=True)
except Exception as e:
# 从标记为挂
g_slave_redis_is_ok[index] = False
# 从也挂了下回只能尝试使用主
g_main_redis_is_ok[index] = True
# 抛出异常
raise Exception(repr(e))
# 按理不可能出现这种情况,主从皆False,全挂的情况也会至少打开一个
g_main_redis_is_ok[index] = True
raise Exception('内部错误,set_redis_key_nx_运行异常') def get_redis_keys_pattern(key_pattern):
from builtins import enumerate key_set = set()
# 主库找
for index, is_ok in enumerate(g_main_redis_is_ok):
if is_ok:
key_set.update(redis.Redis(connection_pool=g_main_redis_pool_list[index]).keys(key_pattern))
# 从库找
for index, is_ok in enumerate(g_slave_redis_is_ok):
if is_ok:
key_set.update(redis.Redis(connection_pool=g_slave_redis_pool_list[index]).keys(key_pattern)) return key_set if __name__ == "__main__":
# set_redis_key_ex('ab','a',10)
print(get_redis_key('ab').decode())

2. 使用方法

import FuncDefine
with FuncDefine.RedisLock(rediskey) as lock:
if not lock.hold_lock:
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值

Redis解决网络抖动问题的更多相关文章

  1. 豌豆夹Redis解决方式Codis源代码剖析:Proxy代理

    豌豆夹Redis解决方式Codis源代码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描写叙述: Codis is a proxy b ...

  2. SQLServer 2012之AlwaysOn —— 指定数据同步链路,消除网络抖动导致的提交延迟问题

    事件起因:近期有研发反应,某数据库从08切换到12环境后,不定期出现写操作提交延迟的问题: 事件分析:在排除了系统资源争用等问题后,初步分析可能由于网络抖动导致同步模式alwayson节点经常出现会话 ...

  3. centos6.7用yum安装redis解决办法及IP限制配置

    在centos6.7用yum安装redis解决办法 - bluesky1 - 博客园 http://www.cnblogs.com/lanblogs/p/6104834.html yum instal ...

  4. Android App卡顿慢优化之解决内存抖动及内存泄漏

    前面一篇博客说到了,内存抖动的第二种情况,就是必须在短时间内创建对象,但是要控制数量:这个问题目前可以使用对象池的方法解决. 3)Object Pools 在程序里面经常会遇到的一个问题是短时间内创建 ...

  5. Linux 将进程放入后台执行,解决网络,ssh断开导致进程结束(nohup, setsid, &, disown)

    Linux 将进程放入后台执行,解决网络,ssh断开导致进程结束(nohup,  setsid,  &, disown) 1.nohup 命令 我们知道,当用户注销(logout)或者网络断开 ...

  6. 使用Spring Session和Redis解决分布式Session跨域共享问题

    http://blog.csdn.net/xlgen157387/article/details/57406162 使用Spring Session和Redis解决分布式Session跨域共享问题

  7. 170222、使用Spring Session和Redis解决分布式Session跨域共享问题

    使用Spring Session和Redis解决分布式Session跨域共享问题 原创 2017-02-27 徐刘根 Java后端技术 前言 对于分布式使用Nginx+Tomcat实现负载均衡,最常用 ...

  8. 为npm设置代理,解决网络问题

    为npm设置代理,解决网络问题 npm config set proxy=http://127.0.0.1:1080

  9. 如何运用PHP+REDIS解决负载均衡后的session共享问题

    一.为什么要使用Session共享? 稍大一些的网站,通常都会有好几个服务器,每个服务器运行着不同功能的模块,使用不同的二级域名,而一个整体性强的网站,用户系统是统一的,即一套用户名.密码在整个网站的 ...

  10. ping、网络抖动与丢包

    基本概念: ping: PING指一个数据包从用户的设备发送到测速点,然后再立即从测速点返回用户设备的来回时间.也就是俗称的“网络延迟”   一般以毫秒(ms)计算   一般PING在0~100ms都 ...

随机推荐

  1. 关于Java中对象的向上转型和向下转型

    什么是多态? 同一个类调用同一个方法会产生不同的影响/结果 这就是多态 public class Pet{ public void eat(){ System.out.println("Pe ...

  2. pandlepanlde-01-必备数学知识

    文章目录 必备数学知识 数学基础知识 高等数学 线性代数 行列式 矩阵 向量 线性方程组 矩阵的特征值和特征向量 二次型 概率论和数理统计 随机事件和概率 随机变量及其概率分布 多维随机变量及其分布 ...

  3. Docker私有仓库harbor

    Docker私有仓库harbor 目录 Docker私有仓库harbor Harbor私有仓库介绍 Harbor部署 harbor页面不显示排错思路 Harbor的使用 Harbor拉镜像 自制镜像推 ...

  4. 从浏览器输入域名开始分析DNS解析过程

    摘要:DNS(Domain Name System)是域名系统的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,用于 TCP/IP 网络. 本文分享自华为云社区<DNS那些事--从浏 ...

  5. 日增数据超10PB!揭秘沃尔玛Lakehouse架构选型之路

    沃尔玛系统产生了世界上最大和最多样化的数据集之一,每天数据增长超 10 PB. 来自许多不同的来源及其支持的后端系统,一系列大量的业务事件流被发送到主要由 Apache Kafka 支持的消息传递层. ...

  6. C++中的字符串编码处理

    今天由于在项目中用到一些与C++混合开发的东西 ,需要通过socket与C++那边交换数据,没啥特别的,字节码而已,两边确定一种编码规则就行了.我们确定的UTF-8.关于C++的 这种又是宽字节 又是 ...

  7. laravel ServiceProvider 服务提供者使用案例

    1. 实例化一个类 2.全局注册这个类 3.在控制器中使用 public function register() { $this->app->singleton('wxminapp', f ...

  8. Mac常用文件解压命令

    tar 解压:tar xvf fileName.tar 压缩:tar cvf fileName.tar directoryName rar 1.安装rar 下载RAR https://www.rarl ...

  9. GaussDB(DWS)迁移实践丨row_number输出结果不一致

    摘要:迁移前后结果集row_number字段值前后不一致,前在DWS上运行不一致. 本文分享自华为云社区<GaussDB(DWS)迁移 - oracle兼容 --row_number输出结果不一 ...

  10. Mysql DDL执行方式-pt-osc介绍 | 京东云技术团队

    1 引言 大家好,接着上次和大家一起学习了<MySQL DDL执行方式-Online DDL介绍>,那么今天接着和大家一起学习另一种MySQL DDL执行方式之pt-soc. 在MySQL ...