1. redis是什么
2. 为什么用redis
3. redis 数据结构
4. redis中的对象类型
5. redis都能做什么?怎么实现的的?
6. redis使用过程中需要注意什么
7. 数据持久化
8. 集群是怎么访问的
9. redis单线程是什么鬼
10. 过期策略
11. 内存淘汰策略
12. 什么情况下不适合用redis
13. 运维工具:怎么样快速定位问题
14. 同类的产品有哪些
1. redis是什么
Redis(Remote Dictionary Server)是一个由Salvatore Sanfilippo写的key-value存储系统。是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 , , , , 与范围查询, , 和 索引半径查询。 Redis 内置了 ,, , 和不同级别的 , 并通过 和自动 提供高可用性(high availability)。
2. 为什么用redis
1)速度快,完全基于内存,使用C语言实现,网络层使用epoll解决高并发问题,单线程模型避免了不必要的上下文切换及竞争条件;
注意:单线程仅仅是说在网络请求这一模块上用一个线程处理客户端的请求,像持久化它就会重开一个线程/进程去进行处理
2)丰富的数据类型,Redis有8种数据类型,当然常用的主要是 String、Hash、List、Set、 SortSet 这5种类型,他们都是基于键值的方式组织数据。
每一种数据类型提供了非常丰富的操作命令,可以满足绝大部分需求,如果有特殊需求还能自己通过 lua 脚本自己创建新的命令(具备原子性)
3)除了提供的丰富的数据类型,Redis还提供了像慢查询分析、性能测试、Pipeline、事务、Lua自定义命令、Bitmaps、HyperLogLog、发布/订阅、
Geo等个性化功能。
4)Redis的代码开源在GitHub,代码非常简单优雅,任何人都能够吃透它的源码;它的编译安装也是非常的简单,没有任何的系统依赖;有非常活跃的社区,
各种客户端的语言支持也是非常完善。
5)支持事务(没用过)、持久化、主从复制让高可用、分布式成为可能。
3. redis 数据结构:
1)简单动态字符串
2)链表:链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。
链表被广泛用于redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等
3)字典:又称为符号表,关联数组或映射,是一种用于保存键值对的抽象数据结构。
redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。
字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。
4)跳跃表:跳跃表是以各种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
redis只有两个地方用到跳跃表:一个是实现有序集合键,另一个是在集群节点中用作内部数据结构。
5)整数集合:整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。
整数集合可以保存的类型为:int16_t,int32_t,int64_t 的整数值,并且保证集合中不会出现重复元素。
当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素的类型都要长时,整数集合需要先进行升级,
然后才能将新元素添加到整数集合里面。整数集合只支持升级操作,不支持降级操作。
4. redis中的对象类型
Redis并没有使用之前介绍的数据结构来实现键值对数据库,而是基于那些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型对象。每种类型的对象至少都有两种或者以上的编码方式,不同的编码可以在不同的使用场景上优化对象的使用效率。
Redis会共享值为0到9999的字符串对象。Redis只对包含整数值的字符串对象进行共享。对象会记录自己的最后一个被访问的时间,这个时间可以用于计算对象的空转时间,用以判断回收内存。Redis中的每个对象都由一个redisObject结构表示,该结构中和保存数据相关的三个属性分别是type属性,encoding属性和ptr(pointer ,指针)属性。
1)String(字符串):字符串的编码可以是int,raw 或者 embstr(短字符串)。在条件满足的情况下,int和embstr会被转换成raw编码的字符串对象。
2)List(列表):列表对象的编码可以是ziplist(压缩列表)或者linkedlist(双端列表)
3)Hash(哈希):哈希对象的编码可以是ziplist(压缩列表)或者hashtable(字典)
4)Set(集合):集合对象的编码可以是intset(整数集合)或者hashtable(字典)
5)ZSet(有序集合):有序集合对象,编码可以是ziplist(压缩列表)或者skiplist(字典+跳跃表,使用两种结构更高效)
6)Bitmaps(位图):BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上底层也是通过对字符串的操作来实现。
bitmaps一般的使用场景:
a. 各种实时分析.
b. 存储与对象ID关联的节省空间并且高性能的布尔信息.
7)HyperLogLog:
8)Geo(地理位置信息)
5. redis都能做什么?怎么实现的的?
1)缓存
a. 数据和缓存的操作时序,结论是清楚的:先淘汰缓存,再写数据库,再淘汰缓存。
原因:
假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cache中是旧数据,数据不一致。
假设先淘汰缓存,再写数据库:第一步淘汰缓存成功,第二步写数据库失败,则只会引发一次Cache miss。
第二次淘汰缓存的目的是避免写数据库期间,有新的缓存添加。当然,如果这种情况下,二次淘汰缓存失败,仍然有可能有脏数据……
b. 更新缓存 VS 淘汰缓存
淘汰缓存,避免两个并发写操作,导致脏数据
并且淘汰缓存操作简单,并且带来的副作用只是增加了一次cache miss,建议作为通用的处理方式。
参见:
2)分布式锁:
建议通过引入Redisson相关jar包使用。
加锁注意:线程唯一标记(自己加的锁,只能自己解,只能解自己加的锁),过期时间(避免系统崩溃后形成死锁),没有已存在的锁才能加锁成功
解锁注意:只能解自己的锁,不能直接使用del,避免锁过期删除了别人的锁(锁过期导致)。所以一般使用lua脚本执行,保证原子性。
参见:
3)排行榜(List/Set):
a. 获取排名和分数 通过zscore指令获取指定元素的权重,通过zrank指令获取指定元素的正向排名,通过zrevrank指令获取指定元素的反向排名[倒数第一名]。
正向是由小到大,负向是由大到小。
b. 根据排名范围获取元素列表 通过zrange指令指定排名范围参数获取对应的元素列表,携带withscores参数可以一并获取元素的权重。通过zrevrange指令
按负向排名获取元素列表[倒数]。正向是由小到大,负向是由大到小。
c. 根据score范围获取列表 通过zrangebyscore指令指定score范围获取对应的元素列表。通过zrevrangebyscore指令获取倒排元素列表。正向是由小到大,
负向是由大到小。参数-inf
表示负无穷,+inf
表示正无穷。
4)计数器/限速器(统计播放数据/浏览量/在线人数等):
a. 字符串可以作为计数器:INCRBY(整数加法),DECRBY(整数减法)
b. hash结构也可以当成计数器来使用,对于内部的每一个key都可以作为独立的计数器。如果value值不是整数,调用hincrby指令会出错。
利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力;限速器比较典型的
使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力;
5)好友关系(点赞/共同好友)
利用集合(Set)的一些命令,比如求交集、并集、差集等。可以方便搞定一些共同好友、共同爱好之类的功能;
6)简单的订阅消息(订阅发布/阻塞队列):
利用List来实现一个队列机制,比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的DB压力,完全可以用List来完成异步解耦;
7)用户是否登录过:
a. Bitmaps的最大优点就是存储信息时可以节省大量的空间。例如在一个系统中,不同的用户被一个增长的用户ID表示。
40亿(2^32=4*1024*1024*1024≈40亿
)用户只需要512M内存就能记住某种信息
8)Session共享
默认Session是保存在服务器的文件中,如果是集群服务,同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用Redis保存Session后,
无论用户落在那台机器上都能够获取到对应的Session信息。
9)分布式全局唯一id(string)
可以每次id加1,获取一个;也可以一次加100,批量获取
10)抽奖活动(set)
sadd key {userId} # 参加抽奖活动
smembers key # 获取所有抽奖用户,大轮盘转起来
spop key count # 抽取count名中奖者,并从抽奖活动中移除
srandmember key count # 抽取count名中奖者,不从抽奖活动中移除
6. redis使用过程中需要注意什么
1)缓存需要注意什么
a. 读多写少,更新频率低的时候才使用缓存
b. 先淘汰缓存,再写数据库,再淘汰缓存
2)分布式锁需要注意什么
a. 只能自己解锁,只能解自己的锁,加过期时间避免死锁,解锁时保持原子性
b. 单节点无法保证高可用;
主从架构可能从库复制延迟,引起锁重复(主库挂了,从库没有复制到新锁);
集群同样可能导致锁重复。
系统GC导致锁过期,引发锁重复又该怎么办?
分布式锁存在的问题,看大神论战:
3)怎样避免雪崩
所谓“缓存雪崩“,是指缓存的机器挂了,或者数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库
CPU和内存负载过高,甚至宕机。
这种问题的解决策略,一般有以下2个方面:
a. 提高缓存的HA。比如缓存的主从复制。
b. 对DB的访问实行限流、降级。
c. 缓存过期时间。以redis为例,将过期设置放到1数据库,真实数据放到0数据库,key值相同,假设都为key1。
应用程序首先判断1库这条数据是否失效,当1库标记数据库数据失效或过期,在1库中设置新的过期时间。然后从数据库取数据更新0数据库中
的数据。如果判断1数据库未失效,从0数据库取出数据返回。
d. 启动缓存时,进行数据预热。或者 缓存重启时,有之前的持久化文件预热。
4)避免缓存穿透
所谓“缓存穿透“,就是指某个key,先查cache没查到,再查db也没有查到。
这种key的存在,会导致cache一直没办法命中,压力一直打在db上面。如果访问很高频,可能会压垮DB。
解决办法其实也很简单:当查询DB没查到时,往缓存中写入一个空值(缺省值),这样第2次再查,就不会打到DB上了。
7. 数据持久化
Redis有两种持久化的方式:快照(RDB
文件)和追加式文件(AOF
文件):
a. RDB持久化方式会在一个特定的间隔保存那个时间点的一个数据快照。
b. AOF持久化方式则会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议一致,以追加的方式进行保存。
c. Redis的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器的运行时间里。
d. 两种方式的持久化是可以同时存在的,但是当Redis重启时,AOF文件会被优先用于重建数据。
参见:
8. 集群是怎么访问的
1)Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
Redis 集群提供了以下两个好处:将数据自动切分(split)到多个节点的能力。当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。
2)Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于
这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群可以有三个哈希槽, 其中:
a. 节点 A 负责处理 0 号至 5500 号哈希槽。
b. 节点 B 负责处理 5501 号至 11000 号哈希槽。
c. 节点 C 负责处理 11001 号至 16384 号哈希槽。
3)主从复制模型:我们为主节点 B 添加了从节点 B1 , 那么当主节点 B 下线的时候, 集群就会将 B1 设置为新的主节点, 并让它代替下线的主节点 B , 继续处理 5501 号至 11000 号的
哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。不过如果节点 B 和 B1 都下线的话, Redis 集群还是会停止运作。
4)开启集群需要手动执行命令
5)主节点挂了,从节点会变成主节点。当原主节点重启后,原主节点会成为新主节点的从节点。
6)增加和删除节点,都需要手动移动数据。
参见:
9. redis单线程是什么鬼
1)线程安全是指 redis是单线程,将变量拷贝到线程内存中。因为是单线程,所以这个变量是公用的。redis实际上是采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,
不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。
2)Redis 对于 I/O 多路复用模块的设计非常简洁,通过宏保证了 I/O 多路复用模块在不同平台上都有着优异的性能,将不同的 I/O 多路复用函数封装成相同的 API 提供给上层使用。
整个模块使 Redis 能以单进程运行的同时服务成千上万个文件描述符,避免了由于多进程应用的引入导致代码实现复杂度的提升,减少了出错的可能性。
参见:
10. 过期策略
1)定期删除:Redis 默认会每秒进行十次过期扫描(100ms一次),过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
从过期字典中随机 20 个 key;删除这 20 个 key 中已经过期的 key;如果过期的 key 比率超过 1/4,那就重复步骤 1;
2)惰性删除:所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除,不会给你返回任何东西。
11. 内存淘汰策略
这个问题可能有小伙伴们遇到过,放到Redis中的数据怎么没了?
因为Redis将数据放到内存中,内存是有限的,比如redis就只能用10个G,你要是往里面写了20个G的数据,会咋办?当然会干掉10个G的数据,然后就保留
10个G的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了
Redis提供的内存淘汰策略有如下几种:
1)noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
2)volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。(这个是使用最多的)
3)volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。
4)volatile-random 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。
5)allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
6)allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。
12. 什么情况下不适合用redis
1)更新频率太快
2)数据量太大
13. 运维工具:怎么样快速定位问题
1)redis desk manager
2)Medis
14. 同类的产品有哪些
1)memcached
a. 存储方式:memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。redis有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化
(笔者注:有快照和AOF日志两种持久化方式,在实际应用的时候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做dump)。
b. 数据支持类型:redis在数据支持上要比memecache多的多。
c. 使用底层模型不同:新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
d. 分布式环境:memcached的分布式由客户端实现,通过一致性哈希算法来保证访问的缓存命中率;Redis的分布式由服务器端实现,通过服务端配置来实现分布式;
e. 相对memcached而言,redis的面世时间更晚且具备更多功能,因此开发人员通常将其视为默认性首选方案。不过有两类特殊场景仍然是memcached的一家天下。首先就是对小型静态数据进行缓存处理,
最具代表性的例子就是HTML代码片段。memcached的内部内存管理机制虽然不像redis的那样复杂,但却更具实际效率——这是因为memcached在处理元数据时所消耗的内存资源相对更少。作为
memcached所支持的惟一一种数据类型,字符串非常适合用于保存那些只需要进行读取操作的数据,因为字符串本身无需进行进一步处理。
参见:
参见: