0-redis中的基础数据结构于核心原理以及常见问题

Redis 基础数据结构

Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。

list (列表)

Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n),这点让人非常意外。 当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。

右边进左边出:队列

右边进右边出:栈

常见问题

1redis中的list的基本属性?

List数据结构是链表结构,这意味这无论数据量多大,头尾操作数据还是很快的,list的容量是2的32次方减1个元素,即4294967295个元素数量。

2、为什么使用redis中的list数据类型?

依靠redis内存中操作数据的优势,又提供一些列实用独特的Api操控数据,用着简便,速度又快,又能实现特有的数据特征排序读写,做时间轴数据,评论列表,消息传递等等,又提供简便的分页,读写操作。你用不用。

3、为什么使用消息队列?

举个简单的例子,功能是这样子的,你要呈现页面给用户看,在你呈现页面之前有一个很复杂耗时的运算要操作,但是这个操作不影响页面呈现的数据,也不作为页面呈现的数据。 

方案一:运算完,呈现页面。

方案二:把要计算的数据,抛到持久化的消息队列中,不做耗时的运算,直接呈现页面。然后用另外一个程序来对消息队列中的数据单独做运算。 

显而易见,方案二是最佳答案,你用不用消息队列。

4、为什么不使用成熟的rabbitmq而使用redis实现的消息队列? 

Rabbitmq只关注数据的先进先出,没有数据优先级的概念,假如你想给那个数据来个先处理的特权,那么不好意思,我不支持,但是rabbitmqn也可以变通着来处理,就是建立多个队列用程序路由来实现这个特权功能。那么redis实现的消息队列,是可以灵活掌控的,后面做示范。

Redis实现消息队列,并自带优先级功能

1、首先redis中的list是链表结构,具备消息队列中的先进先出特征。

2、从上面的几个高级命令中可以看出,list有几个自带阻塞功能,时间设置为0,可以视为永不休息的监听进程。

Set

Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。 

redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 

集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。 

Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值NULL。 当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。

hash (字典)

Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同 Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。

hash 结构也可以用来存储用户信息,不同于字符串一次性需要全部序列化整个对象,hash 可以对 用户结构中的每个字段单独存储。这样当我们需要获取用户信息时可以进行部分获取。而以整个字符串的形式去保存用户信息的话就只能一次性全部读取,这样就会比较浪费网络流量。 hash 也有缺点,hash 结构的存储消耗要高于单个字符串,到底该使用 hash 还是字符串,需要根据实际情况再三权衡。

 zset (有序集合)

zset 似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。

zset 可以用来存粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。我们可以对粉丝列表按关注时间进行排序。

zset 还可以用来存储学生的成绩,value 值是学生的 ID,score 是他的考试成绩。我们可以对成绩按分数进行排序就可以得到他的名次。

其他命令:

keys:

全量遍历键用来列出所有满足特定正则字符串规则的key,当redis数据量比较大时,性能比较差,要避免使用

scan:渐进式遍历键

scan 参数提供了三个参数,第一个是 cursor 整数值,第二个是 key 的正则模式,第三个是遍历的 limit hint。第一次遍历时,cursor 值为 0,然后将返回结果中第一个整数值作为下一次遍历的 cursor。一直遍历到返回的 cursor 值为 0 时结束。

Redis存储键值对实际使用的是hashtable的数据结构

Info:

查看redis服务运行信息,分为 9 大块,每个块都有非常多的参数,这 9 个块分别是:

Server 服务器运行的环境参数

Clients 客户端相关信息

Memory 服务器运行内存统计数据

Persistence 持久化信息

Stats 通用统计数据

Replication 主从复制相关信息

CPU CPU 使用情况

Cluster 集群信息

KeySpace 键值对统计数量信息

Redis核心原理

Redis的单线程和高性能

Redis 单线程为什么还能这么快?

因为它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。正因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些耗时的指令(比如keys),一定要谨慎使用,一不小心就可能会导致 Redis 卡顿。

Redis 单线程如何处理那么多的并发客户端连接?

RedisIO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。

Nginx也是采用IO多路复用原理解决C10K问题

缓存技术 RDB 和AOF技术

RDB技术

AOF技术 

实验

我在10s内设定 添加key 22 33,立刻杀死redis 进程,验证下redis的rdb备份机制 

 

 但是我强制杀死redis进程

root@vpc-usg-cyz-resource:/opt/redis/bin# netstat -luntp | grep 6379
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      21440/./redis-serve
root@vpc-usg-cyz-resource:/opt/redis/bin# kill -9 21440

重启后发现只有两条数据,之前的22 33 没有保存下来

 每60 s至少有10000个key变化才保存到rdb,不满足我上面的操作,所以直接杀死redis进程,redis不会吧22 和33刷到rdb文件中

save 900 1
save 300 10
save 60 10000

建议使用AOF方式,只需要配置

appendonly yes

查看 vim appendonly.aof文件

*2
$6
SELECT
$1
0
*3
$3
set
$2
11
$3
aaa

Redis 4.0 混合持久化

重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。AOF在重写(aof文件里可能有太多没用指令,所以aof会定期根据内存的最新数据生成aof文件)时将重写这一刻之前的内存rdb快照文件的内容和增量的 AOF修改内存数据的命令日志文件存在一起,都写入新的aof文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,原子的覆盖原有的AOF文件,完成新旧两个AOF文件的替换;

AOF根据配置规则在后台自动重写,也可以人为执行命令bgrewriteaof重写AOF。 于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

开启混合持久化:

# aof-use-rdb-preamble yes   

混合持久化aof文件结构

缓存淘汰策略

当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率基本上等于不可用。

在生产环境中我们是不允许 Redis 出现交换行为的,为了限制最大使用内存,Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。

当实际内存超出 maxmemory 时,Redis 提供了几种可选策略 (maxmemory-policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务。

noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。

volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。

volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。

volatile-random 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。

allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。

allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。

volatile-xxx 策略只会针对带过期时间的 key 进行淘汰,allkeys-xxx 策略会对所有的 key 进行淘汰。如果你只是拿 Redis 做缓存,那应该使用 allkeys-xxx,客户端写缓存时不必携带过期时间。如果你还想同时使用 Redis 的持久化功能,那就使用 volatile-xxx 策略,这样可以保留没有设置过期时间的 key,它们是永久的 key 不会被 LRU 算法淘汰。

常见问题:

Q: 混合重写必须要aof和rdb同时设置为yes么  还是混合的这个设为yes就行?

答: aof和混合那个都要设为yes,混合的基础是aof

Q:redis重启后RDB和AOF文件里面的内容会清空吗?

答: 不会

Q:AOF文件是存在哪的。

答: redis.conf文件里的dir属性配置的路径

Q:手动重写aof文件,如果文件很大,重写 需要花很长时间,这个时候刚好又有新操作的数据怎么办?

答: 新操作会存在内存里,等到aof重写完再最加到aof文件的末尾,aof重写的数据就是重写开始之前的内存数据

Q: scan命令,第三个参数,比如每次读取1000个,这个是怎么读取的,下次读取另外1000个的时候,会不会把之前已经读过的数据又读了一次,如果不会,因为hash是无序的,那么是怎么读的呢 

答: 不会重复读取,是按照hash的一维数组逐个往下读取的

Q: 比如这时候rdb了 。然后 有新增了很多数据, 然后重写了。 多出来的aof命令是怎么存进去的 。

答: 会等rdb写完aof文件之后再把之后的数据命令追加到aof文件末尾

Q:redis hash冲突后进行rehash 如果再冲突继续rehash?到最后还会不会有冲突的结果?

答: 不是hash冲突后马上进行rehash,是redis扩容后之前的元素再做rehash重新定位,扩容是根据redis内部的扩容因子来计算的,跟hashmap扩容类似,冲突一直都可能有,只不过redis会尽量通过扩容来减小冲突

Q: 客户端槽位定位到某个主节点,但这个主节点挂了,会重新定位到其他小集群的主节点还是等从节点选举出新的主节点?  主节点挂了槽位会重新分配吗?

答:等从节点选出新的主节点;不会

Q: 数据迁移的时候,客户端会同步写入吗?还是会等迁移完成后才会写入

答:迁移时可能会同步写入,但如果写的是正在迁移的槽位就会阻塞等待迁移完成

Q:master挂了,salve重新选举,课堂上说slave的数据可能会比master少,那master挂了以后,新选举的master比原来的master数据少了,少了的数据就直接忽略了吗?

答:是有可能的,但是概率不大,缓存在特殊场景下一般是允许有少量数据丢失的,毕竟数据库里还有一份可以重写读入的

Q: redis集群重启,要不要重新cluster create?还是只要重启节点就行了?

答:不需要,只要重启节点就行了

Q:  3主3从,一个slave和master网络异常(实际master是好的),slave发起选举,这时候是什么机制避免slave选上master?

答:这种只有尽量把节点超时时间设置长点

Q: master挂了后,Slave重新选举,如果选举的slave不是slave中最新的,选举后当master了,那么不是会丢失一部分数据吗,这样怎么解决,还是说丢就丢了。

答:是有可能丢一点数据的,但是概率不大,缓存在特殊场景下一般是允许有少量数据丢失的,毕竟数据库里还有一份可以重写读入的

Q: 刚老师上课演示的时候,是新增1个小集群,2个节点,如果要新增2个小集群,4个节点,那么等节点全部加入集群后再分配槽也是可以的吧?

答:应该是可以的,可以自己用伪分布式环境试验下

Q: 老师搭建的集群   需不需要安装ssh 配置免密钥登录 啥的

答:不需要,不是已经配置密码了么,redis有自己的访问密码

Q: 如果被选成master的sleaver的rank不是最新的,会不会丢数据啊?

答:有可能,但是概率不大,即便丢一点数据关系也不大哈,毕竟是缓存,数据库里还有一份

Q: 老师,集群内的数据如何同步数据?

Q: 缓存的数据什么时候刷新?

答:一般是增量同步aof的持久化操作命令,也有可能同步快照

第二个问题没看懂

Q: jedis操作redis集群,要add每个节点,如果节点非常多,这明显很麻烦,有什么办法吗?

答:我上课演示过了,可以只添加部分节点,仔细回看下视频    

Q: 集群重启之后需要重新create吗?我尝试重启集群节点后好像没有自动组建集群,但是重新create会报错:node not empty,每次重启必须删除node.conf和持久化文件才能创建。重启集群具体该如何操作呢

答:不需要,重启集群只要把所有redis节点重启就好了

Q: redis允许多个指定IP访问怎么配置?

bind 192.168.3.31 192.168.3.32

Q: 验证集群连接的时候,为什么输入具体ip不行,输入127.0.0.1可以呢

答: 要确定ip是否正确,还有最好把防火墙关闭再试试,或者从另外一台机器上用这台机器的ip连接试试

Q: redis搭建主从结构情况下,为保证redis数据不丢失,怎么跳转持久化策略啊?

答: 用aof或者混合持久策略,因为集群里每台机器的内存不会太大用aof也是可以的

Q: 在lru缓存淘汰策略中,上课时您说淘汰的数据不写入AOF里,那它不就永远消失了吗?再次访问时候怎么办?

答: Lru缓存淘汰策略不是不写入aof,是从redis内存和aof持久化文件里把满足lru策略需要清理掉的缓存数据直接删除,已经删除的数据再次访问缓存里肯定没有了,但是你数据库里还有哈,可以再次查询数据库把结果放入redis

Q: 这6个实例需要密码都一样吗?如果不一样,能启动吗

答: 密码必须都一样哈,集群是一个统一的整体

Q: 启动集群,只用在任意一个机器上执行集群命令就行
截图的命令就行了吗

答: 是的

Q:Scan命令count的条数与显示的条数不一致

答: 如上图,scan 6 match name* count 2的时候出现了3条,scan 9 match name* count 2的时候出现了1条。

是因为hash碰撞吗,我画了个图,是不是下面这张情况?

答:是的,hash有碰撞

2、课程文档里面 这个有点搞不清,如下图

这个结构里面,scan返回的第一行数据,下一个游标的位置:6,9之类的,是对应数组的下标吗?

答:是的,我这里用的二进制表示的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值