呕心沥血编写的万字Redis入门到精通的教程

462 阅读39分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

Redis

概述

Redis中文官网:www.redis.net.cn/

Redis全称REmote DIctionary Server,是一个由Salvatore Sanfilippo写的key-value存储系统。

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供了多种语言的API。

特点

完全开源免费,遵守BSD协议,是一个高性能的key-value数据库。

支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载使用

不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储

支持数据的备份,即master-slave模式的数据备份

支持集群部署

性能极高 – Redis读的速度是110000次/s,写的速度是81000次/s 。

所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。

支持 publish/subscribe, 通知, key 过期等特性

入门

任何一门新技术的学习阶段为了降低学习成本,建议使用windows版本进行学习。

安装

windows版本下载地址:github.com/microsoftar…

下载解压即可

outpackredis.png

启动

在redis的安装目录下打开cmd窗口,执行redis-server.exe即可,如下图即为启动成功。

start.png 启动的时候可以加载指定的配置文件:redis-server.exe redis.conf

如果不指定配置文件,加载默认的配置文件。

连接

现在redis服务器已经启动成功了,我们可以使用自带的客户端工具进行连接使用。

在redis安装目录下面打开新的cmd窗口,执行 redis-cli.exe -h 127.0.0.1 -p 6379,如下图连接成功。

connect.png

配置

redis的配置文件位于安装目录下的redis.conf文件,windows版本的配置文件为redis.windows.conf

可以通过CONFIG命令查看或设置配置项。

使用格式:

CONFIG GET 配置项名称

比如我们查看日志输出级别配置项:loglevel

CONFIG_test.png 使用 * 可以查看所有的可配置项

127.0.0.1:6379> CONFIG GET *
  1) "dbfilename"
  2) "dump.rdb"
  3) "requirepass"
  4) ""
  5) "masterauth"
  6) ""
  7) "unixsocket"
  8) ""
  9) "logfile"
......

修改配置项

CONFIG set 配置项 值

redis.conf 配置项说明

官网配置介绍:www.redis.net.cn/tutorial/35…

  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. 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

单线程多路IO复用

big_io.png 我们以买票举个例子,A、B、C三人买票,A想买北京的票,但是没有了,现在A把自己的需求告诉了黄牛,黄牛去车站买票,A继续去做自己其他的事情了。B想买去上海的票,也没有了,B把自己的需求告诉了黄牛,然后自己也去做其他的事情了,C同理。现在黄牛去带着A B C三人需求去买票就是一个单线程,A B C三人就相当于 多路io,这样的好处就是A B C可以节省出时间去做其他的事情。

redis的单线程多路IO复用的原理也是这样,这样CPU可以节省出更多的资源去做其他的事情了。

中级

数据类型

官网命令查询:www.redis.net.cn/order/

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

String(字符串)

简介

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

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

一个键最大能存储512MB

命令
1. set   <key> <value>  :添加键值对
	*NX:当数据库中key不存在时,可以将key-value添加数据库
	*XX:当数据库中key存在时,可以将key-value添加数据库,与NX参数互斥
	*EX:key的超时秒数
	*PX:key的超时毫秒数,与EX互斥
2.get   <key>           :查询对应键值
3.append  <key> <value> :将给定的<value> 追加到原值的末尾
4.strlen  <key>         :获得值的长度
5.setnx  <key> <value>  :只有在 key 不存在时    设置 key 的值
6.incr  <key>			:将 key 中储存的数字值增1,只能对数字值操作,如果为空,新增值为1
7.decr  <key>			:将 key 中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1
8.incrby / decrby  <key> <步长>    :将 key 中储存的数字值增减。自定义步长。
9.mset  <key1><value1><key2><value2>  ..... :一次性set多个key  value
10.mget  <key1><key2><key3> .....  :一次性get多个value
11.msetnx <key1><value1><key2><value2>  .....  :当key不存在时,一次性set多个,原子性,有一个失败都失败
12.getrange  <key><起始位置><结束位置> :获得值的范围,类似java中的substring,前包,后包
13.setrange  <key><起始位置><value>  :用 <value>  覆写<key>所储存的字符串值,从<起始位置>开始(索引从0开始
14.setex  <key><过期时间><value> :设置键值的同时,设置过期时间,单位秒。
15.getset <key><value> :以新换旧,设置了新值同时获得旧值。

Hash(哈希)

简介

hash 是一个键值对集合,是一个string类型的field(字段)和value(字段值)的映射表,比较适合存对象类型的数据。

每个 hash 可以存储 2的32次方 - 1 键值对(40多亿)

命令
hset <key><field><value><key>集合中的  <field>键赋值<value>
hget <key1><field><key1>集合<field>取出 value 
hmset <key1><field1><value1><field2><value2>... 批量设置hash的值
hexists<key1><field>查看哈希表 key 中,给定域 field 是否存在。 
hkeys <key>列出该hash集合的所有field
hvals <key>列出该hash集合的所有value
hincrby <key><field><increment>为哈希表 key 中的域 field 的值加上增量 1   -1
hsetnx <key><field><value>将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .

List(列表)

简介

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

列表最多可存储 2的32次方- 1 元素,约40多亿

命令
lpush/rpush  <key><value1><value2><value3> .... 从左边/右边插入一个或多个值。
lpop/rpop  <key>				从左边/右边吐出一个值。值在键在,值光键亡。
rpoplpush  <key1><key2><key1> 列表右边吐出一个值,插到<key2>列表左边。
lrange <key><start><stop>		按照索引下标获得元素(从左到右)
lrange mylist 0 -1   			0左边第一个,-1右边第一个,(0-1表示获取所有)
lindex <key><index>				按照索引下标获得元素(从左到右)
llen <key>						获得列表长度 
linsert <key>  before <value><newvalue><value>的后面插入<newvalue>插入值
lrem <key><n><value>			从左边删除n个value(从左到右)
lset<key><index><value>			将列表key下标为index的值替换成value

Set(集合)

简介

Set是string类型的无序集合。不允许重复的值。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

命令
sadd <key><value1><value2> ..... 	将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
smembers <key>						取出该集合的所有值。
sismember <key><value>				判断集合<key>是否为含有该<value>值,有1,没有0
scard<key>							返回该集合的元素个数。
srem <key><value1><value2> .... 	删除集合中的某个元素。
spop <key>							随机从该集合中吐出一个值。
srandmember <key><n>				随机从该集合中取出n个值。不会从集合中删除 。
smove <source><destination>value	把集合中一个值从一个集合移动到另一个集合
sinter <key1><key2>					返回两个集合的交集元素。
sunion <key1><key2>					返回两个集合的并集元素。
sdiff <key1><key2>					返回两个集合的差集元素(key1中的,不包含key2中的)

zset(sorted set:有序集合)

简介

zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

命令
zadd  <key><score1><value1><score2><value2>…  	将一个或多个 member 元素及其 score 值加入到有序集 key 当中。

zrange <key><start><stop>  [WITHSCORES]   		返回有序集 key 中,下标在<start><stop>之间的元素,带WITHSCORES,可以让分数一起和值返回到结果集。

zrangebyscore key minmax [withscores] [limit offset count]	返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。 

zrevrangebyscore key maxmin [withscores] [limit offset count]      同上,改为从大到小排列。 
zincrby <key><increment><value>      	为元素的score加上增量
zrem  <key><value>						删除该集合下,指定值的元素 
zcount <key><min><max>					统计该集合,分数区间内的元素个数 
zrank <key><value>						返回该值在集合中的排名,从0开始。

新类型

Bitmaps

Redis提供了Bitmaps,这个“数据类型”可以实现对位的操作,即value值只能是0或者1

Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。

可以把Bitmaps想象成一个以“位”为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量offset

应用场景

可以使用Bitmaps来统计一个网站的日访问用户,用每天的日期作为bitmaps的key,使用用户的id作为偏移量offset,访问的用户将value设置为1.

例如: setbit user:20220415 11 1 :表示在20220415这天,id为11的用户访问了网站。

命令
setbit  key  offset  value  :设置值

getbit key  offset :获取值

bitcount  key  :统计value为1的个数

bittop  or   key3  key1  key2     :key1 和key2 的并集放到key3中

bittop  and   key3  key1  key2     :key1 和key2 的交集放到key3中

bitop.png

Bitmaps和set对比

有人会说这样的需求set类型的数据也可以实现,下面我们看一下他们的区别:

set和Bitmaps存储一天活跃用户对比
数据类型每个用户id占用空间需要存储的用户量全部内存量
集合类型64位5000000064位*50000000 = 400MB
Bitmaps1位1000000001位*100000000 = 12.5MB

HyperLogLog

简介

在一些项目中我们要统计页面的访问流量即PV(PageView),而有些场景我们需要统计页面的用户访问量UV(user view),这就要求里面的用户数据是不重复的。

实现这个场景的方案有很多,比如通过mysql中的distinct对pv数据进行去重,又比如redis中的set,bitmaps都可以实现对页面的UV统计,但是这些方案随着数据量的不断增大就导致占用资源空间较大,所以redis针对这个提出了一个新的类型HyperLogLog。

HyperLogLog是用来统计基数问题的数据类型。

基数:就是key中数据的不重复的个数,{1,2,3,4,2,1,5}的去重后的集合为{1,2,3,4,5},基数为5.

命令
pfadd  key   value1  value2 ......   :添加元素到HyperLogLog中,执行这个命令后如果HyperLogLog的key中基数发生变化则返回1,否则返回0.

pfcount  key1   key2 .....               :查看key中的基数,可以统计多个key的基数和(应用场景,统计多天的UV)

pfmerge  destkey  srckey1  srckey2 ......:将多个srckey中的数据合并放入到destkey 中(应用场景:合并一个月的访问用户)

hyperLogLog.png

Geospatial

简介

Redis3.2版本增加了对Geo类型的支持,Geo即为Geospatial,地理信息。也就是我们可以在redis中通过设置经纬度管理坐标信息的数据。

南北两极无法直接添加,一般会下载城市数据,直接通过 Java 程序一次性导入。

有效的经度从 -180 度到 180 度。有效的纬度从 -85.05112878 度到 85.05112878 度。当坐标位置超出指定范围时,该命令将会返回一个错误。

已经添加的数据,是无法再次往里面添加的。

命令

geoadd key 经度 纬度 name :添加name地点的坐标信息到key中。

geopos key name : 获取key中name地点的坐标

geodist key name1 name2 单位[m|km|ft|mi] :计算key中 name1到name2地点的直线距离,并可指定单位

georadius key 经度 纬度 radius 单位 :查找key中 以给定的经纬度为中心,半径为radius范围下的地点。可以指定单位

m 表示单位为米[默认值]。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位

发布订阅(sub/pub)

在redis中提供了发布订阅模式,可以实现一个简单的发布订阅任务,客户端A订阅一个队列,然后客户端B往队列中发送消息,客户端A就会收到消息。

pub_sub.png

使用

开启两个客户端订阅队列channel

sub_2.png 开启一个客户端往队列channel中发送消息

publish.png 消息发送完成后就会发现两个订阅者会收到发送者发送的helloworld消息。

Redis事务

介绍

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队。

Redis事务是通过MULTI、EXEC、DISCARD、WATCH、UNWATCH这几个命令来实现的

由于Redis中所有的命令都是原子性的,所以保证Redis事务的实现就是保证一系列命令连续且不被打断的执行。

redis事务不支持回滚

涉及命令

MULTI

开启一个事务队列,开启事务队列后,将在该事务队列下执行的命令进行入队操作。

EXEC

提交事务,开始执行该事务队列

DISCARD

清空事务队列中的命令集,并关闭事务。

WATCH

监控一个key,可以在事务开启前监控一个key,如果该key在事务EXEC执行前被别的请求修改了,那么该事务将会执行失败,可以用来当作乐观锁。

UNWATCH

清空监控

应用

开启事务&提交事务

multi-1.png

DISCARD

discard.png

因为执行了discard,清空并关闭了该事务,所以在执行EXEC提交事务的时候报错了就。

WATCH

watch.png

watch开启监控看k1后,然后创建事务队列,在事务提交执行之前,被监控的k1被另一个请求修改了,所以事务提交失败。

watch是一个乐观锁的操作,在watch监视了一个key后,当事务执行操作到watch所监视的key时,会判断key的版本号,如果版本号被修改,那么就会操作失败。

注意

在开启事务命令入队的过程中,如果某个命令入队报错,那么这个事务中所有的命令都会执行失败。

如果在命令入队的时候没有报错,在exec事务提交执行的时候报错,那么只有报错的命令执行失败。

分布式锁

介绍

分布式锁是在分布式系统中保证共享资源安全性的一种锁,

需要具有的特性

  1. 互斥性:在任意一个时刻,只有一个客户端持有锁
  2. 无死锁:当持有锁的客户端宕机或者发生错误的时候,也能释放锁。
  3. 容错性高:只要大部分的redis节点活着,客户端就可以持有和释放锁

实现

可以使用redis中的setnx ,del命令实现。

通过一个死循环去往redis中setnx一个键值,并设置过期时间,如果返回成功,那么当前线程即获取到了锁,然后进行相应的业务代码。

当业务代码处理完后,调用del命令删除该键值,即为释放锁。

其他的线程同时调用setnx时,因为返回false,那么说明当前锁被其他线程持有,所以会一直处于死循环的等待过程中。

Java应用

这块内容之后单独整理文档,这里先不写了。

高级

持久化

简介

redis是内存数据库,数据保存在内存中,如果在项目运行的过程中发生服务器宕机或者redis挂了的情况,可能存在数据丢失的情况。而保证数据的完整性和安全性是非常重要的。所以就需要使用redis数据的持久化。

RDB机制

将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。

提供了三种触发的机制:

  1. 执行save命令

    该方式会使redis处于阻塞状态,在save命令执行期间,其他的客户端发来的命令不能被执行,直到save执行完毕。

  2. 执行bgsave命令

    执行bgsave命令后,redis进程会fork一个子进程,子进程去生成rdb文件。

  3. 通过配置文件自动化

    在redis安装目录下的redis.conf配置文件中,可以搜索rdb找到rdb配置项,进行自动化rdb配置。

快照生成的过程
  1. redis使用fork函数复制一份当前进程副本(子进程)
  2. 父进程继续接受并处理客户端发来的命令,而子进程开始将内存中的数据写入到硬盘****中临时文件
  3. 子进程写入完所有数据后用该临时文件替换旧的RDB文件,至此,一次快照操作完成。
缺点

第一种save的方式不推荐使用,因为会阻塞redis,我们只说第二和第三种,在rdb机制触发的时候,redis进程会fork一个子进程去生成rdb快照,那么当子进程创建后,子进程便有了和主进程同样的数据,然后子进程开始去做rdb操作,主进程还是会继续处理客户端发来的请求。

这时候子进程和主进程就存在一个数据差。如果这时候redis挂掉了,那么这个数据差就会丢失了。

所以RDB的缺点是最后一次持久化后的数据可能丢失。

注意
  1. redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的。
  2. 这就使得我们可以通过定时备份RDB文件来实现redis数据库的备份,RDB文件是经过压缩的二进制文件,占用的空间会小于内存中的数据,更加利于传输。
  3. 可以通过lastsave 命令获取最后一次成功执行快照的时间
  4. 执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义

AOF机制

介绍

AOF全称为append only file,默认情况下没有开启,开启AOF机制后,每当客户端执行一条更改redis数据的命令,redis就会将该命令写入磁盘的aof文件中。这个过程会降低redis的性能,但是这个影响是可以接受的。另外使用高性能的硬盘可以提高aof的性能。

AOF重写原理(优化AOF文件)
  1. Redis可以在AOF文件体积变得过大时,自动地后台对AOF进行重写
  2. 重写后的新AOF文件包含了恢复当前数据集所需的最小命令集合
  3. 整个重写操作是绝对安全的,因为Redis在创建新的AOF文件的过程中,会继续将命令追加到现有的AOF文件里面,即使重写过程中发生停机,现有的AOF文件也不会丢失。而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作。
  4. AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。
AOF持久化流程
  1. 客户端的请求写命令会被append追加到AOF缓冲区内;
  2. AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
  3. AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
  4. Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;

aof.png

开启及配置aof

在redis.conf文件中的 APPEND ONLY MODE 块进行开启和配置,默认 不开启。

AOF文件损坏以后如何修复

服务器可能在程序正在对AOF文件进行写入时停机,如果停机造成AOF文件出错(corrupt),那么Redis在重启时会拒绝载入这个AOF文件,从而确保数据的一致性不会被破坏。

当发生这种情况时,可以以以下方式来修复出错的AOF文件:

  1、为现有的AOF文件创建一个备份。

  2、使用Redis附带的redis-check-aof程序,对原来的AOF文件进行修复。

  3、重启Redis服务器,等待服务器字啊如修复后的AOF文件,并进行数据恢复

Rewrite压缩

redis中的aof持久化有Rewrite压缩策略的配置,当aof文件达到了配置的阈值时,自动触发Rewrite机制,这时redis会fork一个子进程去做rewrite操作,将aof中的命令集优化成占用资源更小的命令,rewrite完成后会覆盖掉当前的aof文件。

优缺点
  1. 备份机制更稳健,丢失数据概率更低。
  2. 可读的日志文本(里面都是一条条命令),通过操作AOF稳健,可以处理误操作
  3. 比起RDB占用更多的磁盘空间
  4. 恢复备份速度要慢。
  5. 每次读写都同步的话,有一定的性能压力.

如何选择RDB和AOF

  1. 一般来说,如果对数据的安全性要求非常高的话,应该同时使用两种持久化功能。
  2. 如果可以承受数分钟以内的数据丢失,那么可以只使用RDB持久化。
  3. 有很多用户都只使用AOF持久化,但并不推荐这种方式:因为定时生成RDB快照(snapshot)非常便于进行数据库备份,并且RDB恢复数据集的速度也要比AOF恢复的速度要快
  4. 两种持久化策略可以同时使用,也可以使用其中一种。如果同时使用的话,那么Redis启动时,会优先使用AOF文件来还原数据。
  5. 如果只是做纯内存缓存,可以都不用.

注意

  1. AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

主从复制

简介

主从复制的机制就是master/slaver机制,主机服务更新数据后自动的将数据同步到从机服务上。master负责 写操作,slaver负责读操作

这样带来的好处:

  1. 读写分离,能缓解系统的压力,更容易拓展系统。
  2. 因为需要有多个slaver,能够容灾快速恢复。

master_slaver.png

搭建环境

现在我们开始搭建一主二从的环境

因为我们现在只在本机电脑上进行的操作,所以我们将一主二从的环境也搭建在本地电脑,通过不同的端口搭建启动三台redis服务。

在真实项目中,通常搭建在不同ip服务器上的。

一、在redis解压目录下创建redis_conf问文件夹用来统一存放配置文件

confdir.png

二、拷贝解压目录下的redis.windows.conf文件到redis_conf文件夹下,并将其中的aof关闭。

三、创建我们自己的配置文件,引入公共配置文件,同时添加特有的配置。

master_slaver1.png

//6379配置文件内容
include ./redis.windows.conf    //引入公共配置
port 6379                       //端口号,可覆盖公共配置文件内配置的端口
dbfilename dump6379.rdb         //配置rpd文件

//6380配置文件内容
include ./redis.windows.conf
port 6380
dbfilename dump6380.rdb

//6381配置文件内容
include ./redis.windows.conf
port 6381
dbfilename dump6381.rdb

四、指定配置文件分别启动三个redis服务

redis-server.exe  redis_conf/redis6379.conf
redis-server.exe  redis_conf/redis6380.conf
redis-server.exe  redis_conf/redis6381.conf

start_success.png

五、查看三台服务的运行信息

命令:info replication

先通过客户端连接redis-server

redis-cli.exe -h 127.0.0.1 -p 6379
redis-cli.exe -h 127.0.0.1 -p 6380
redis-cli.exe -h 127.0.0.1 -p 6381

info.png

可以看到redis启动后默认都是master

六、配置主从关系

分别将6380 6381配置成6379的从机

命令:slaveof  <ip><port>

在6380  6381上分别执行:
slaveof 127.0.0.1 6379

slave_of.png

七、查看三台服务的运行信息

命令:info replication

master_info.png

slaver_info.png

八、在主机写数据,在从机就能读到数据,在从机写数据会报错,报错如下

slaver_write_fales.png

九、主机挂掉,重新启动后仍然为主机

十、从机挂掉,从机挂掉重启后就会变成一个独立的节点,所以它的角色就是自己的master,需要手动的执行命令将其添加到主从关系中

slaveof   ip   port

一主二仆

上面我们搭建的环境就是一主二仆的环境,有几个注意点:

  1. 主机挂掉,重新启动后仍然为主机,从机不会上任,在从机上执行info replication可以看到主机挂了
  2. 从机挂掉,重新启动后会变成一个独立的节点,此时它的角色就是自己的master,需要手动的执行slaveof命令将其加入到主从关系中。
  3. 从机挂掉重启或者一个新的从机加入到主从关系中,会同步主机的所有数据,不会存在数据差。

主从复制原理

  1. slave从机加入到主从关系中后,会向master主机发送一个同步的指令。
  2. master主机接收到slaver从机同步的指令后,fork持久化进程生成rpd文件,然后将rpd文件发送到slaver从机进行同步。
  3. slaver从机收到rpd文件后进行加载,这里是一个全量复制的步骤
  4. master主机当执行了新的修改命令后,会主动将修改命令同步到slaver从机(这里是一个增量复制)。

薪火相传

一主二仆的关系图:

yizhuerpu.png

薪火相传的关系图:

xinhuoxiangchuan.png

搭建薪火相传的环境就是将一台slaver从机的主机ip配置成另一台slaver从机,以上面的6379 6380 6381 举例,可以将6381的master配置成6380,这样6381就成了6380的子孙了。

注意此时6380的角色还是slaver,它只是多了个后代。

shili_xinhuoxiangchuan.png

薪火相传的目的可以解决读数据的压力

其他的特点和一主二仆一样

反客为主

当master主机挂掉后,让某台slaver从机升为master角色。

手动在从机上执行 slaveof no one ,将从机变成主机。

fankeweizhu.png

哨兵模式

简介

上面的反客为主模式,当主机master挂掉后,需要手动执行slaveof no one命令将从机升为master角色。这就需要运维人员手动的去切换,假如是凌晨1点redis的master挂了,运维人员也去睡觉了,那么怎么办呢,怎么能做到反客为主的自动切换呢?

redis中提供了一种哨兵模式的机制,可以实现自动的反客为主的角色切换。

环境搭建
  1. 还是准备三套redis的环境,我们还是使用刚才的6379 6380 6381三套环境,并配置好一主二仆的关系。

  2. 准配并配置哨兵配置文件

    在redis_conf下创建sentinel.conf,注意文件名称不能错,并在文件中配置如下内容

    port 26379
    sentinel monitor mymaster 127.0.0.1 6379 1
    
    //该命令讲解
    sentinel 哨兵
    monitor  监控
    mymaster 给主机master起的名字
    127.0.0.1主机ip
    6379     主机端口
    1        需要几个从机同意才能选择那个从机上升为master主机
    
  3. 启动哨兵,在redis解压目录下执行如下命令

    linux版本命令:
    redis-sentinel  redis_conf/sentinel.conf
    
    windows版本命令:
    redis-server.exe  redis_conf/sentinel.conf --sentinel
    

sentinel.png

  1. 开始测试,我们将master主机手动挂掉,并观察哨兵控制台输出

shaobing_result.png

我们发现哨兵模式选举出6380作为了新的master主机节点了,同时哨兵模式也开始监控6380主机节点。

然后将6379当作了slaver从机,也就是6379重启后,会自动变成slaver从机节点(注意不用手动的slaveof了)

复制延时

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

哨兵切换主从的流程
  1. 新主登基:master宕机后,哨兵从master的所有slaver中选举一个,将其转成新的master主机。
  2. 群仆俯首:新master上任后,哨兵回向原master的从机发送slaveof新主机的命令
  3. 旧主俯首:当原master重新上线后,哨兵会向其发送slaveof新主机的命令,原master变为新主机的slaver。
选举条件

选择新master的条件依次为:

  1. 选择优先级靠前的。
  2. 选择偏移量大的
  3. 选择runid最小的slaver从机
优先级配置:在redis.conf中的slave-priority为优先级配置项,值越小优先级越高。

偏移量:是指获得原主机数据最全的

runid:每个redis实例启动后都会随机生成一个40位的runid
jedis连接哨兵模式的主从复制
private static JedisSentinelPool jedisSentinelPool=null;

public static  Jedis getJedisFromSentinel(){
	if(jedisSentinelPool==null){
        Set<String> sentinelSet=new HashSet<>();
        sentinelSet.add("192.168.11.103:26379");//连接哨兵服务

        JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10); //最大可用连接数
		jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
		jedisPoolConfig.setMinIdle(5); //最小闲置连接数
		jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
		jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
		jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong

		jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
		return jedisSentinelPool.getResource();
	}else{
		return jedisSentinelPool.getResource();
    }
}

缓存击穿

简介

qingqiu.png

一个正常请求的发起流程如上图所示,我们发起一个请求,是先请求到了redis上,如果在redis中命中了数据,那么redis会将需要的数据直接返回,如果没有命中,那么就会去请求数据库,然后返回并同步写入到缓存中。

但是在实际过程中可能会有一种情况,比如我们携带一个id去请求这个id的数据,在redis中没有命中,然后请求被传到了数据库层,但是在数据库层也没有命中。

那么下次访问还是会穿过缓存redis直接请求到了数据库层,如果这种请求大量的存在,就会导致数据库压力剧增,从而导致数据库崩溃。

通常造成这种问题的原因是系统遭受到了黑客攻击

解决方案

  1. 对null值也进行缓存,并设置一个短的失效时间。

    比如第一次带id的请求穿过了redis请求到了数据库,数据库返回null,将该值缓存在redis中,并设置一个过期时间,那么多时间该id就不会请求到数据库了。

    问题:这种处理方式不是很好,因为如果是黑客攻击的话,id一定是自动生成的,且每次请求肯定都不一样。

  2. 设置可访问的白名单

    将数据库存在的数据id内置在redis中,可以放到bitmaps中,存在的id的value为1,每次请求过来先判断在bitmaps中是否存在,如果存在再继续下面的请求操作。

    问题:因为多了一步id是否在白名单的操作,所以会影响性能。

  3. 使用布隆过滤器

    底层使用的是bitmaps数据类型,原理 2 同理。性能比较高。

  4. 进行实时监控

    可以进行实时监控,如果同一ip的异常请求很多,可以直接禁止该ip访问。

缓存穿透

简介

在项目运行过程中因为某个key过期(注意不是大量的key过期),但是这个key是热门数据,如果大量的并发请求需要获取该key的数据,此时因为该key过期在redis中获取不到,所以这些请求会都打到数据库层,从而造成数据库瞬时崩溃。

解决方案

  1. 预先设置一些热点数据

    将一些热点数据提前存到redis中,并加大这些数据的过期时间。

  2. 实时监控调整

    可以实时观察哪些key是热点数据,手动实时调整这些key的过期时间

  3. 使用锁

    给获取数据的这几步操作加锁,处理成线程同步的,只有一个线程能去数据库获取该key的数据,获取到后同步到redis中。其他的线程处于阻塞状态。

    这种方法效率肯定非常的低。

缓存雪崩

简介

在短时间内大量的key同时过期,然后在此时有很多并发的请求需要获取这些key的数据,因为此时这些key在redis中过期,所以这些请求就发送到了数据库层,从而导致数据库压力剧增,进而导致服务器崩溃(服务器崩溃的同时导致redis,代码工程都会运行异常,即便重启服务器后也会运行异常),缓存雪崩是个很严重的问题,会对服务器造成损坏。

解决方案

  1. 构建多级缓存架构

    nginx缓存 + redis缓存 +其他缓存(ehcache等),当在一级缓存获取不到数据的时候就去第二级获取。

    问题:会造成系统架构成本升高,维护性不好。

  2. 使用锁或者队列

    一个请求一个请求的去获取key的数据,这样肯定就不会造成缓存雪崩了,但是一次只有一个线程执行任务,其他的线程都是处于阻塞的状态,所以会影响系统的性能。

  3. 设置过期标志更新缓存

    设置过期时间的记录,对过期时间进行记录,如果快要过期了,就通知所负责的线程去更新redis中的key

  4. 将缓存过期时间分散开

    我们可以在key原有过期时间的基础上增加一个随机的值,可以是1-5分钟的随机,这样就不会发生大量key同时过期的问题。

集群

简介

集群可以解决容量不足,并发读写压力大的问题

我们使用主从复制中的一主二仆、薪火相传、反客为主模式的时候,当主节点挂掉后,那么新的主节点ip就会发生改变,这样我们在代码中通过远程客户端操作redis的时候就需要手动的更改ip等配置,不太方便。redis集群中这个问题之前都是通过代理主机来解决这个问题,在redis3.0中针对这个问题提供了解决方案:就是“无中心化集群配置”。

什么叫做无中心化集群配置:就是集群中的任何一个ip都能作为集群的入口,我们使用客户端操作redis集群时,连接哪个ip都可以。

redis集群就是水平扩容,将数据存储分布不到不同的节点之上。

cluster.png

也就是说一个redis集群环境最少需要6套redis环境

搭建环境

在这里就简单写一下搭建步骤,具体的搭建过程大家可自行百度。

  1. 准配配置文件

    因为我们都是在一台ip上进行的操作,所以我们启动6个端口模拟集群的环境。分别使用6379、6380、6381、6389、6390、6391六个端口环境。

    配置文件内容:

    include redis.windows.conf  //引入公共配置
    port 6391					//配置端口
    dbfilename "dump6391.rdb"	//配置持久化文件
    dir "D:/Redis-x64-3.0.503/redis_conf"	//配置redis根路径
    cluster-enabled yes			//开启集群模式
    cluster-config-file nodes-6391.conf		//配置生成的集群配置文件
    cluster-node-timeout 15000	//配置节点超时时间
    

    根据以上内容,我们准备6个配置文件。

  2. 启动6台redis

    redis-server.exe  redis6379.conf
    ......
    

    启动成功后可以查看nodes-*.conf文件是否生成。6台redis的该文件都生成后即为启动成功。

  3. windows版本搭建redis集群需要安装Ruby环境。Linux下的redis3.0之后的版本就不需要了。

    Ruby环境安装可自行百度

  4. 合成集群

    linux下命令
    redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391
    
    windows下命令
    redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6389 127.0.0.1:6390 127.0.0.1:6391
    

    replicas 1 :表示以最简单的方式创建集群,即一台主机配一台从机。

  5. 连接集群,使用客户端连接任意一台master主机ip即可

    redis-cli -c -p 6379
    
    -c:表示以集群方式连接
    
  6. 连接成功后,查看集群的分配信息

    cluster nodes
    

    执行该命令后,可以看到自动分配的主机 还有从机信息(包括主、从机ip,主、从机的关系等)

  7. redis集群合成时如何分配

    一个redis集群最少有三个master,然后我们合成的时候指定了--replicas 1 ,表示我们希望一个master有一个slaver。

    在真实的项目集群搭建时,需要保证所有的master不在同一ip上,同一master和slaver不要在同一ip上。

slots

简介

slots为插槽,在redis集群中有插槽的概念,一个redis集群一共有16384 个插槽,即有16384 个slots,这些插槽平均分布在集群中的master上。redis中的每一个key都存在于这16384 个插槽中的一个上。

集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。

比如我们上面搭建的三主三从的集群环境,我们执行cluster nodes命令查看集群信息,可以看到:

节点 A 负责处理 0 号至 5460 号插槽。

节点 B 负责处理 5461 号至 10922 号插槽。

节点 C 负责处理 10923 号至 16383 号插槽。

操作

  1. 我们使用客户端连接到任意一台master上

    redis-cli  -c -p 6379
    
  2. set一个key-value数据

    set  k1   value1
    

    插入成功后,我们可以在控制台看到该key属于哪个插槽的信息,同时会发现客户端连接的ip自动切换成了该插槽所属master的ip了。

    这也就能很明了的观察到“无中心化集群配置”的效果了,任何一个ip都能作为集群的入口。

  3. mset多个key-value数据

    mset  k2 v2  k3 v3 ...
    

    会发现mset执行失败

    注意:因为往集群中存key时,会计算key所属的插槽,多个key基本不会在同一插槽上,所以会执行失败

    解决办法:

    给多个key进行分组,存入时计算组的插槽

    mset   k2{user}  v2  k3{user}  v3
    
    说明:分配这些数据到同一组中,组名为user
    

故障恢复

  • 当一台master宕机后,那么该master的从机slaver会自动升为master。(我们在配置文件中配置了cluster-node-timeout,到了这个时间后,集群就认为这个master失联了)

  • 当挂掉的master再次启动后,会变为新主机的从机。

  • 如果某一段插槽的主、从节点都挂了,会根据cluster-require-full-coverage配置判断redis集群整体挂掉还是继续运行。

    cluster-require-full-coverage   yes/no
    如果为 yes, 那么就会整体挂掉
    如果为 no ,  那么其他节点正常运行
    

jedis集群开发

public class JedisClusterTest {
  public static void main(String[] args) { 
     Set<HostAndPort>set =new HashSet<HostAndPort>();
     set.add(new HostAndPort("192.168.31.211",6379)); //这里可以add集群中的多个ip,由于每个ip都能作为集群的入口,所以指定一个即可
     JedisCluster jedisCluster=new JedisCluster(set);
     jedisCluster.set("k1", "v1");
     System.out.println(jedisCluster.get("k1"));
  }
}
注意:

即使连接的不是master的ip,集群会自动进行切换,主机写,从机读。(很智能)

无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据。

因为redis3.0后使用的时无中心化集群,所以任何一个ip都能作为集群的入口。

注:本文内容为原创,部分内容参考互联网,转发请备注原地址