MST

星途 面试题库

面试题:复杂业务需求下Redis频道订阅退订动态策略设计

给定一个复杂业务场景,如实时金融数据推送系统,不同类型的用户根据权限订阅不同粒度的频道(如按股票代码、板块等),且业务要求在用户活跃度变化时能动态调整订阅以节省资源。请设计一套完整的Redis频道订阅退订动态调整策略,并详细说明涉及的数据结构、算法以及可能面临的挑战和解决方案。
46.1万 热度难度
数据库Redis

知识考点

AI 面试

面试题答案

一键面试

数据结构设计

  1. 用户权限存储
    • 使用Redis的Hash结构,以用户ID作为key,权限信息作为field - value对。例如:HSET user:123 permission:stock_code "AAPL,GOOG" permission:sector "Technology"。这样可以方便地获取每个用户对于不同粒度频道的订阅权限。
  2. 用户订阅关系存储
    • 使用Redis的Set结构,以频道名称作为key,用户ID集合作为value。例如,对于股票代码为AAPL的频道,SADD channel:AAPL 123 456,其中123和456是订阅该频道的用户ID。这有助于快速获取订阅某个频道的所有用户。
    • 同时,为了反向查询用户订阅了哪些频道,可以使用Hash结构,以用户ID为key,频道名称集合作为field - value对。例如:HSET user_subscriptions:123 channel:AAPL 1 channel:Tech_sector 1。这里的1表示已订阅。
  3. 用户活跃度存储
    • 使用Redis的Sorted Set结构,以用户ID作为member,活跃度分数作为score。例如:ZADD user_activity 100 123,表示用户123的活跃度分数为100。通过这种结构可以方便地根据活跃度对用户进行排序。

算法设计

  1. 订阅算法
    • 当用户订阅频道时,首先检查用户权限。从用户权限Hash结构中获取用户对于该频道粒度的权限。
    • 如果有权限,将用户ID添加到频道对应的Set结构中,并在用户订阅Hash结构中添加相应记录。例如,对于用户123订阅股票代码AAPL的频道:
      # 检查权限
      permission = redis.hget("user:123", "permission:stock_code")
      if "AAPL" in permission:
          redis.sadd("channel:AAPL", 123)
          redis.hset("user_subscriptions:123", "channel:AAPL", 1)
      
  2. 退订算法
    • 用户退订频道时,从频道对应的Set结构中移除用户ID,并在用户订阅Hash结构中删除相应记录。例如,对于用户123退订股票代码AAPL的频道:
      redis.srem("channel:AAPL", 123)
      redis.hdel("user_subscriptions:123", "channel:AAPL")
      
  3. 动态调整算法
    • 定期(例如每分钟)检查用户活跃度Sorted Set。设定一个活跃度阈值,如活跃度分数低于50的用户视为低活跃度用户。
    • 对于低活跃度用户,遍历其订阅的频道,根据业务规则判断是否可以退订。例如,如果业务允许低活跃度用户只保留按板块订阅,而退订按股票代码的订阅:
      low_activity_users = redis.zrangebyscore("user_activity", 0, 50)
      for user_id in low_activity_users:
          subscriptions = redis.hgetall("user_subscriptions:" + user_id)
          for channel in subscriptions.keys():
              if "stock_code" in channel:
                  redis.srem(channel, user_id)
                  redis.hdel("user_subscriptions:" + user_id, channel)
      

可能面临的挑战及解决方案

  1. 高并发订阅退订
    • 挑战:在实时金融数据推送系统中,可能会有大量用户同时进行订阅和退订操作,导致Redis的写入压力增大。
    • 解决方案:使用Redis的事务(MULTI - EXEC)机制,将多个相关操作打包成一个原子操作,减少并发冲突。例如,将添加用户到频道Set和更新用户订阅Hash结构的操作放在一个事务中执行。同时,可以考虑使用Redis集群,通过数据分片分散写入压力。
  2. 数据一致性
    • 挑战:在动态调整订阅时,可能会出现部分数据更新成功,部分失败的情况,导致数据不一致。
    • 解决方案:除了使用事务保证操作原子性外,还可以引入版本号机制。在每次更新数据时,增加版本号,读取数据时也读取版本号。如果发现版本号不一致,说明数据在读取后被其他操作修改,需要重新读取和操作。
  3. 大规模数据存储
    • 挑战:随着用户数量和频道数量的增加,Redis的内存占用可能会过高。
    • 解决方案:可以采用数据淘汰策略,如使用volatile - lru(对设置了过期时间的key使用LRU算法淘汰)或allkeys - lru(对所有key使用LRU算法淘汰)策略,在内存不足时自动淘汰不常用的数据。另外,可以定期清理无效数据,如长时间未活跃且已退订所有频道的用户记录。