暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Redis有序集合ZSET妙用

稻壳编程 2021-06-02
647

211工程院校贵州大学管理学院硕士研究生、全国百强城商行资讯科技部五级专技工程师、互联网金融行业资深DevOps研发工程师、金融科技运维自动化平台研发项目经理。曾在国内多家知名互联网公司 平安科技、微众银行、顺丰科技、魅族任职. 具有多年国内一线互联网公司自动化运维平台设计与开发经验。

 1 

 

前言

ZSET保留了集合中元素不能重复的特点,但不同的是ZSET中的元素是有顺序的。排序的依据是与每个元素对应的score, 在ZSET中元素不可重复但score可以重复,就好比是一个班级,学生的学号是不相同的,但是考试成绩是可以相同的。如下图所示:


本文以下内容介绍ZSET的几种应用场景与之对应的示例代码在生产中应用需考虑更多细节。



 2 

排行榜

通过ZSET能实现实时热点排行,如当日最热帖topX。实现思路为将日期作为key, 帖子ID作为有序集合中的元素(member), 帖子每被点击一次就在现有分数(score)的基础上增量一个值,如下图所示:

示例代码如下:

多次执行模拟点击帖子代码,每次模拟点击总量100次,随机点击现有的26个帖子。

    #!/usr/bin/python
    date = "2021-06-01 00:00:00"
    import random
    def topicRead():
    r = redis.Redis(host="172.16.70.143", port=6379, decode_responses=True, db=0)
    topic_list = list(set(string.ascii_letters.lower()))
    for _ in range(100):
    s = random.choice(topic_list)
    r.zincrby(date, s, 1)
    print("Done..")

    获取当日最热帖TOP3

      #!/usr/bin/python
      date = "2021-06-01 00:00:00"
      def getRank():
      w = r.zrevrange(date, 0, 2, withscores=True)
      print(w)
        # 代码输出
        [('b', 75.0), ('h', 71.0), ('u', 70.0)]


         3 

        访问频次限制

        通过ZSET实现滑动窗口,实现接口调用频次限制、用户登录频次限制等类似功能。以用户登录频次限制为例,将用户ID作为key,分数(score)为访问时间戳, 元素(member)值能保证唯一性即可,本例中使用用户名和时间戳组合。登录频次只需要统计某个key下指定时间戳区间内的元素个数,就能得到该时间窗口内的用户登录次数,通过与频次限制值相比较,进而确定是放行还是抑制。本例中登录频次限制策略为20秒内,连续登录超过三次即触发抑制策略,示例代码如下所示:
          #!/usr/bin/python
          import redis, time
          def access_limits(username, sec, count):
          timestamp = int(time.time())
          r = redis.Redis(host="172.16.70.143", port=6379, decode_responses=True, db=0)
          login_count = r.zcount(username, timestamp-sec, timestamp)
          if login_count <= count:
          s = "用户:%s 在%s秒内 已登录%s次..放行.."%(username, sec, login_count)
          r.zadd(username, '_'.join([username, str(timestamp)]), timestamp) ##登录的时间戳 打一下
          print(s)
          else:
          r.zadd(username, '_'.join([username, str(timestamp)]), timestamp) ##登录的时间戳 打一下
          s = "用户:%s 在%s秒内 已登录%s次..限制登录.."%(username, sec, login_count)
          print(s)
          if __name__ == '__main__':
          username = '2019020758'
          access_limits(username, 20, 3)

            #代码输出: 
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录0次..放行..
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录1次..放行..
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录2次..放行..
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录3次..放行..
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录4次..限制登录..
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录5次..限制登录..
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录6次..限制登录..
            ###
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录1次..放行..
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录2次..放行..
            (workplace) [root@localhost python]# python demo7.py
            用户:2019020758 在20秒内 已登录3次..放行..
            (workplace) [root@localhost python]# python demo7.py


             4 

            延时队列

            已知ZSET使用分数(score)作为排序依据,那么若将分数(score)设置为想要执行的任务的时间戳,将任务(member)与时间戳(score)写入zset集合的指定key中, 则欲执行的任务会自动按照时间戳的大小排序。运行一个死循环持续从Zset中获取指定key下的任务(member)与分数(score),如果当前时间戳大于等于key中的分数(score),则将其对应的任务取出并消费,实现延时任务执行队列的目的。示例代码如下所示:
              #!/usr/bin/python
              import redis, time


              def task_producer(task_name, exetime):
              time_stamp = time.mktime(time.strptime(exetime, "%Y-%m-%d %H:%M:%S"))
              r = redis.Redis(host="172.16.70.143", port=6379, decode_responses=True, db=0)
              r.zadd("task_list", task_name, time_stamp)
              s = "任务%s加入队列成功, 执行时间%s"%(task_name, time_stamp)
              print(s)


              def callback(x):
              print("正在执行回调函数..")
              print(x)
              print("回调函数执行完成.....")


              def task_consumer(callback):
              r = redis.Redis(host="172.16.70.143", port=6379, decode_responses=True, db=0)
              while 1:
              timestamp = int(time.time())
              print("当前时间: %s" %timestamp)
              w = r.zrevrangebyscore("task_list", timestamp, 0, withscores=True)
              ## 执行任务 成功后删除任务
              for j in w:
              print(j, '11111')
              callback(j[0])
              r.zrem("task_list", j[0])
              print("已删除任务%s_%s"%("task_list", j[0]))
              time.sleep(2)


              if __name__ == '__main__':
              task_producer("hello1", "2021-06-01 18:45:21")
              task_producer("hello2", "2021-06-01 18:45:34")
              task_producer("hello3", "2021-06-01 18:45:46")
              task_consumer(callback=callback)
                代码输出:
                任务hello1加入队列成功, 执行时间1622587521.0
                任务hello2加入队列成功, 执行时间1622587534.0
                任务hello3加入队列成功, 执行时间1622587546.0
                当前时间: 1622587524
                ('hello1', 1622587521.0) 11111
                正在执行回调函数..
                hello1
                回调函数执行完成.....
                已删除任务task_list_hello1
                当前时间: 1622587526
                当前时间: 1622587528
                当前时间: 1622587530
                当前时间: 1622587532
                当前时间: 1622587534
                ('hello2', 1622587534.0) 11111
                正在执行回调函数..
                hello2
                回调函数执行完成.....
                已删除任务task_list_hello2
                当前时间: 1622587536
                当前时间: 1622587538
                当前时间: 1622587540
                当前时间: 1622587542
                当前时间: 1622587544
                当前时间: 1622587546
                ('hello3', 1622587546.0) 11111
                正在执行回调函数..
                hello3
                回调函数执行完成.....
                已删除任务task_list_hello3
                当前时间: 1622587548

                 5 

                结束语

                如果本文可以对您的工作学习带来帮助,请扫描左侧赞赏码以资鼓励作者;文章勘误请扫描右侧二维码联系作者。


                文章转载自稻壳编程,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                评论