1.发布订阅模式
1.1列表的局限性
lpop和lpush可以实现消息队列,但是消息不停的要用lpop来查看list中是否有等待消息处理,为了减少通信消耗,可以让lpop睡眠一会,再进行消费;这样会存在两个问题?
1.如果生产者的速度远远大于消费者的速度,则导致list浪费;
2.消息的实时性降低;
list还提供了阻塞命令:blpop没有任何元素弹出时,将会被阻塞;
blpop queue 5
基于list实现的消息队列不支持一对多关系
2 redis事务
redis本身就是一个原子操作,如果涉及到多个命令的执行,需要用到事务操作,例如我们之前说的用setnx实现分布式锁,我们先set,然后设置对key设置expire,防止del发生异常的时候锁不会被释放,业务处理完了以后再del,这三个动作我们就希望它们作为一组命令执行。
Redis事务特性:
1.按进入队列的顺序执行
2.不会受到其他客户端请求的影响
Redis事务涉及的命令:multi(开启事务),exec(执行事务),discard(取消事务),watch(监视);
2.2 事务的用法
案例场景:tom和mic各有1000元,tom需要向mic转账100元。
tom的账户余额减少100元,mic的账户余额增加100元。
127.0.0.1:6379>set tom 1000
OK
127.0.0.1:6379>set mic 1000
OK
127.0.0.1:6379>multi
OK
127.0.0.1:6379>decrby tom 100
QUEUED
127.0.0.1:6379>incrby mic 100
QUEUED
127.0.0.1:6379> exec
1)(integer)900
2)(integer)1100
127.0.0.1:6379>get tom "900"
127.0.0.1:6379>get mic "1100"
通过multi 的开启事务命令,事务不能嵌套,多个multi命令效果一样
multi执行后,客户端可以继续向服务端发生许多命令,这些命令都不会被执行,而被放到一个队列中,当exec命令被调用时,所有队列中的命令才会被执行;
通过exec的命令执行事务。如果没有执行exec,所有的命令都不会被执行。如果中途不想执行事务了,怎么办?可以调用discard可以清空事务队列,放弃执行。
2.3 watch命令
redis可以为wath提供乐观锁行为,也就是多个线程更新变量时,会跟原来的值做比较,只要它没有被其他线程修改的情况下,才能更新成功。
我们可以用watch监视一个或者多个key,如果开启事务之后,至少有一个被监视key键在 exec 执行之前被修改了, 那么整个事务都会被取消(key提前过期除外)。可以用unwatch取消。
2.4事务执行时可能遇到的问题
事务在执行前和执行后发生的错误
2.4.1 事务在执行前发生的错误
入队的命令存在语法错误,包括参数值和参数名
127.0.0.1:6379>multi OK
127.0.0.1:6379>set gupao 666
QUEUED
127.0.0.1:6379>hset qingshan 2673 (error)ERRwrongnumberofargumentsfor'hset'command 127.0.0.1:6379>exec (error)EXECABORTTransactiondiscardedbecauseofpreviouserrors.
在这种情况下事务会被拒绝执行,也就是队列中所有的命令都不执行
2.4.2 事务在执行后发生的错误
比如,类型错误,比如对String使用了Hash的命令,这是一种运行时错误。
127.0.0.1:6379>multi
OK
127.0.0.1:6379>set k1 1
QUEUED
127.0.0.1:6379>hset k1 a b
QUEUED
127.0.0.1:6379>exec
1)OK
2)(error)WRONGTYPEOperationagainstakeyholdingthewrongkindofvalue 127.0.0.1:6379>getk1 "1"
最后我们发现setk11的命令是成功的,也就是在这种发生了运行时异常的情况下,只有错误的命令没有被执行,但是其他命令没有受到影响。
这个显然不符合我们对原子性的定义,也就是我们没办法用Redis 的这种事务机制来实现原子性,保证数据的一致。
3.redis为什么会这么快
1.单线程
2.纯内存结构
3.多路复用
3.1 文件描述符
Linux系统将所有设备都当作文件来处理,而Linux用文件描述符来标识每个文件
对象。文件描述符(FileDescriptor)是内核为了高效管理已被打开的文件所创建的索引,用于指向被打开的文件,所有执行 I/O操作的系统调用都通过文件描述符;文件描述符是一个简单的非负整数,用以表明每个被进程打开的文件。
Linux系统里面有三个标准文件描述符。0:标准输入(键盘);1:标准输出(显示器);2:标准错误输出(显示器)。
3.2 多路复用
多路指的是多个TCP连接(Socket或Channel)。
复用指的是复用一个或多个线程。
它的基本原理就是不再由应用程序自己监视连接,而是由内核替应用程序监视文件描述符。
客户端在操作的时候,会产生具有不同事件类型的socket。在服务端,I/O 多路复用程序(I/OMultiplexingModule)会把消息放入队列中,然后通过文件事件分派器(Fileevent Dispatcher),转发到不同的事件处理器中。
多路复用有很多的实现,以select为例,当用户进程调用了多路复用器,进程会被阻塞。内核会监视多路复用器负责的所有socket,当任何一个socket的数据准备好了,多路复用器就会返回。这时候用户进程再调用read操作,把数据从内核缓冲区拷贝到用户空间。
所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪(readable)状态, select()函数就可以返回。
Redis 的多路复用, 提供了 select, epoll, evport, kqueue 几种选择,在编译的时候来选择一种。
4内存回收
Reids所有的数据都是存储在内存中的,在某些情况下需要对占用的内存空间进行回收。内存回收主要分为两类,一类是key过期,一类是内存使用达到上限(max_memory)触发内存淘汰;
4.1主动淘汰机制–定时过期
每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
4.2被动淘汰机制–惰性过期
只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
4.3淘汰策略
Redis的内存淘汰策略,是指当内存使用达到最大内存极限时,需要使用淘汰算法来决定清理掉哪些数据,以保证新数据的存入。
#maxmemory
建议使用 volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的key。
4.4. 淘汰原理
Redis LRU对传统的LRU算法进行了改良,通过随机采样来调整算法的精度。如果淘汰策略是LRU,则根据配置的采样值maxmemory_samples(默认是5个),随机从数据库中选择m个 key, 淘汰其中热度最低的 key对应的缓存数据。所以采样参29
数m配置的数值越大, 就越能精确的查找到待淘汰的缓存数据,但是也消耗更多的CPU计算,执行效率降低。
最低热度的数据?
Redis中所有对象结构都有一个lru字段, 且使用了unsigned的低24位,这个字段用来记录对象的热度。对象被创建时会记录lru值。在被访问的时候也会更新lru的值。但是不是获取系统当前的时间戳,而是设置为全局变量server.lruclock的值。
5.持久化机制
Redis速度快,很大一部分原因是因为它所有的数据都存储在内存中。如果断电或者宕机,都会导致内存中的数据丢失。为了实现重启后数据不丢失,Redis提供了两种持久化的方案,一种是RDB快(RedisDataBase),一种是AOF(AppendOnlyFile)。
5.1RDB
RDB 是 Redis 默认的持久化方案。当满足一定条件的时候,会把当前内存中的数据写入磁盘,生成一个快照文件dump.rdb。Redis 重启会通过加载dump.rdb文件恢复数据。
1.自动触发
a)配置规则触发。
redis.conf, SNAPSHOTTING,其中定义了触发把数据保存到磁盘的触发频率。如果不需要RDB方案,注释save或者配置成空字符串""。
RDB还有两种触发方式:
b)shutdown触发,保证服务器正常关闭。
c)flushall,RDB文件是空的,没什么意义(删掉dump.rdb演示一下)。
2.手动触发
如果我们需要重启服务或者迁移数据,这个时候就需要手动触RDB快照保存。Redis提供了两条命令:
a)save
save在生成快照的时候会阻塞当前Redis服务器, Redis不能处理其他命令。如果内存中的数据比较多,会造成Redis长时间的阻塞。生产环境不建议使用这个命令。为了解决这个问题,Redis提供了第二种方式。
b)bgsave
执行bgsave时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
5.1.3RDB文件的优势
一、优势
1.RDB 是一个非常紧凑(compact)的文件,它保存了 redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。
2.生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
3.RDB 在恢复大数据集时的速度比AOF的恢复速度要快。
二、劣势
1、RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,频繁执行成本过高。
2、在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照之后的所有修改(数据有丢失)。如果数据相对来说比较重要,希望将损失降到最小,则可以使用AOF方式进行持久化。
6.2 AOF
Append Only File AOF:Redis 默认不开启。AOF采用日志的形式来记录每个写操作,并追加到文件中。开启后,执行更改Redis数据的命令时,就会把命令写入到AOF文件中。Redis 重启时会根据日志文件的内容把写指令从前到后执行一次以完成数据的恢复工作。
由于AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的进行,AOF 的文件会越来越大,文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。
例如set gupao 666,执行1000次,结果都是gupao=666。
为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。可以使用命令bgrewriteaof来重写。
AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件。
6.2.3 AOF 优势与劣势
1、AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已。
缺点:
1、对于具有相同数据的的 Redis,AOF 文件通常会比 RDF 文件体积更大(RDB存的是数据快照)。
2、虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。在高并发的情况下,RDB 比 AOF 具好更好的性能保证。
6.3 两种方案比较
那么对于 AOF 和 RDB 两种持久化方式,我们应该如何选择呢?
如果可以忍受一小段时间内数据的丢失,毫无疑问使用 RDB 是最好的,定时生成RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要
比 AOF 恢复的速度要快。否则就使用 AOF 重写。但是一般情况下建议不要单独使用某一种持久化机制,而是应该两种一起用,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。