redis原理

redis 基础知识

redis使用场景

Redis数据类型的应用业务场景分析   https://www.cnblogs.com/lippon/p/14226772.html

zset相关

zset:有序集合,当节点数量较小的时候,采用的是压缩list,当数据量较大的时候采用的是zskiplist,其score值和value值在zset中没有映射函数,是用户提供的score

当同时满足下面2个条件时会用到压缩列表,否则会用跳表:

  • 集合中元素都小于64字节

  • 集合中元素个数小于128个

  • 当然这个也是可以配置的,在redis.conf文件中:

zset保存了分数值,所以对于阅读量、点击量排行等场景可以很方便的使用。score使用了double类型进行存储,所以存在小数点精度问题, 并且set-样也是 string 类型元素的集合,且不允许重复的成员,当score相同时候,会按照插入的field的字典序排序,即abcacd靠前,因为第一个字母都是a,但是b比c靠前。

zslnode有n多个forward节点,但只有一个backward节点。

zslnode的层数是通过随机算法分配的,通过随机算法可以给节点分配合理的层数,但是由于ZSKIPLIST_P=0.25(也就是晋升率25%),所以官方的跳跃列表更加的扁平化,层高相对比较低,在单个层中需要遍历的节点数量稍微多点;且官方中最高层只有ZSKIPLIST_MAXLEVEL 32层,随机层数算法如下:

/*
 * 返回一个随机值,用作新跳跃表节点的层数。
 *
 * 返回值介乎 1 和 ZSKIPLIST_MAXLEVEL 之间(包含 ZSKIPLIST_MAXLEVEL),
 * 根据随机算法所使用的幂次定律,越大的值生成的几率越小。
 *
 * T = O(N)
 */
int zslRandomLevel(void)
{
    int level = 1;

    while ((rand()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;

    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

SDS空间预分配策略和惰性删除策略

空间预分配用于优化 SDS 的字符串增长操作: 当 SDS 的 API 对一个 SDS 进行修改, 并且需要对 SDS 进行空间扩展的时候, 程序不仅会为 SDS 分配修改所必须要的空间, 还会为 SDS 分配额外的未使用空间。

其中, 额外分配的未使用空间数量由以下公式决定:

如果对 SDS 进行修改之后, SDS 的长度(也即是 len 属性的值)将小于 1 MB , 那么程序分配和 len 属性同样大小的未使用空间, 这时 SDS len 属性的值将和 free 属性的值相同。 举个例子, 如果进行修改之后, SDS 的 len 将变成 13 字节, 那么程序也会分配 13 字节的未使用空间, SDS 的 buf 数组的实际长度将变成 13 + 13 + 1 = 27 字节(额外的一字节用于保存空字符)。
如果对 SDS 进行修改之后, SDS 的长度将大于等于 1 MB , 那么程序会分配 1 MB 的未使用空间。 举个例子, 如果进行修改之后, SDS 的 len 将变成 30 MB , 那么程序会分配 1 MB 的未使用空间, SDS 的 buf 数组的实际长度将为 30 MB + 1 MB + 1 byte 。

惰性删除:

惰性空间释放用于优化 SDS 的字符串缩短操作: 当 SDS 的 API 需要缩短 SDS 保存的字符串时, 程序并不立即使用内存重分配来回收缩短后多出来的字节, 而是使用 free 属性将这些字节的数量记录起来, 并等待将来使用。

SDS字符串和C字符串的区别

1.通过使用SDS字符串(len记录字符串长度),使得获取字符串长度的复杂度从O(N)变为O(1)

2.杜绝缓存区溢出,C字符串不记录自身长度,在拼接字符串时可能造成缓存区溢出

3.通过未使用空间free,减少修改字符串带来的内存重分配次数

 

hash相关

redis的hash数据结构使用siphash算法,而memcached作者使用jenkins_hash或者murmur3_hash

hash攻击,如果hash函数存在偏向性,,那么hash函数可能在特定模式下的输入会导致hash第二维链表长度不均匀,甚至所有的元素都集中到个别链表中,导致查询效率急速下降,从o(1)退化到o(n)

 

 

redis scan命令详解

scan的遍历顺序是高位进位加法遍历,是考虑到字节的扩容和缩容时避免曹伟的遍历重复和遗漏

例如redis原来的槽位总共8,keyA在槽7,如果扩容2倍以后,现有槽位16,那么keyA有可能现在在槽位7或者15,那么如果从0开始逐渐增加,可能会导致keyA重复遍历.但是不可能会导致遗漏,如果是高位加法,不会导致重复遍历,但是有可能遗漏

假如槽位缩容从16-》8,那么keyA就一定在槽位7,如果从0开始最贱增加,可能会导致遗漏,但是不会导致重复遍历,但是高位加法的话,可能会导致重复遍历,但不会导致遗漏

 

REDIS 事务

Redis事务详解,吃透数据库没你想的那么难 https://blog.51cto.com/u_14230003/2530143

涉及到multi,exec,discard,watch/unwatch5个指令

EXEC指令将会触发事务中所有的操作被写入AOF文件(如果开启了AOF),然后开始在内存中实施这些数据变更操作;

 

 

 

redis常见问题和解决办法

阿里面试Redis最常问的三个问题:缓存雪崩、击穿、穿透  https://blog.csdn.net/qq_35190492/article/details/102889333

redis作者建议的最大集群规模是1000个节点,因为和一致性哈希构建的redis集群不一样,redis cluster不能做成超大规模的集群,它适合作为中等规模集群的解决方案

redis采用单线程模型,出去bgsave和aof rewrite会另外新建进程外,所有的其你去和操作都是在主进程内完成,其中比较重量级的请求和操作类型有:

客户端请求

集群通信

从节点同步

AOF文件

其他定时任务

redis分布式锁

分布式锁是一种思想,它的实现方案有很多,许多的分布式软件例如redis,memcache以及数据库都会存在具体的实现,实现方案大同小异,分布式锁主要包含三个过程:加锁,解锁以及锁超时。

补充,redis的优点:

  1. 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
  2. 支持丰富数据类型,支持string,list,set,sorted set,hash
  3. 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
  4. 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

redis分布式锁实现

满足特性

一个redis分布式锁需要实现三个特性才能满足最低要求:

1.独享(排斥性),首先任何一个时刻只有一个客户端持有锁。

2,无死锁:当持有锁的客户端崩溃或者网络异常情况下,锁可以被释放,可被其他客户端获取

3,容错:当集群中的某些redis节点服务停止,只要大部分节点还活着,redis锁还可以被获取。

最简单的实现-基于故障转移的实现

redis分布式锁最简单的一个实现就是在redis中设置一个key,并设置key的过期失效时间,以保证锁会被自动释放,当客户端释放该锁的时候,删除该key即可。这是目前很多分布式锁的实现算法,简单明了,但是却存在一个问题:这个算法存在一个严重的单节点失败问题。或许进一步有人会说增加一个slave节点,但其实是实现不同的,因为它不能实现资源的独享,因为redis的主从同步通常是异步的。

例如,在这种场景(主从结构)中存在明显的竞态:

  1. 客户端A从master获取到锁
  2. 在master将锁同步到slave之前,master宕掉了。
  3. slave节点被晋级为master节点
  4. 客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。安全失效!

即当某个节点停止服务的时候,多个客户端同时获取同一把锁,如果能够接受这种小概率的错误,那么这种实现方案是可以被接受的。

单节点redis实现分布式锁算法

set resource random_value NX PX 3000

这种算法的主要核心在于:在上锁过程中,A客户端给key设置某一个随机数,这个随机数在任意客户端任意时刻需要满足唯一性,满足唯一性是为了更安全的释放锁。当A在处理某个阻塞任务时,即便这个锁在中途由于时间失效而被释放,并且B客户端获取到该锁后,A客户端完成阻塞任务,需要释放该锁,也会由于key值不相同,而无法释放由B加上的锁。

key的失效时间被称为“锁定有效时”,它不仅仅是key的自动失效时间,也是锁在被一个客户端获取后还可被另外一个客户端获取的有效时间。单节点分布式锁算法也是实现分布式加锁算法的基础。

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

redlock算法

相关文章  https://blog.csdn.net/weixin_39312465/article/details/88677892

概念:

1.TTL:Time To Live;只 redis key 的过期时间或有效生存时间

2.clock drift:时钟漂移;指两个电脑间时间流速基本相同的情况下,两个电脑(或两个进程间)时间的差值;如果电脑距离过远会造成时钟漂移值 过大

3.脑裂://TODO

 

Redission源码原理

Java中最为流行的redis分布式库中最为流行的便是redission,redission的原理如下:

redission源码分析

redission内部有多种锁的实现形式,默认的是redissionLock(集群单节点的锁,并没有实现红锁),然后还有RedissionRedLock(这种锁实现了redlock的算法)

redissionLock计算slot

redissionLock会将一段lua脚本发送到redis节点中(因为redis中的操作都是原子性的),通过lua脚本可以将一堆业务复杂的执行逻辑l封装,并交由redis原子性执行,lua脚本如下

这段脚本的含义是:

如果key[1]不存在,则设置ke1[1]的值argv[2],并设置过期时间argv[1];【保证当A1客户端还在阻塞执行的时候,不为其他客户端解锁】

如果key[1]值存在并且存储的值为argv[2],则重入锁并加1【可以保证watch dog监督延长加锁时间】

其中argv[1]表示过期时间,argv[2]表示客户端编号UUID:线程id(长整型),类似8743c9c0-0795-4907-87fd-6c719a6b4586:1

其中key[1]就是业务设置的key。

 

 

redis高并发数据同步一致性问题解决

https://zhuanlan.zhihu.com/p/141537171  这里提到延迟双删(先删缓存,更新数据库,再删除缓存)或者(先更新数据库,再异步删除缓存)使用cannal监听binlog,高并发下延迟双删并不太好

https://blog.csdn.net/simba_1986/article/details/77823309  这里

https://blog.kido.site/2018/12/01/db-and-cache-01/ 数据与缓存一致性文章

 

 

为什么hash比string更节省空间

1,redis的可以承载来了很多特新,过期时间,LRU,Cluster节点信息,系统关系信息,因此和String类型的key-value相比,hash可以更大现在读的减少可以的数量,从而更节省空间使用

2  hash 在额定数量及容积下,将会一一维线性的紧凑格式存储ziplist,这种村粗形式更加节省空间。但是超过额定阈值后会转换成hashtable格式并重新存储,而hashtable的存储对于内存的使用不占优势

    hash-max-ziplist-entries:hash以ziplist存储的最大对象数量,单位:个,默认512

    hash-max-ziplist-value:hash以ziplist存储的单个对象占用空间,单位:bytes 默认64

当超过了任意一个限制条件时,hash都会转成字典

但是ziplist的缺点是格式读取速度相对低效,因此在实际应用中,对应存储格式的选型,需要根据实际需求作为衡量标准。

3,key对应多种数据结构,而hash key对应的value只能是string,这个简单结构比多重复杂结构更加高效,redis不支持嵌套数据的,例如在hash中的value还嵌套hash.

 

拒绝bigkey

string类型控制在10KB以内,hash,list,set,zset元素个数最好不要超过5000

bigkey过期时间自动删除问题:例如一个200万的zset设置一小时过期,会触发del操作,造成阻塞,而且该操作不会出现在慢查询中

控制key的生命周期:建议使用expire设置过期时间,条件允许可以打撒过期时间,防止几种过期

 

例如hgetall lrange,smembers,zrange,sinter并非不能使用,但是要明确N的值,有便利的需求可以使用hscan,sscan ,zscan代替.

推荐使用批量操作条效率

原生命令例如mget,mset,非原生命令:可以使用pipeline提高效率,注意控制一次批量曹总的元素个数

注意两个不同

1:原生是原子操作,pipeline是非原子操作

2.pipeline可以打包不同的命令,原生做不到

3pipeline需要客户端和服务端同时支持

 

monitor命令可以打印出当前所有的执行命令,但是性能有问题

 

redis过期键淘汰策略

redis会将有过期时间的key单独存放到过期字典中,然后默认每10秒扫描过期删除,随机获取20个key,删除这20个中已经过期的key,如果过期的key比例超过了1/4,则继续走删除流程,直达过期字典中的过期Key变的稀疏。

因为扫描删除并不是后台处理的,而是会占据主线程的时间片,所以如果有大量的key同时过期,则会持续扫描,循环很多次,频繁回收内存也产生cpu消耗,因此会阻塞,而这个卡顿不会出现在慢查询中。

主节点在删除完过期key以后,会发送delete命令通知从节点删除过期key,所以当主节点还没有发送delete命令宕机了,则会发生主从数据不一致情况。

当实际内容超过maxmemory的时候,会腾出新的空间继续提供读写服务(惰性策略)

删除策略:

noneviction:默认,不会删除,不会造成数据丢失,但是会让命令执行不下去,需要人工处理

volatile-lru:躺尸淘汰设置了过期时间的key,最少使用的key优先被淘汰,没有设置过期时间的key不会被淘汰,保证需要持久化的数据不会被淘汰

volatile-ttl:比较剩余寿命,剩余寿命越短越先优先淘汰

volatile-random:过期key集合中随机key

allkeys-lru:在全体key中用lru算法淘汰key

allkeys-random:在全体key中随机淘汰

redis使用近似lru算法,随机次奥洋maxmmory_samples个key,淘汰最旧的key,这样节省内存,redis4.0引入LFU淘汰策略:volatile-lfu ,allkeys-lfu

结论:做缓存的话,可以使用allkeys-,需要持久化的,使用volatile

 

redis持久化策略-分为两种,都需要配置

RDB:

AOF:将执行的命令以文本协议 形式写到AOF缓冲区中,优化点主节点不持久化,从节点开启aof持久化

appendonly yes

appendfsync

    always:每次更改都需要写磁盘

    everysec:没秒写磁盘

   no:取决于操作系统

 

AOF持久化:redis需要每秒1此同步aof日志到磁盘中,确保消息尽量不丢失,需要调用sync函数,这个操作比较耗时,会导致主线程的效率降低,所以redis也将这个操作移到异步线程中完成,执行aof sync操作的线程是一个独立的异步线程

和惰性删除不是一个线程,同样它也有一个属于自己的任务队列,队列里面只用来存放aofsync队列。

全量同步:当增加一个salve或者salve起来的时候,会从主节点中获取全部的数据,

增量同步:主节点每修改一个数据,都会同步到slave中

 

水平扩容

 

flushdb和flushall的区别,以及unlink

1.flushall 清空数据库并执行持久化操作,也就是rdb文件会发生改变,变成76个字节大小(初始状态下为76字节),所以执行flushall之后数据库真正意义上清空了.

2.flushdb 清空数据库,但是不执行持久化操作,也就是说rdb文件不发生改变.而redis的数据是从rdb快照文件中读取加载到内存的,所以在flushdb之后,如果想恢复数据库,则可以直接kill掉redis-server进程,然后重新启动服务,这样redis重新读取rdb文件,数据恢复到flushdb操作之前的状态.

这两个命令式及其韩漫的操作,redis4.0给这两个命令提供了异步化,在指令后面增加async参数可以将命令扔给后台线程执行

 

unlink xxkey这个删除指令是在redis4.0中引入的,它能对删除操作进行懒处理,丢给后台线程来异步回收内存,,它能解决下面情况:删除指令会直接释放对象的内存,在大部分情况下,这个指令非常快,没有明显延迟

不过如果被删除的key是一个非常大的对象,比如一个包含了上千万个元素的hash,那么这个删除指令就会导致单线程卡顿

但是不是所有的unlink命令斗湖延后处理,如果对应的key所占的内存很小,延后处理就没偶遇必要了,这个时候redis就会将对应的key的内存立即回收,和del命令一样

 

异步化的主要过程:主线程接受到flushall async或者unlink命令后,将对象的索引从“大树”中摘除,会将这个key的内存回收操作包装成一个任务,塞进异步任务队列中,后台线程会从这个异步队列中取任务,任务队列被主线程和异步线程同时操作,所以这个队列必须是一个线程安全的队列。

 

pipeline模式

pipeline一次性发送多条米宁在读取返回数据,pipeline不只减少了RTT的消耗,也减少了网络IO的read/write系统调用,通过piepline模式的使用,美妙的处理能力与非管道模式比有10+倍的提升。

redis集群不存在中心或者代理节点,每个节点都包含集群中所有的节点信息。

集群中的节点不会代理请求,如果client将命令发送到错误的节点上,操作失败并返回“”-MOVED“” "-ASK" 供client进行永久或者临时的节点切换

当进行批量操作时,同一批操作可能涉及到多个server,如果client接收到MOVED、ASK,同时通过节点上的集群信息得到对应的server,可以最大化减少MOVED的错误,但是由于client无法感知到server的变化,因此MOVED无法完全避免,ASK属于key迁移的中间状态,在集群迁移过程中无法避免,因此在集群添加或者及诶点减少,或者主从切换的时候使用pipeline模式,很容易发生MOVED/ASK异常,导致后续操作复杂度提高很多,因此jedisCluster要提供一个稳定的pipeline操作时非常复杂的,但是具体到具体业务上,我们可以限定使用场景,这样既可以实现集群下的pipeline,复杂度也不会提高太多,。

 

跳表和自平衡树

跳跃列表(Skip List)与其在Redis中的实现详解 https://www.jianshu.com/p/09c3b0835ba6

 

save和bgsave

 SAVE 命令在执行时会直接阻塞当前的线程,由于 Redis 是 单线程 的,所以 SAVE 命令会直接阻塞来自客户端的所有其他请求,这在很多时候对于需要提供较强可用性保证的 Redis 服务都是无法接受的。我们往往需要 BGSAVE 命令在后台生成 Redis 全部数据对应的 RDB 文件,当我们使用 BGSAVE 命令时,Redis 会立刻 fork 出一个子进程,子进程会执行『将内存中的数据以 RDB 格式保存到磁盘中』这一过程,而 Redis 服务在 BGSAVE 工作期间仍然可以处理来自客户端的请求。

 Redis 选择使用了 fork 的方式来解决快照持久化的问题,那就说明这两个问题已经有了答案,首先 fork 之后的子进程是可以获取父进程内存中的数据的,而 fork 带来的额外性能开销相比阻塞主线程也一定是可以接受的,只有同时具备这两点,Redis 最终才会选择这样的方案。

在redis2.8x版本以后,redis可以进行无盘复制

redis复制是异步的,但是wait指令可以将异步复制变身同步复制,确保系统的强一致性。wait指令提供两个参数,第一个是从节点的数量N,第二个参数是时间t,以毫秒为单位,两个参数的含义是等待wait指令之前的所有写操同步到x1个从节点,最多等待x2毫秒,如果时间x2=0,则表示无限等待n个从节点同步完成,这个很容易出现阻塞,容易导致redis服务丧失可用性。

将某个key强制分配到指定slot中

jedisCluster.sadd("PhiAd{materialType}qqqq","71","73");
jedisCluster.sadd("PhiAd{materialType}ssss","72","73");
Set<String> set = jedisCluster.sinter("PhiAd{materialType}qqqq","PhiAd{materialType}ssss");
System.out.println(JedisClusterCRC16.getSlot("PhiAd{materialType}qqqq"));
System.out.println(JedisClusterCRC16.getSlot("PhiAd{materialType}ssss"));

解释:redis在使用hash算法将键映射到slot时,只会计算{}里面的内容,若{}内的内容相同,则将键映射到同一个slot

           例子中{}内容均为materialType,这样在JedisClusterCRC16.getSlot(key)时得到相同的slot编码号。

          这样就可以使用jedisCluster.sinter(key1,key2)方法取交集,避免了键在不同的slot时,该方法报错

 

 

redis cluster模式下从节点不可读

redis cluster集群中slave节点能成功复制master节点数据槽数据,但是无法get数据,显示只能到对应的master节点读取: (error) MOVED 742 36.112.201.233:6110

原因:Redis Cluster集群中的从节点,官方默认设置的是不分担读请求的、只作备份和故障转移用,当有请求读向从节点时,会被重定向对应的主节点来处理

解决办法:在get数据之前先使用命令readonly,这个readonly告诉 Redis Cluster 从节点客户端愿意读取可能过时的数据并且对写请求不感兴趣

链接:

https://www.jianshu.com/p/47fd7f86c848

http://redis.cn/topics/distlock.html  这个链接里面有很多redis中可以学习的地方

https://juejin.im/post/5bf3f15851882526a643e207

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值