Redis

redis:

Redis的数据结构:String,List,Hash,Set,ZSet

String:字符串

Hash:键值对 hash表

List:(本质上是一个双向链表---->可以变成是一个消息队列),比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。

Set:set是可以自动排重的,提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能。

内部实现的是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

ZSet:内部是排序的(根据优先级)同时支持去重。

HyperLogLog(基数统计---->有一定误差,根据输入元素来计算基数,而不会储存输入元素本身),一般适用于对查看网站ip地址进行统计

bitmap 底层是Redis key value键值对 value中存储 0 1 来记录用户是否登录,用户签到,统计用户在线的情况 在1的情况下。

GEO 存储地理位置信息

1)Redis的深入理解(速度快的原因)的部分:

1.基于内存;

2.基于单线程:

这里的单线程是指Redis中的网络IO和键值对读写由一个线程完成的。但是其实内部进行了优化操作,比如说对Redis里的数据进行一个持久化的操作RDB,通过写时复制技术,由bgsave生成一个子进程来进行持久化操作。如果是多线程的话,那可能当出现多个线程抢夺共享资源,由于加锁会出现串行的情况。

3.IO多路复用:

img

Redis只运行单线程的请款下,同时存在多个监听多个套接字和已连接的套接字-。Redis封装了4钟多路复用程序,在使用时Redis会根据需要的性能和系统对其选择多路复用函数

Redis使用的多路复用:默认时使用epoll的异步非阻塞

IO多路复用的机制:

select :同步阻塞

poll:同步非阻塞 但是要轮询

使用select/poll的缺点:(1)需要有用户态传换成内核态 开销等待的时间要大(2)select的文件传输数量太小了 (3)poll使用链表保存文件描述符,因此没有了监视文件数量的限制

epoll:异步非阻塞

分为三个 1)epoll_create()创建一个epoll对象;2)调用epoll_ctl向对象添加套接字;3)epoll_wait收集发生事件的连接

优点:一般来说没有最大的并发连接的限制,跟数目和内存关系比较大

只是管理活跃的连接

共享内存,省略掉内存拷贝

4.支持多种数据结构:底层做出了很多改变

img

对于字符串 :String

SDS 简单动态字符串

1)使用SDS 相较于之前的char来存储字符串来说 来说可以获取常数级长度 时间复杂度会有一个提升 O(1) 之前是O(n)

杜绝缓冲区溢出

2)SDS有空间分配策略,如果对SDS进行修改,API会先检查空间是否足够,如果不够API会自动将SDS空间所需的大小,然后才会执行。

减少修改字符串时带来的内存分配次数

对于Redis这种可能会出现频繁操作的数据库来说 ,如果频繁对内存进行分配/释放操作会耗费时间

空间预处理:

在对SDS空间进行扩展的时候,SDS会先判断未使用空间的大小能否满足,如果足够就不用进行内存分配。

惰性空间释放:

当需要缩短字符串操作时,并不会让内存立即重新回收,字符串缩短后空出的内存,会使用free属性将这些字节数量记录起来,等待之后的重新使用。

二进制安全

原来的c语言的字符串中,字符串中不能包含空字符,否则最先被程序读入的空字符串被误认为是字符串结尾,只能保存文本数据而不能保存图片,音视频等二进制文件;但是SDS都会处理二进制的方式来处理SDS保存在buf数组的内容。

字典:键值对

哈希表 一个hash表可以有多个hash节点

img

哈希碰撞的解决办法:链式哈希

当需要进行扩容时:Redis 默认使用了两个全局哈希表:哈希表1和哈希表2。一开始,当你刚插入数据时,默认使用哈希表1,此时的哈希表2并没有被分配空间。随着数据逐步增多,Redis 开始执行 rehash,是先将数据存储在表1中,然后当需要扩容的时候将表1中的数据映射并拷贝到表2中。

如果数据是很大的情况下,一次迁移所有数据是不合理的,会造成Redis线程阻塞,影响其他的请求。所以redis使用的是渐进式的rehash

双向链表:List 应用场景:列表键, 发布与订阅, 慢查询, 监视器

Zset:有序集合

跳表实现:链表+多级索引

与红黑树的区别:

Redis中会有大量的范围查询的功能需求,红黑树在范围查找这方面的效率不如跳跃表,所以在范围查询方面使用跳跃表更优

相比于单用链表查询时间复杂度O(n),跳表查询的时间复杂度是O(logn)。通过在每个节点维持多个指向其他节点的指针。

压缩列表:只适合元素数和value都不大的时候才会作为hash和Zset的内部数据结构。List,hash,ZSet

hash:限制512元素个数 value默认不超过64字节

Zset:限制128个元素 value默认不超过64字节

目的是节省内存空间,它是通过一片连续的内存空间来存储数据。它每个节点的内存大小是不同的,每个节点可以保存字节数组或是一个整数值。以为它是连续紧凑存储,所以当有新的元素需要扩展内存,如果数据量过多,重新分配内存和拷贝的开销比较大。所以ziplist不适合存储过多元素或是过大的字符串。

img

如果查找头尾元素通过表头三个字段的时间复杂度O(1),其他元素是逐个查找O(n)。

连续更新:

如果压缩列表中每个节点的长度都是250,因为是小于254,所以每个节点中的 previous_entry_length 长度1字节就能够保存了。

这时候,在头部节点插入了一个新的元素 entryNew,然后长度是大于254,那么后面的节点中 entry1 的 previous_entry_length 长度为1字节,就不能保存了,需要扩容成5字节,然后 entry1 节点进行扩容了,变成了254,所以后面的节点也就需要一次扩容,这就引发一连串的扩容。也就是连锁更新。

2)Redis实现布隆过滤器:主要解决缓存穿透 使用的是bitmap位图

布隆过滤器(Bloom Filter)的核心实现是一个超大的长度为m的位数组和k个哈希,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在

img

优点:

  • 支持海量数据场景下高效判断元素是否存在

  • 布隆过滤器存储空间小,并且节省空间,不存储数据本身,仅存储hash结果取模运算后的位标记,只存储 0 1两个数值

  • 不存储数据本身,比较适合某些保密场景

缺点:

  • 不存储数据本身,所以只能添加但不可删除,因为删掉元素会导致误判率增加

  • 由于存在hash碰撞,匹配结果如果是“存在于过滤器中”,实际不一定存在

  • 当容量快满时,hash碰撞的概率变大,插入、查询的错误率也就随之增加了

3)持久化机制:

RDB

  • 「save」:会阻塞当前 Redis 服务器响应其他命令,直到 RDB 快照生成完成为止,对于内存 比较大的实例会造成长时间阻塞,所以线上环境不建议使用

  • 「bgsave」:Redis 主进程会 fork 一个子进程,RDB 快照生成有子进程来负责,完成之后,子进程自动结束,bgsave 只会在 fork 子进程的时候短暂的阻塞,这个过程是非常短的,所以推荐使用该命令来手动触发。

    当有新的请求写入时,会执行写时复制的机制。

    写时复制技术(在RDB中)

    fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。

    Redis中Copy On Write:

    在Redis中触发RDB持久化操作:

    • SAVE:在主线程中执行,会导致主线程阻塞

    • BGSAVE:创建一个子进程,专门用于RDB持久化操作,避免了主线程阻塞,这也是Redis的默认配置。

    下图为RDB中的写时复制技术示意图:(以BGSAVE为例)

  • 在上图可以看到主线程调用fork()函数生成一个子进程,父子进程都是共享信息的(共享内存),当有写入指令执行时,会将修改的数据复制到额外的空间进行存储,而其他未更改的信息还是共享的。当要存储信息时,子进程会读取共享的内存空间和修改过的数据。避免了要额外开辟空间,提高了效率。但是如果写的操作过多,也会出现内存的消耗,影响效率。

AOF

AOF会将命令放入到aof_buff中,也会调用fork()函数生成子进程,将内存中的数据先写入到缓冲区中。

当有新的请求需要处理时(写操作),会同时将这个命令追加到APOF缓冲区和AOF重写缓冲区。

  • AOF缓冲区的内容会定期被写入和同步到AOF文件中,对现有的AOF文件的处理工作会正常进行;

  • 从创建子进程开始,服务器执行的所有写操作都会被记录到AOF重写缓冲区中;

当子进程完成对AOF文件重写之后,它会向父进程发送一个完成信号,父进程接到该完成信号之后,会调用一个信号处理函数,该函数完成以下工作:

将AOF重写缓存中的内容全部写入到新的AOF文件中;这个时候新的AOF文件所保存的数据库状态和服务器当前的数据库状态一致; 对新的AOF文件进行改名,原子的覆盖原有的AOF文件;完成新旧两个AOF文件的替换。(原来的文件冗余,需要替换)

优缺点:

RDB优点:(1)恢复信息速度快;(2)适合冷备份

缺点:(1)不能实时存储数据;(2)容易发生数据损失

AOF优点:(1)存储信息速度快(近乎秒级);(2)适合热备份

缺点:(1)恢复信息速度慢;(2)存储的信息容量比较大

4)淘汰策略

①、定时删除  在设置某个key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。

优点:定时删除对内存是最友好的,能够保存内存的key一旦过期就能立即从内存中删除。

缺点:对CPU最不友好,在过期键比较多的时候,删除过期键会占用一部分 CPU 时间,对服务器的响应时间和吞吐量造成影响。

②、惰性删除  设置该key 过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。

优点:对 CPU友好,我们只会在使用该键时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。

缺点:对内存不友好,如果一个键已经过期,但是一直没有使用,那么该键就会一直存在内存中,如果数据库中有很多这种使用不到的过期键,这些键便永远不会被删除,内存永远不会释放。从而造成内存泄漏。

③、定期删除  每隔一段时间,我们就对一些key进行检查,删除里面过期的key。

优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。

缺点:难以确定删除操作执行的时长和频率。

如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好。

如果执行的太少,那又和惰性删除一样了,过期键占用的内存不会及时得到释放。

另外最重要的是,在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除,那么就会返回这个键的值,这是业务不能忍受的错误。

Redis的过期删除策略就是:惰性删除和定期删除两种策略配合使用。

惰性删除:Redis的惰性删除策略由 db.c/expireIfNeeded 函数实现,所有键读写命令执行之前都会调用 expireIfNeeded 函数对其进行检查,如果过期,则删除该键,然后执行键不存在的操作;未过期则不作操作,继续执行原有的命令。

定期删除:由redis.c/activeExpireCycle 函数实现,函数以一定的频率运行,每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。

注意:并不是一次运行就检查所有的库,所有的键,而是随机检查一定数量的键。

Redis提供8种数据淘汰策略:

  1. volatile-lru:从已设置过期时间的数据集中挑选最近最少使⽤的数据淘汰。

  2. volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。

  3. allkeys-lru:当内存不⾜以容纳新写⼊数据时,在键空间中,移除最近最少使⽤的 key(常用)

  4. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。

  5. volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。

  6. no-eviction:禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错OOM。 4.0 版本后增加以下两种:

  7. volatile-lfu:从已设置过期时间的数据集中挑选最不经常使⽤的数据淘汰。

  8. allkeys-lfu:当内存不⾜以容纳新写⼊数据时,在键空间中,移除最不经常使⽤的 key。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值