目录
什么是Redis?
Redis(Remote Dictionary Server)是一个使用了c语言编写的,开元的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。
其中可以储存键和5种不同类型的值,键的类型只能是字符串,值支持5种数据类型:(string)字符串、(list)列表、(set)集合、(hash)哈希散列、(zset)有序集合。
string字符串
redis是使用的C语言开发,但c中并没有字符串类型,只能使用指针或符数组的形式标识一个字符串,所以redis设计里一种简单动态字符串(SDS[Simple Dynamic String])作为底实现:
定义SDS对象,此对象中包含三个属性:
- len buf中已经占有的长度(表岁吃字符串的时间长度)
- free buf中使用的缓冲区长度
- buf[]时机保存字符串数据的地方
list列表
redis对链表的结构支持使用在它储存的世界中独树一帜,一个列表结可以有序的储存多个字符串,拥有例如:lpush lpop rpush rpop 等操作命令。在3.2版本之前,列表是使用ziplist和likedlist实现的,而在3.2版本之后,重新引入了一个quicklist的数据结构,列表的底层都是由quicklist实现的,它结合了ziplist和linkedlist的优点。
set集合
redis的几个和列表都可以储存多个字符串,他们之间的不同在于列表可以储存多个相同的字符串,而几个则通过使用散列表(hashtable)来保证自己储存的每个字符串都是各不相同的(这些散列表只有键,但没有与键相关联的值),redis中的几个是无需的。还可能存在另一种集合,那就是intset,它是用于储存整的的有序集合,里面存放着统一类型的整数。共有三种整数:int_16t、int_32t、int_64t。查找的时间复杂度为O(logN)。它是插入的时候,有可能会涉嫌级到升级。这时候会对内存重新重新分配,所以此时的时间复杂度就是O(N)级的了。
hash哈希散列
hash底层的数据结构实心有两种:
- 一种是ziplist,上面提到过。当储存的数据超过配置的阀值是就是转用话说她报了的结构。这种转换比较消耗性能,所以应该尽量避免这种转换操作。同时满足一下两个条件是会使用这种构造:
- 1)当键的个数小于hash-max-ziplist-entries(默认512)
- 2)当所有值都小于hash-max-ziplist-value(默认64)
- 另一种就是hashtable。这种结构的时间复杂度为O(1),但是会消耗较多的内存空间
zset有序集合
有序集合和散列一样,都用于存储键值对:有序集合的键被称为成员(member),每个成员都是各不相同的。有序集合的值则被称为分值(score),分值必须为浮点数。有序集合是redis里面唯一一个既可以根据成员访问元素(这一点和散列一样),又可以根据分值以及分值的排列顺序访问元素的结构。它的存储方式也有两种:
- 是ziplist结构。
与上面的hash中的ziplist类似,member和score顺序存放并按score的顺序排列
- 另一种是skiplist与dict的结合。
redis有哪些优缺点?
优点:
- 支持数据持久化,支持AOF和RDB两种持久化方式
- 支持事务,redis的所有操作都是原子性,同时还支持对几个操作合并后的原子性执行
- 读写性能优异,redis能读的速度约是110000次/秒,写的速度约是8100次/秒
- 数据机构丰富,除了支持string类型的value外还支持hash、set、list、zset等数据结构。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离
缺点:
- Redis较难支持在线扩容,在集群容量达到上限是在线扩容会变得很复杂,为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此redis适合的场景只要局限在娇小的数据量的高性能操作合运算上
- Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
缓存穿透、缓存击穿、缓存雪崩的解决方案
缓存穿透
指查询一个一定不存在的数据,如果从储存层查不到 数据则不写入缓存,浙江导致这个不存在的数据每次请求都要到DB去查询,可能导致DB挂掉。要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
解决方案:
- 查询返回的数据为空,人把这个空结果进行缓存,但过期的时间会比较短
- 布隆过滤器:将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定存在的数据会被子这个bitmap拦截掉,从而避免了对DB的查询
缓存击穿
对于设置了过期时间的 key,缓存在某个时间点过期的时候,恰好这时间点对这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。
解决方案:
- 使用互斥锁:当缓存失效时,不立即去 load db,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db 的操作并回设缓存,否则重试 get 缓存的方法。
- 永远不过期:物理不过期,但逻辑过期(后台异步线程去刷新)
缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多 key,击穿是某一个key 缓存。
解决方案:
- 缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
redis为啥这么快
-
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
-
数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
-
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
-
使用多路 I/O 复用模型,非阻塞 IO;
-
使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;