网络搜集整理,有些匆忙,未整理出处,请见谅。因作者水平有限,文中不当之处,烦请批评指证~

redis宕机如何解决?如果是项目上线的宕机呢?
宕机:服务器停止服务
如果只有一台redis,肯定会造成数据丢失,无法挽救
多台redis或者是redis集群 ,宕机则需要分为在主从模式下区分来看:

slave从redis宕机
配置主从复制的时候才配置从的redis,从的会从主的redis中读取主的redis的操作日志,求达到主从复制。
1)在Redis中从库重新启动后会自动加入到主从架构中,自动完成同步数据;
2)如果从数据库实现了持久化,可以直接连接到主的上面,只要实现增量备份(宕机到重新连接过程中,主的数据库发生数据操作,复制到从数据库),重新连接到主从架构中会实现增量同步。
Master 宕机
假如主从都没数据持久化,此时千万不要立马重启服务,否则可能会造成数据丢失,正确的操作如下:
在slave数据上执行SLAVEOF ON ONE,来断开主从关系并把slave升级为主库
此时重新启动主数据库,执行SLAVEOF,把它设置为从库,连接到主的redis上面做主从复制,自动备份数据。
以上过程很容易配置错误,可以使用redis提供的哨兵机制来简化上面的操作。简单的方法:redis的哨兵(sentinel)的功能。

复制redis中sentinel.conf,根据情况进行配置

MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?

由maxmemory-policy 参数设置淘汰策略:

CONFIG SET maxmemory-policy volatile-lru      #淘汰有过时期的最近最好使用数据

redis缓存穿透:查询一个数据库中不存在的数据,比如商品详情,查询一个不存在的ID,每次都会访问DB,如果有人恶意破坏,很可能直接对DB造成过大地压力
解决方案:当通过某一个key去查询数据的时候,如果对应在数据库中的数据都不存在,我们将此key对应的value设置为一个默认的值,比如“NULL”,并设置一个缓存的失效时间,这时在缓存失效之前,所有通过此key的访问都被缓存挡住了。后面如果此key对应的数据在DB中存在时,缓存失效之后,通过此key再去访问数据,就能拿到新的value了。另一个方案是使用布隆过滤器

12.1:缓存空值存在的问题:

12.2:布隆过滤器:

布隆过滤器存在的问题:相对来说布隆过滤器搞起来代码还是比较复杂的,现阶段我们暂时还不需要,后面实在需要再考虑去做,什么阶段做什么样的事情,不是说这个系统一下子就能做的各种完美。

reds缓存雪崩:是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案:将系统中key的缓存失效时间均匀地错开,防止统一时间点有大量的key对应的缓存失效。比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

redis缓存击穿(热点Key):缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询

请求/响应协议和RTT
Redis是使用client - server模型和所谓的request/response协议的TCP服务器。(阻塞模式)
RTT(往返时间)
例如,如果RTT时间是250毫秒(在因特网上的链路非常慢的情况下),即使服务器能够每秒处理100k个请求,我们也能够以每秒最多四个请求进行处理。
如果使用的接口是环回接口,则RTT要短得多(例如我的主机报告0,044毫秒,ping 127.0.0.1),但如果你需要连续执行多次写入,它仍然很多。
采用Redis管道(Pipelining),一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。redis很早就开始支持。开启管道后,往返延迟被改善的相当低了,测试中速度效率提升了5倍。
管道(Pipelining) VS 脚本(Scripting)
大量 pipeline 应用场景可通过 Redis 脚本(Redis 版本 >= 2.6)得到更高效的处理,后者在服务器端执行大量工作。脚本的一大优势是可通过最小的延迟读写数据,让读、计算、写等操作变得非常快(pipeline 在这种情况下不能使用,因为客户端在写命令前需要读命令返回的结果)。
应用程序有时可能在 pipeline 中发送 EVAL 或 EVALSHA 命令。Redis 通过 SCRIPT LOAD 命令(保证 EVALSHA 成功被调用)明确支持这种情况。

redis使用两种不同的持久化方法来将数据存储到硬盘里面。一种方法叫快照(snapshotting),他可以将存在某一时刻的所有数据都写入硬盘里面。另一种方法叫做只追加文件(append-only file,AOF),他会在执行写命令时,将被执行的写命令复制到硬盘里面。这两种方既可以同时使用,又可以单独使用,有些情况下甚至两种方法都不可用,具体根据用户的数据以及应用来决定。

名词:ASAP As Soon As Possible 尽可能
RESP Redis Serialization Protocol .redis的序列化协议
replica 从5.0开始原slave改叫replica,相关的配置参数也做了同样的改名
jemalloc 内存分配器
SDS 简单动态字符串

相关面试题:

  • Redis有哪些数据结构?

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。

如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。

  • 使用过Redis分布式锁么,它是什么回事?

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
SETNX 是SET if Not eXists的简写。
这时候对方会告诉你说你回答得不错,然后接着问如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后回答:我记得set指令有非常复杂的参数(2.6.12开始),这个应该是可以同时把setnx和expire合成一条指令来用的!对方这时会显露笑容,心里开始默念:摁,这小子还不错。

给锁加上超时功能:如下图

redis实战

  • 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。

对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

  • 使用过Redis做异步队列么,你是怎么用的?

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

如果对方追问可不可以不用sleep呢?list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

如果对方追问能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。

如果对方追问pub/sub有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

如果对方追问redis如何实现延时队列?我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

到这里,面试官暗地里已经对你竖起了大拇指。但是他不知道的是此刻你却竖起了中指,在椅子背后。

  • 如果有大量的key需要设置同一时间过期,一般需要注意什么?

如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

  • Redis如何做持久化的?

bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

对方追问那如果突然机器掉电会怎样?取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

对方追问bgsave的原理是什么?你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

  • Pipeline有什么好处,为什么要用pipeline?

可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。
附: 但是注意,如果使用Pipeline。当节点个数扩充后,会导致长连接数目成倍数上涨。

  • Redis的同步机制了解么?

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

  • 是否使用过Redis集群,集群的原理是什么?

Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

二:

  1. 使用Redis有哪些好处?
    (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
    (2) 支持丰富数据类型,支持string,list,set,sorted set,hash
    (3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
    (4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

2.redis相比memcached有哪些优势?

(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型

(2) redis的速度比memcached快很多(数据大于100k的时候memcached快)

(3)value大小:redis最大可以达到512M,而memcache只有1MB

(4)发布订阅,脚本存储过程,持久化数据等

(5)redis 本身支持主从复制 memcached需要借助repcached(复制缓冲区技术)

画外音:memcached利用其多线程优势,单实例吞吐量极高,可以达到 几十万QPS 适用于最大程度抗量的,就是无法持久化,数据不能备份,只能用用于缓存,而且重启之后数据全部丢失的。

  1. redis常见性能问题和解决方案:
  2. Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件。

写内存快照时,save命令调度rdbSave函数,会阻塞主线程的工作;
AOF在重写的时候会占大量的CPU和内存资源。如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。

  1. 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  2. 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  3. 尽量避免在压力很大的主库上增加从库
  4. 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

  1. redis 适合的场景

Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别。

  1. 会话缓存(Session Cache)

用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。

  1. 队列

Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。

  1. 排行榜

集合(Set)和有序集合(Sorted Set)也使得这些操作变的非常简单。当要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:

ZRANGE user_scores 0 10 WITHSCORES

排行榜(leader board)按照得分进行排序。ZADD命令可以直接实现这个功能,而ZREVRANGE命令可以用来按照得分来获取前100名的用户,ZRANK可以用来获取用户排名,非常直接而且操作容易。

这就像Reddit的排行榜,得分会随着时间变化。LPUSH和LTRIM命令结合运用,把文章添加到一个列表中。一项后台任务用来获取列表,并重新计算列表的排序,ZADD命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索,即使是负载很重的站点。

4)计数器

Redis在内存中对数字进行递增或递减的操作实现的非常好。Redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统。

进行各种数据统计的用途是非常广泛的,比如想知道什么时候封锁一个IP地址。INCRBY命令让这些变得很容易,通过原子递增保持计数;GETSET用来重置计数器;过期属性用来确认一个关键字什么时候应该删除。

  1. 发布/订阅

发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!。

6) 需要精准设定过期时间
可以把有序集合(sorted set)的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。

Redis如何做到和mysql数据库的同步呢
步骤是实时获取mysql的 binlog进行解析,然后修改redis,以下有两种实现:
https://github.com/liukelin/canal_mysql_nosql_sync 首先了解web服务器的结构图

然后是解决方案:

基于 Canal 的 MySql RabbitMQ Redis/memcached/mongodb 的nosql同步 (多读、nosql延时不严格 需求)

1.mysql主从配置

2.对mysql binlog(row) parser 这一步交给canal

3.MQ对解析后binlog增量数据的推送

4.对MQ数据的消费(接收+数据解析,考虑消费速度,MQ队列的阻塞)

5.数据写入/修改到nosql (redis的主从/hash分片)

6.保证对应关系的简单性:一个mysql表对应一个 redis实例(redis单线程,多实例保证分流不阻塞),关联关系数据交给接口业务

数据:mysql->binlog->MQ->redis(不过期、关闭RDB、AOF保证读写性能) (nosql数据仅用crontab脚本维护)

请求:http->webserver->redis(有数据)->返回数据 (完全避免用户直接读取mysql)

->redis(无数据)->返回空

7.可将它视为一个触发器,binlog为记录触发事件,canal的作用是将事件实时通知出来,并将binlog解析成了所有语言可读的工具。
在事件传输的各个环节 提高 可用性 和 扩展性 (加入MQ等方法)最终提高系统的稳定。

传统 Mysql Redis/memcached nosql的缓存 (业务同步)
从cache读取数据->
1.对数据在mysql的hash算法分布(db/table/分区),每个hash为节点(nosql数据全部失效时候,可保证mysql各节点可支持直接读取的性能)

2.mysql主从

3.nosql数据的hash算法分布(多实例、DB),每个hash为节点

4.nosql数据震荡处理 (当某节点挂了寻找替代节点算法(多层hash替代节点)。。。)

5.恢复节点数据

6.请求:http->webserver->【对key计算一致性hash节点】
->connect对应的redis实例

->1.redis(有数据)-> 返回数据

		           ->2.redis(无数据)-> mysql (并写入数据redis) -> 返回数据

                           ->3.redis节点挂掉-> 业务寻址hash替代节点 
                                                     -> 3.1 redis(有数据) -> 返回数据

						     -> 3.2 redis(无数据) -> mysql(并写入数据redis) -> 返回数据

为什么要使用消息队列(MQ)进行binlog传输:
1.增加缓冲,binlog生产端(canal client)只负责生产而不需要考虑消费端的消费能力, 不等待阻塞。

2.binlog 消费端: 可实时根据MQ消息的堆积情况,动态 增加/减少 消费端的数量,达到合理的资源利用和消费

Redis可以用来做数据库吗?
是否可以用来作为数据库,还是看业务,架构是技术对业务妥协的结果!
非核心业务,比如运营推广,数据聚合统计这种允许数据少量丢失的业务可以全用redis,扩展方便,效率高,业务量也不大。特别是运营推广这种时效性很强的业务,在推广结束后数据接没用了,Redis内存压力也不会很大。

按照发展阶段来看。 产品初期,业务需求多变,数据量很小,数据结构朝令夕改,这时候如果用mysql很有可能会在改数据库结构上疲于奔命,如果用Redis,由于没有Scheme约束,数据结构的变更相对容易,比起mysql能轻松不少。

产品中期,业务需求逐渐稳定,可以将核心数据导到mysql中落地,其余数据仍然放在Redis中。

产品后期,业务需求基本稳定,数据应该尽量都落地到mysql,Redis做高速缓存,或者先写到Redis,再异步刷到mysql。

按照对数据的需求来看 mysql能支持对各个字段的查询,Redis的查询仅限于对key的简单匹配,如果要对value进行复杂查询,不适合用Redis。Redis新增了计数器、bitmaps以及地理位置索引的支持,特别是地理位置索引,可以方便的做附近搜索,有需求的话可以考虑。

redis是目前公认的速度最快的基于内存的键值对数据库,但redis的缺点也非常明显,仅提供最基本的hash set, list, sorted set等基于数据类型,不分表,没有schema,没有索引,没有外键,缺少int/date等基本数据类型,多条件查询需要通过集合内联(sinter,zinterstore)和连接间接实现,操作不便,开发效率低,可维护性不佳;因此一般不将其视为完整的数据库单独使用,很多网站将redis作为高速缓存和session状态存储层,然后再与其他数据库搭配使用。

使用redis来实现排行榜功能:
排行榜是一个很普遍得请求,使用redis中有序集合的特性来实现排行榜是一个又好又快地选择

zset 中的 value 使用用户的 user_id,score 就是用户游戏的得分;
如果读取压力大,可以考虑读写分离,master上写用户的分数,多个slave上读

一般排行榜都是有时效性的,比如“用户积分榜”,如果没有实效性一直按照总榜来,可能榜首总是那几个老用户。对新用户来说,那真是太令人沮丧了。
首先来个“今日积分榜”排序规则是今日用户新增积分从多到少。
那么用户增加积分时,都操作一下记录当天积分增加的有序集合。
假设今天是 2017 年 03 月 06 日,UID 为 1 的用户因为某个操作,增加了 5 个积分。
Redis 命令如下:
ZINCRBY rank:20170306 5 1

按照分数从高到低,获取 top10:
ZREVRANGE rank:20170306 0 9 withscores

那么昨日榜单也就出来了:
ZREVRANGE rank:20170305 0 9 withscores

利用并集实现多天的积分总和,实现“上周积分榜”:
ZUNIONSTORE rank:last_week 7 rank:20150323 rank:20150324
rank:20150325 rank:20150326 rank:20150327 rank:20150328
rank:20150329 WEIGHTS 1 1 1 1 1 1 1

这样就将 7 天的积分记录合并到有序集合 rank:last_week 中了。权重因子 WEIGHTS 如果不给,默认就是 1。为了不隐藏细节,特意写出。
那么查询上周积分榜 Top10 的信息就是
ZREVRANGE rank:last_week 0 9 withscores

Redis缓存更新的套路
要么通过2PC或是Paxos协议保证一致性,要么就是拼命的降低并发时脏数据的概率,而Facebook使用了这个降低概率的玩法,因为2PC太慢,而Paxos太复杂。当然,最好还是为缓存设置上过期时间。

5、Redis的全称是什么?
Remote Dictionary Server。

一个字符串类型的值能存储最大容量是多少?
512M

为什么Redis需要把所有数据放到内存中?
Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。将数据存入内存里面,发送给Redis的命令请求也就不需要经过典型的查询分析器(parser)或者查寻优化器(optimizer)进行处理。如果不将数据放在内存中,磁盘I/O速度(每次数据更新导致的随机读写)为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

Redis的内存划分:
1.数据
redis对外提供五种数据类型,字符串,哈希,列表,集合,有序集合,内部每种类型可能有两种或者更多的内部编码实现,此外,redis在存储对象时,并不是直接将数据扔进内存,而是会对对象进行各种包装:如redisObject、SDS等

2.进程本身运行需要的内存
画外音:除了主进程,子进程也会如redis执行AOF RDB重写时创建的子进程

3.缓冲内存
缓冲区内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等,其中,客户端缓冲存储用户连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令,这些内存由jemalloc分配,因此会统计在used_memory中。

4.内存碎片
内存碎片是redis在分配、回收物理内存过程中产生的。例如,对数据的频繁修改,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但是redis又无法有效利用,这就形成了内存碎片。

执行set hello world时,所涉及的数据模型

(1)dictEntry:Redis是Key-Value数据库,因此对每个键值对都会有一个dictEntry,里面存储了指向Key和Value的指针;next指向下一个dictEntry,与本Key-Value无关。
(2)Key:图中右上角可见,Key(”hello”)并不是直接以字符串存储,而是存储在SDS结构中。
(3)redisObject:Value(“world”)既不是直接以字符串存储,也不是像Key一样直接存储在SDS中,而是存储在redisObject中。实际上,不论Value是5种类型的哪一种,都是通过redisObject来存储的;而redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。不过可以看出,字符串对象虽然经过了redisObject的包装,但仍然需要通过SDS存储。
实际上,redisObject除了type和ptr字段以外,还有其他字段图中没有给出,如用于指定对象内部编码的字段;后面会详细介绍。
(4)jemalloc:无论是DictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。以DictEntry对象为例,有3个指针组成,在64位机器下占24个字节,jemalloc会为它分配32字节大小的内存单元。

jemalloc
Redis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。
jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。
jemalloc划分的内存单元如下图所示:

lru
lru记录的是对象最后一次被命令程序访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)
refcount与共享对象
refcount记录的是该对象被引用的次数,类型为整型。refcount的作用,主要在于对象的引用计数和内存回收。当创建新对象时,refcount初始化为1;当有新程序使用该对象时,refcount加1;当对象不再被一个新程序使用时,refcount减1;当refcount变为0时,对象占用的内存会被释放。
Redis中被多次使用的对象(refcount>1),称为共享对象。Redis为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对象。这个被重复使用的对象,就是共享对象。目前共享对象仅支持整数值的字符串对象。
共享对象的具体实现:
Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。
虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。
就目前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是09999的整数值;当Redis需要使用值为09999的字符串对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值进行改变。
共享对象的引用次数可以通过object refcount命令查看,如下图所示。命令执行的结果页佐证了只有0~9999之间的整数会作为共享对象。

ptr
ptr指针指向具体的数据,如前面的例子中,set hello world,ptr指向包含字符串world的SDS。
redisObject总结
综上所述,redisObject的结构与对象类型、编码、内存回收、共享对象都有关系;一个redisObject对象的大小为16字节:
4bit+4bit+24bit+4Byte+8Byte=16Byte。
typedef struct redisObject{
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 指向底层数据结构的指针
void *ptr;
// 引用计数
int refcount;
// 记录最后一次被程序访问的时间(4.0为24,2.6为22)
unsigned lru:22;
}robj


redis集群方案:
业界已经成熟的解决方案:
1。redis3.0之后自身支持redis-cluster集群,采用无中性化结构,每个节点保存数据和整个集群状态,每个节点和其他节点连接。redis-cluster是一种服务端分片技术(哈希槽)

2.twemproxy代理方案
Redis代理中间件twemproxy是一种利用中间件做分片的技术。twemproxy处于客户端和服务器的中间,将客户端发来的请求,进行一定的处理后(sharding),再转发给后端真正的redis服务器。也就是说,客户端不直接访问redis服务器,而是通过twemproxy代理中间件间接访问。降低了客户端直连后端服务器的连接数量,并且支持服务器集群水平扩展。
twemproxy中间件的内部处理是无状态的,它本身可以很轻松地集群,这样可以避免单点压力或故障。

3.哨兵模式2.8
Sentinel(哨兵)是Redis的高可用性解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器以及这些主服务器下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。(ping-超时标记下线)

4.codis
codis是一个分布式的Redis解决方案,由豌豆荚开源,连接codis proxy和连接原生的redis server没什么明显的区别,上层应用可以像使用单机的redis一样使用,codis底层会处理请求的转发,不停机的数据迁移等工作,所有后边的事情,对于前面的客户端来说是透明的,可以简单的认为后边连接的是一个内存无限大的redis服务。

5.客户端分片
一致性哈希实现

Redis集群方案什么情况下会导致整个集群不可用?
有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。

Redis Java客户端的选择
Redis的Java客户端很多,官方推荐的有三种:Jedis、Redisson和lettuce。
jedis轻量级,简洁,便于集成和改造
Redisson支持一些高级特性,程序复杂度会提高

Redis如何设置密码及验证密码?
设置密码:config set requirepass 123456

授权密码:auth 123456

说说Redis哈希槽的概念?
Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

19、Redis集群的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.

20、Redis集群会有写操作丢失吗?为什么?
Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。

21、Redis集群之间是如何复制的?
异步复制

22、Redis集群最大节点个数是多少?
16384个。

23、Redis集群如何选择数据库?
Redis集群目前无法做数据库选择,默认在0数据库。

24、怎么测试Redis的连通性?
ping
26、怎么理解Redis事务?
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

27、Redis事务相关的命令有哪几个?
MULTI、EXEC、DISCARD、WATCH

28、Redis key的过期时间和永久有效分别怎么设置?
EXPIRE和PERSIST命令。

29、Redis如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面.

30、Redis回收进程如何工作的?
一个客户端运行了新的命令,添加了新的数据。

Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。

一个新的命令被执行,等等。

所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

31、Redis回收使用的是什么算法?
LRU算法

32、Redis如何做大量数据插入?
Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。

33、为什么要做Redis分区?
分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。

34、你知道有哪些Redis分区实现方案?
客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。

代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy

查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。

35、Redis分区有什么缺点?
涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。

同时操作多个key,则不能使用Redis事务.

分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set).

当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。

分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。

36、Redis持久化数据和缓存怎么做扩容?
如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。

如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。

37、分布式Redis是前期做还是后期规模上来了再做好?为什么?
既然Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。

一开始就多设置几个Redis实例,例如32或者64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。

这样的话,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的Redis实例从第一台机器迁移到第二台机器。

38、Twemproxy是什么?
Twemproxy是Twitter维护的(缓存)代理系统,代理Memcached的ASCII协议和Redis协议。它是单线程程序,使用c语言编写,运行起来非常快。它是采用Apache 2.0 license的开源软件。 Twemproxy支持自动分区,如果其代理的其中一个Redis节点不可用时,会自动将该节点排除(这将改变原来的keys-instances的映射关系,所以你应该仅在把Redis当缓存时使用Twemproxy)。 Twemproxy本身不存在单点问题,因为你可以启动多个Twemproxy实例,然后让你的客户端去连接任意一个Twemproxy实例。 Twemproxy是Redis客户端和服务器端的一个中间层,由它来处理分区功能应该不算复杂,并且应该算比较可靠的。

39、支持一致性哈希的客户端有哪些?
Redis-rb、Predis等。

40、Redis与其他key-value存储有什么不同?
Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。

Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,应为数据量不能大于硬件内存。在内存数据库方面的另一个优点是, 相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。 同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

41、Redis的内存占用情况怎么样?
给你举个例子: 100万个键值对(键是0到999999值是字符串“hello world”)在我的32位的Mac笔记本上 用了100MB。同样的数据放到一个key里只需要16MB, 这是因为键值有一个很大的开销。 在Memcached上执行也是类似的结果,但是相对Redis的开销要小一点点,因为Redis会记录类型信息引用计数等等。

当然,大键值对时两者的比例要好很多。

64位的系统比32位的需要更多的内存开销,尤其是键值对都较小时,这是因为64位的系统里指针占用了8个字节。 但是,当然,64位系统支持更大的内存,所以为了运行大型的Redis服务器或多或少的需要使用64位的系统。

42、都有哪些办法可以降低Redis的内存使用情况呢?
如果你使用的是32位的Redis实例,可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。

43、查看Redis使用情况及状态信息用什么命令?
info

44、Redis的内存用完了会发生什么?
如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将Redis当缓存来使用配置淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。

45、Redis是单线程的,如何提高多核CPU的利用率?
可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。

46、一个Redis实例最多能存放多少的keys?List、Set、Sorted Set他们最多能存放多少元素?
理论上Redis可以处理多达232的keys,并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys。我们正在测试一些较大的值。

任何list、set、和sorted set都可以放232个元素。

换句话说,Redis的存储极限是系统中的可用内存值。

47、Redis常见性能问题和解决方案?
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

48、Redis提供了哪几种持久化方式?
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.

AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.

如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.

你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始。

49、如何选择合适的持久化方式?
一般来说, 如果想达到足以媲美PostgreSQL的数据安全性, 你应该同时使用两种持久化功能。如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。

有很多用户都只使用AOF持久化,但并不推荐这种方式:因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外, 使用RDB还可以避免之前提到的AOF程序的bug。

50、修改配置不重启Redis会实时生效吗?
针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。 从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索 ‘CONFIG GET *’ 命令获取更多信息。

但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。

Redis启动加载过程

  1. 初始化全局服务器配置
  2. 加载配置文件(如果指定了配置文件,否则使用默认配置)
  3. 初始化服务器
  4. 加载数据库
  5. 网络监听