谈谈redis相关知识点

Posted 梦回的承诺

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了谈谈redis相关知识点相关的知识,希望对你有一定的参考价值。

第一:redis介绍

redis是一种key-value内存数据库,是一种NoSql数据库(泛指非关系学数据库),NOSQL数据库主要解决高并发,高可扩展,高可用,大数据存储问题而产生的数据库解决方案。是关系型数据库的良好补充。

补充知识点:NOSQL数据库分类

1:key-value键值存储数据库:代表是redis。主要是作为内容缓存,应用于处理大量数据的高访问负载。主要优势是快速查询,劣势是存储的数据缺少结构化。

2:列存储数据库:代表是HBase,主要是作为分布式文件系统。主要优势是查询速度快,可扩展性强,主要应用于大数据存储。

3:文档型数据库:MongoDB,主要应用于web应用,也是key-value数据类型。优势是数据结构要求不严格。

 

Redis和MongoDB来说,大家一般称之为Redis缓存、MongoDB数据库。

Redis主要把数据存储在内存中,其“缓存”的性质远大于其“数据存储“的性质,其中数据的增删改查也只是像变量操作一样简单;

MongoDB却是一个“存储数据”的系统,增删改查可以添加很多条件,就像SQL数据库一样灵活,这一点在面试的时候很受用。

 

redis的主要功能是缓存服务器,还有就是解决分布式集群架构中的session分离问题(session共享)

 

 

第二:redis的存储类型主要有下面五种(value)

1:string(字符类型):当value为整数类型时,可以使用INCR key操作,这个操作是原子操作

2:hash(散列类型)

3:list(列表类型):实现原理是双向链表

4:set(集合类型):其中的数据不重复也没有顺序

5:sortedset(有序集合类型,简称zset)

 

使用setnx key value,可以实现分布式锁

 

有序集合类型和列表类型对比:

key *:查询所有的可以值

del:删除指定key

exists:判断key是否存在

expire:设置key的生存时间(秒为单位)

TTL:看指定key还有多少生存时间

rename:重命名key

type:判断key的类型

 

 

第三:redis的事务

1:MULTI:用于标记事务块的开始,Redis会将后续的命令逐个放入队列中,然后使用EXEC命令原子化地执行这个命令序列。

2:EXEC:在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态

3:DISCARD:清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态

4:WATCH:当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的状态。

语法:watch key [key…]

 注意事项:使用该命令可以实现redis的乐观锁

5:UNWATCH:清除所有先前为一个事务监控的键。

 

为什么redis不支持事务回滚?

  1. 大多数事务失败是因为语法错误或者类型错误,这两种错误,在开发阶段都是可以预见的
  2. redis为了性能方面就忽略了事务回滚

 

 

第四:redis分布式锁

单应用中使用锁:synchronize、Lock

分布式应用中使用锁:多进程

基于数据库的乐观锁实现分布式锁

基于zookeeper临时节点的分布式锁

基于redis 的分布式锁

加锁:

方式1(使用set命令实现) --推荐:

String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);

                   if("OK".equals(result))

                            return true;

                  

                   return false;

方式2(使用setnx命令实现):

Long result = jedis.setnx(lockKey, requestId);

                   if(result == 1)

                            jedis.expire(lockKey, expireTime);

                            return true;

                  

                   return false;

解锁:

方式1(del命令实现):

if (requestId.equals(jedis.get(lockKey)))

                 jedis.del(lockKey);

            

方式2(redis+lua脚本实现)--推荐:

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 (result.equals(1L))

                            return true;

                  

                   return false;

锁的注意事项:

  1. 互斥性在任意时刻,只有一个客户端能持有锁
  2. 同一性:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
  3. 可重入性:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

 

第五:redis持久化方案

1:RDB(默认):通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。

  1. 符合自定义配置的快照规则
  2. 执行save或者bgsave命令
  3. 执行flushall命令
  4. 执行主从复制操作

 Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。

  根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。

     快照过程

  1. redis使用fork函数复制一份当前进程的副本(子进程)
  2. 父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件。

当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此,一次快照操作完成。 

      RDB优缺点

  1. 缺点:使用RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这个时候我们就需要根据具体的应用场景,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受范围。如果数据相对来说比较重要,希望将损失降到最小,则可以使用AOF方式进行持久化
  2. 优点: RDB可以最大化Redis的性能:父进程在保存RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无序执行任何磁盘I/O操作。同时这个也是一个缺点,如果数据集比较大的时候,fork可以能比较耗时,造成服务器在一段时间内停止处理客户端的请求;

2:AOF(默认不开启):开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件,这一过程显然会降低Redis的性能,但大部分情况下这个影响是能够接受的,另外使用较快的硬盘可以提高AOF的性能

       AOF重写原理(优化AOF文件)

  1. Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写
  2. 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合
  3. 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
  4. AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松

 

同步磁盘数据

Redis每次更改数据的时候, aof机制都会将命令记录到aof文件,但是实际上由于操作系统的缓存机制数据并没有实时写入到硬盘,而是进入硬盘缓存再通过硬盘缓存机制去刷新到保存到文件

 

AOF文件损坏以后如何修复

服务器可能在程序正在对 AOF 文件进行写入时停机, 如果停机造成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件, 从而确保数据的一致性不会被破坏。

当发生这种情况时, 可以用以下方法来修复出错的 AOF 文件:

  1. 为现有的 AOF 文件创建一个备份
  2. 使用 Redis 附带的 redis-check-aof 程序,对原来的 AOF 文件进行修复。

redis-check-aof --fix readonly.aof

    3.重启 Redis 服务器,等待服务器载入修复后的 AOF 文件,并进行数据恢复。

 

 

        如何选择RDB和AOF

  1. 一般来说,如果对数据的安全性要求非常高的话,应该同时使用两种持久化功能。
  2. 如果可以承受数分钟以内的数据丢失,那么可以只使用 RDB 持久化。
  3. 有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快
  4. 两种持久化策略可以同时使用,也可以使用其中一种。如果同时使用的话, 那么Redis重启时,会优先使用AOF文件来还原数据

 

第六:Redis的主从复制与哨兵机制

 

持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,不过通过redis的主从复制机制就可以避免这种单点故障。

 

只有一个主redis,可以有多个从redis。

主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求。

一个redis可以即是主又是从。

 

Redis主从复制的缺点:没有办法对master进行动态选举,需要使用Sentinel机制完成动态选举。

Sentinel(哨兵)进程是用于监控redis集群中Master主服务器工作的状态。

在Master主服务器发生故障的时候,可以实现Master和Slave服务器的切换,保证系统的高可用(HA)。

第六:Redis的集群Redis Cluster

redis3.0以后推出的redis cluster 集群方案,redis cluster集群保证了高可用、高性能、高可扩展性。

架构细节:

(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.

(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护

 

Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点

 

 

redis-cluster投票:容错

(1)节点失效判断:集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),认为该master节点挂掉.

(2)集群失效判断:什么时候整个集群不可用(cluster_state:fail)? 

  • 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
  • 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。

 

Redis集群最少需要三台主服务器,三台从服务器

 

第七:LUA脚本使用

Redis中使用LUA的好处

1.    减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行

2.    原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心会出现竞态条件

3.    复用性,客户端发送的脚本会永远存储在redis中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑

 

第八:Redis消息模式

1:队列模式:使用list类型的lpush和rpop实现消息队列

* 消息接收方如果不知道队列中是否有消息,会一直发送rpop命令,如果这样的话,会每一次都建立一次连接,这样显然不好。

* 可以使用brpop命令,它如果从队列中取不出来数据,会一直阻塞,在一定范围内没有取出则返回null、

 

2:发布订阅模式

                          订阅消息

                        发布消息

 

第九:缓存相关:缓存击穿;缓存穿透,缓存失效等

缓存数据的步骤

  1. 查询缓存,如果没有数据,则查询数据库
  2. 查询数据库,如果数据不为空,将结果写入缓存

缓存穿透:一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

缓存穿透解决:

1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。

2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。(布隆表达式

 

缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。

缓存雪崩解决:

1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

2:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

3:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期(此点为补充)

 

缓存击穿:对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

缓存击穿解决:

使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。

 

if(redis.sexnx()==1)

      //查询数据库

      //加入线程

 

 

以上是关于谈谈redis相关知识点的主要内容,如果未能解决你的问题,请参考以下文章

谈谈关于PHP的代码安全相关的一些致命知识

谈谈Redis的各种集群方案及优缺点对比

谈谈VMware虚拟机中的网络问题

带你整理面试过程中关于Redis 中数据结构的相关知识点

需求获取常见的方法是进行客户访谈,结合你的实践谈谈会遇到什么问题,你是怎么解决的?

头条高级面试题:请谈谈Redis 9种数据结构以及它们的内部编码实现