Redis 学习总结

NoSQL 是什么?

NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,
泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。

NoSQL 数据库四大分类

  1. KV键值:Redis
  2. 文档型数据库(bson格式比较多):MongoDB
  3. 列存储数据库:HBase
  4. 图关系数据库:Neo4J、 InfoGrid

四者对比

分布式数据库中CAP原理CAP+BASE

1.CAP

C: Consistency(强一致性)

A: Availability(可用性)

P: Partition tolerance(分区容错性)

CAP理论的核心:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

三进二

2. BASE

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

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

它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法

Redis 是什么?

Redis(REmote DIctionary Server(远程字典服务器)):是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。

Redis 官网:https://redis.io

Redis 能干嘛?

Redis被广泛应用于缓存方向, 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。

为什么要使用Redis(为什么要使用缓存)?

1. 高性能

在不做缓存的情况下,用户发送请求访问数据库中数据,此过程是非常慢的,因为这些数据要从硬盘读取。而将用户访问的数据存放于缓存中,下一次访问这些数据可以直接从缓存中获取。而操作缓存就是直接操作内存,速度是非常快的。当数据库中数据改变之后,同步更新缓存中数据即可。

2. 高并发

直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以可以考虑把数据库中的部分数据(热点数据)转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库,大大提高系统并发。

Redis 与 Memcached 的区别(面试可能会问到)

  1. Redis 支持丰富的数据类型;Memcached 支持简单数据类型Stirng
  2. Redis 支持数据持久化;Memcached 不支持数据持久化
  3. Redis 原生支持cluster 模式; Memcached 需要依赖客户端实现往集群中写入分片数据
  4. Redis 使用单进程的多路IO复用模型;Memcached使用多线程非阻塞IO复用模型

两者比较

Redis 数据类型及使用场景分析

1. String

常用命令: set,get,decr,incr,mget 等。

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

应用:常规key-value缓存应用; 常规计数:微博数,粉丝数等。

2. Hash

常用命令: hget,hset,hgetall 等。

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

应用:存储用户信息,商品信息等等

3. List

常用命令: lpush,rpush,lpop,rpop,lrange等

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。它的底层实际是个双向链表。另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询 ,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。

应用:微博的关注列表,粉丝列表,消息列表等

4. Set

常用命令: sadd,spop,smembers,sunion 等

set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。

当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。

应用:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具体命令如下:

1
sinterstore key1 key2 key3     将交集存在key1内

5. Zset(sorted set:有序集合)

常用命令: zadd,zrange,zrem,zcard等

和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。

应用: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。

更多Redis 常用命令http://redisdoc.com/

Redis 配置文件redis.conf解析

http://download.redis.io/redis-stable/redis.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
参数说明
redis.conf 配置项说明如下:
1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no
2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid
3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
4. 绑定的主机地址
bind 127.0.0.1
5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
loglevel verbose
7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout
8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
databases 16
9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save <seconds> <changes>
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
11. 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
12. 指定本地数据库存放目录
dir ./
13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
slaveof <masterip> <masterport>
14. 当master服务设置了密码保护时,slav服务连接master的密码
masterauth <master-password>
15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
requirepass foobared
16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128
17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory <bytes>
18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no
19. 指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof
20. 指定更新日志条件,共有3个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec

21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
vm-enabled no
22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0
24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
vm-page-size 32
25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728
26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4
27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes
28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes
30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf

Redis 持久化机制

如何保证 Redis 挂掉之后在重启数据可以恢复???

Redis 支持两种数据持久化方式:快照(Snapshotting ,RDB)追加文件(Append Only File,AOF)

1. RDB

是什么?

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。

怎么做?

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失

Fork:Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

如何配置?

快照持久化是Redis默认采用的持久化方式(dump.rdb),在redis.conf配置文件中默认有此下配置:

1
2
3
save 900 1      #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

如何创建?

  • 配置文件中默认的快快照配置(save选项)
  • 使用 save 命令:save时只管保存,其它不管,全部阻塞
  • 使用 bgsave 命令:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间
  • 执行 flashall 命令:执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义

如何恢复?

备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可

1
2
3
127.0.0.1:6379> config get dir
1) "dir"
2) "/myredis"

优劣势?

  • 优势:适合大规模的数据恢复;对数据完整性和一致性要求不高
  • 劣势:在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改;Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑

2. AOF

是什么?

以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件。

怎么做?

开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof

redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

如何配置?

默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:

1
appendonly yes

如何创建?

在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

1
2
3
appendfsync always     #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步

如何恢复?

  1. 修改默认的appendonly no,改为yes
  2. 将有数据的aof文件复制一份保存到对应目录(config get dir)
  3. 恢复:重启redis然后重新加载

注意:若AOF 文件损坏,可使用Redis-check-aof –fix进行修复

优劣势?

  • 优势:AOF 文件有序保存了对数据库执行的所有写入操作,这些写入操作以Redis 协议的格式保存,

    因此文件内容便于读懂并进行分析

  • 劣势:相同数据集的数据,aof文件体积远大于rdb文件体积;根据所使用的fsync策略,AOF的速度可能会慢于RDB(每秒同步策略效率较好,不同步效率和rdb相同)

AOF 重写(Rewrite)

AOF采用文件追加方式,文件会越来越大。为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。可以使用BGREWRITEAOF命令(与BGSAVE创建快照原理十分相似 )。

举例:假设用户对Redis设置了如下配置选项并且启用了AOF持久化。那么当AOF文件体积大于64mb,并且AOF的体积比上一次重写之后的体积大了至少一倍(100%)的时候,Redis将执行BGREWRITEAOF命令。

1
2
auto-aof-rewrite-percentage 100  
auto-aof-rewrite-min-size 64mb

原理:

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换旧的AOF文件 ,以此完成AOF文件重写操作

rewrite原理

Redis 4.0 对于持久化机制的优化

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分就是压缩格式不再是 AOF 格式,可读性较差。

Redis 事务

Redis事务提供了一种将多个命令请求打包,在一个队列中一次性、顺序性、排他性的执行一系列的命令。在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。

常用命令:

Redis 事务常用命令

三阶段

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

三特性

  1. 单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断
  2. 没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行
  3. 不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制

Redis 的发布订阅

发布订阅:进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

常用命令:

发布订阅

注意: 在做进程间通信是,一般使用专门的消息队列组件(ActiveMQ、RabbitMQ、RocketMQ等)

有关消息队列问题请转移到此处: 消息队列总结

Redis 主从复制

是什么?

Redis主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

能干嘛?

  • 读写分离
  • 容灾恢复

常用三情景

  1. 一主二仆:一个Master两个Slave
  2. 薪火相传:上一个Slave可以是下一个Slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力
  3. 反客为主:使当前数据库停止与其他数据库的同步,转成主数据库

复制的原理

  1. Slave启动成功连接到master后会发送一个sync命令

  2. Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步

  3. 全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。

  4. 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步

    (但是只要是重新连接master,一次完全同步(全量复制)将被自动执行)

哨兵模式(sentinel)

是什么?

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

怎么做?

实验

复制的缺点

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

缓存雪崩、缓存穿透和缓存击穿问题

缓存雪崩

是什么?

缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

如何解决?

  1. 线程互斥:只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据,每个时刻只有一个线程在执行请求,减轻了db的压力,但缺点也很明显,降低了系统的qps。
  2. 交错失效时间:错开不同的失效时间即可从一定长度上避免这种问题,在缓存进行失效时间设置的时候,从某个适当的值域中随机一个时间作为失效时间即可。
  • 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略,将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
  • 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
  • 事后:利用 redis 持久化机制保存的数据尽快恢复缓存

    高可用

缓存穿透

是什么?

黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

如何解决?

  1. 布隆过滤器:类似于哈希表的一种算法,用所有可能的查询条件生成一个bitmap ,在进行数据库查询之前会使用这个bitmap进行过滤,如果不在其中则直接过滤,从而减轻数据库层面的压力
  2. 缓存空结果:如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存击穿

是什么?

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

如何解决?

  1. 二级缓存:对于热点数据进行二级缓存,并对于不同级别的缓存设定不同的失效时间,则请求不会直接击穿缓存层到达数据库。
  2. 结合LRU算法:由于热点可能随着时间的变化而变化,针对固定的数据进行特殊缓存是不能起到治本作用的,结合LRU算法能够较好的帮我们解决这个问题

如何保证数据库与缓存数据的一致性

在使用缓存的过程中,避免不了对数据和缓存进行写入、删除操作,在这个过程中就一定会牵扯数据一致性问题。

一般来说,在系统不是严格要求缓存与数据库完全一致的情况下,缓存可以稍微跟数据库偶尔有不一致的情况。

解决方案:读请求写请求串行化到一个内存队列中去。不过这样做会使得系统吞吐量大幅度降低。

参考:redis系列之数据库与缓存数据一致性解决方案