【狂神说Java】Redis最新超详细版教程通俗易懂 | bilibili
外链图片正常显示的浏览地址
官网:https://redis.io/
中文网:http://www.redis.cn/
下载安装包
程序建议放在 /opt 目录下
解压 tar -zvxf
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HqqVXaKR-1609299330682)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130844.png)]
基本的环境安装
yum install gcc-c++
# 默认安装gcc版本为4.8.5,而安装Redis 6.0.9版本时会因为gcc版本过低(至少要gcc 8),make失败,报错:[server.o] Error 1
# 解决办法。升级gcc,升级过程如下:
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
echo "source /opt/rh/devtoolset-9/enable" >> /etc/profile
解决办法引用自:https://blog.csdn.net/weixin_40836179/article/details/108245436
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H2eFLIhG-1609299330686)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130845.png)]
Redis的默认安装路径:/usr/local/bin
将Redis配置文件,复制到当前目录下 /usr/local/bin/redis-config
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YnWSZ29y-1609299330687)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130846.png)]
Redis默认不是后台启动,修改配置文件daemonize
设置,将其设置为后台启动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8IM88hiZ-1609299330690)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130847.png)]
将no修改为yes
通过指定的配置文件启动Redis服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SfI0bBJ8-1609299330691)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130848.png)]
通过客户端连接本机Redis
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ph2ylck5-1609299330693)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130849.png)]
查看Redis的进程是否开启
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R5l9zgm2-1609299330695)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130850.png)]
关闭Redis服务 shutdown
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2S6BzyV-1609299330696)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130851.png)]
redis-benchmark是官方自带的压力测试工具。
图片来自Redis 性能测试 | 菜鸟教程 (runoob.com):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JoG28axf-1609299330697)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130852.png)]
# 测试:100个并发连接,每个并发10w请求
redis-benchmark -h localhost -p 6379 -c 100 -n 10000
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPnygYOM-1609299330698)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130853.png)]
Redis默认有16个数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRtZbZVo-1609299330698)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130854.png)]
默认使用的是第0个,可以使用select
进行切换
127.0.0.1:6379> ping # 测试连通状况
PONG
127.0.0.1:6379> select 3 # 切换到3号数据库
OK
127.0.0.1:6379[3]> dbsize # 当前数据库大小
(integer) 0
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1hQrb4Wa-1609299330699)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130855.png)]
127.0.0.1:6379[7]> keys * # 查看当前数据库所有key值
1) "name"
127.0.0.1:6379[7]> flushdb # 清空当前数据库
OK
127.0.0.1:6379[7]> keys *
(empty array)
127.0.0.1:6379[7]> FLUSHALL # 清空全部数据库
OK
Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,而是机器的内存和网络带宽。
Redis是基于C语言,官方提供的数据位10w+的QPS,完全不比同样的kv键值数据库Memecache差。
核心:Redis是将所有的数据全部放在内存中,多线程的上下文切换会消耗时间,对于内存系统来说,如果没有上下文切换,效率就是最高的,多次读写都是在一个CPU上的,所以在内存情况下,单线程是最佳方案。
> set <key> <value>
> get <key>
> keys *
> flushdb
> flushall
> move <key> <db> # 移动至
> del <key> # 移除
> exists <key> # 判断是否存在
> expire <key> <seconds> # 设置过期时间,单位秒
> tll <key> # (time to live)查看还有多久过期(-1 表示不会过期,-2 表示当前库中没有/已经过期)
> type <key> # 查看的类型
官网命令帮助文档:Redis命令中心(Redis commands) – Redis中国用户组(CRUG)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RJFSvN7w-1609299330700)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130856.png)]
127.0.0.1:6379> set key1 "v1"
OK
127.0.0.1:6379> append key1 "v2" # 向已有的中追加,返回追加后的字符串长度;若不存在将等价于set
(integer) 4
127.0.0.1:6379> strlen key1 # 获得字符串长度
(integer) 4
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views # 整数原子+1
(integer) 1
127.0.0.1:6379> decr views # 整数原子-1
(integer) 0
127.0.0.1:6379> get views
"0"
incrby(decrby) <key> <increment(decrement)> # 设置步长,增加(减少)
# 字符串范围 range
127.0.0.1:6379> set key1 "hello, world"
OK
127.0.0.1:6379> getrange key1 5 # 截取字符串[0:5],可以使用负数定位,与Python类似
"hello,"
127.0.0.1:6379> setrange key1 2 "resetv3" # 从指定位置开始用新的覆盖
(integer) 12
127.0.0.1:6379> get key1
"heresetv3rld"
> setex <key> <seconds> <value> # (set with expire) 设置过期时间,如果已存在则用覆盖原来的内容
> setnx <key> <value> # (set if not exist) 如果不存在就set,返回1;如果已经存在就无作用,返回0
> mset <key> <value> [<key> <value> ...] # 可以批量设置键值对
> mget <key> [<key> ...] # 批量get
> msetnx <key> <value> [<key> <value> ...] # 批量使用setnx,原子性操作,要么全都成功,要么全都失败。
# 对象
> set user:1 {name:zhangsan,age:13} # set一个json字符串
> mset user:1:name zhangsan user:1:age 13 # 通过mset批量设置对象单个值
# user:{id}:{filed} 如此设计在Redis中是完全可以的
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 13
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "13"
> getset <key> <value> # 先get再set
127.0.0.1:6379> getset key2 3232
(nil) # 如果原来不存在值则返回nil
127.0.0.1:6379> getset key2 323
"3232" # 如果原来存在则返回旧值并设置新值
127.0.0.1:6379> get key2
"323"
value除了是字符串还可以是数字
可以做栈、队列、阻塞队列等。
几乎所有的list命令都是用l
开头
> lpush <key> <value> # 将一个或多个值插入列表头部(左)
> rpush <key> <value> # 尾部(右)
> lrange <key> <start> <end> # 获取列表指定区间的值
> lpop <key> # 左移除
> rpop <key> # 右移除
> lindex <key> <index(int)> # 获取列表的第个值
> llen <key> # 列表长度
> lrem <key> <count> <element> # 移除列表中个
> ltrim <key> <start> <end> # 截取列表从至区间的元素
> rpoplpush <source> <destination> # 移除
> lset <key> <index> <value> # 将已存在的列表的第值替换,不能超过列表的范围(更新操作)
> linsert <key> <BEFORE|AFTER> <pivot> <element> # 将某个具体的插入列表中某个元素的前面或者后面。
127.0.0.1:6379> linsert list1 BEFORE two insertItem
(integer) 3
127.0.0.1:6379> lrange list1 0 -1
1) "one"
2) "insertItem"
3) "two"
set中的值不能重复
> sadd <key> <value> # 添加元素,不可重复(return 0/1)
> smembers <key> # 展示集合中的所有元素
> sismember <key> <member> # 判断某个元素是否在集合中(return 0/1)
> scard <key> # 集合中的元素个数
> srem <key> <member> [<member> ...] # 移除
> srandmember <key> [<count>] # 在集合中随机抽选个元素,默认1
> spop <key> [<count>] # 随机移除个元素,默认1
> smove <source> <distination> <member> # 将
> sdiff <key> [<key> ...] # 差集
> sinter <key> [<key> ...] # 交集
> sunion <key> [<key> ...] # 并集
Map集合,key-Map集合。(Java中的Map约等于Python中的字典)本质和String类型没有太大区别,还是一个简单的key-value
> hset <key> <field> <value> [<field> <value> ...] # 存值,和是map的键值对,不会覆盖
> hget <key> <field> # 取值
> hmset # 同时设置多个,会覆盖
> hmget
> hgetall <key> # 获取哈希中的所有键值对
> hdel <key> <field> [<field> ...] # 删除
> hlen <key> # 长度
> hexists <key> <field> # 判断哈希中的指定字段是否存在
> hkeys / hvals <key> # 哈希的全部键/值
> hincrby <key> <field> <increment(int)> # 哈希中字段的值按照步长自增
> hsetnx <key> <field> <value> # 不存在则set,存在则不成功
127.0.0.1:6379> hset hash1 field1 satone
(integer) 1
127.0.0.1:6379> hset hash1 field1 satone
(integer) 0
127.0.0.1:6379> hmset hash1 field1 hello field2 world
OK
127.0.0.1:6379> hmget hash1 field2 field1
1) "world"
2) "hello"
127.0.0.1:6379> hgetall hash1
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel hash1 field1
(integer) 1
127.0.0.1:6379> hlen hash1
(integer) 1
127.0.0.1:6379> hkeys hash1
1) "field2"
在set的基础上增加了一个值。
> zadd <key> [NX|XX] [CH] [INCR] <score> <member> [<score> <member> ...]
> zrange <key> <start> <stop>
> zrangebyscore <key> <min> <max> [WITHSCORES] # 从最小值到最大值排序.(-inf,+inf 负无穷,正无穷) 默认为闭区间,可用"(int"取开区间
> zrem <key> <member> [<member> ...] # 移除
> zcard <key> # 元素个数
> zrevrange <start> <stop> # 可以倒序排列
127.0.0.1:6379> ZREVRANGE s 0 -1
1) "zhangsan"
2) "xiaohong"
3) "lisi"
> zcount <key> <min> <max> # 指定区间内的元素个数
key - (经度、纬度、成员名称)
经度的取值范围:[-180, 180]
纬度的取值范围:[-85.05112878, 85.05112878]
Redis的Geo在Redis3.2版本推出,这个功能可以推算地理位置信息,比如两地之间的距离等。
底层
Geo的底层实现是Zset数据类型,可以使用所有Zset命令
127.0.0.1:6379> type chain:city
zset
Google地图
Geo专用的只有6个命令
> geoadd <key> <longitude> <latitude> <member> [<longitude> <latitude> <member> ...] # 经度、维度、成员(南北极无法直接添加,一般会下载城市数据,通过程序一次性导入)
> geodist <key> <member1> <member2> [m|km|ft|mi] # 获取两点直线距离
> geohash <key> <member> [<member> ...] # 获取返回11个字符的GeoHash,缩短字符、损失精度
> geopos <key> <member> [<member> ...] # 获取成员的经纬度(坐标值)
> georadius <key> <longitude> <latitude> <radius> <m|km|ft|mi> [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT <count>] [ASC|DESC] [STORE <key>] # 某个位置半径内的成员(WITHCOORD:输出经纬度,WITHDIST:输出距离,WITHHASH:输出HASH,COUNT :最多输出个结果,ASC|DESC:升序|降序)
> georadiusbymember <key> <member> <radius> <m|km|ft|mi> [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT <count>] [ASC|DESC] [STORE <key>] # 某个成员半径内的成员
127.0.0.1:6379> geoadd chain:city 116.40 39.90 Beijing
(integer) 1
127.0.0.1:6379> geoadd chain:city 121.47 31.23 Shanghai
(integer) 1
127.0.0.1:6379> geoadd chain:city 106.50 29.53 Chongqing
(integer) 1
127.0.0.1:6379> geoadd chain:city 114.05 22.52 Shenzhen 120.16 30.24 Hangzhou 108.96 34.26 xian
(integer) 3
127.0.0.1:6379> geodist chain:city Beijing Shanghai
"1067378.7564"
127.0.0.1:6379> geodist chain:city Beijing Shanghai km
"1067.3788"
127.0.0.1:6379> geopos chain:city Shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379> geohash chain:city Beijing
1) "wx4fbxxfke0"
127.0.0.1:6379> georadius chain:city 110 30 1000 km WITHDIST COUNT 2 ASC
1) 1) "Chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> georadius chain:city 110 30 1000 km WITHDIST COUNT 2 DESC
1) 1) "Hangzhou"
2) "977.5143"
2) 1) "Shenzhen"
2) "924.6408"
基数:不重复的元素的数量,可以接受误差
Redis2.8.9版本更新了Hyperloglog数据结构
Redis Hyperloglog 基数统计算法
优点:占用的内存是固定的,2^64不同的元素的基数,只需要使用12KB内存。如果从内存角度来比较,Hyperloglog为首选。
官方表示,Hyperloglog基数统计有0.81%错误率,但是在大部分应用场景可以忽略。如果不允许容错,则不推荐使用Hyperloglog。
底层
127.0.0.1:6379> pfadd hyper 1 2 3
(integer) 1
127.0.0.1:6379> type hyper
string
127.0.0.1:6379> get hyper
"HYLL\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80]f\x80Mt\x80Q,\x8cC\xf3"
应用场景
网页的UV(一个人访问多次,但是还是算作一个人)
传统方式:用set保存用户的ID,然后统计set中的元素数量作为标准判断
这个方式如果保存大量的用户ID就会比较麻烦。但是最终的目的是为了计数,并不是为了保存用户ID。
专属命令
> pfadd <key> <element> [<elememt> ...] # 添加
> pfcount <key> [<key> ...] # 统计
> pfmerge <destkey> <sourcekey> [<sourcekey> ...] # 合并(会去重)
应用场景
统计用户信息,区分活跃用户和不活跃用户,登录用户和未登录用户
设计365天打卡
只有两个状态的都可以使用Bitmaps
底层
127.0.0.1:6379> setbit bitmap1 0 0
(integer) 0
127.0.0.1:6379> type bitmap1
string
127.0.0.1:6379> get bitmap1
"\x00"
命令
> setbit <key> <offset> <value> # 使用漂移量按位设置
> getbit <key> <offset>
> bitcount <key> [<start> <end>] # 统计
MySQL中的事务
Redis中的事务
Redis事务的本质:一组命令的集合(队列),在事务执行的过程中,会按照顺序执行。
一次性、顺序性、排他性(执行过程中不允许被干扰)
------ 队列 set set set ... 执行 ------
Redis单条命令是保证原子性的,但是Redis的事务不保证原子性
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行,只有发起执行命令时才会执行。
Redis事务:
执行事务示例
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> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
5) "v1"
放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> discard # 放弃事务
OK
127.0.0.1:6379> get k3 # 命令队列里的所有命令都不会被执行
(nil)
异常
编译型异常(代码/命令有错):事务中所有命令都不会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> get k3
(nil)
127.0.0.1:6379> get k1
(nil)
运行时异常,如果事务队列中存在语法性异常,那么命令执行时,其他命令是可以正常执行的,错误命令单独抛出异常(Redis事务不存在原子性)
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 执行的时候会失败
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range # 虽然第一条命令报错了,但是事务依旧正常执行成功了
2) OK
3) "v2"
127.0.0.1:6379> mget k1 k2
1) "v1"
2) "v2"
监控:Watch
悲观锁:
乐观锁:
很乐观,认为任意时刻都不会出现问题,无论做什么都不上锁。更新数据的时候,判断在此期间是否有人修改过数据。1. 获取;2. 更新
使用watch
可以给加乐观锁,当事务执行过程中被其他客户端修改,则会导致事务执行失败。
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> decrby money 20 # 在watch加监视后修改了money
(integer) 80
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 40
QUEUED
127.0.0.1:6379> incrby out 40
QUEUED
127.0.0.1:6379> exec # money在事务外被修改,导致事务执行失败
(nil)
127.0.0.1:6379> unwatch # 解除监视
OK
使用Java操作Redis
Jedis是Redis官方推荐的Java连接开发工具,是使用Java操作Redis的中间件。
配置文件
单位
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OpmUHyQ1-1609299330700)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201202183431.png)]
对大小写不敏感
包含(Include)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IsyFuJx7-1609299330701)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201202183437.png)]
网络
bind 127.0.0.1 # 绑定的IP
protected-mode yes # 保护模式,默认开启
port 6379 # 端口
tcp-backlog 511
timeout 0
tcp-keepalive 300
通用(General)
daemonize yes # 以守护进程的方式运行,默认为no
supervised no # 管理守护进程,默认no,一般不用动
pidfile /var/run/redis_6379.pid # 如果以后台方式运行,需要指定pid进程文件
# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 适用生产环境
# warning (only very important / critical messages are logged) 只打印非常关键的信息
loglevel notice
logfile "" # 日志的文件位置名
databases 16 # 数据库数量,默认16
always-show-logo yes # 是否总是显示logo
快照(Snapshotting)
持久化,在规定的时间内执行了多少次操作,则会持久化(快照)到文件,.rdb,.aof
Redis 是内存数据库,如果没有持久化,数据断电即失
save 900 1 # 900秒内如果至少有1个key进行了修改,就进行持久化操作
save 300 10 # 300秒内如果有至少10个key进行了修改,就进行持久化操作
save 60 10000 # 60秒内如果有至少1w个key进行了修改,就进行持久化操作
stop-writes-on-bgsave-error yes # 持久化如果出错,知否还继续工作,默认yes
rdbcompression yes # 是否压缩rdb文件,默认yes
rdbchecksum yes # 是否校验rdb文件
dbfilename dump.rdb # rdb保存时的文件名
rdb-del-sync-files no #
dir ./ # rdb保存的目录
复制(Replication)
安全 (Security)
requirepass foobared # 设置密码,默认被注释掉,无密码
127.0.0.1:6379> config get requirepass # 获取Redis密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123123" # 设置Redis密码
OK
127.0.0.1:6379> exit
[root@VM-8-2-centos ~]# redis-cli
127.0.0.1:6379> ping # 有密码,无权限
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123123 # 登录
OK
127.0.0.1:6379> ping # 正常使用
PONG
限制(Clients)
maxclients 10000 # 最大连接客户端数
maxmemory <bytes> # 配置最大内存容量
maxmemory-policy noeviction # 配置内存到达上限的处理策略
Redis中的六种垃圾回收策略:redis中maxmemory和淘汰策略 - 简书 (jianshu.com)
volatile-lru:从已设置过期时间的内存数据集中挑选最近最少使用的数据 淘汰;
volatile-ttl: 从已设置过期时间的内存数据集中挑选即将过期的数据 淘汰;
volatile-random:从已设置过期时间的内存数据集中任意挑选数据 淘汰;
allkeys-lru:从内存数据集中挑选最近最少使用的数据 淘汰;
allkeys-random:从数据集中任意挑选数据 淘汰;
no-enviction(驱逐):禁止驱逐数据。(默认淘汰策略。当redis内存数据达到maxmemory,在该策略下,直接返回OOM错误);
aof配置(Append only mode)
详见AOF章节
重点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jie54y9W-1609299330702)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203095604.png)]
在指定时间间隔内,将内存中的数据即快照写入磁盘,恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就保证了极高的性能。如果需要进行大规模的数据恢复,切对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效。RDB的缺点是最后一次持久化后的数据可能丢失。Redis默认使用RDB持久化,一般情况下不需要修改这个配置。
RDB保存的文件默认是dump.rdb,可以在Redis.conf文件中进行修改。
备份会自动生成dump.rdb
文件
只需要将rdb文件放在Redis的启动目录下,Redis启动的时候会自动检查dump.rdb
,恢复里面的数据
查看Redis的目录
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"
优点:
缺点:
将所有修改数据的命令都记录下来恢复的时候把这个文件都执行一遍
AOF保存的文件是appendonly.aof
文件
在同时存在rdb和aof文件的情况下,redis会优先选择aof进行数据恢复
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RoahqInf-1609299330702)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203103927.png)]
如果执行错误的指令,也会被记录到aof文件中,并且重启Redis会因为aof文件中的错误指令而启动失败。
那么如果在实际生产中环境中,网络丢包、延迟、病毒、大文件运行失败等等因素导致aof文件破损。aof文件损坏了,该怎么修复?
redis-check-aof --fix
进行修复Redis还提供了 redis-check-rdb
工具,修复rdb文件方法与aof类似。
appendonly no # 默认不开启aof模式,默认是使用rdb方式持久化,在几乎所有的情况下,rdb完全够用
appendfilename "appendonly.aof" # aof持久化文件的名字
# appendfsync always # 每次修改了值都同步,性能消耗大
appendfsync everysec # 每秒执行一次同步,可能会丢失这1s的数据
# appendfsync no # 不执行同步,这个时候操作系统自己同步数据,速度最快
no-appendfsync-on-rewrite no # 重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性。
auto-aof-rewrite-percentage 100 # 设置触发重写的基准值
auto-aof-rewrite-min-size 64mb # 设置触发重写的基准值
AOF采用文件追加方式,文件会越来越大。为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。请见配置文件默认是,auto-aof-rewrite-percentage 100的意思是超过100%,也就是一倍;auto-aof-rewrite-min-size 64mb是超过64mb.
优点:
缺点:
save 900 1
这条规则即可Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-voMezg79-1609299330703)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203110402.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gQns3NiC-1609299330704)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203110755.png)]
命令测试
订阅者客户端
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1 # 开始监听,等待信息
1) "message"
2) "channel1"
3) "helloworld"
127.0.0.1:6379> psubscribe ch* # 按照ch*模式进行订阅,*为通配符,意为订阅所有ch开头的频道
Reading messages... (press Ctrl-C to quit)
1) "psubscribe" # 返回值类型,显示订阅成功
2) "ch*" # 订阅模式
3) (integer) 1 # 订阅的频道数量
1) "pmessage" # 接收值的类型,消息
2) "ch*" # 频道匹配模式
3) "channl1" # 消息本身的频道
4) "??" # 消息内容
发送者客户端:
127.0.0.1:6379> PUBLISH channel1 helloworld
(integer) 1 # 接收到消息的客户端数量
127.0.0.1:6379>
Redis是通过C实现的,通过分析Redis源码里的pubsub.c文件,了解发布和订阅机制的底层实现,可以加深对Redis的理解
Redis通过publish、subscribe和psubscribe等命令实现发布和订阅功能
通过subscribe命令订阅某频道后,redis-server里维护了一个字典,字典的键就是一个个channel,而字典的值是一个链表,链表中保存了所有订阅这个channel的客户端。subscribe命令的关键就是将客户端添加到给定的channel订阅链表中。
将一台Redis服务器的服务,复制到其他Redis服务器。数据的复制是单向的,只能由主节点复制到从节点。
一个主节点可以有多个从节点,但一个从节点只能有一个主节点。
主从复制的作用主要包括:
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tnITBhSM-1609299330705)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203112850.png)]
主从复制,读写分离。80%的情况下都是在进行读操作,减轻服务器的压力,最低一主二从。
只需要配置从库,不用配置主库
127.0.0.1:6379> info replication # 查看当前库的信息
# Replication
role:master # 角色 master
connected_slaves:0 # 没有Slave
master_replid:8abb42c6c2219b7ca6a7ef83315757d94595750c
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
单机模拟主从机,修改配置的对应信息:
启动三个Redis服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qL62W8ki-1609299330706)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203114946.png)]
> slaveof <host> <port>
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 # 有两个Slave
slave0:ip=127.0.0.1,port=6380,state=online,offset=70,lag=1 # slave0的信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=70,lag=1 # slave1的信息
master_replid:a02c004682dba83eb5d22d42c81dd89514387683
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70
真实生产环境中,主从配置在conf文件中进行设置,在Redis启动时就会设置好主从角色
127.0.0.1:6379> set k1 v1 # 在主机中写入
OK
127.0.0.1:6380> keys * # 从机中能读到主机写入的信息
1) "k1"
127.0.0.1:6380> set k2 v2 # 但是从机不能写入
(error) READONLY You can't write against a read only replica.
主机断线重连,不影响主从复制关系
Slave启动成功连接到Master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据的命令,在后台进程执行完毕后,Master将传送整个数据文件到Slave,并完成一次完全同步
全量复制:Slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传给Slave,完成同步
但是只要是重新连接Master,就会自动执行一次完全同步(全量复制)。
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6381> slaveof 127.0.0.1 6380
OK
127.0.0.1:6380> info replication # 查询中间节点6380的信息
# Replication
role:slave # 角色依旧是 Slave , 不能写入
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:2175
slave_priority:100
slave_read_only:1
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=2175,lag=1
master_replid:a02c004682dba83eb5d22d42c81dd89514387683
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2175
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:977
repl_backlog_histlen:1199
也可以完成主从复制,但是79主机节点断线后,80节点使用slaveof no one
命令将自己设置为主节点,拥有写入权限。
但是如果79节点重新上线,需要重新配置主从关系
自动选举主节点的模式
哨兵(Sentinel)可以后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,Redis提供了哨兵的命令,哨兵是一个独立的进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控多个Redis实例。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kLnW1L0J-1609299330706)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203135830.png)]
集群多哨兵模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jHqot0sb-1609299330707)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203140028.png)]
假设主服务器宕机,哨兵1先检测到这个结果,系统不会立马进行重新选举的过程,仅仅哨兵1主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间会进行一次投票,投票由一个哨兵发起,进行故障转移操作。切换成功后,会通过发布订阅模式,让各个哨兵把自己的监控的从服务器切换为主机,这个过程称为客观下线。
测试
配置哨兵配置文件 sentinel.conf
# sentinel monitor 被监控的名字 host port 1
sentinel monitor mRedis 127.0.0.1 6379 1
后面的数字1,代表如果主机宕机,Slave投票让谁接替成为主机
使用配置文件启动哨兵,并且shutdown
主机
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3I3R4GPc-1609299330708)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203141648.png)]
哨兵重新选择了80节点作为新的主节点。
宕机的79节点重新上线,会被哨兵自动变为当前主节点的从节点
# 哨兵输出
6743:X 03 Dec 2020 14:18:13.146 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mRedis 127.0.0.1 6380
# 重新上线的79节点Info
127.0.0.1:6379> info replication
# Replication
role:slave # 变为了Slave
master_host:127.0.0.1
master_port:6380 # 主节点为80节点
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:8943
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:8141663dbb7b0831c2cacb51ecff01d885976bbf
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:8943
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:8659
repl_backlog_histlen:285
优点:
缺点:
目前大型项目最流行的集群模式
服务的高可用问题
概念
用户想要查询一个数据,发现Redis内存数据库中没有,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求持久层数据库,这回给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4xmiNQAh-1609299330709)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203143903.png)]
当存储层不命中后,及时返回的空对象也将其缓存起来,同时设置一个过期时间,之后再反问这个数据将会从缓存中获取,保护了后端数据源
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q0d62C4U-1609299330710)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203143911.png)]
但是这种方法会存在两个问题:
概述
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就会穿破缓存,直接请求数据库,就像在屏障上凿开了一个洞
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会使数据库瞬间压力过大
概念
是指在某一个时间段,缓存集中过期失效,Redis宕机
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0HrVImr-1609299330711)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203145628.png)]
pip install redis
import redis
# 连接服务器的Redis
r = redis.StrictRedis(host="tx.lesely.tk",port=6379,db=0)
# Redis命令 = Python方法,命令返回值 = 方法返回值