Redis详解

Posted Serendipity sn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis详解相关的知识,希望对你有一定的参考价值。

Redis

目录

1.Redis的简单概述

  1. 为什么要用NoSQL

    • 传统的数据库数据存储在硬盘中,拿取数据时要从硬盘读入,当数据量太大时,IO也会吃不消
    • 传统的关系型数据库的表结构有很强的约束,必须按照这么个格式存储,就不够灵活。所以当有新需求需要加字段的时候就需要修改表的结构,如果表的数据很多的话修改表的结构可能会长时间锁表,而导致表的不可用。
    • Nosql基于内存,无需IO,且存储结构灵活
    • 方便扩展(数据之间没有关系,很好扩展!)
    • 大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
    • 数据类型是多样型的!(不需要事先设计数据库,随取随用)
  2. Redis的简单介绍

redis是Nosql数据库中使用较为广泛的非关系型内存数据库,redis内部是一个key-value存储系统。它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型,类似于java中的map)。Redis基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。

  1. CPA理论

    • C:强一致性 Consistency

    • A:可用性 Availablility

    • P:分区容错性 Partition tolerancy

    • CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
      最多只能同时较好的满足两个。
      因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类

      • CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
      • CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
      • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些
    • 分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。

      • CA 传统Oracle数据库

      • AP 大多数网站架构的选择

      • CP Redis、Mongodb

        注意:分布式架构的时候必须做出取舍。一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。

    BASE

    BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。

    BASE其实是下面三个术语的缩写:
    基本可用(Basically Available)
    软状态(Soft state)
    最终一致(Eventually consistent)

2.Redis的常用命令

Redis命令参考:http://redisdoc.com/persistence/index.html

<1>对Redis库操作的命令

Redis默认有16个库,类似数组下标从0开始,初始默认使用0号库

  • Select 数据库号 切换数据库
  • dbsize 查看当前数据库key的个数
  • flushdb 清空当前库
  • flushall 清空所有库

<2>对key的常用操作命令

  • keys * 查看所有key
  • exists key的名字 判断是否存在这个key
  • move key db 将key移动到第db个数据库
  • expire key 秒钟 为key设置过期时间
  • ttl kye 查看还有多少秒过期 -1表示永不过期 -2表示已经过期

<3>Redis的五大数据类型

  • string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。

    string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。

    string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M

  • Hash(哈希)
    Redis hash 是一个键值对集合。
    Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

    类似Java里面的Map<String,Object>

  • List(列表)
    Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。它的底层实际是个链表

  • Set(集合)
    Redis的Set是string类型的无序集合。它是通过HashTable实现实现的,不允许重复

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

<4>五大数据类型的常用操作命令

3.1 String类型

  • set/get/del/append/strlen key 分别表示/设置/获取/删除/拼接/获取长度

  • 如果值(value)是数字

    incr/decr key 将key对应的value的值加/减一

    incrby/decrby key increment 将key对应的value的值加/减 increment

  • getrange key start end 返回key对应的value中的子字符串 从start开始到end结束

  • setrange key offset value 用value覆盖掉key所存储的字符串值,从偏移量offset开始

  • setex key seconds value 设置key value,并且过期时间为seconds(秒 )

  • setnx key value 当key不存在时设置

  • mget key1 key2 … 同时获取多个key对应的value

  • mset key value [key value …] 同时设置多个key value

  • getset key value 先设置key的值为value,在返回旧的value

3.2List类型

17.rpushx key value 为已经存在的列表添加值

3.3 set类型

3.4Hash类型

3.5Zset类型

3.Redis的内存分配

假如你的Redis内存满了怎么办? 长期的把Redis作为缓存使用,总有一天会存满的时候对吧。

这个面试题不慌呀,在Redis中有配置参数maxmemory可以设置Redis内存的大小。

在Redis的配置文件redis.conf文件中,配置maxmemory的大小参数如下所示:

实际生产中肯定不是100mb的大小哈,不要给误导了,这里我只是让大家认识这个参数,一般小的公司都是设置为3G左右的大小。

除了在配置文件中配置生效外,还可以通过命令行参数的形式,进行配置,具体的配置命令行如下所示:

//获取maxmemory配置参数的大小
127.0.0.1:6379> config get maxmemory
//设置maxmemory参数为100mb
127.0.0.1:6379> config set maxmemory 100mb

4.Redis的淘汰策略

Redis提供了6种的淘汰策略,其中默认的是noeviction,这6中淘汰策略如下:

  • noeviction(默认策略):若是内存的大小达到阀值的时候,所有申请内存的指令都会报错。
  • allkeys-lru:所有key都是使用LRU算法(最近最少使用)进行淘汰。
  • volatile-lru:所有设置了过期时间的key使用LRU算法进行淘汰。
  • allkeys-random:所有的key使用随机淘汰的方式进行淘汰。
  • volatile-random:所有设置了过期时间的key使用随机淘汰的方式进行淘汰。
  • volatile-ttl:所有设置了过期时间的key根据过期时间进行淘汰,越早过期就越快被淘汰。

可以在配置文件中设置,也可以使用命令

// 获取maxmemory-policy配置
127.0.0.1:6379> config get maxmemory-policy
// 设置maxmemory-policy配置为allkeys-lru
127.0.0.1:6379> config set maxmemory-policy allkeys-lru

设置过期键的淘汰策略

  1. 定时删除:创建一个定时器,定时的执行对key的删除操作。
  2. 惰性删除:每次只有再访问key的时候,才会检查key的过期时间,若是已经过期了就执行删除。
  3. 定期删除:每隔一段时间,就会检查删除掉过期的key。

5.Redis的持久化方式

<1>为什么需要持久化

Redis对数据的操作都是基于内存的,当遇到了进程退出、服务器宕机等意外情况,如果没有持久化机制,那么Redis中的数据将会丢失无法恢复。有了持久化机制,Redis在下次重启时可以利用之前持久化的文件进行数据恢复。Redis支持的两种持久化机制:

  • RDB:把当前数据生成快照保存在硬盘上。
  • AOF:记录每次对数据的操作到硬盘上。

<2>RDB

RDB(Redis DataBase)持久化是把当前Redis中全部数据生成快照保存在硬盘上。RDB持久化可以手动触发,也可以自动触发。保存在dump.rdb文件中

  1. 触发方式

save 和bgsave

  • save:执行save命令会手动触发RDB持久化,但是save命令会阻塞Redis服务,直到RDB持久化完成。当Redis服务储存大量数据时,会造成较长时间的阻塞
  • bgsave:Redis服务一般不会阻塞。Redis进程会执行fork操作创建子进程,RDB持久化由子进程负责,不会阻塞Redis服务进程(主进程不进行IO)。Redis服务的阻塞只发生在fork阶段,一般情况时间很短。

在配置文件中设置了save的相关配置,如sava m n,它表示在m秒内数据被修改过n次时,自动触发bgsave操作

  1. 优点

适合大数据量的恢复,且恢复数据的速度较快

  1. 缺点

会在一定的时间间隔内进行快照,所以当Redis意外宕机时,可能会丢失最后一次快照之后的部分数据

<3>AOF

AOF(Append Only File)持久化是把每次写命令追加写入日志中,当需要恢复数据时重新执行AOF文件中的命令就可以了。AOF解决了数据持久化的实时性,也是目前主流的Redis持久化方式。

AOF保存的位置是appendonly.aof文件中

Rewrite

  • AOF文件会随着时间持续增长,过大时,会fork出一条新的进程来将文件重写(先写临时文件,最后再rename)
  • 新进程会将内存中的数据库内容用最简命令的方式重写一个AOF文件。然后更新原来的AOF

优点

随数据的变化实时更新,更准确的恢复数据库

缺点

效率较RDB慢,且aof文件要远大于rdb文件

6.Redis的主从模式

  1. 一主多从

一主多从,主数据库(master)可以读也可以写(read/write),从数据库仅读(only read)。

但是,主从模式一般实现读写分离**,**主数据库仅写(only write),减轻主数据库的压力

步骤:

  • 当slave启动后会向master发送SYNC命令,master节后到从数据库的命令后通过bgsave保存快照(RDB持久化),并且期间的执行的些命令会被缓存起来。
  • 然后master会将保存的快照发送给slave,并且继续缓存期间的写命令。
  • slave收到主数据库发送过来的快照就会加载到自己的数据库中。
  • 最后master讲缓存的命令同步给slave,slave收到命令后执行一遍,这样master与slave数据就保持一致了。

命令:在从库下执行 slaveof 主库IP 主库端口

​ info replication 查看本机的主从设置,查看自己是主机还是从机

每次与master断开连接后,都需要重新连接,除非配置进redis.conf文件

注意:

  • 当从机接入主机之后,会将主机之前的数据也全部录入
  • 当主机宕机之后,从机仍然为从机(静默),当注解接入之后,仍是主机的从机
  • 当从机宕机,重新接入之后,不再是原主机的从机,而自身称为主机
  1. 薪火相传

上一个从机可以是下一个从机的主机(类似树结构),从机同样也可以接收其他从机的连接和同步请求,该从机可以作为下一个链条的主机,这样可以有效的减轻最上层主机的写压力

但是中途变更转向,会清除之前的数据,重新复制新的主机的数据

  1. 反客为主 哨兵模式

从机变主机: slaveof no one

哨兵能够后台监控主机是否故障,如果故障了根据投票数自动将从库转变为主库

步骤:

  • 新建sentinel.conf(名字不能错)文件
  • 配置哨兵:sentinel moniter +被监控数据库名字(如:127.0.0.1 6379 1 后面的1表示主机宕机后从机投票,得票数最高的成为主机)
  • 启动哨兵 Redis-snetinel /sentinel.conf

注意:当之前监控的主机宕机之后,有新的从机称为主机,但当原来的主机重新工作之后,会称为新主机的从机

7.缓存击穿,穿透和雪崩

<1>缓存穿透

概念

在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。

解决方案

  1. 布隆过滤器

    利用布隆过滤器,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。

    布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结

    构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函

    数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

    限制:

    1.只适合元素是否存在的问题

    2.只能保证概率的正确性

    3.对元素没有要求了

    布隆过滤器的内部结构:

把一个元素放入到该集合中:

1.把元素,分别经过n个哈希函数进行计算,并转化为位图的合法的bit “下标”

2.把所有"下标"处的位图位置改为1

判断给定的元素是否在该集合中

1.把元素,分别经过n个哈希函数进行计算,并转化为位图的合法位置

2.如果所有位置中,有0,则标识元素一定不在集合中

3.如果所有位置,都是1,则大概率,元素在集合中

4.但也有可能,这个元素不在集合中,但是标志位因为其他元素都改变为了1(这种情况称为假阳性)

删除元素

不能再布隆过滤器中删除元素,删除元素改变了位图中下标的状态,可能对其他数据产生影响
如果真要删除,可以使用计数方式删除

设计多个hash函数的原因:

1.由于元素不限,所有,必然有着hash冲突的情况。(不同的元素,映射到了相同的下标出)

2.在这个情况下,hash函数个数太少,假阳性概率就会特别高,所有通过添加多个hash函数,使得假阳性概率降低

  1. 缓存空对象

一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。

这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高

<2>缓存击穿

概念

相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方案

  1. 设置热点数据永不过期

这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

  1. 加互斥锁(分布式锁)

在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。

<2>缓存雪崩

概念

大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

  1. redis高可用

这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群

  1. 限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

  1. 数据预热

数据预热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

8.事务

  1. Redis事务没有隔离级别的概念
  2. Redis单条命令是保证原子性的,但是事务不保证原子性!

<1>事务的操作过程

  • 开启事务(multi
  • 命令入队
  • 执行事务(exec
  • 事务的取消(DISCARD)

<2>事务的出错

  1. 代码语法错误(编译时异常)所有的命令都不执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> error k1 # 这是一条语法错误命令
(error) ERR unknown command `error`, with args beginning with: `k1`, # 会报错但是不影响后续命令入队 
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. # 执行报错
127.0.0.1:6379> get k1 
(nil) # 其他命令并没有被执行

  1. 代码逻辑错误 (运行时异常) 其他命令可以正常执行 >>> 所以不保证事务原子性

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379>INCR k1 # 这条命令逻辑错误(对字符串进行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 运行时报错
4) "v2" # 其他命令正常执行

# 虽然中间有一条命令报错了,但是后面的指令依旧正常执行成功了。
# 所以说Redis单条指令保证原子性,但是Redis事务不能保证原子性。

<3>事务的监控

悲观锁:

  • 很悲观,认为什么时候都会出现问题,无论做什么都会加锁

乐观锁:

  • 很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
  • 获取version
  • 更新的时候比较version

使用watch key监控指定数据,相当于乐观锁加锁。

watch 监控某个key,当开启事务,在事务期间,有其他线程过来将监控的key修改,则事务执行时失败回滚

unwatch进行解锁。

每次提交执行exec后都会自动释放锁,不管是否成功

以上是关于Redis详解的主要内容,如果未能解决你的问题,请参考以下文章

redis空间键详解

Redis事务详解

Redis的appendfsync参数详解

Redis高可用方案详解

详解Redis Cluster集群

redis详解