Redis
一、简介:
redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value的NoSql数据库。
二、特点
优点: 速度快、高性能
数据类型丰富
基于内存存储,又支持持久化
支持事务
缺点:不具备自动容错和恢复功能
较难支持在线扩容
三、数据类型:
1、字符串
字符串类型是redis最基础的数据结构,首先键是字符串类型,而且其他几种结构都是 在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习尊定基础。字符 串类型实际上可以是字符串(简单的字符串、复杂的字符串(xml、json)、数字(整数、浮 点数)、二进制(图片、音频、视频)),但最大不能超过512M。
使用场景:
[缓存功能]:字符串最经典的使用场景,redis最为缓存层,Mysql作为储存层,绝大 部分请求数据都是redis中获取,由于redis具有支撑高并发特性,所以缓存通常能起到加 速读写和降低 后端压力的作用。
[计数器]:许多运用都会使用redis作为计数的基础工具,他可以实现快速计数、查询 缓存的功能,同时数据可以一步落地到其他的数据源。如:视频播放数系统就是使用redis 作为视频播放数计数的基础组件。
[共享session]:出于负载均衡的考虑,分布式服务会将用户信息的访问均衡到不同服 务器上,用户刷新一次访问可能会需要重新登录,为避免这个问题可以用redis将用户 session集中管理,在这种模式下只要保证redis的高可用和扩展性的,每次获取用户更新 或查询登录信息都直接从redis中集中获取。
[限速]:处于安全考虑,每次进行登录时让用户输入手机验证码,为了短信接口不被频 繁访问,会限制用户每分钟获取验证码的频率。
2、哈希
在redis中哈希类型是指键本身又是一种键值对结构,value={{k,v},......{k,v}}
使用场景:
哈希结构相对于字符串序列化缓存信息更加直观,并且在更新操作上更加便捷。所以常 常用于**用户信息**等管理,但是哈希类型和关系型数据库有所不同,哈希类型是稀疏的, 而关系型数据库是完全结构化的,关系型数据库可以做复杂的关系查询,而redis去模拟 关系型复杂查询开发困难,维护成本高。
3、列表
列表类型是用来储存多个有序的字符串,列表中的每个字符串成为元素(element),一 个列表最多可以储存2的32次方-1个元素,在redis中,可以队列表两端插入(pubsh) 和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下表的元素等,列表是一种 比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发中有很多应用场景。
优点:
1.列表的元素是有序的,这就意味着可以通过索引下标获取某个或某个范围内的元素列表。
2.列表内的元素是可以重复的。
使用场景:
[消息队列]: redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端是用 lupsh从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞时的“抢”列表尾部的 元素,多个客户端保证了消费的负载均衡和高可用性
[文章列表]:每个用户都有属于自己的文章列表,现在需要分页展示文章列表,此时可 以考虑使用列表,列表不但有序同时支持按照索引范围获取元素。
使用列表技巧:
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpush+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列)
4、集合
集合类型也是用来保存多个字符串的元素,但和列表不同的是集合中不允许有重复的元 素,并且集合中的元素是无序的,不能通过索引下标获取元素,redis除了支持集合内的增 删改查,同时还支持多个集合取交集、并集、差集,并合理的使用好集合类型,能在实际开 发中解决很多实际问题。
使用场景:
[ 标签(tag)]:集合类型比较典型的使用场景,如一个用户对娱乐、体育比较感兴趣, 另一个可能对新闻感兴趣,这些兴趣就是标签,有了这些数据就可以得到同一标签的人,以 及用户的共同爱好的标签,这些数据对于用户体验以及曾强用户粘度比较重要。
sadd=tagging(标签)
spop/srandmember=random item(生成随机数,比如抽奖)
sadd+sinter=social Graph(社交需求)
5、有序集合
有序集合和集合有着必然的联系,他保留了集合不能有重复成员的特性,但不同得是, 有序集合中的元素是可以排序的,但是它和列表的使用索引下标作为排序依据不同的是,它 给每个元素设置一个分数,作为排序的依据。(有序集合中的元素不可以重复,但是csore 可以重复,就和一个班里的同学学号不能重复,但考试成绩可以相同).
使用场景:
[排行榜]:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜 单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。
四、数据结构
1、字符串
动态字符串,String采用预分配冗余空间的方法。
2、哈希
类似与java语言的hashmap,也是无序的二维结构,也即数组加列表的结构,然而也有 不同,比如rehash,刷新字典操作,hashmap是全部热hash,当字典足够多时,性能不是 很好的,所以redis进行改造,采用渐进式的方式,为什么说是渐进式?因为redis不会全 部reload,而是保存新旧两个字典,然后采用定时任务,将旧hash的数据搬到新的hash, 搬后在回收hash内存空间。
3、列表
采用快速列表,由压缩列表和双向的指针组成,如图,减少内存空间
4、集合
类似java里的set集合。
5、有序集合
有序集合是redis里比较有特色的,它类似于SortedSet和HashMap的组合。其内部实 现是一种被称作跳跃列表的数据结构。有序集合一方面它就是一个set,所以每个元素都是 唯一的,然后它可以给每个value赋值一个score,再根据这个score进行排序,score就 相当于一个权限排序的标识。
五、持久化
RDB:
AOF:
AOF重写:
六、主从集群
七、为什么这么快?
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用 多路复用非阻塞I/O 模型;
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。 这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
八、Redis事务
redis中的事务跟关系型数据库中的事务是一个相似的概念,但是有不同之处。关系型数据库事务执行失败后面的sql语句不在执行,而redis中的一条命令执行失败,其余的命令照常执行。
redis中开启一个事务(断开连接)是使用multi,相当于begin tran,exec提交事务,discard丢弃事务(恢复连接),watch / unwatch 监控key,当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的。
>Redis会将一个事务中的所有命令序列化,然后按顺序执行。Redis不可能在一个Redis事务的执行过程中插入执行另一个客户端发出的请求。这样便能保证Redis将这些命令作为一个单独的隔离操作执行。
> 在一个Redis事务中,Redis要么执行其中的所有命令,要么什么都不执行。因此,Redis事务能够保证原子性。EXEC命令会触发执行事务中的所有命令。因此,当某个客户端正在执行一次事务时,如果它在调用MULTI命令之前就从Redis服务端断开连接,那么就不会执行事务中的任何操作;相反,如果它在调用EXEC命令之后才从Redis服务端断开连接,那么就会执行事务中的所有操作。当Redis使用只增文件(AOF:Append-only File)时,Redis能够确保使用一个单独的write 系统调用,这样便能将事务写入磁盘。然而,如果Redis服务器宕机,或者系统管理员以某种方式停止Redis服务进程的运行,那么Redis很有可能只执行了事务中的一部分操作。Redis将会在重新启动时检查上述状态,然后退出运行,并且输出报错信息。使用redis-check-aof工具可以修复上述的只增文件,这个工具将会从上述文件中删除执行不完全的事务,这样Redis服务器才能再次启动。
check-and-set(乐观锁)
乐观锁介绍:
watch指令在redis事物中提供了CAS的行为。为了检测被watch的keys在是否有多个clients同时改变引起冲突,这些keys将会被监控。如果至少有一个被监控的key在执行exec命令前被修改,整个事物将会回滚,不执行任何动作,从而保证原子性操作,并且执行exec会得到null的回复。
乐观锁工作机制:
watch 命令会监视给定的每一个key,当exec时如果监视的任一个key自从调用watch后发生过变化,则整个事务会回滚,不执行任何动作。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然exec,discard,unwatch命令,及客户端连接关闭都会清除连接中的所有监视。还有,如果watch一个不稳定(有生命周期)的key并且此key自然过期,exec仍然会执行事务队列的指令。
九、其他
缓存雪崩:
由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
解决办法: 将缓存失效时间分散开
缓存穿透:
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
解决办法:①最常见的则是采用布隆过滤器 ②查询的空数据也进行缓存
缓存预热:
缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存更新:
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
1、定时去清理过期的缓存;
2、当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
缓存降级:
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
内存淘汰机制:
redis 内存数据集大小上升到一定大小的时候,就会进行数据淘汰策略。
内存淘汰策略:
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
allkeys-lru:从数据集中挑选最近最少使用的数据淘汰。
allkeys-random:从数据集中任意选择数据淘汰,当内存达到限制的时候,对所有数据集挑选随机淘汰,可写入新的数据集。
no-enviction:当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,所有引起申请内存的命令会报错。
为什么单线程?
redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处理这个事。在内存的情况下,这个方案就是最佳方案