参考
https://blog.csdn.net/zdp072/article/details/50991116
https://blog.csdn.net/qq_34337272/article/details/80012284#commentBox
坑人无数的Redis面试题(未整理,写的很棒,超出了理论范围,结合实际中的问题,给出不错的解决方案) https://blog.csdn.net/u011405515/article/details/79190652
速度快:
因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1),每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。支持丰富数据类型:
支持string,list,set,sorted set,hash支持事务:
操作不是原子性的,即使某些操作失败,也不会影响别的操作的执行丰富的特性:
可用于缓存,按key设置过期时间,过期后将会自动删除Memcached:一款完全开源、高性能的、分布式的内存系统
;
Redis:一个开源的、Key-Value型、基于内存运行并支持持久化的NoSQL数据库
;
Memcached追求的高性能的内存服务
;而Redis追求的不仅仅是内存运行,还有数据持久化的需求。
存储方式
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
Redis有部份存在硬盘上,这样能保证数据的持久性(即master-slave模式的数据备份)。
数据支持类型
Memcache对数据类型支持相对简单String。
Redis有复杂的数据类型:String,list,set,zset,hash。
value大小
redis的value大小最大可以达到1GB,而memcache只有1MB
Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。
网络IO之间的对比:
Memcached是多线程,非阻塞IO复用的网络模型,分为监听主线程
和worker子线程
,监听线程监听网络连接,接受请求后,将连接描述字pipe传递给worker线程,进行读写IO,网络层使用libevent封装的事件库,多线程模型可以发挥多核作用
,但是引入了cache coherency和锁的问题,比如:memcached最常用的stats命令,实际memcached所有操作都要对这个全局变量加锁,进行技术等工作,带来了性能损耗。
Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll, kqueue和select,对于单存只有IO操作来说,单线程可以将速度优势发挥到最大,但是redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型施加会严重影响整体吞吐量,CPU计算过程中,整个IO调度都是被阻塞的。
redis利用队列技术将并发访问变为串行访问
,消除了传统数据库串行控制的开销。
基于以上的内容大致可以总结如下:
在高并发场景的压力下,多线程非阻塞式IO的Memcached表现会更加优异。
过期删除策略的不同
memcached只是用了惰性删除,而redis同时使用了惰性删除与定期删除,这也是二者的一个不同点(可以看做是redis优于memcached的一点)
详情见 Redis过期策略 实现原理
Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次(AOF有3种同步方式:1.每秒同步,2.每修改同步,3.不同步)
为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
尽量避免在压力很大的主库上增加从库
主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
常用命令: set,get,decr,incr,mget 等。
数字
。计数
功能的缓存:微博数,粉丝数等。常用命令: hget,hset,hgetall 等。
Hash是一个string类型的field
和value(
的映射表,hash特别适合用于存储对象。 比如我们可以Hash数据结构来存储用户信息,商品信息
等等。
以cookieId作为key
,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果
。常用命令: lpush,rpush,lpop,rpop,lrange等
list就是链表,Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,最新消息排行等功能都可以用Redis的list结构来实现。
Redis list的实现为一个双向链表
,即可以支持反向查找和遍历
,更方便操作,不过带来了部分额外的内存开销
。
使用List的数据结构,可以做简单的消息队列
的功能。另外还有一个就是,可以利用lrange命令
,做基于redis的分页功能
,性能极佳,用户体验好。
常用命令:
sadd,spop,smembers,sunion 等
set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的(没有重复的数据)。
可以做全局去重的功能
。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。
另外,就是利用交集、并集、差集
等操作,可以计算共同喜好,全部的喜好,自己独有的喜好
等功能。
常用命令: zadd,zrange,zrem,zcard等
和set相比,sorted set增加了一个权重参数score
,使得集合中的元素能够按score进行有序排列
。
举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜
,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用Redis中的SortedSet结构进行存储。
可以做排行榜应用
,取TOP N操作
。最后一个应用就是可以做范围查找
。
相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略(回收策略)。redis 提供 6种数据淘汰策略:
Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问
。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:
注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题。
# 9 Redis回收进程如何工作的? Redis回收使用的是什么算法?
Redis内存回收:LRU算法(有源码分析,我还没看,推荐):https://www.cnblogs.com/WJ5888/p/4371647.html
Redis中采用两种算法进行内存回收,引用计数算法
以及LRU算法
。
LRU算法采用的数据结构是HashMap+Double LinkedList,时间复杂度为O(1)。其含义是:当内存容量不足时,为了保证程序的运行,就要淘汰一些数据,选择最近一段时间内,最久未使用的对象将其淘汰,至于为什么要选择最久未使用的,可以想想,最近一段时间内使用的东西,我们是不是可能一会又要用到呢,而很长一段时间内都没有使用过的东西,也许永远都不会再使用~淘汰的不是内存中的对象,而是页选择一页(一般是4KB)将其交换到虚拟内存区(Swap区)。
使用管道(pipelining)
https://blog.csdn.net/csdnlijingran/article/details/88032979
《redis的持久化和缓存机制》 :https://blog.csdn.net/tr1912/article/details/70197085?foxhandler=RssReadRenderProcessHandler
扩容即redis集群:https://blog.csdn.net/csdnlijingran/article/details/88065336
不要使用redis去做消息队列,这不是redis的设计目标。做网站过程接触比较多的还是使用redis做缓存
,比如秒杀系统
,首页缓存
等等。
Redis为了达到最快的读写速度
将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速
和数据持久化
的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。
会话缓存(Session Cache)
最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化
。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息
全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
全页缓存
除基本的会话token之外,Redis还提供很简便的全页缓存功能。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降。
再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。
此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
队列
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列
平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
排行榜/计数器
Redis在内存中对数字进行递增或递减的操作
实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
ZRANGE user_scores 0 10 WITHSCORES
发布/订阅
最后是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。用Redis的发布/订阅功能来建立聊天系统!
Redis 发布订阅(pub/sub)是一种消息通信模式
:发送者(pub)发送消息,订阅者(sub)接收消息。Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
高可用(High Availability),是当一台服务器停止服务后,对于业务及用户毫无影响。 停止服务的原因可能由于网卡、路由器、机房、CPU负载过高、内存溢出、自然灾害等不可预期的原因导致,在很多时候也称单点问题
。
主备方式
这种通常是一台主机、一台或多台备机,在正常情况下主机对外提供服务,并把数据同步到备机,当主机宕机后,备机立刻开始服务。
Redis HA中使用比较多的是keepalived,它使主机备机对外提供同一个虚拟IP,客户端通过虚拟IP进行数据操作,正常期间主机一直对外提供服务,宕机后VIP自动漂移到备机
上。
优点是
对客户端毫无影响,仍然通过VIP操作。
缺点是
在绝大多数时间内备机是一直没使用,被浪费着的。
主从方式
这种采取一主多从
的办法,主从之间进行数据同步。 当Master宕机后,通过选举算法(Paxos、Raft)从slave中选举出新Master继续对外提供服务,主机恢复后以slave的身份重新加入。
主从另一个目的是进行读写分离
,这是当单机读写压力过高的一种通用型解决方案。 其主机的角色只提供写操作或少量的读,把多余读请求通过负载均衡算法分流到单个或多个slave服务器上。
缺点
是主机宕机后,Slave虽然被选举成新Master了,但对外提供的IP服务地址却发生变化了
,意味着会影响到客户端
。 解决这种情况需要一些额外的工作,在当主机地址发生变化后及时通知到客户端,客户端收到新地址后,使用新地址继续发送新请求。
无论是主备还是主从都牵扯到数据同步的问题,这也分2种情况:
同步方式:当主机收到客户端写操作后,以同步方式把数据同步到从机上,当从机也成功写入后,主机才返回给客户端成功,优点
也称数据强一致性。缺点
很显然这种方式性能会降低不少。解决办法
当从机很多时,可以不用每台都同步,主机同步某一台从机后,从机再把数据分发同步到其他从机上,这样提高主机性能分担同步压力。 在redis中是支持这种配置的,一台master,一台slave,同时这台slave又作为其他slave的master。
异步方式:主机接收到写操作后,直接返回成功,然后在后台用异步方式
把数据同步到从机上。 优点
这种同步性能比较好,缺点
但无法保证数据的完整性,比如在异步同步过程中主机突然宕机了,也称这种方式为数据弱一致性
。
Redis主从同步采用的是异步方式
,因此会有少量丢数据的危险。
主备方式
keepalived方案配置简单、人力成本小,在数据量少、压力小的情况下推荐使用。 如果数据量比较大,不希望过多浪费机器,还希望在宕机后,做一些自定义的措施,比如报警、记日志、数据迁移等操作,推荐使用主从方式
,因为和主从搭配的一般还有个管理监控中心。
宕机通知这块,可以集成到客户端组件上,也可单独抽离出来。 Redis官方Sentinel支持故障自动转移、通知等,详情见低成本高可用方案设计(四)。
分布式
(distributed), 是当业务量、数据量增加时,可以通过任意增加减少服务器
数量来解决问题。
集群时代,至少部署两台Redis服务器构成一个小的集群,主要有2个目的:
高可用性:
在主机挂掉后,自动故障转移,使前端服务对用户无影响。读写分离:
将主机读压力分流到从机上。可在客户端组件上实现负载均衡,根据不同服务器的运行情况,分担不同比例的读请求压力。当缓存数据量不断增加时,单机内存不够使用,需要把数据切分不同部分,分布到多台服务器上。
可在客户端对数据进行分片,数据分片算法一致性Hash。
大规模分布式集群时代
当数据量持续增加时,应用可根据不同场景下的业务申请对应的分布式集群。 这块最关键的是缓存治理这块,其中最重要的部分是加入了代理服务
。 应用通过代理访问真实的Redis服务器进行读写,这样做的好处
是:
直接
访问Redis服务器难以管理,而造成风险。做对应的安全措施
,比如限流、授权、分片。目前楼主公司使用的是客户端组件和代理两种方案并存,因为通过代理会影响一定的性能。 代理这块对应的方案实现有Twitter的Twemproxy和豌豆荚的codis。
最大内存设置
默认情况下,在32位OS中,Redis最大使用3GB的内存,在64位OS中则没有限制。
在使用Redis时,应该对数据占用的最大空间有一个基本准确的预估
,并为Redis设定最大使用的内存
。否则在64位OS中Redis会无限制地占用内存(当物理内存被占满后会使用swap空间),容易引发各种各样的问题。
通过如下配置控制Redis使用的最大内存:
maxmemory 100mb
在内存占用达到了maxmemory后,再向Redis写入数据时,Redis会:
根据配置的数据淘汰策略尝试淘汰数据
,释放空间
如果没有数据可以淘汰
,或者没有配置数据淘汰策略
,那么Redis会对所有写请求返回错误
,但读请求仍然可以正常执行
在为Redis设置maxmemory时,需要注意:
如果采用了Redis的主从同步
,主节点向从节点同步数据时
,会占用掉一部分内存空间
,如果maxmemory过于接近主机的可用内存
,就会导致数据同步时内存不足。所以设置的maxmemory不要过于接近主机可用的内存,留出一部分预留用作主从同步。
没有整理
https://redisbook.readthedocs.io/en/latest/feature/transaction.html
Redis 的事务保证了 ACID 中的一致性(C)和隔离性(I)
,但并不保证原子性(A)和持久性(D)。
1 单个 Redis 命令的执行是原子性的
,但是 Redis 事务的执行并不是原子性的
。
如果一个事务队列中的所有命令都被成功地执行,那么称这个事务执行成功
。
另一方面,如果 Redis 服务器进程在执行事务的过程中被停止
—— 比如接到 KILL 信号、宿主机器停机,等等,那么事务执行失败
。
当事务失败时,Redis 也不会进行任何的重试或者回滚动作
。
2 一致性(Consistency)
Redis 的一致性问题可以分为三部分来讨论:入队错误
、执行错误
、Redis 进程被终结
。
(1)入队错误
在命令入队的过程中,如果客户端向服务器发送了错误的命令
,当客户端执行 EXEC 命令时, Redis 会拒绝执行
, 并返回失败信息。因此,带有不正确入队命令的事务不会被执行,也不会影响数据库的一致性
。
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> set key
(error) ERR wrong number of arguments for 'set' command
redis 127.0.0.1:6379> EXISTS key
QUEUED
redis 127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
(2)执行错误
如果命令在事务执行的过程中发生错误
,比如说,对一个不同类型的 key 执行了错误的操作
, 那么 Redis 只会将错误包含在事务的结果中
, 这不会引起事务中断或整个失败,不会影响已执行
事务命令的结果,也不会影响后面要执行
的事务命令, 所以它对事务的一致性也没有影响。
(3)Redis 进程被终结
如果 Redis 服务器进程在执行事务的过程中被其他进程终结
,或者被管理员强制杀死,那么根据 Redis 所使用的持久化模式
,可能有以下情况出现:
★ 内存模式:如果 Redis 没有采取任何持久化机制
,那么重启之后的数据库总是空白的,所以数据总是一致的。
★ RDB 模式:Redis 只有在事务执行之后,保存 RDB 的工作才有可能开始。
所以当 RDB 模式下的 Redis 服务器进程在事务中途被杀死时,事务内执行的命令,不管成功了多少,都不会被保存到 RDB 文件里
。恢复数据库需要使用现有的 RDB 文件,而这个 RDB 文件的数据保存的是最近一次的数据库快照
(snapshot),所以它的数据可能不是最新的
,但只要 RDB 文件本身没有因为其他问题而出错,那么还原后的数据库就是一致的。
★ AOF 模式:因为保存 AOF 文件的工作在后台线程进行
,所以即使是在事务执行的中途,保存 AOF 文件的工作也可以继续进行,因此,根据事务语句是否被写入并保存到 AOF 文件,有以下两种情况发生:
1)如果事务语句未写入到 AOF 文件
,或 AOF 未被 SYNC 调用保存到磁盘,那么当进程被杀死之后,Redis 可以根据最近一次成功保存到磁盘的 AOF 文件来还原数据库,只要 AOF 文件本身没有因为其他问题而出错,那么还原后的数据库总是一致的,但其中的数据不一定是最新的
。
2)如果事务的部分语句被写入到 AOF 文件
,并且 AOF 文件被成功保存,那么不完整的事务执行信息就会遗留在 AOF 文件里,当重启 Redis 时,程序会检测到 AOF 文件并不完整,Redis 会退出,并报告错误。需要使用 redis-check-aof 工具将部分成功的事务命令移除之后,才能再次启动服务器。还原之后的数据总是一致的,而且数据也是最新的(直到事务执行之前为止)。
3 隔离性(Isolation)
Redis 是单进程程序
,并且它保证在执行事务时,不会对事务进行中断
,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的
。
4 持久性(Durability)
因为事务不过是用队列包裹起了一组 Redis 命令,并没有提供任何额外的持久性功能,所以事务的持久性由 Redis 所使用的持久化模式决定:
(1)在单纯的内存模式
下,事务肯定是不持久的。
(2)RDB
模式下的 Redis 事务也是不持久的。
(3)在 AOF
的“总是 SYNC ”模式下,事务的每条命令在执行成功之后,都会
立即调用 fsync 或 fdatasync 将事务数据写入到 AOF 文件
。但是,这种保存是由后台线程进行的,主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的。
其他 AOF 模式也和“总是 SYNC ”模式类似,所以它们都是不持久的。
过程是:RedisServer结构体
中(Redis使用C语言完成)有一个Dictionary数组
,默认容量大小是16
, 每个 Dictionary结构体中有三个Dict结构体指针
,Dict其实就是一个KV结构
,类似于HashMap
,其中两个作为数据存储的库
,一个作为当前使用
,一个作为备用rehash
;另外一个Dict存储键的过期时间
。Redis的rehash过程其实就是将上述的16个Dictionary进行rehash,Redis内部支持一个周期循环运行的ctro函数
,这个函数定期检查
上述的16个Dictionary是否需要rehash
,如果有需要,ctro会完成Dictionary的rehash过程,周期结束后并且记录当前循环到Dictionary数组中的哪一个角标
,方便再次循环时从上一次循环停止的地方开始
。
其中还有一点,Redis的rehash是延迟性的
,每次put会将新元素加入到新的表中,每次查询时,首先会在新的表中查找
,如果没有
,在旧的表中查找
,查找到的话将这个元素插入到新的表中(这个过程也会涉及到检查元素是否过期
的问题)