一起养成写作习惯!这是我参与「掘金日新计划 · 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…
下载解压即可
启动
在redis的安装目录下打开cmd窗口,执行redis-server.exe即可,如下图即为启动成功。
启动的时候可以加载指定的配置文件:redis-server.exe redis.conf
如果不指定配置文件,加载默认的配置文件。
连接
现在redis服务器已经启动成功了,我们可以使用自带的客户端工具进行连接使用。
在redis安装目录下面打开新的cmd窗口,执行 redis-cli.exe -h 127.0.0.1 -p 6379,如下图连接成功。
配置
redis的配置文件位于安装目录下的redis.conf文件,windows版本的配置文件为redis.windows.conf
可以通过CONFIG命令查看或设置配置项。
使用格式:
CONFIG GET 配置项名称
比如我们查看日志输出级别配置项:loglevel
使用 * 可以查看所有的可配置项
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…
-
Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no
-
当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid
-
指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
-
绑定的主机地址
bind 127.0.0.1
-
5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
-
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
loglevel verbose
-
日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout
-
设置数据库的数量,默认数据库为0,可以使用SELECT
<dbid>
命令在连接上指定数据库iddatabases 16
-
指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save
<seconds>
<changes>
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
-
指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
-
指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
-
指定本地数据库存放目录
dir ./
-
设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
slaveof
<masterip>
<masterport>
-
当master服务设置了密码保护时,slav服务连接master的密码
masterauth
<master-password>
-
设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH
<password>
命令提供密码,默认关闭requirepass foobared
-
设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128
-
指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory
<bytes>
-
指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no
-
指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof
-
指定更新日志条件,共有3个可选值: no:表示等操作系统进行数据缓存同步到磁盘(快) always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec
-
指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
vm-enabled no
-
虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
-
将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0
-
Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
vm-page-size 32
-
设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728
-
设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4
-
设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes
-
指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
-
指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes
-
指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf
单线程多路IO复用
我们以买票举个例子,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中
Bitmaps和set对比
有人会说这样的需求set类型的数据也可以实现,下面我们看一下他们的区别:
set和Bitmaps存储一天活跃用户对比 | |||
---|---|---|---|
数据类型 | 每个用户id占用空间 | 需要存储的用户量 | 全部内存量 |
集合类型 | 64位 | 50000000 | 64位*50000000 = 400MB |
Bitmaps | 1位 | 100000000 | 1位*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 中(应用场景:合并一个月的访问用户)
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就会收到消息。
使用
开启两个客户端订阅队列channel
开启一个客户端往队列channel中发送消息
消息发送完成后就会发现两个订阅者会收到发送者发送的helloworld消息。
Redis事务
介绍
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。
Redis事务是通过MULTI、EXEC、DISCARD、WATCH、UNWATCH这几个命令来实现的
由于Redis中所有的命令都是原子性的,所以保证Redis事务的实现就是保证一系列命令连续且不被打断的执行。
redis事务不支持回滚
涉及命令
MULTI
开启一个事务队列,开启事务队列后,将在该事务队列下执行的命令进行入队操作。
EXEC
提交事务,开始执行该事务队列
DISCARD
清空事务队列中的命令集,并关闭事务。
WATCH
监控一个key,可以在事务开启前监控一个key,如果该key在事务EXEC执行前被别的请求修改了,那么该事务将会执行失败,可以用来当作乐观锁。
UNWATCH
清空监控
应用
开启事务&提交事务
DISCARD
因为执行了discard,清空并关闭了该事务,所以在执行EXEC提交事务的时候报错了就。
WATCH
watch开启监控看k1后,然后创建事务队列,在事务提交执行之前,被监控的k1被另一个请求修改了,所以事务提交失败。
watch是一个乐观锁的操作,在watch监视了一个key后,当事务执行操作到watch所监视的key时,会判断key的版本号,如果版本号被修改,那么就会操作失败。
注意
在开启事务命令入队的过程中,如果某个命令入队报错,那么这个事务中所有的命令都会执行失败。
如果在命令入队的时候没有报错,在exec事务提交执行的时候报错,那么只有报错的命令执行失败。
分布式锁
介绍
分布式锁是在分布式系统中保证共享资源安全性的一种锁,
需要具有的特性
- 互斥性:在任意一个时刻,只有一个客户端持有锁
- 无死锁:当持有锁的客户端宕机或者发生错误的时候,也能释放锁。
- 容错性高:只要大部分的redis节点活着,客户端就可以持有和释放锁
实现
可以使用redis中的setnx ,del命令实现。
通过一个死循环去往redis中setnx一个键值,并设置过期时间,如果返回成功,那么当前线程即获取到了锁,然后进行相应的业务代码。
当业务代码处理完后,调用del命令删除该键值,即为释放锁。
其他的线程同时调用setnx时,因为返回false,那么说明当前锁被其他线程持有,所以会一直处于死循环的等待过程中。
Java应用
这块内容之后单独整理文档,这里先不写了。
高级
持久化
简介
redis是内存数据库,数据保存在内存中,如果在项目运行的过程中发生服务器宕机或者redis挂了的情况,可能存在数据丢失的情况。而保证数据的完整性和安全性是非常重要的。所以就需要使用redis数据的持久化。
RDB机制
将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
提供了三种触发的机制:
-
执行save命令
该方式会使redis处于阻塞状态,在save命令执行期间,其他的客户端发来的命令不能被执行,直到save执行完毕。
-
执行bgsave命令
执行bgsave命令后,redis进程会fork一个子进程,子进程去生成rdb文件。
-
通过配置文件自动化
在redis安装目录下的redis.conf配置文件中,可以搜索rdb找到rdb配置项,进行自动化rdb配置。
快照生成的过程
- redis使用fork函数复制一份当前进程的副本(子进程)
- 父进程继续接受并处理客户端发来的命令,而子进程开始将内存中的数据写入到硬盘****中的临时文件。
- 当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此,一次快照操作完成。
缺点
第一种save的方式不推荐使用,因为会阻塞redis,我们只说第二和第三种,在rdb机制触发的时候,redis进程会fork一个子进程去生成rdb快照,那么当子进程创建后,子进程便有了和主进程同样的数据,然后子进程开始去做rdb操作,主进程还是会继续处理客户端发来的请求。
这时候子进程和主进程就存在一个数据差。如果这时候redis挂掉了,那么这个数据差就会丢失了。
所以RDB的缺点是最后一次持久化后的数据可能丢失。
注意
- redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的。
- 这就使得我们可以通过定时备份RDB文件来实现redis数据库的备份,RDB文件是经过压缩的二进制文件,占用的空间会小于内存中的数据,更加利于传输。
- 可以通过lastsave 命令获取最后一次成功执行快照的时间
- 执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
AOF机制
介绍
AOF全称为append only file,默认情况下没有开启,开启AOF机制后,每当客户端执行一条更改redis数据的命令,redis就会将该命令写入磁盘的aof文件中。这个过程会降低redis的性能,但是这个影响是可以接受的。另外使用高性能的硬盘可以提高aof的性能。
AOF重写原理(优化AOF文件)
- Redis可以在AOF文件体积变得过大时,自动地后台对AOF进行重写
- 重写后的新AOF文件包含了恢复当前数据集所需的最小命令集合。
- 整个重写操作是绝对安全的,因为Redis在创建新的AOF文件的过程中,会继续将命令追加到现有的AOF文件里面,即使重写过程中发生停机,现有的AOF文件也不会丢失。而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作。
- AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。
AOF持久化流程
- 客户端的请求写命令会被append追加到AOF缓冲区内;
- AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
- AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
- Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;
开启及配置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文件。
优缺点
- 备份机制更稳健,丢失数据概率更低。
- 可读的日志文本(里面都是一条条命令),通过操作AOF稳健,可以处理误操作
- 比起RDB占用更多的磁盘空间
- 恢复备份速度要慢。
- 每次读写都同步的话,有一定的性能压力.
如何选择RDB和AOF
- 一般来说,如果对数据的安全性要求非常高的话,应该同时使用两种持久化功能。
- 如果可以承受数分钟以内的数据丢失,那么可以只使用RDB持久化。
- 有很多用户都只使用AOF持久化,但并不推荐这种方式:因为定时生成RDB快照(snapshot)非常便于进行数据库备份,并且RDB恢复数据集的速度也要比AOF恢复的速度要快。
- 两种持久化策略可以同时使用,也可以使用其中一种。如果同时使用的话,那么Redis启动时,会优先使用AOF文件来还原数据。
- 如果只是做纯内存缓存,可以都不用.
注意
- AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)
主从复制
简介
主从复制的机制就是master/slaver机制,主机服务更新数据后自动的将数据同步到从机服务上。master负责 写操作,slaver负责读操作
这样带来的好处:
- 读写分离,能缓解系统的压力,更容易拓展系统。
- 因为需要有多个slaver,能够容灾快速恢复。
搭建环境
现在我们开始搭建一主二从的环境
因为我们现在只在本机电脑上进行的操作,所以我们将一主二从的环境也搭建在本地电脑,通过不同的端口搭建启动三台redis服务。
在真实项目中,通常搭建在不同ip服务器上的。
一、在redis解压目录下创建redis_conf问文件夹用来统一存放配置文件
二、拷贝解压目录下的redis.windows.conf文件到redis_conf文件夹下,并将其中的aof关闭。
三、创建我们自己的配置文件,引入公共配置文件,同时添加特有的配置。
//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
五、查看三台服务的运行信息
命令: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
可以看到redis启动后默认都是master
六、配置主从关系
分别将6380 6381配置成6379的从机
命令:slaveof <ip><port>
在6380 6381上分别执行:
slaveof 127.0.0.1 6379
七、查看三台服务的运行信息
命令:info replication
八、在主机写数据,在从机就能读到数据,在从机写数据会报错,报错如下
九、主机挂掉,重新启动后仍然为主机
十、从机挂掉,从机挂掉重启后就会变成一个独立的节点,所以它的角色就是自己的master,需要手动的执行命令将其添加到主从关系中
slaveof ip port
一主二仆
上面我们搭建的环境就是一主二仆的环境,有几个注意点:
- 主机挂掉,重新启动后仍然为主机,从机不会上任,在从机上执行info replication可以看到主机挂了
- 从机挂掉,重新启动后会变成一个独立的节点,此时它的角色就是自己的master,需要手动的执行slaveof命令将其加入到主从关系中。
- 从机挂掉重启或者一个新的从机加入到主从关系中,会同步主机的所有数据,不会存在数据差。
主从复制原理
- slave从机加入到主从关系中后,会向master主机发送一个同步的指令。
- master主机接收到slaver从机同步的指令后,fork持久化进程生成rpd文件,然后将rpd文件发送到slaver从机进行同步。
- slaver从机收到rpd文件后进行加载,这里是一个全量复制的步骤
- master主机当执行了新的修改命令后,会主动将修改命令同步到slaver从机(这里是一个增量复制)。
薪火相传
一主二仆的关系图:
薪火相传的关系图:
搭建薪火相传的环境就是将一台slaver从机的主机ip配置成另一台slaver从机,以上面的6379 6380 6381 举例,可以将6381的master配置成6380,这样6381就成了6380的子孙了。
注意此时6380的角色还是slaver,它只是多了个后代。
薪火相传的目的可以解决读数据的压力
其他的特点和一主二仆一样
反客为主
当master主机挂掉后,让某台slaver从机升为master角色。
手动在从机上执行 slaveof no one ,将从机变成主机。
哨兵模式
简介
上面的反客为主模式,当主机master挂掉后,需要手动执行slaveof no one命令将从机升为master角色。这就需要运维人员手动的去切换,假如是凌晨1点redis的master挂了,运维人员也去睡觉了,那么怎么办呢,怎么能做到反客为主的自动切换呢?
redis中提供了一种哨兵模式的机制,可以实现自动的反客为主的角色切换。
环境搭建
-
还是准备三套redis的环境,我们还是使用刚才的6379 6380 6381三套环境,并配置好一主二仆的关系。
-
准配并配置哨兵配置文件
在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主机
-
启动哨兵,在redis解压目录下执行如下命令
linux版本命令: redis-sentinel redis_conf/sentinel.conf windows版本命令: redis-server.exe redis_conf/sentinel.conf --sentinel
- 开始测试,我们将master主机手动挂掉,并观察哨兵控制台输出
我们发现哨兵模式选举出6380作为了新的master主机节点了,同时哨兵模式也开始监控6380主机节点。
然后将6379当作了slaver从机,也就是6379重启后,会自动变成slaver从机节点(注意不用手动的slaveof了)
复制延时
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
哨兵切换主从的流程
- 新主登基:master宕机后,哨兵从master的所有slaver中选举一个,将其转成新的master主机。
- 群仆俯首:新master上任后,哨兵回向原master的从机发送slaveof新主机的命令
- 旧主俯首:当原master重新上线后,哨兵会向其发送slaveof新主机的命令,原master变为新主机的slaver。
选举条件
选择新master的条件依次为:
- 选择优先级靠前的。
- 选择偏移量大的
- 选择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();
}
}
缓存击穿
简介
一个正常请求的发起流程如上图所示,我们发起一个请求,是先请求到了redis上,如果在redis中命中了数据,那么redis会将需要的数据直接返回,如果没有命中,那么就会去请求数据库,然后返回并同步写入到缓存中。
但是在实际过程中可能会有一种情况,比如我们携带一个id去请求这个id的数据,在redis中没有命中,然后请求被传到了数据库层,但是在数据库层也没有命中。
那么下次访问还是会穿过缓存redis直接请求到了数据库层,如果这种请求大量的存在,就会导致数据库压力剧增,从而导致数据库崩溃。
通常造成这种问题的原因是系统遭受到了黑客攻击
解决方案
-
对null值也进行缓存,并设置一个短的失效时间。
比如第一次带id的请求穿过了redis请求到了数据库,数据库返回null,将该值缓存在redis中,并设置一个过期时间,那么多时间该id就不会请求到数据库了。
问题:这种处理方式不是很好,因为如果是黑客攻击的话,id一定是自动生成的,且每次请求肯定都不一样。
-
设置可访问的白名单
将数据库存在的数据id内置在redis中,可以放到bitmaps中,存在的id的value为1,每次请求过来先判断在bitmaps中是否存在,如果存在再继续下面的请求操作。
问题:因为多了一步id是否在白名单的操作,所以会影响性能。
-
使用布隆过滤器
底层使用的是bitmaps数据类型,原理 2 同理。性能比较高。
-
进行实时监控
可以进行实时监控,如果同一ip的异常请求很多,可以直接禁止该ip访问。
缓存穿透
简介
在项目运行过程中因为某个key过期(注意不是大量的key过期),但是这个key是热门数据,如果大量的并发请求需要获取该key的数据,此时因为该key过期在redis中获取不到,所以这些请求会都打到数据库层,从而造成数据库瞬时崩溃。
解决方案
-
预先设置一些热点数据
将一些热点数据提前存到redis中,并加大这些数据的过期时间。
-
实时监控调整
可以实时观察哪些key是热点数据,手动实时调整这些key的过期时间
-
使用锁
给获取数据的这几步操作加锁,处理成线程同步的,只有一个线程能去数据库获取该key的数据,获取到后同步到redis中。其他的线程处于阻塞状态。
这种方法效率肯定非常的低。
缓存雪崩
简介
在短时间内大量的key同时过期,然后在此时有很多并发的请求需要获取这些key的数据,因为此时这些key在redis中过期,所以这些请求就发送到了数据库层,从而导致数据库压力剧增,进而导致服务器崩溃(服务器崩溃的同时导致redis,代码工程都会运行异常,即便重启服务器后也会运行异常),缓存雪崩是个很严重的问题,会对服务器造成损坏。
解决方案
-
构建多级缓存架构
nginx缓存 + redis缓存 +其他缓存(ehcache等),当在一级缓存获取不到数据的时候就去第二级获取。
问题:会造成系统架构成本升高,维护性不好。
-
使用锁或者队列
一个请求一个请求的去获取key的数据,这样肯定就不会造成缓存雪崩了,但是一次只有一个线程执行任务,其他的线程都是处于阻塞的状态,所以会影响系统的性能。
-
设置过期标志更新缓存
设置过期时间的记录,对过期时间进行记录,如果快要过期了,就通知所负责的线程去更新redis中的key
-
将缓存过期时间分散开
我们可以在key原有过期时间的基础上增加一个随机的值,可以是1-5分钟的随机,这样就不会发生大量key同时过期的问题。
集群
简介
集群可以解决容量不足,并发读写压力大的问题
我们使用主从复制中的一主二仆、薪火相传、反客为主模式的时候,当主节点挂掉后,那么新的主节点ip就会发生改变,这样我们在代码中通过远程客户端操作redis的时候就需要手动的更改ip等配置,不太方便。redis集群中这个问题之前都是通过代理主机来解决这个问题,在redis3.0中针对这个问题提供了解决方案:就是“无中心化集群配置”。
什么叫做无中心化集群配置:就是集群中的任何一个ip都能作为集群的入口,我们使用客户端操作redis集群时,连接哪个ip都可以。
redis集群就是水平扩容,将数据存储分布不到不同的节点之上。
也就是说一个redis集群环境最少需要6套redis环境
搭建环境
在这里就简单写一下搭建步骤,具体的搭建过程大家可自行百度。
-
准配配置文件
因为我们都是在一台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个配置文件。
-
启动6台redis
redis-server.exe redis6379.conf ......
启动成功后可以查看nodes-*.conf文件是否生成。6台redis的该文件都生成后即为启动成功。
-
windows版本搭建redis集群需要安装Ruby环境。Linux下的redis3.0之后的版本就不需要了。
Ruby环境安装可自行百度
-
合成集群
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 :表示以最简单的方式创建集群,即一台主机配一台从机。
-
连接集群,使用客户端连接任意一台master主机ip即可
redis-cli -c -p 6379 -c:表示以集群方式连接
-
连接成功后,查看集群的分配信息
cluster nodes
执行该命令后,可以看到自动分配的主机 还有从机信息(包括主、从机ip,主、从机的关系等)
-
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 号插槽。
操作
-
我们使用客户端连接到任意一台master上
redis-cli -c -p 6379
-
set一个key-value数据
set k1 value1
插入成功后,我们可以在控制台看到该key属于哪个插槽的信息,同时会发现客户端连接的ip自动切换成了该插槽所属master的ip了。
这也就能很明了的观察到“无中心化集群配置”的效果了,任何一个ip都能作为集群的入口。
-
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都能作为集群的入口。
注:本文内容为原创,部分内容参考互联网,转发请备注原地址