2

redis分布式锁原子性设置超时问题

 1 year ago
source link: https://www.cnblogs.com/zhanchenjin/p/17159875.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1.问题:

  最近客服有报无法上报运动记录,通过日志查看是分布式锁等待超时所致。

  redis出现一个分布式锁的TTL为-1,正常情况都会设置超时时间的。

  

841795-20230227152915314-543813772.png

2.分析:

通过k8s发现sport服务在50几天内重启了40几次,机器上内存比较紧缺,暂时只能重启,占用内存高的问题也先不解决。

看下之前加锁的代码:

acquire_lock

 def acquire_lock(redis_client, lock_key, lock_value, expire=10, timeout=5):
    """
    获取锁
    :param redis_client: redis;连接
    :param lock_key: lock key
    :param lock_value: lock value
    :param expire: lock key过期时间
    :param timeout: 等待超时
    :return:
    """
    assert isinstance(redis_client, RedisClient), [type(RedisClient), RedisClient]
    wait_time = 0
    sleep_time = 0.05
    with redis_client.ctx_conn as conn:
        while True:
            stime = time.time()
            if conn.execute('SETNX', lock_key, lock_value):
                conn.execute('EXPIRE', lock_key, expire)
                return True
            elif not conn.execute('TTL', lock_key):
                conn.execute('EXPIRE', lock_key, expire)
            logger.info("acquire_lock :%s" % lock_key)
            gevent.sleep(sleep_time)
            etime = time.time()
            wait_time += etime - stime
            if wait_time >= timeout:
                break
    assert 0, [conn, lock_key, lock_value, expire, timeout]

从现状来分析,应该是SETNX之后没有执行EXPIRE,正常执行肯定没有问题;

我估计是因为进程刚好执行到SETNX的时候被k8s杀了,导致EXPIRE没有去执行

3.解决方案:

  • set需要同时兼容NX EX的原子功能

  研究了一下redis的set命令,果然有这个功能

  SET', lock_key, lock_value, NX, EX, expire

  • 兼容没有设置超时的时候设置一下超时

  判断TTL 是-1的时候:

  ttl当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间

原子操作set

 def acquire_lock(redis_client, lock_key, lock_value, expire=10, timeout=5):
    """
    获取锁
    :param redis_client: redis;连接
    :param lock_key: lock key
    :param lock_value: lock value
    :param expire: lock key过期时间
    :param timeout: 等待超时
    :return:
    """
    assert isinstance(redis_client, RedisClient), [type(RedisClient), RedisClient]
    wait_time = 0
    sleep_time = 0.05
    logger.info("acquire_lock lock_key:%s lock_value:%s" % (lock_key, lock_value))
    with redis_client.ctx_conn as conn:
        while True:
            stime = time.time()
            if conn.execute('SET', lock_key, lock_value, "NX", "EX", expire):
                return True
            elif conn.execute('TTL', lock_key) == -1:       
                conn.execute('EXPIRE', lock_key, expire)
            gevent.sleep(sleep_time)
            etime = time.time()
            wait_time += etime - stime
            if wait_time >= timeout:
                break
    assert 0, [conn, lock_key, lock_value, expire, timeout]

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK