1. Redis底层ZSet跳表是如何设计与实现的
    跳表【skipList】其实就是有层级的列表。
    比如我们想查找19,首先和6比较,大于6之后,在和9进行比较,然后在和12进行比较…最后比较到21的时候,发现21大于19,说明查找的点在17和21之间,从这个过程中,我们可以看出,查找的时候跳过了3、7、12等点。

    Redis底层ZSet实现压缩列表和跳表如何选择
    压缩列表:
    因为ziplist是紧凑存储,没有冗余空间,意味着新插入元素,就需要扩展内存:1. 分配新的内存,将原数据拷贝到新内存; 2. 扩展原有内存;所以ziplist 不适合存储大型字符串,存储的元素也不宜过多。
    满足下面条件,zset、hash的底层存储结构不再是ziplist:
hash-max-ziplist-entries 512  # hash 的元素个数超过 512 就必须用标准结构存储
hash-max-ziplist-value 64  # hash 的任意元素的 key/value 的长度超过 64 就必须用标准结构存储
zset-max-ziplist-entries 128  # zset 的元素个数超过 128 就必须用标准结构存储
zset-max-ziplist-value 64  # zset 的任意元素的长度超过 64 就必须用标准结构存储
  1. Redis高并发场景热点缓存如何重建
    热点key不设置过期时间,但是存在一个逻辑过期时间,逻辑过期时间保存在key相应 的value中
    若发现逻辑过期时间到期,则返回老值,异步更新value值,存在的缺点会导致数据的短暂不一致
  2. Java面试在项目中是如何使用redis的 redis面试必会6题经典_加锁

  3. 参考博客:
  4. 高并发场景缓存穿透&失效&雪崩如何解决
  1. 缓存穿透:
  1. 布隆过滤
  2. 缓存空对象. 将 null 变成一个值.
  1. .缓存雪崩
  1. 加锁排队. 限流-- 限流算法. 1.计数 2.滑动窗口 3. 令牌桶Token Bucket 4.漏桶 leaky bucket [1]
  2. 数据预热
  3. 做二级缓存,或者双缓存策略。
  4. 缓存永远不过期:
  1. 从缓存上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
  2. 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期.


  1. Redis集群架构如何抗住双十一的洪峰流量
    这个没有找到合适的参考资料,我认为根据实际业务,去构建redis集群即可,保证redis有3个主节点,每个主节点至少有1个从节点。
  2. Redis缓存与数据库双写不一致如何解决
@startuml
gateway -> server_cluster:  request
server_cluster --> redis_or_zookeeper_cluster: 创建更新临时标记
server_cluster --> mysql_cluster: 更新数据库
server_cluster --> redis_cluster: 更新缓存
server_cluster --> redis_cluster: 删除更新临时标记
@enduml

创建临时更新标记是因为在数据强一致性业务里,读请求需要等待更新操作完成才能进行下去,所以这里要一个更新的标记让读请求进行加锁阻塞住,等待更新操作完成后解锁,这里需要设置锁超时,以免发送死锁,响应内容应根据实际内容去响应。由于读请求需要阻塞,在大量并发情况下,必要时需要进行限流,不然很容易搞垮服务器。
参考博客:

  1. Redis分布式锁主从架构锁失效问题如何解决
    Redis分布式锁主从架构锁的缺点在它加锁时只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:
    在Redis的master节点上拿到了锁,但是这个加锁的key还没有同步到slave节点。master故障,发生故障转移,slave节点升级为master节点,导致锁丢失;

参考博客:
参考博客: https://zhuanlan.zhihu.com/p/266933567

  1. 从CAP角度解释下Redis&Zookeeper锁架构异同
  1. zk分布式锁,就是某个节点尝试创建临时znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能注册个监听器监听这个锁。释放锁就是删除这个znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。
  2. redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能;zk分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小
  3. 如果是redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间之后才能释放锁;而zk的话,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁

  1. 超大并发的分布式锁架构该如何设计
    以redsession原理来讲:
  1. 以毫秒为单位获取当前时间;使用相同的key和具有唯一性的value(例如UUID+TID)顺序从每一个Redis节点中加锁。
  2. 客户端需要设置连接、响应超时,并且超时时间应该<锁失效时间。这样可以避免Redis宕机,客户端还在等待响应结果。
  3. 如果Redis没有在单位时间内响应,客户端应该快速失败请求另外的Redis;当满足>=N/2+1个节点加锁成功,且锁的使用时间<失效时间时,才算加锁成功;
  4. 加锁成功后,key的真正有效时间等于有效时间减去获取锁使用时间;
  5. 客户端加锁失败时,需要在所有Redis节点上进行解锁,以防止在某些Redis节点上加锁成功但客户端无响应或超时而影响其它客户端无法加锁。
  6. 只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。

  1. 双十一亿级用户日活统计如何用Redis快速计算
    用BitMap:
//设置20200429 活跃用户1
127.0.0.1:6379> setbit 20200429 1 1
(integer) 0
//设置20200429 活跃用户2
127.0.0.1:6379> setbit 20200429 2 1
(integer) 0
//设置20200430 活跃用户1
127.0.0.1:6379> setbit 20200430 1 1
(integer) 0
//设置20200430 活跃用户2
127.0.0.1:6379> setbit 20200430 2 1
(integer) 0
//设置20200430 活跃用户3
127.0.0.1:6379> setbit 20200430 3 1
(integer) 0
//使用命令AND统计在0429并且0430活跃的用户设置到key "active:users"
127.0.0.1:6379> BITOP and active:users 20200429 20200430
(integer) 1
//0429并且0430活跃的用户也就是咱们的用户1 和用户2 记录数是 2
127.0.0.1:6379> bitcount active:users
(integer) 2
127.0.0.1:6379> 
//使用命令OR统计在0429并且0430活跃的用户设置到key "active:users1"  
127.0.0.1:6379> bitop or active:users1 20200429 20200430
(integer) 1
//0429、0430活跃的用户也就是咱们的用户1 和用户2 用户3 记录数是 3
127.0.0.1:6379> bitcount active:users1 
(integer) 3
setbit 20200429 1 1

  1. 双十一电商推荐系统如何用Redis实现
    基于用户兴趣的推荐
SADD {categories} organic dairy
SADD {category}:organic:items milk carrots tomatoes
SADD {category}:dairy:items milk butter cheese
SADD {user}:U1:categories organic dairy
SADD {user}:U2:categories dairy
//当客户U1打开她的应用,她将会收到关于以下商品的促销信息:
SUNION {category}:organic:items {category}:dairy:items //milk, carrots, tomatoes, butter, cheese

基于用户-物品关系的协同过滤

SADD {user}:U1:items milk bananas
SADD {user}:U2:items milk carrots bananas
SADD {user}:U3:item milk 
SADD {item}:milk:user U1 U2 U3
SADD {item}:bananas:user U1 U2 
SADD {item}:carrots:user U2 
//什么物品将会被推荐给用户U1?
SMEMBERS {user}:U1:items //milk, banana
SUNION {item}:milk:users {items}:banana:users //U1, U2, U3
SUNIONSTORE {user}:U1:all_recommended {user}:U1:items {user}:U2:items {user}:U3:items //milk, bananas, carrots
//推荐carrots给U1
SDIFF {user}:U1:all_recommended {user}:U1:items //milk, bananas, carrots - milk, bananas = carrots

基于用户-物品关系及其打分的协同过滤

zadd userid:U1:items 4 milk 5 bananas
zadd userid:U2:items 3 milk 4 carrots 5 bananas
zadd userid:U3:items 5 milk
zadd item:milk:scores 4 U1 3 U2 5 U3
zadd item:bananas:scores 5 U1 5 U2
zadd item:carrots:scores 4 U2
ZRANGE userid:U1:items 0 -1

//计算最相似用户
ZUNIONSTORE userid:U1:same_items 2 item:milk:scores item:bananas:scores
zrange userid:U1:same_items 0 -1 withscores
//计算U1与U2的评分差异
ZINTERSTORE rms:U1:U2 2 userid:U1:items userid:U2:items WEIGHTS 1 -1
zrange rms:U1:U2 0 -1 withscores //{(bananas, 0), (milk, 1)};
//计算U1与U2的评分差异
ZINTERSTORE rms:U1:U3 2 userid:U1:items userid:U3:items WEIGHTS 1 -1
zrange rms:U1:U2 0 -1 withscores //{(milk, -1)};
//计算U1与U2的评分相似度 
绝对值(开方((0-1)/2))=0.5 //2是元素数量
//计算U1与U2的评分相似度 
绝对值(开方((-1)))=1
//结论U2与U1的相似性要高于U3与U1的相似性,所以U2是最相近的用户
//找到推荐物品。具有最高分的物品推荐给了U1. 
ZUNIONSTORE recommendations:U1 3 user:U1:items user:U2:items user:U3:items WEIGHTS -1 1 1 AGGREGATE MIN
recommendations:U1 = {(bananas, -5), (milk, -4), (carrots, 4)}
//具有最高分的物品最值得推荐,所以推荐carrots给U1.

  1. 双十一电商购物车系统如何用Redis实现
    采用 hash 数据结构
//增加购物车,key=商品ID,value=数量
hset cart:1001 10021 1
hset cart:1001 10025 1
hset cart:1001 10025 1
//全选功能-获取所有该用户的所有购物车商品
hgetall cart:1001
//商品数量-购物车图标上要显示购物车里商品的总数
hlen cart:1001
//删除-要能移除购物车里某个商品,删除了购物车里商品ID为10079的商品
hdel cart:1001 10079 
//增加某个商品的数量 ,使购物车产品id为10021的商品数量增加了1.
hincrby cart:1001 10021 1
//减少某个商品的数量 ,使购物车产品id为10021的商品数量减少了1.
hincrby cart:1001 10021 -1

  1. 类似微信的社交App朋友圈关注模型如何设计实现
    用zset数据结构
//假设两个用户。用户ID分别为1001和2001。
zadd 1001:follow time(时间戳) 2001  
zadd 2001:fans time(时间戳) 1001  
//取消关注
zrem 1001:follow 2001
zrem 2001:follow 1001
//查看粉丝列表
zrange 1001:fans 0 -1
//查看关注列表
zrange 1001:follow 0 -1
//关注数量
zcard 1001:follow
//我单向关注Ta。即我关注的Ta,但是Ta并没有关注我的
zscore 1001:follow 2001 //true
zscore 1001:follow 2001 //false
//Ta单向关注我。即Ta关注我了,我并没有关注Ta
zscore 1001:follow 2001 //false  
zscore 1001:fans 2001 //true  
//互相关注。即我关注了Ta,Ta也关注了我
zscore 1001:follow 2001 //true  
zscore 1001:fans 2001 //true

  1. 美团单车如何基于Redis快速找到附近的车
    使用Geo指令,Geo的底层是zset数据结构
//添加Palermo,Catania的地理空间位置到Sicily
GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"

//从Sicily里返回Palermo,Catania的位置(经度和纬度)
GEOPOS Sicily Palermo Catania

//计算Palermo,Catania两个位置之间的距离,支持单位:m(米,默认单位);km(千米);mi(英里);ft(英尺)。
GEODIST Sicily Palermo Catania km

//georadius 格式:
// GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
//参数说明:
//WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
//WITHCOORD: 将位置元素的经度和维度也一并返回。
//WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
//COUNT 限定返回的记录数。
//ASC: 查找结果根据距离从近到远排序。
//DESC: 查找结果根据从远到近排序。
//以(15,37)为中心,返回Sicily当中,与中心的距离不超过200km的所有位置元素。
GEORADIUS Sicily 15 37 200 km WITHDIST

//以Agrigento为中心,返回Sicily当中,与中心的距离不超过200km的所有位置元素。
GEORADIUSBYMEMBER Sicily Agrigento 100 km

  1. Redis 6.0 多线程模型比单线程优化在哪里了
    Redis6.0 引入的多线程部分,实际上只是用来处理网络数据的读写和协议解析,执行命令仍然是单一工作线程。