Redis升华篇

本章主要会讲解1、Redis的一些其他比较高级一些用法,2、Redis 的一些底层原理等  3、Redis 的持久化机制等内容

一、Reidis的高级特性用法

        消息队列  我们都知道Redis有列表/队列的特性及用法(list)先进先出(FIFO).这样是否会有问题呢?比如:消费者不停的而去消费(lpop)就如同写了个循环一样(即需要不停的去通信),但是为了减少这一消耗,客户端可以sleep,再去请求,但是这样就没有问题了么?(list 列表不支持一次生产,多次消费的场景)

       1、如果sleep的话,sleep 的长短很难控制,这样就失去了消息的实时性了

       2、如果生产者产生的消息比消费者消费的信息更快,那么随着时间的推移是否生成者就会占用大量信息,列表这样也就会占用大量的内存(没有消费的消息堆积)

       使用过消息的中间件的小伙伴,应该都知道有种发布订阅模式。这种模式是其实就是支持一次生产,多次消费的场景的。其实Redis 内部也是支持这样的场景的。这种模式下就不需要像之前一样,消费者需要不断的请求,生产者是否生产了消息。这2者实现了解耦(没有直接的关系)

       订阅频道(channel:会有很多频道,其实就可以理解为一个频道就是一个队列(queue),,订阅者可以订阅一个或多个。消息的发布者也可以给指定的频道发生消息,只有消息到达了频道,这样所有订阅了这个频道的下订阅者(消费者)都会接受到这个消息,这特别需要注意的是:只有消息发生出去了,就不会持久化了,就会从队列中移除掉,因此消费者如果要接收到该频道的消息就必须要提前在该频道发消息之前就要订阅,如果滞后了的话,是接受不到消息的(即接收不到该频道之前的所有消息,因为已经从队列中移除了) 

    正常用法:

      > subscribe channel-1 channel-2  (订阅者一次订阅一个或多个)

     > publish channel-1 2673  (发布者一次只能给一个频道发送消息,不支持多个频道)

    > unsubscribe channel-2  (取消订阅)

  按照规则订阅频道 用法:支持?和*占位符。?代表一个字符,*代表 0 个或者多个字符

  消费者:

    cli1 > psubscribe *sport

   cli2 > psubscribe stars*

   cli3 > psubscribe stars-NBA

成产者:

  cli4  > publish NBA-sport KeBi
  cli4  > publish stars-NBA YaoMing
  cli4 > publish New-weather rain

事务 什么是事务?我们都知道要实现原子性:要么成功,要么失败,这叫原子性? 一个命令其实是能保证原子性的。

      其实很多时候,我们并不总是用一个命令的,往往一个命令达不到我们想要的结果,因此可能一个逻辑中需要实现多个命令按照一定的顺序执行的,这样就有了事务的概念了(需要通过事务去控制这个逻辑的原子性)

 Redis 也有自己的事务控制 特点 1、按照队列顺序执行,2、不受其他客户端请求的影响

命令:multi(开启事务),exec(执行事务),discard(取消事务),watch(监视)

用法(结合场景)

转账业务:A现有200块,B 现有50块,A向B 转了100块。A变成了100块,B 变成了150块

这里需要注意下:multi 开启了事务,那么multi 嵌套多层无用,效果一样的

通过 exec 的命令执行事务。如果没有执行 exec,所有的命令都不会被执行

中途放弃:可以调用 discard 可以清空事务队列,放弃执行

watch 命令:可以用 watch 监视一个或者多个 key,如果开启事务之后,至少有一个被监视key 键在 exec 执行之前被修改了,那么整个事务都会被取消(key 提前过期除外)。可以用 unwatch 取消

         

注意:在事务中,如果发生在exec 执行之前,那么整个事务队列中的命令都不会执行,但是如果发生在exec之后显然事务异质性完,是不会受影响的,仅仅是错误的命令不会执行而已

LUA脚本,这种脚本其实就有点类似于数据库的函数(即存储过程)

特点:1、一次可发送多条命令,减少网络的开销  2、一段LUA脚本可包含多个命令,作为一个整体执行,不会被切断,保证了原子性  3、可以作为文件的形式,实现命令集的复用

语法格式:eval lua-script key-num [key1 key2 key3 ....] [value1 value2 value3 ....]

 eval 代表执行 Lua 语言的命令。
 lua-script 代表 Lua 语言脚本内容。
 key-num 表示参数中有多少个 key,需要注意的是 Redis 中 key 是从 1 开始的,如果没有 key 的参数,那么写 0。
 [key1 key2 key3…]是 key 作为参数传递给 Lua 语言,也可以不填,但是需要和 key-num 的个数对应起来。
 [value1 value2 value3 ….]这些参数传递给 Lua 语言,它们是可填可不填的。

使用 redis.call(command, key [param1, param2…])进行操作。需要用return ,否则报错

缓存LUA脚本 为什么?原因:如果脚本过长,每次执行都需要叫脚本上传到服务端,会占用比较大的网络开销,因此就提供了EVALSHA 命令。

如何缓存?执行 script load 命令时会计算脚本的 SHA1 摘要并记录在脚本缓存中,执行 EVALSHA 命令时 Redis 会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了则执行脚本,否则会返回错误:"NOSCRIPT No matching script. Please use
EVAL." (记得后面需要带参数,哪怕参数为0

Redis 底层原理分析

   首先Redis给我们的感觉就是很快,redis为什么块?

这个具体有多快,需要根据各自的机器而定(根据官方的数据,Redis 的 QPS 可以达到 10 万左右(每秒请求数))

那么Redis快的原因是什么?

1、纯内存结构存储,因为是K-V 结构,所以查询速度是0(1).

2、单线程操作.  优势:a、减少创建线程带来的消耗. b、避免CPU上下文切换带来的消耗  c、减少了线程之间的竞争,避免了因线程竞争带来的锁的问题.

      那么Redis为什么是单线程

 原因:因为据官方说法,因为单线程已经够用了,就没必要用多线程(没必要浪费CPU资源).CPU不是Redis的瓶颈,而是Redis的内存或者网络宽带,因此Redis采用的是单线程

    那么为什么Redis为什么快?

原因:既然快的话,就得从数据的存储的地方内存说起了,我们都知道操作内存显然是比磁盘要快的多的,这也是其中一个快的原因,当然跟其内存结构也是有关系的

内存:早期的我们数据时直接根据内存地址,访问的是直接的物理地址。

 由于这样的寻址方式会已有缺陷:(1、多用户多任务操作系统,存在共享的内存,如果每个进程都独占一个物理内存地址,那么主内存的物理地址迟早会被占领完的,最好就是在不同的时候,会有不同的进程来共享这块物理地址  2、既然存在共享,那么很有可能就是会有一个进程修改另外一个进程的内容,导致数据混乱,甚至有可能导致物理地址损坏等,导致其他程序发生异常 ) 为了解决 这个问题,因此虚拟内存就产生了,  即就在物理地址和CPU之间加了一个虚拟内存,在每个线程创建的时候,都会分配一个虚拟物理地址,然后虚拟地址与物理地址之间的映射来找到是的物理地址,这样就 不会直接接触物理地址,目前应该大多系统都有用到虚拟内存,windows,linux等,在32位的操作的系统上,虚拟地址空间是:2^32 = 4G 那么 64的系统,是否就是2^64  实际上不可能有这个大的虚拟地址空间的,也用不到这么大的空间,而且太大的,会加大寻址等系统的开销,Linux一般用低于48位,即 2^48. 在实际情况下,物理地址的空间一般都是远小于虚拟地址空间。引入虚拟内存的优势(1、提供了更大的地址空间,而且地址都是连续的,这样链接就更简单, 2、隔离实际的物理地址,不同的线程之间操作不影响 3、还可以把不同线程之间的共享区域,映射到不同的虚拟地址上进行内存的共享)

分析了内存,那么接下来就是并非任何用户都可以直接操作内存数据的,这样为了将保证内核的安全,因此就讲系统的内虚拟内存分为了(用户空间,内核空间) 

用户空间:存放的是用户程序的代码和数据 。 内核空间:存放的是内核代码和数据,操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的权限。

线程进入内核空间就变成了内核态(内核空间以执行任意命令,调用系统的一切资源),进入用户空间就变成了用户态(只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称 system call),才能向内核发出指令)

因此如果一个线程进来了,需要获取数据,那么首先进入的就是用户空间,然后映射找到对应的物理地址,然后去内核空间,将数据复制出来。 因此在这期间线程都是阻塞的不占用CPU资源,等待被唤醒,继续执行。

  因此为了解决线程阻塞的问题:1、采用多线程或者线程池处理,但是在高并发的情况下,也是会消耗资源的 2、采用轮询的方式,定期去请求内存空间。当时这个会有一定的延迟。那么是否有一种机制会处理多个用户端的请求,并且用户端不需要等待,当数据处理好了之后,会自动告诉该线程。(它就是:I / O的 多路复用)

3、多路复用(异步非阻塞的I/O多路复用,解决了并发的连接)

    什么是I / O 多路复用?

         I/O 指的是网络 I/O.多路指的是多个 TCP 连接(Socket 或 Channel).复用指的是复用一个或多个线程.它的基本原理就是不再由应用程序自己监视连接,而是由内核替应用程序监视文件描述符

 多路复用有很多的实现,以 select 为例,当用户进程调用了多路复用器,进程会被阻塞。内核会监视多路复用器负责的所有 socket,当任何一个 socket 的数据准备好了,多路复用器就会返回。这时候用户进程再调用 read 操作,把数据从内核缓冲区拷贝到用户空间,因此就减少了阻塞时间。

内存回收机制

我们都知道Redis 有过期机制,那么有哪些呢?数据回收包括哪些呢?

我的理解是按:1、设置里过期时间的,2、未设置过期时间的 3、noeviction (不删除数据)

设置过期时间的(在所有设置了时间的数据中):volatile-lru(最近最少使用),volatile-lfu (最近最不频繁使用)volatile-ttl  (最近将过期的) volatile-random (随机)

未设置过期时间的(在所有的数据中):allkeys-lru,allkeys-lfu,allkeys-random

Redis 持久化 

   最后聊一下 数据的持久化,Redis 既机制 然现在这么火,很显然必然会有宕机的时刻,因此持久化是必须的的。

Redis 目前提高了2中持久化机制  1、RDB 快照(Redis DataBase)  2、AOF (Append Only File)

   RDB 触发机制

 1)配置文件中修改触发条件(  RDB 还有两种触发方式)


2)shutdown 触发,保证服务器正常关闭。
3)flushall,RDB 文件是空的,没什么意义(删掉 dump.rdb 演示一下)

手动触发

 1)save   因为redis 是单线程,如果直接使用save,那么如果数据量多的话,会造成redis阻塞,生产环境不建议这样使用

2)bgsave  这个命令就刚好弥补了save的缺陷,当执行该命令时,会fork 一个子线程来处理这个命令,主线程还是就行运行,但是不会记录fork之后数据,而且在fork时,会有短暂的影响主线程(时间很短)

  RDB 的优缺点

    优势:1、数据紧凑,适合做数据的备份及灾备  2、bgsave时 fork子进程,主线程无需进行其他I/O 操作 3、RDB进行大的数据恢复时比AOF 数据快些。

   劣势: 1、保存粒度不够,没办法实时持久化或秒级持久化。每次bgsave,都需要fork 子进程,使用频繁的话陈成本高 2、 实时性不高,数据丢失的量可能会比AOF多些

    AOF 

    Redis 默认不开启。AOF 采用日志的形式来记录每个写操作,并追加到文件中。开启后,执行更改 Redis 数据的命令时,就会把命令写入到 AOF 文件中。在重启时,会把所有记录的命令都执行一遍。

    AOF的触发机制

1)配置文件

appendonly   : Redis 默认只开启 RDB 持久化,开启 AOF 需要修改为 yes
appendfilename "appendonly.aof" :路径也是通过 dir 参数配置 config get dir

AOF 并非都是实时持久化到磁盘的,由于操作系统的缓存机制,AOF并没有真正写入磁盘。什么时候把缓冲区的内容写入到 AOF 文件。

AOF 持久化策略(硬盘缓存到磁盘),默认 everysec
 no 表示不执行 fsync,由操作系统保证数据同步到磁盘,速度最快,但是不太安全;
 always 表示每次写入都执行 fsync,以保证数据同步到磁盘,效率很低;
 everysec 表示每秒执行一次 fsync,可能会导致丢失这 1s 数据。通常选择 everysec ,
兼顾安全性和效率

    那么现在有个疑问,如果每次都是追加,不停的往后面加命令,这个文件肯定会很大,而且可能会产生一个命令反复执行,那么久会追加很多重复的命令,这个如何解决?

  AOF  内部会有一个命令重写的机制,当达到一个阈值时,那么就会触发这哥重写机制,将所有命令重写一次,把重复的命令合并等,生成一个新的文件替换原来的文件。

        

    如果在AOF重写期间被修改了如何处理??

在进行AOF重写时,主线程会执行:1、处理正常的命令请求 2、将写命令追加到现在AOF文件中,3.将写命令追加到AOF 重写缓存中。

AOF优缺点

 优势:1、AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已

  2、数据保存的粒度更细

劣势: 1、有时候AOF 的优势也会是它 的劣势,在高并发的情况下,性能可能会受影响 2、相同的数据,AOF 文件可能会比较大

 至于这2中持久方式,有各自的优缺点。RDB的话,如果能忍受小段时间的数据丢失,那么RDB无疑会好些,但是如果对数据实时性要求比较高,那么AOF 会好些。一般情况可能会2中机制都会使用是上,并不是某一种单独的使用。具体情况还得根据时间业务场景来做选择。

今天就先到这,内容尚有欠缺,还请各位大神指点。谢谢!!!

猜你喜欢

转载自blog.csdn.net/u010200793/article/details/105004205