首页 > 数据库 >Redis解决网络抖动问题

Redis解决网络抖动问题

时间:2023-07-22 17:13:17浏览次数:36  
标签:index ok 抖动 Redis redis 网络 key main pool

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值

标签:index,ok,抖动,Redis,redis,网络,key,main,pool
From: https://www.cnblogs.com/huxiaofeng1029/p/17573715.html

相关文章

  • Redis 入门
    Redis入门教程0x01概述Redis(RemoteDictionaryServer)是一个开源的基于内存的数据存储系统,可以用作数据库缓存和消息队列等场景,是一种NoSQL数据类型基本数据类型字符串String列表List集合Set有序集合SortedSet哈希Hash高级数据类型消息队列Stream地......
  • redis del namespace
    RedisDel命令解析与示例简介在Redis中,DEL命令被用来删除一个或多个键。这个命令可以用于删除任何类型的键,包括字符串、列表、哈希表、集合和有序集合。在删除键时,与被删除键关联的数据也会被同时删除。本文将介绍DEL命令的用法、参数、返回值以及示例代码。DEL命令的用......
  • redis dbtree
    RedisDBTree:ExploringtheDataStructureRedisisanopen-source,in-memorydatastructurestorethatiscommonlyusedasadatabase,cache,andmessagebroker.Itsupportsvariousdatastructures,suchasstrings,hashes,lists,sets,andsortedsets,......
  • redis cluster 删除key
    RedisCluster删除Key简介Redis是一个开源的内存数据库,它提供了多种数据结构和丰富的功能。RedisCluster是Redis的分布式解决方案,它允许将数据分布在多个节点上,提高了数据的可用性和性能。在RedisCluster中删除Key是一项常见的操作。本文将介绍如何使用RedisCluster删除Key,并......
  • redis 6.2.6 for windows
    Redis6.2.6forWindowsRedisisanopen-source,in-memorydatastructurestorethatcanbeusedasadatabase,cache,andmessagebroker.Itisknownforitssimplicity,highperformance,andrichsetofdatatypes.Inthisarticle,wewillexplorehowt......
  • redis自动刷新过期时间
    Redis自动刷新过期时间Redis是一个开源的内存数据库,它提供了一些强大的功能,如缓存、消息队列和键值存储等。在缓存应用中,我们经常会遇到一个问题,就是如何处理缓存的过期时间。当缓存过期后,我们需要从数据库中重新加载数据,并将其重新设置到Redis中。为了解决这个问题,我们可以使用Re......
  • redis主从模式修改密码
    Redis主从模式修改密码概述在Redis主从模式中,需要修改密码时,需要在主节点上修改密码,并且同步到所有的从节点上。下面将详细介绍如何实现这个过程。修改密码流程下面是修改Redis主从模式密码的流程:步骤描述步骤1连接到主节点步骤2修改主节点密码步骤3获取从......
  • redis重置过期时间
    Redis重置过期时间Redis是一款开源的高性能键值存储系统,常用于缓存、消息队列等场景。在Redis中,可以为每个键设置过期时间,过期时间到达后,Redis会自动删除该键。但有时候,我们需要动态地重置键的过期时间,以延长其存活时间。本文将介绍如何在Redis中重置键的过期时间,并提供相应的代码......
  • redis中地区的存八个小时格式怎么写
    使用Redis存储地区信息并设置过期时间在一些应用程序中,我们经常需要存储地区信息,并设置一个过期时间来保持数据的新鲜度。例如,一个电子商务网站可能会存储用户所在地区的商品偏好,然后根据地区信息来展示相关商品。在这种情况下,我们可以使用Redis来存储地区信息,并设置一个合适的过......
  • redis中的Cursor使用实例
    Redis中的Cursor使用实例Redis是一种高性能的键值存储系统,常用于缓存、消息队列和排行榜等应用场景。在Redis中,Cursor是一种用于遍历集合元素的机制。通过使用Cursor,我们可以逐步地获取集合中的元素,而不需要一次性将整个集合加载到内存中。什么是Cursor在Redis中,Cursor是一个游......