Redis

2020-05-07  本文已影响0人  西谷haul

本文大部分内容参考自此链接及尚硅谷阳哥的redis教学视频。

0、前言(几个面试题):

0.1、Redis的优势:

  • 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
  • 支持数据持久化,支持AOF和RDB两种持久化方式。
  • 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
  • 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
  • 支持大量集群节点。
    假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数Redis中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。同样,我们可以把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接打到缓存而不是数据库(即半路拦截掉了)。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!在我们业务中,包括热点词查询、一些实时排行榜数据、访问量点赞量统计、Session共享等等都可以引入Redis来处理。

0.2、Redis与Memcached,为什么选择redis呢?

0.3、聊一下对缓存穿透、缓存击穿、缓存雪崩的理解吧

缓存穿透:指缓存和数据库中都没有的数据,导致所有的请求都打到数据库上,然后数据库还查不到(如null),造成数据库短时间线程数被打满而导致其他服务阻塞,最终导致线上服务不可用,这种情况一般来自黑客同学。

缓存击穿:指缓存中没有但数据库中有的数据(一般是热点数据缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去查,引起数据库压力瞬间增大,线上系统卡住。

互斥锁:缓存击穿后,多个线程会同时去查询数据库的这条数据,那么可以在第一个查询数据的请求上使用一个互斥锁来锁住它。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

 static Lock reenLock = new ReentrantLock();
    public List<String> getData04() throws InterruptedException {
        List<String> result = new ArrayList<String>();
        // 从缓存读取数据
        result = getDataFromCache();
        if (result.isEmpty()) {
            if (reenLock.tryLock()) {
                try {
                    System.out.println("拿到锁了,从DB获取数据库后写入缓存");
                    // 从数据库查询数据
                    result = getDataFromDB();
                    // 将查询到的数据写入缓存
                    setDataToCache(result);
                } finally {
                    reenLock.unlock();// 释放锁
                }

            } else {
                result = getDataFromCache();// 先查一下缓存
                if (result.isEmpty()) {
                    System.out.println("我没拿到锁,缓存也没数据,先小憩一下");
                    Thread.sleep(100);// 小憩一会儿
                    return getData04();// 重试
                }
            }
        }
        return result;
    }

缓存雪崩:指缓存同一时间大面积的失效,缓存击穿升级版。后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:

1、安装配置

redis-check-aof:修复有问题的aof文件
redis-check-dump:修复有问题的dump.rdb文件
redis-cli:客户端入口
redis-sentinel:redis集群使用
redis-server:redis服务器启动命令

将redis.conf文件中daemonize no改为yes,让服务在后台启动,将redis.conf复制一份,放在一个文件夹下,比如说放在/usr/myconf/redis.conf。

//运行自定义的redis.conf文件
redis-server /usr/myconf/redis.conf
//启动
redis-cli
//单实例关闭:
redis-cli shutdown
//多实例关闭:
redis-cli -p 6379 shutdown

2、基础了解

//查看当前库key数量
dbsize
//查看所有的key
keys *
//查询出以k开头的key
keys k?
//清空当前库
flushdb
//通杀所有库
flushall

默认16个数据库(在redis.conf中可以进行设置),从下标0开始,可以使用SELECT <dbid>命令连接上指定数据库id ,比如:select 0。
redis索引都是从0开始,
redis的默认端口号是6379,
redis统一密码管理,16个库都是相同的密码

//默认没有密码,如下将密码设置为root
config set requirepass "root"
//填写密码如下
auth root

image.png

3、数据类型

一、五大类型:

key相关命令

String:是redis最基本的类型,一个key对应一个value。 string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。 String类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M。

String String2

List:简单的字符串列表,按照插入顺序排序。可以添加一个元素导列表的头部(左边)或者尾部(右边)。它的底层实际是个链表 。它是一个字符串链表,left、right都可以插入添加; 如果键不存在,创建新的链表; 如果键已存在,新增内容; 如果值全移除,对应的键也就消失了。 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。

List

hash:Hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 类似Java里面的Map<String,Object>

Hash

Set:Set是string类型的无序集合。它是通过HashTable实现实现的。

Set

Zset:Redis zset 和 set 一样也是String类型元素的集合,且不允许重复的成员。 不同的是每个元素都会关联一个double类型的分数。 redis正是通过分数来为集合中的成员进行从小到大的排序。 zset的成员是唯一的,但分数(score)却可以重复。

zset

3、redis的配置文件redis.conf的常见配置

配置内存过期的几种策略

redis.conf中默认的是永不过期,是肯定不行的。

4、redis的持久化

一、RDB(Redis DataBase)

在指定的时间间隔内会将内存中的数据集快照写入磁盘中,也就是snapshot快照,默认保存到dump.rdb文件中,他恢复时将快照文件直接读到内存中。

Redis会fork一个子进程来进行持久化,也就是复制一个与当前一模一样的进程。先将数据写入到一个临时文件中,待持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,fork的进程不进行任何的IO操作,这就确保了极高的性能。如果需要进行大规模数据的恢复,而且对于数据恢复的完成性不是非常敏感,那RDB方式要比AOF方式更加高效。RDB备份可能是五分钟恢复一次,这样的话如果在最后五分钟宕机,那么就有可能会丢失最后一次的数据。
RDB保存的是dump.rdb文件

让rdb保存即可生效的命令,save,就不用等15分钟1次,5分钟10,1分钟一万次了。但是save时只管保存,其他不管,全部阻塞。bgsave:redis会在后台异步进行快照操作,快照同时还可以响应客户端的请求,可以通过lastsave获取最后一次保存快照的时间。

image.png

二、AOF(Append Only File)

以日志的形式来记录每个写操作,将Redis执行过的所有的指令记录下来(读操作不记录),只允许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据得恢复工作。

aof和rdb的持久化策略可以同时存在,appendonly.aof文件比dump.rdb文件先生效,如果aof文件损坏(比如说正在编码时突然断电可能会导致)会导致redis启动失败,这时可以用

redis-check-aof --fix appendonly.aof 进行修复

aof采用文件追加的方式,文件会变得越来越大(比如说对某一个key连续减一加一100次)为了避免出现这种情况新增了重写机制,当aof文件的大小超过设定的阈值时,Redis就会启动对文件的压缩,只保留可以恢复数据的最小指令值。
aof会fork出一条新进程来将文件重写(也是先写临时文件最后rename)遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,和快照是有点像的。Redis会记录上次重写的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M是触发。也可以使用命令bgwriteaof 进行手动重写。参考出自redis.conf文件以下:

rewrite的配置

AOF的优势和劣势:

优势:redis可以实现每秒同步,甚至每修改同步,数据丢失的可能性低。劣势:相同数据集的数据而言aof文件远大于rdb文件,恢复速度慢于rdb,aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同。


image.png

三、RDB和AOF的选择

如果对持久话要求恢复快,占用的内存空间小,而对数据的敏感度要求没那么高,那么选择RDB,如果对数据敏感度要求极高那么选择AOF。只做缓存的时候:如果只希望数据在服务器运行时存在,可以不使用任何的持久化方式。

同时开启两种持久化方式时,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据。因为在通常情况下AOF文件保存的数据要比RDB文件保存的数据集要完整。因为RDB文件只用作后备用途,(主从策略)建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。

如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。 如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。

5、redis事务

  • DISCARD:取消事务,放弃执行事务块内的所有命令。
  • EXEC:执行所有事务块内的命令。
  • MULTI:标记一个是事务块的开始。
  • UNWATCH:取消WATCH命令对所有key的监视。
  • WATCH key [key ...]:监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所修改,那么事务将被打断。
redis事务怎么玩

如下,分为这几种情况,在执行过程中,如果出现错误的命令,在使用EXEC命令时会出现全体连坐,就会都不成功,就是case3称为全体连坐。如果在运行时报错,比如说给一个String类型的加减,哪个出问题哪个就会不成功。就是case4称为冤头债主。3和4的差异就在于一个是编译时出现错误,一个是运行时出现错误。

乐观锁和悲观锁:

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁 举例来说svn加锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改, 所以不会上锁 ,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能执行更新 举例来说svn不加锁

阶段

1、开启:以MULTI开始一个事务
2、入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
3、执行:由EXEC命令触发事务

特性

1、单独的隔离操作:事务中所有命令都会序列化、按顺序的执行。事务在执行的过程中,不会被其他客户端发来的命令请求所打断。
2、没有隔离级别的概念:对列中的命令没有提交前不会实际的被执行。
3、不保证原子性:redis同一个事务如果有一条命令执行失败,其他命令仍然会被执行。没有回滚,这里指的是case4冤头债主。

Redis事务实现

1、事务开始

MULTI命令的执行,标识着一个事务的开始。MULTI命令会将客户端状态的 flags 属性中打开REDIS_MULTI 标识来完成的。

2、命令入队

当一个客户端切换到事务状态之后,服务器会根据这个客户端发送来的命令来执行不同的操作。如果客户端发送的命令为MULTI、EXEC、WATCH、DISCARD中的一个,服务器会立即执行这个命令。如果客户端发送的是四个命令以外的其他命令,服务器并不立即执行这个命令,将命令放入一个事务队列里面。首先检查此命令的格式是否正确,如果不正确,服务器会在客户端状态(redisClient)的 flags 属性关闭 REDIS_MULTI 标识,并且返回错误信息给客户端。如果正确,将这个命令放入一个事务队列里面,然后向客户端返回 QUEUED 回复,事务队列是按照FIFO的方式保存入队的命令。

3、事务执行

客户端发送 EXEC 命令,服务器执行 EXEC 命令逻辑。如果客户端状态的 flags 属性不包含 REDIS_MULTI 标识,或者包含 REDIS_DIRTY_CAS 或者REDIS_DIRTY_EXEC 标识,那么就直接取消事务的执行。否则客户端处于事务状态(flags 有 REDIS_MULTI 标识),服务器会遍历客户端的事务队列,然后执行事务队列中的所有命令,最后将返回结果全部返回给客户端;Redis 不支持事务回滚机制,但是它会检查每一个事务中的命令是否错误。Redis 事务不支持检查那些程序员自己逻辑错误。例如对 String 类型的数据库键执行对 HashMap 类型的操作!

  • WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set(CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
  • MULTI命令用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
  • EXEC执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 null 。
  • DISCARD命令客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
  • UNWATCH命令可以取消watch对所有key的监控。

6、redis的订阅功能

先订阅后发布 后才能收到消息
1、可以一次性订阅多个,SUBSCRIBE c1 c2 c3
2、消息发布,PUBLISH c2 hello-redis

3、订阅多个,通配符, PSUBSCRIBE new
4、收取消息, PUBLISH new1 redis2015

7、redis的复制

是什么:

就是我们说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slave机制,Master以写为主,Slave以读为主(读写分离)。

能干嘛:

读写分离,容灾恢复

怎么玩:

1、配从不配主(对从机进行配置而不配置主机)查看数据库机器的信息命令

info replication

2、从库配置:salveof主库IP 主库端口 就可以作为从机

slaveof 127.0.0.1 6379

(slave每次与master断开后,都需要重新连接,不然就自己就是master自己单干,除非配置了redis.conf文件)
3、修改配置文件细节实现多台redis启动
(1)拷贝多个redis.conf
(2)开启daemonize yes
(3)修改Pid文件名字
(4)指定端口
(5)修改Log文件名字
(6)修改Dump.rdb文件名字
4、常见几种
一主二从:

从机执行slaveof 主机IP 主机端口 后就成为了主机的从机,可以获取到主机中的数据,主机可以写读,但是从机只能读。(所谓读写分离,主机写从机读)
主机如果挂掉了回来后仍然是主机
从机挂掉后回来也会变成单独的主机,需要重新和主机进行连接(redis.conf中没有进行特殊配置的情况下)

薪火相传:
解决痛点:中心化过于严重

所谓一主二仆和薪火相传

上一个Slave可以是下一个slave的Master,Slave同样可以接收其他Slaves的连接和同步请求,那么该slave作为链条中的下一个master,可以有效的减轻master的写压力。

反客为主:

如果主机挂掉,在从机上面输入命令slaveof no one从机就会成为新的“领导”主机,而以前的“同事”从机也会成为自己的“下属”也就是从机。
如果主机又恢复回来了,那么主机又会成为master,但是他是一个光杆司令,以前的小弟已经跟别人混了。

哨兵模式(就是反客为主的自动版):

(1)添加sentinel.conf文件
在文件中添加哨兵监控的机器:sentinel monitor 自己起数据库名字 127.0.0.1 6379 1(最后的数字1表示主机挂掉后salve投票让谁阶梯成为主机,得票数多的成为主机)
(2)启动哨兵
redis-sertinel /usr/local/redis/sentinel.conf(随便写的目录)
(3)如果原有的master挂了,会投票生成一个新的master,和反客为主不同的是,如果原来的老领导(master)又回来了,会变成slave了
(4)一组sentinel可以同时监控多个master

复制原理:

slave启动成功连接到一个master后会发送一个sync命令,Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步。
全量复制:而slave服务在接收到数据库文件后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步,但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。

复制延时

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

Redis线程模型、单线程快的原因

Redis基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器 file event handler。这个文件事件处理器,它是单线程的,所以 Redis 才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了 Redis 内部的线程模型的简单性。
文件事件处理器的结构包含4个部分:多个Socket、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。
多个 Socket 可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个 Socket,会将 Socket 放入一个队列中排队,每次从队列中取出一个 Socket 给事件分派器,事件分派器把 Socket 给对应的事件处理器。然后一个 Socket 的事件处理完之后,IO多路复用程序才会将队列中的下一个 Socket 给事件分派器。文件事件分派器会根据每个 Socket 当前产生的事件,来选择对应的事件处理器来理。

单线程快的原因:
1)纯内存操作
2)核心是基于非阻塞的IO多路复用机制
3)单线程反而避免了多线程的频繁上下文切换带来的性能问题

redis 的过期策略和内存淘汰策略是一个东西么?

内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。

缓存主要存在内存中,内存空间是有限的,比如 redis 就只能用 10G,要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,保留另外 10G 数据。那么干掉哪些数据,保留哪些数据?要根据热度干掉不常用的数据。

Redis是key-value数据库,可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。Redis中同时使用了惰性过期和定期过期两种过期策略。

  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
    (expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键)
    随机取20个设置了过期策略的key;检查20个key中过期时间中已过期的key并删除;如果有超过25%的key已过期则重复第一步;这种循环随机操作会持续到过期key可能仅占全部key的25%以下时,并且为了保证不会出现循环过多的情况,默认扫描时间不会超过25ms;

如果定期删除漏掉了很多过期 key,也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,就要提到了Redis的内存淘汰机制

Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据,如何淘汰旧数据给新数据腾出内存空间。redis 内存淘汰机制有以下几个:

上一篇下一篇

猜你喜欢

热点阅读