Redis

12 minute read

redis 数据结构及其底层实现 https://juejin.cn/post/6844903929545932808 https://juejin.cn/post/6844903936520880135 https://juejin.cn/post/6863258283483807752 string list set zset hash

Redis的key是顶层模型,它的value是扁平化的。Redis中,所有的value都是一个object typedef struct redisObject { unsigned [type] 4; unsigned [encoding] 4; unsigned [lru] REDIS_LRU_BITS; int refcount; void *ptr; } robj; type:数据类型,就是我们熟悉的string、hash、list等。 encoding:内部编码,其实就是本文要介绍的数据结构。指的是当前这个value底层是用的什么数据结构。因为同一个数据类型底层也有多种数据结构的实现,所以这里需要指定数据结构。 REDIS_LRU_BITS:当前对象可以保留的时长。这个我们在后面讲键的过期策略的时候讲。 refcount:对象引用计数,用于GC。 ptr:指针,指向以encoding的方式实现这个对象的实际地址。

string: 在Redis内部,string类型有两种底层储存结构 int:存放整数类型; SDS:存放浮点、字符串、字节类型;底层是一个char数组,buf最大容量为512M

list: list底层有两种数据结构:链表linkedlist和压缩列表ziplist。 当list元素个数少且元素内容长度不大时,使用ziplist实现,否则使用linkedlist。 Redis使用的链表是双向链表 压缩列表有点儿类似数组,通过一片连续的内存空间,来存储数据。不过,它跟数组不同的一点是,它允许存储的数据大小不同。 每个节点上增加一个length属性来记录这个节点的长度,这样比较方便地得到下一个节点的位置。 压缩列表不只是list的底层实现,也是hash的底层实现之一。当hash的元素个数少且内容长度不大时,使用压缩列表来实现。

hash: hash底层有两种实现:压缩列表和字典(dict) 压缩列表:上文已讲过 字典:字典其实就类似于Java语言中的Map,Python语言中的dict。与Java中的HashMap类似,Redis底层也是使用的散列表作为字典的实现,解决hash冲突使用的是链表法。 在键增加或减少时,会扩容或缩容,并且进行rehash,渐进式rehash

set: set的实现比较简单。 如果是整数类型,就直接使用整数集合intset。使用二分查找来辅助,速度还是挺快的。不过在插入的时候,由于要移动元素,时间复杂度是O(N)。 如果不是整数类型,就使用上面在hash那一节介绍的字典。key为set的值,value为空。

zset: 如果元素个数不多且不大,就使用压缩列表ziplist来存储。不过由于zset包含了score的排序信息,所以在ziplist内部,是按照score排序递增来存储的。意味着每次插入数据都要移动之后的数据。 跳表(skiplist)是另一种实现zset的数据结构 同list类似,Redis内部也不是直接使用的跳表,而是使用了一个自定义的数据结构来持有跳表

bitmap: 位图 redis提供了这样这样一种数据结构、setbit getbit,Redis 实现布隆过滤器的底层就是通过 bitmap 这种数据结构 redisson这种框架已经封装好相关api https://www.cnblogs.com/ysocean/p/12594982.html https://juejin.cn/post/6844903862072000526

geo: 这种数据结构可以将用户给定的地理位置(经度和纬度)信息储存起来,并对这些信息进行操作 https://my.oschina.net/xsh1208/blog/2218494

Redis对外暴露的是对象(数据类型),而每个对象都是用一个redisObject持有,通过不同的编码,映射到不同的数据结构。 从最开始的那个图可以知道,有时候不同对象可能会底层使用同一种数据结构,比如压缩列表和字典等。

redis是有C语言实现的 string 底层是simple dynamic string,动态字符串,是redis自己实现的数据结构,优点是无需担心内存溢出,常数时间能获取长度,扩容会预分配内存

hash 扩容,会生成一个新的hash表ht[1],原hash表 ht[0] 渐进式hash+辅助rehash 渐进式hash,每一次操作会将ht[0]的数据迁移到ht[1]中,ht[0]不再新增数据,也就是ht[0]处于不断缩小的状态 辅助rehash,当服务器负载不高的时候,会自动作hash迁移 优点:采取分治的思想实现平滑迁移 缺点:内存使用量变大

只有在没有执行redis持久化的时候,才可以执行扩缩容

持久化 RDB方式比较粗粒度,发生故障时,两次持久化之间的数据会丢失 AOF方式会写磁盘,如果io慢,会阻塞主进程

redis6.0引入多线程,指的是网络io处理方面引入多线程,read/write系统调用相对于命令的执行占用了大量的CPU时间,执行命令仍然是单线程,看过初略对比,多线程读写性能是单线程的两倍

redis事务 客户端执行multi命令,客户端状态切换,从非事务状态切换到事务状态,不支持嵌套事务 redis事务不支持回滚 Redis事务执行过程中,如果一个命令执行出错,那么就返回错误,然后还是会接着继续执行下面的命令。

命令入队 如果客户端发送的命令为 EXEC、DISCARD、WATCH、MULTI 四个命令的其中一个,那么服务器立即执行这个命令。 与此相反,如果客户端发送的命令是 EXEC、DISCARD、WATCH、MULTI 四个命令以外的其他命令,那么服务器并不立即执行这个命令。 首先检查此命令的格式是否正确,如果不正确,服务器会在客户端状态(redisClient)的 flags 属性打开 REDIS_MULTI 标识,并且返回错误信息给客户端。 如果正确将这个命令放入一个事务队列里面,然后向客户端返回 QUEUED 回复。

watch命令作用? 在exec执行之前监控key是否修改过 https://blog.csdn.net/neweastsun/article/details/106949294

客户端发送 EXEC 命令,服务器执行 EXEC 命令逻辑。

如果客户端状态的 flags 属性不包含 REDIS_MULTI 标识,或者包含 REDIS_DIRTY_CAS 或者 REDIS_DIRTY_EXEC 标识,那么就直接取消事务的执行。 否则客户端处于事务状态(flags 有 REDIS_MULTI 标识),服务器会遍历客户端的事务队列,然后执行事务队列中的所有命令,最后将返回结果全部返回给客户端;

redis作缓存

什么是缓存雪崩? 缓存数据设置的过期时间是相同的,并且Redis恰好将这部分数据全部删光了。这就会导致在这段时间内,这些缓存同时失效,全部请求到数据库中。 例如, 1,Redis挂掉了,请求全部走数据库。 2,对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。

如何预防缓存雪崩? 1,缓存数据设置的过期时间是相同 解决在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。 2,Redis挂掉 事发前:实现Redis的高可用(主从架构+Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生。 事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的) 事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。

什么是缓存穿透? 查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。 请求的数据在缓存大量不命中,导致请求走数据库。

如何预防缓存穿透? 1,接口参数校验,由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层! 2,当我们从数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。这种情况我们一般会将空对象设置一个较短的过期时间。 从缓存取不到的数据,在数据库中也没有取到,这时也可以将对应Key的Value对写为null、位置错误、稍后重试这样的值具体取啥问产品,或者看具体的场景,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。

Redis还有一个高级用法布隆过滤器(Bloom Filter)这个也能很好的防止缓存穿透的发生, 他的原理也很简单就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。

什么是缓存击穿? 至于缓存击穿嘛,这个跟缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,打崩了DB, 而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发, 大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。

如何保证缓存和数据库的一致性? 四种缓存模式: cache aside模式 cache和数据库分开 读操作,缓存没有时,读数据库然后写进缓存;写操作,先写数据库,然后使缓存失效 什么时候使用: 1,当Cache不提供原生的Read-Through和Write-Through操作的时候 2,资源的需求是不可预测的时候。Cache-Aside模式令应用可以根据需求来加载数据。对于应用需求什么数据,不需要提前做出假设。

read through模式 缓存和数据库是一个整体 write through模式 缓存和数据库是一个整体 write back模式 读写都在缓存,异步批量更新数据库

什么是分布式锁? 分布式锁是不同进程间采用互斥的方式操作共享资源。

为什么需要分布式锁? Volatile、Synchronized、ReentrantLock等保证进程内的线程安全,但无法同时锁住两台不同机器上的代码,所以需要分布式锁。

解决了什么问题? 1,提升效率:加锁是为了避免不必要的重复处理。例如防止幂等任务被多个执行者抢占。此时对锁的正确性要求不高; 2,保证正确性:加锁是为了避免Race Condition导致逻辑错误。例如直接使用分布式锁实现防重,幂等机制。此时如果锁出现错误会引起严重后果,因此对锁的正确性要求高。

分布式锁有哪些场景? 修改文章,多个客户端同时修改同一篇文章 分布式服务基于原数据进行更新操作的时候都有类似需求

分布式锁有哪些方案? 1,Reids的分布式锁,很多大公司会基于Redis做扩展开发。 redis分布式锁 2,基于Zookeeper zookeeper分布式锁 3,基于数据库,比如Mysql。

redis分布式锁实现方案

加锁:set函数可以实现对key设置值,为防止死锁,并设置过期时间,根据返回的true或false判断是否已经加锁 public class RedisLockDemo {     private static final String SET_IF_NOT_EXIST = ”NX”;     private static final String SET_WITH_EXPIRE_TIME = ”PX”;     /*      获取分布式锁      * @param jedis Redis客户端      * @param lockKey 锁      * @param requestId 请求标识      * @param expireTime 超期时间      * @return 是否获取成功      */     public static boolean getLock(Jedis jedis, String lockKey, String requestId, int expireTime) {     // 两步合二为一,一行代码加锁并设置 + 过期时间。         if (1 == jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime)) {             return true;//加锁成功         }         return false;//加锁失败     }}

解锁:利用lua脚本实现解锁的原子性,根据返回的true或false判断是否已经解锁 public class RedisTool {     private static final Long RELEASE_SUCCESS = 1L;     /**      * 释放分布式锁      * @param jedis Redis客户端      * @param lockKey 锁      * @param requestId 请求标识      * @return 是否释放成功      */    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {         String script = ”if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;         Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));         if (RELEASE_SUCCESS.equals(result)) {             return true;         }         return false;     }}

当使用redis作分布式锁的时候,也需要考虑redis的部署问题 redis有3种部署方式: 1,单机模式 2,master-slave + sentinel选举模式 3,redis cluster模式 使用redis做分布式锁的缺点在于:如果采用单机部署模式,会存在单点问题,只要redis故障了。加锁就不行了。 采用master-slave模式,加锁的时候只对一个节点加锁,即便通过sentinel做了高可用,但是如果master节点故障了,发生主从切换,此时就会有可能出现锁丢失的问题。 如果采用redis cluster模式,同样会有上述问题,加锁会先对集群中的某个主节点加锁,然后异步复制到其备节点。如果还没复制到备节点时,主节点宕机,这时备节点上选举为主节点。这把锁已经丢失。 所以这种方式有以下俩个缺点: 1,会有锁丢失的问题 2,set key value nx px 3000 如果3000毫秒之后业务逻辑还没执行完,key过期,锁释放。其他线程获取到锁。这样一来也会造成线程安全问题。

目前已经有成熟的Redis分布式锁开源框架 Redisson 操作封装了很多细节,提供了两个接口,只需要通过它的api中的lock和unlock即可完成分布式锁 1,redisson所有指令都通过lua脚本执行,redis支持lua脚本原子性执行 2,redisson设置一个key的默认过期时间为30s,如果某个客户端持有一个锁超过了30s怎么办? 3,redisson中有一个watchdog的概念,翻译过来就是看门狗,它会在你获取锁之后,每隔10秒帮你把key的超时时间重新延后30s,这样的话,就不会出现业务没执行完而key过期了,其他线程获取到锁的问题了。 4,redisson的“看门狗”逻辑保证了没有死锁发生。(如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期了,其他线程可以获取到锁)

zookeeper分布式锁实现方案

使用zk的临时顺序节点的特性来实现 1,客户端调用create()方法创建名为“/dlm-locks/lockname/lock-”的临时顺序节点。 2,客户端调用getChildren(“lockname”)方法来获取所有已经创建的子节点。 3,客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,就是看自己创建的序列号是否排第一, 如果是第一,那么就认为这个客户端获得了锁,在它前面没有别的客户端拿到锁。如果创建的节点不是所有节点中需要最小的,那么则监视比自己创建节点的序列号小的最大的节点,进入等待。 直到下次监视的子节点变更的时候,再进行子节点的获取,判断是否获取锁。

目前有成熟的开源zk客户端curator,也可以实现分布式锁 也封装了很多逻辑,对外提供了两个接口 InterProcessMutex interProcessMutex = new InterProcessMutex(client,”/anyLock”); interProcessMutex.acquire(); interProcessMutex.release();`

zk和redis分布式锁的对比 先说Reids: Rdis只保证最终一致性,副本间的数据复制是异步进行(Set是写,Get是读,Reids集群一般是读写分离架构,存在主从同步延迟情况), 主从切换之后可能有部分数据没有复制过去可能会丢失锁情况,故强一致性要求的业务不推荐使用Reids,推荐使用zk。 Redis集群各方法的响应时间均为最低。随着并发量和业务数量的提升其响应时间会有明显上升(公有集群影响因素偏大),但是极限qps可以达到最大且基本无异常 再说ZK: 使用ZooKeeper集群,锁原理是使用ZooKeeper的临时节点,临时节点的生命周期在Client与集群的Session结束时结束。 因此如果某个Client节点存在网络问题,与ZooKeeper集群断开连接,Session超时同样会导致锁被错误的释放(导致被其他线程错误地持有),因此ZooKeeper也无法保证完全一致。 ZK具有较好的稳定性;响应时间抖动很小,没有出现异常。但是随着并发量和业务数量的提升其响应时间和qps会明显下降。

对于redis的分布式锁而言,它有以下缺点: 1,它获取锁的方式简单粗暴,获取不到锁直接不断尝试获取锁,比较消耗性能。 2,另外来说的话,redis的设计定位决定了它的数据并不是强一致性的,在某些极端情况下,可能会出现问题。锁的模型不够健壮 即便使用redlock算法来实现,在某些复杂场景下,也无法保证其实现100%没有问题,关于redlock的讨论可以看How to do distributed locking

但是另一方面使用redis实现分布式锁在很多企业中非常常见,而且大部分情况下都不会遇到所谓的“极端复杂场景” 所以使用redis作为分布式锁也不失为一种好的方案,最重要的一点是redis的性能很高,可以支撑高并发的获取、释放锁操作。

对于zk分布式锁而言: 1,zookeeper天生设计定位就是分布式协调,强一致性。锁的模型健壮、简单易用、适合做分布式锁。 如果获取不到锁,只需要添加一个监听器就可以了,不用一直轮询,性能消耗较小。 但是zk也有其缺点:如果有较多的客户端频繁的申请加锁、释放锁,对于zk集群的压力会比较大。

场景问题: 高并发的情况下,kafka消费到第一条记录,在下沉服务处理好,但由于网络抖动没有正确返回给上层,上层服务再次调用服务,怎么样保证只处理一次请求? 分布式锁,redis分布式锁和zookeeper分布式锁的实现

codis是redis cluster的中间件客户端 redis cluster,主要是针对海量数据+高并发+高可用的场景。redis cluster 支撑 N 个 redis master node,每个 master node 都可以挂载多个 slave node。这样整个 redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。

redis cluster Cluster中的每个节点都维护一份在自己看来当前整个集群的状态,主要包括: 当前集群状态 集群中各节点所负责的slots信息,及其migrate状态 集群中各节点的master-slave状态 集群中各节点的存活状态及不可达投票

自动将数据进行分片,每个 master 上放一部分数据 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。

16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。 cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

优点:无中心节点(所有Redis节点都是对等的节点,同步数据使用的是Gossip协议),数据按照槽存储分布在多个 Redis 实例上,可以平滑的进行节点 扩容/缩容,当节点数量改变时,只需要动态更改节点负责的槽就行,这对于客户端来说是透明的。不需要依赖中间件,运维成本低。 缺点:严重依赖 Redis-trib 工具,缺乏监控管理,Failover节点的检测过慢,Gossip协议传播消息到最终一致性有一定的延迟。

集群元数据的维护有两种方式:集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。

集中式是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 storm。 它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。

redis维护集群元数据采用另一个方式, gossip 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。

集中式与gossip比较: 集中式的好处在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;不好在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。 gossip好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。

gossip协议 gossip 协议包含多种消息,包含 ping,pong,meet,fail 等等。 meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。 ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。 pong:返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。 fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。

ping 时要携带一些元数据,如果很频繁,可能会加重网络负担。 每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 cluster_node_timeout / 2,那么立即发送 ping,避免数据交换延时过长,落后的时间太长了。 比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以 cluster_node_timeout 可以调节,如果调得比较大,那么会降低 ping 的频率。 每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含 3 个其它节点的信息,最多包含 总节点数减 2 个其它节点的信息。

优势 可扩展性:网络可以允许节点的任意增加和减少,新增加的节点的状态最终会与其他节点一致。 容错:网络中任何节点的宕机和重启都不会影响Gossip消息的传播,Gossip 协议具有天然的分布式系统容错特性。 去中心化:Gossip协议不要求任何中心节点,所有节点都可以是对等的,任何一个节点无需知道整个网络状况,只要网络是连通的,任意一个节点就可以把消息散播到全网。 最终一致性:Gossip协议实现信息指数级(logN)的快速传播,因此在有新信息需要传播时,消息可以快速地发送到全局节点,在有限的时间内能够做到所有节点都拥有最新的数据。

缺陷 消息的延迟:由于Gossip协议中,节点只会随机向少数几个节点发送消息,消息最终是通过多个轮次的散播而到达全网的,因此使用 Gossip 协议会造成不可避免的消息延迟。不适合用在对实时性要求较高的场景下。 消息冗余:Gossip协议规定,节点会定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤,因此就不可避免的存在消息重复发送给同一节点的情况,造成了消息的冗余,同时也增加了收到消息的节点的处理压力。 而且,由于是定期发送,因此,即使收到了消息的节点还会反复收到重复消息,加重了消息的冗余。

面试题: Redis 集群数据是如何复制? https://segmentfault.com/a/1190000022808576 https://www.cnblogs.com/amei0/p/8177076.html

  1. redisCluster的缺陷: a,键的批量操作支持有限,比如mset, mget,如果多个键映射在不同的槽,就不支持了 b,键事务支持有限,当多个key分布在不同节点时无法使用事务,同一节点是支持事务 c,键是数据分区的最小粒度,不能将一个很大的键值对映射到不同的节点 d,不支持多数据库,只有0,select 0 e,复制结构只支持单层结构,不支持树型结构。

基于Gossip协议的一些有名的系统:Apache Cassandra,Redis(Cluster模式),Consul等。 https://www.cnblogs.com/charlieroro/articles/12655967.html https://juejin.cn/post/6844904002098823181

redis zsort为什么使用跳跃表,而不使用红黑树? 首先分析下Redis的有序集合支持的操作: 1)插入元素 2)删除元素 3)查找元素 4)有序输出所有元素 5)查找区间内所有元素 其中,前4项红黑树都可以完成,且时间复杂度与跳表一致。 但是,最后一项,红黑树的效率就没有跳表高了。在跳表中,要查找区间的元素,我们只要定位到两个区间端点在最低层级的位置,然后按顺序遍历元素就可以了,非常高效。 而红黑树只能定位到端点后,再从首位置开始每次都要查找后继节点,相对来说是比较耗时的。 此外,跳表实现起来很容易且易读,红黑树实现起来相对困难,所以Redis选择使用跳表来实现有序集合。

sortedset同时会由两种数据结构支持,ziplist和skiplist. 只有同时满足如下条件是,使用的是ziplist,其他时候则是使用skiplist 有序集合保存的元素数量小于128个 有序集合保存的所有元素的长度小于64字节 当ziplist作为存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表结点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值. 当使用skiplist作为存储结构时,使用skiplist按序保存元素分值,使用dict来保存元素和分值的对应关系 https://blog.csdn.net/weixin_32394085/article/details/105289495 1)在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。 2)平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。 3)从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。 4)查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。 5)从算法实现难度上来比较,skiplist比平衡树要简单得多。 https://blog.csdn.net/qq9808/article/details/104865385

redis中的set和hset有什么区别? https://www.jianshu.com/p/41c3f1f57d1f set 就是普通的已key-value 方式存储数据,可以设置过期时间。时间复杂度为 O(1),没多执行一个 set 在redis 中就会多一个 key , hset 则是以hash 散列表的形式存储。超时时间只能设置在 大 key 上,单个 filed 则不可以设置超时 时间复杂度我百度了很多文章都说是 O(1) 但是我下面给出的参考文章说时间上的时间复杂度其实是 O(N) N 值是单个hash 上的 filed 个数 hash 上单个不适合存储大量的 filed 并且如果 filed 多了比较消耗cpu,但同时以 散列表存储则比较节省内存。 在实际的使用过程中应该使用 set 存储单个大文本非结构化数据 hset 则存储结构化数据,一个 hash 存储一条数据,一个 filed 则存储 一条数据中的一个属性,value 则是属性对应的值。

红黑树: https://blog.csdn.net/u010899985/article/details/80981053

redis的过期策略实现方式? https://blog.csdn.net/xiangnan129/article/details/54928672 懒汉式删除+定期删除

如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。

redis问题? 1、介绍一下redis5种基本数据类型和及其底层实现? 应用场景是什么? https://juejin.cn/post/6844903929545932808 https://my.oschina.net/xsh1208/blog/2218494 2、你了解到除了这5种数据类型之外,还有其它的数据类型吗?应用场景是什么? https://my.oschina.net/xsh1208/blog/2218494 https://juejin.cn/post/6844903862072000526 https://www.cnblogs.com/54chensongxia/p/13794391.html 3、redis zset为什么使用跳表,不使用红黑树? https://segmentfault.com/a/1190000037473381 4、redis hash扩容是怎么实现的? https://juejin.cn/post/6844903929545932808 5、redis key的过期删除是怎样实现的? https://segmentfault.com/a/1190000023098431 https://learnku.com/articles/55941 6、redis日志持久化有几种方式?优劣点是什么? https://cloud.tencent.com/developer/article/1442600 https://my.oschina.net/freelife/blog/4684893 https://blog.csdn.net/qq_32680371/article/details/111305595 https://www.cnblogs.com/liang24/p/14180984.html 7、redis为什么使用单线程?redis6.0引入了多线程指的是什么?redis单线程能支持这么高性能的原因有哪些? https://www.cnblogs.com/javastack/p/12848446.html https://blog.csdn.net/weixin_39946534/article/details/111170960 https://blog.csdn.net/xlgen157387/article/details/79470556 https://zhuanlan.zhihu.com/p/321901006 8、你了解io多路复用吗?redis是怎么实现的? https://www.jianshu.com/p/5158cec8673e https://blog.csdn.net/weixin_43314519/article/details/110604422 https://blog.nowcoder.net/n/7ec5076b77034999940db55ce12d451e?from=nowcoder_improve 9、redis事务有哪些相关命令? redis事务能回滚吗? https://blog.51cto.com/14799494/2490313 https://zhuanlan.zhihu.com/p/156889090 https://www.nowcoder.com/discuss/594650?type=5&channel=-1&source_id=discuss_tag_discuss_hot_nctrack 10、redis作缓存时,与数据库的一致性是如何保证的? 11、redis作缓存时、缓存击穿、缓存雪崩、缓存穿透的原因及解决方法? 12、分布式锁有哪些实现方案?基于redis实现的分布式锁的应用场景是什么? 13、你在项目中是怎么应用redis的? https://cloud.tencent.com/developer/article/1701574 https://blog.csdn.net/QQ1006207580/article/details/103243281 14、redis cluster的架构是怎么设计的?集群数据怎么同步?有台机器挂了会影响业务吗? https://cloud.tencent.com/developer/article/1444057 https://juejin.cn/post/6869619522559541255 https://blog.51cto.com/14279308/2484807 15、redis 操作5种基本类型的命令有什么? https://my.oschina.net/xsh1208/blog/2218494 16、redis pipeline机制是什么? https://zhuanlan.zhihu.com/p/72867786 https://juejin.cn/post/6844903646908399630 https://haicoder.net/note/redis-interview/redis-interview-redis-pipeline.html 17、redis的应用场景有哪些? https://www.cnblogs.com/songgj/p/8444984.html https://stor.51cto.com/art/202005/616005.htm

PS: https://www.jianshu.com/p/c7c352cb14fe

Updated:

Leave a comment