看了网上的redis的一些面试题,加上自己的一些理解,总结如下:
目录
什么是redis
Remote dictonary server是一个开源的使用key-value存储的内存数据库服务器,redis的键是字符串,其值可以是string,hash,list,set,zset
Redis的五种数据类型
- string,string
- string,hash
eg; key-k1,value-id=1,name=”zhangsan”,age=21
- string,list
eg:key-k1,value=[北京,上海,深圳,北京]
- string,set
eg:key-k1,value=[北京,上海,深圳]
- string,zset同set,不同的是每个元素都会关联一个分数,分数可以重复,分数是double类型,redis通过分数来为集合中的成员进行有小到大的排序
eg: key-k1,[100北京,90上海,80深圳]
Redis常用命令
Redis的默认端口是6379
- 查看gcc编译器是否安装:yum list installed | grep gcc
或者rmp -qa|grep gcc
- 启动redis进入到src目录下执行./redis-server ../redis.conf
- 查看redis有没有进程ps -ef | grep redis
- 结束redis进程 在src目录下执行./redis-cli shutdown
或者 ./redis-cli -h 127.0.0.1 -p 6379
- 输入ping,redis返回pong,表示正常
- 选择数据库 select
- 删除所有库的数据flush all
- 删除当前数据库的数据flushdb
- 退出exit,quit
- 得到当前数据库的数目dbsize
- 得到redis中的所有配置config get *
- 永久关闭/开启防火墙chkconfig iptables on/off
- 临时关闭防火墙service iptables start/stop
- 查找当前数据库下的key keys *[速度慢,最好不用]
- 查找某个key是否存在exist key
- 将当前的key移动到指定的数据库中move key db
- 设置key的过期时间expire key seconds
- 查看key还有多少秒过期ttl key
- 删除key del key
- 移除key的过期时间persist key
- 随机返回key空间的一个key randonmkey
- 返回key的类型type
五种数据类型各自对应的操作命令
- String-string:
设置key对应的值Set key value
得到某个key对应的值get k
incr key[用于计数器] 自增
decr key自减
incrby key increment指定增长值
decrby key increment指定减少值
Setex设置key的值,并设置多少秒过时
Setnx如果不存在,就设置
Getset返回key原来的值,并设置新值
- String-hash
设置单个hash类型hset key field value
设置多个hmset key field value field value
得到单个hget key field
得到多个hgetall key
删除单个hdel key属性名
删除多个hdelall key
- String-list
Lpush key v1 v2向链表的头部插入数据
Rpush key v1 v2向链表的尾部插入数据
Lrange key a b获取指定列表中指定区间的元素0表示第一个1表示第二个… -1表示倒数第一个 -2表示倒数第二个
Eg:lrange k1 0 -1获取k1从第一个到最后一个的元素
Lpop从左边获取列表key的一个元素,并将该元素移除
Lpush从右边获取该列表key的一个元素,并将该元素移除
Lindex获取下标为指定元素的值
Llen key获取指定key的长度
- String-set
Sadd:将一个或者朵儿member元素加入到集合key当中,已经存在于集合的member元素将不会再加入
Smembers key取集合中的所有元素
Sismember判断member元素是否为集合key中的成员sismember Beijing
Scard:取集合中的元素个数
Srem key value删除集合中的元素
随机移除集合中的数据spop key count
求两个集合的交集sinter k1 k2
求两个集合的并集sunion k1 k2
求两个集合的差集sdiff k1 k2
- String-zset
Zadd key weight value向集合中添加元素
Zrange key a b取集合中的元素按照从小到大的顺序
Zrevrange key a b取集合中的元素,按照从大到小
Zrank k1 v取某个元素在集合中的序号,按照从小到大的顺序
Zrevrank k1 v取某个元素在集合中的序号,按照从大到小的顺序
Zrem删除元素
Zcard获取集合中元素的个数
消息队列
消息队列是实现异构系统之间通信的一种方式,A系统将消息发送到消息队列服务器上,B系统从消息队列接收到消息
Java常用的消息队列有ActiveMQ RabbitMQ kafka
消息队列有两种模式 1.点对点 简称为p2p (point to point) 2.发布与订阅(publish/subsrcibe)简写为pub/sub
点对点;A发消息,B,C,D中有一个人拿到消息,消息就删除,其他人拿不到
发布-订阅 发消息B,C,D三个人都能拿到消息,三个人都拿到消息才删除
消息队列的核心三要素;
生产者producer消费者consumer消息服务broker
消息队列的两种用途 系统解耦,异步处理,不需要实时返回结果的场景
Redis事务
当一个client在一个连接中发出multi命令时,这个命令暂时不会被执行,放在一个队列中,当执行exec命令时,redis会顺序的执行队列中的所有命令
Discard放弃执行所有的,,命令
事务的复杂情况:悲观锁和乐观锁
悲观锁:每次拿数据之前都认为别人会修改锁,所以每次在拿数据的时候都会先上锁,这样别人想拿到这个数据就会block阻塞,直到拿到锁。行锁,表锁,读锁,写锁等都是在操作之前上锁,让别人无法操作该数据
乐观锁[版本控制的概念]每次拿数据都认为别人不会上锁,但是在更新的时候会判断一下再次期间别人有没有去更新这一条数据,一般使用版本号机制进行判断。乐观锁使用与多读的应用类型可以提高吞吐量。
Update users set money=money-50,version=verson+1 where id=1 and verion=#{version}
Redis的watch机制实现乐观锁
Eg:set k1 1
Watch k1
Set k1 2
Multi
Set k1 3
Exec
提交事务,但是k1的值不会被修改为3,k1的值仍然是2,因为事务提交之k1的值已经被修改过了
乐观锁可以解决超卖问题
超卖是指没有库存了,但是仍然显示可以买
Redis的持久化
持久化是指把事务放在不会丢失的地方
Redis持久化的两个方式;RDB AOF
RDB:在指定的时间间隔内将内存中的所有数据的快照写入磁盘,保存为一个后缀.rdb的文件,数据恢复时,将从磁盘的筷子文件直接再读取到内存,redis已经默认开启rdb的持久化
Rdb实现过程;redis在将内部的数据写到磁盘的快照文件时,将当前fork分支分出一个子进程,然后在子进程中循环所有的内存数据,并且将内存数据先写到一个临时文件中,然后将这个临时文件重命名为.rdb文件,覆盖掉原来的rdb文件
总结rdb持久化:由于存储的是数据快照文件,直接将快照文件里的数据再放入内存中就好了,恢复数据很方便,也比较块,
缺点:会丢失最后一次快照以后更改的数据
如果能应用能容忍数据丢失,那么rdb是不错的选择
由于要经常操作磁盘,RDB经常会fork出一个子进程,如果你的redis数据库很大的话,fork占用比较多的时间,并且可能会影响redis暂停服务一些时间(millsecond),如果数据库超级大,并且你的服务器cpu比较弱,有可能会达到一秒
Aof方式(Append only file)redis每次接收到一条改变数据的命令时,它将 该命令写到一个AOF文件中,只记录写操作,读操作不记录,当redis重启时,它通过执行AOF文件中的所有命令里恢复数据
Redis没有开启默认Aof持久化
在redis的配置文件中,将appendonly中的aof改成yes就代表开启了持久化
Appendfilename指定aof文件名,默认文件名为appendonly.aof
Appendsync:配置向aof文件中命令数据的策略
- No不主动进行同步操作,而是完全交由操作系统来做,每30秒执行一次,比较快,但不是很安全
- Always每次执行都会执行同步,慢一些但是很安全
- Eveysec美秒执行以同步操作,比较平衡,介于速度和安全之间
Dir指定aof和rdb文件的目录
Auto-aof-rewrite-percentage当目前aof文件超过上一次重写的aof文件大小的百分之多少时会进行重写,如果之前没有进行重写,则以启动时的aof文件大小为依据
Auto-aof-rewrite-percentage当前aof文件超过一次重写的aof文件大小会进行重写,如果没有重写,则以启动时的aof文件大小为依据
总结:append-only持久化方式是一个可以提供完全数据保障的方案
对于一般性的业务需求,减一使用rdb的方式进行持久化,原因是rdb的开销比aof方式要低很多,对那些无法忍受数据丢失的应用,才使用aof方式
Redis集群
通过持久化,保持不会丢失数据,但是当电脑或服务器出故障,也会导致数据丢失
为了避免单点故障,将数据复制多分部署在不同的服务器上,即使有一台服务器出现故障,其他服务器依然可以提供服务,当一台服务器的数据更新后,自动将更新的数据部署到其他服务器上
如何实现:redis提供了复制功能来自动实现多台redis服务器的数据同步
我们可以通过部署多台redis,并在配置文件中指定这几台redis之间的主从关系,主负责写入数据,同时把写吐的数据实时同步到从机器,这种模式叫做主从复制,即master/slave,并且默认master用于写,slave用于读,向salve写数据会导致错误,实现redis的主从复制,只需要修改redis的主配置文件,即redis.conf
Redis主从复制的过程
- Slave与master建立连接,发送sync同步命令
- Master启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始执行写命令并缓存
- 后台完成保存后,就将此文件发送给slave
- Slave将此文件保存到硬盘上
主从复制的容灾处理
当master服务器发生故障,需要手动将slave中的一个提升为master,剩下的slave挂至新的master上,这种处理也叫冷处理(人工处理是冷处理)机器处理称为热处理
处理步骤;
- 执行命令slave of no one将其中一台服务器提升为master,剩下的slave挂至新的master上,
- 执行命令slaveof 127.0.0。1端口号 将slave挂至新的master上
主从复制模式小结:
- 主从复制的集群有一个master和多个slave构成,通过在redis.conf配置文件进行配置来实现主从关系
- 当从redis(slave)故障,请求的性能下降
- 当主redis(master)发生故障,写请求无法执行
- 当master发生故障,需要手动将其中一台slave使用slaveof on one命令提升为master,其他slave执行slaveof命令指向这个新的master,从而构成新的主从关系
- 主从复制模式的故障转移需要手动操作,这种处理并不智能,需要自动化处理,这就需要sentinel哨兵,实现故障自动转移
高可用sentinel哨兵
Sentinel哨兵是redis官方提供的高可用方案,使用sentinel哨兵可以监控多个redis服务实例的运行情况
哨兵的基本原理:
Sentinel哨兵用来监视redis的主从服务器,他会不断检查master和slave是否正常
如果是sentinel自身故障了,就无法监控,所以需要哨兵,组成sentinel网络
监控同一个master的各个sentinel哨兵会互相通信,组成一个分布式的sentinel哨兵网络,互相交换彼此关于被监控redis服务器的运行情况
当一个sentinel哨兵被认为监控的redis服务器出现故障时,他会向网络中的其他sentinel哨兵进行确认,判断该服务器是否是真的已故障
如果故障的redis服务器为主服务器,那么sentinel哨兵网络将对故障的热废水服务器进行自动故障转移,通过将故障的主redis服务器的某个服务器提升为新的主服务器,并让其他服务器转移到新的主服务器,以此来实现让系统重新回到正常的状态
待出现故障的旧主服务器重新启动上线时,sentinel哨兵让它变成一个从redis服务器,并挂到新的主redis服务器下
所以哨兵是自动实现故障转移,不需要人工转移,是高可用的一种集群方案
Redis与memcached区别:
- 存储方式不同,redis存在内存中,memcached存储在硬盘上
- 支持的数据类型不同redis支持复杂数据类型,memcached支持简单的数据类型
- Value值的大小不同,redis支持的value最大可以达到1G,memcached可以达到1M
- 使用底层模型不同 他们之间底层实现方式以及与客户端之间通信的应用协议不一样,redis直接自己构建了vm机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
为什么redis把所有数据存放到内存中
Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和持久化的特征,磁盘I/O速度为严重影响redis的性能,在内存越来越便宜的今天,redis将会越来越受欢迎,则数据已有记录数达到内存限制后不能继续插入新值。
Redis常见的性能问题都有哪些?如何解决
- Master写内存快照,save命令调度rdbsave函数,会阻塞住主线程的工作,当快照比较大时,对性能影响是非常大的,会间断性暂停服务,所以master最好不要写内存快照
- Master aof持久化,如果补充协议aof文件,这个持久化方式对性能的影响是最小的,但是aof文件会不断增大,aof文件过大会影响master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和aof日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个slave开启aof备份数据,策略为每秒同步一次
- Master调用bgrewriteaof重写文件,aof在重写的时候会占大量的cpu和内存资源,导致服务load锅盖,出现短暂服务暂停现象
- Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,salve和master最好同一个局域网内
Redis的五个常见应用场景
- 会话缓存(Session Cache)
最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如
Memcached)的优势在于:Redis 提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用 Redis 来缓存会话的文档。甚至广为人知的商业平台 Magento 也提供 Redis 的插件。
2、全页缓存(FPC)
除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地FPC。
再次以 Magento 为例,Magento 提供一个插件来使用
Redis 作为全页缓存后端。
此外,对 WordPress 的用户来说,Pantheon 有一个非常好的插件 wp-redis,这个插件能帮助你以最
快速度加载你曾浏览过的页面。
3、队列
Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的
消息队列平台来使用。Redis 作为队列使用的操作,就类似于本地程序语言(如 Python)对 list 的
push/pop 操作。
如果你快速的在 Google 中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目
的就是利用 Redis 创建非常好的后端工具,以满足各种队列需求。例如,Celery 有一个后台就是使用 Redis 作为 broker,你可以从这里去查看。
4.排行榜/计数器
Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)
也使得我们在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的 10 个用户–我们称之“user_scores”,我们只需要像下面一样执行即可
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样
执行:
ZRANGE user_scores 0 10 WITHSCORES
5、发布/订阅
最后(但肯定不是最不重要的)是 Redis 的发布/订阅功能。发布/订阅的使用场景确实非常多。我已
看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用 Redis 的发布/订阅
功能来建立聊天系统!
Redis的过期策略
Redis的过期策略是:定期删除+惰性删除
定期删除是指每隔多少秒去检查key,看是否过期,过期将其删除,但是是随机检查的,可能有很多key检查不到,然后就有了惰性删除策略,使用某个key之前,检查是否过期,但是即使是这样的情况,还是有很多过期的key没有删除,所以有了淘汰机制
Redis的淘汰机制
Redis提供6种数据淘汰策略:
1、volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
2、volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
3、volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
4、allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
5、allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
6、no-enviction(驱逐):禁止驱逐数据(也就是说会报错)
Redis 确定驱逐某个键值对后,会删除这个数据,并将这个数据变更消息发布到本地(AOF 持久化)和从机(主从连接)。
使用Redis的缺点
-
-
-
- 缓存和数据库双写一致性问题
-
-
解决方法:采取正确的更新策略,先更新数据库,再删除缓存,有可能存在删除缓存失败的问题,可以利用消息队列
-
-
-
- 缓存击中问题(缓存穿透)
-
-
黑客故意去请求缓存中不存在的数据,导致所有的请求都堆到数据库上,导致数据库连接异常
- 互斥锁
- 异步更新,无论key是否取到值,都直接返回。Value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存
- 提供一个能迅速判断请求是否有效的拦截机制,比如利用布隆过滤器,内部维护一些合法的key。迅速判断出请求所携带的key是否合法有效,如果不合法,直接返回
-
-
- 缓存雪崩问题
-
-
同一时间缓存大面积失效,这时候又来了一拨请求,都堆到数据库上,导致数据库连接异常
- 给定缓存的失效时间,加上随机值,避免集体失效
- 互斥锁,但是吞吐量明显下降了
- 双缓存,缓存A和缓存B,一个缓存设计有效时间,另一个缓存不设置有效时间
-
-
- 缓存的并发竞争问题
-
-
事务的时候有可能多个key在不同的redis中,
期望按照key1的value值valueA->valueB->valueC的顺序变化
系统A key 1{valueA 3:00}
系统B key 1{valueB 3:05}
系统C key 1{valueC 3:10}
假设系统B先抢到锁,将key1设置为{valueB 3:05}.接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存的时间戳,那就不做set操作了
如何用Redis实现分布式锁
https://www.cnblogs.com/williamjie/p/9395659.html
使用redis命令set key value nx ex max-lock-time实现枷锁
使用redis命令eval实现解锁
Set只有key-value都比配才有删除锁的权利[保证安全性]
通过timeOut设置过期时间保证不会出现死锁
Set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端持有锁,满足互斥性,其次,由于互斥锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,由于加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。
不能使用jedis.del方法删除锁,要先判断锁的拥有者,客户端只能解自己的锁
参考:https://baijiahao.baidu.com/s?id=1616996633536597016&wfr=spider&for=pc