官网下载
tar -zxvf redis-5.0.7.tar.gz
在此之前确定安装了gcc-c++
,如果为安装先安装 yum install -y gcc-c++
编译
安装
安装的话,我们现在/usr/local
下面创建新的redis安装目录
开始 安装
此时 redis服务和客户端安装在了/usr/local/redis/bin
下面
我们将 配置文件复制到此处
我们可以删除我们编译的 Redis文件夹了
修改redis.conf
将 daemonize no 改为yes
查看 后台运行是否生效
启动redis客户端 测试
配置完成
redis中自带了 性能测试工具-- redis-benchmark
性能测试参数
我们测试一下 100并发连接 每个连接100000 请求数
redis 默认 16个数据库
查看配置文件
默认使用的是第 0 个数据库
我们可以使用select
来切换数据库
[root@centos-docker bin]# ./redis-cli ##进入客户端
127.0.0.1:6379> select 3 ##切换索引为3 的数据库 索引编号默认是 0 - 15
OK
127.0.0.1:6379[3]> DBSIZE ## 查询当前数据库的大小
(integer) 0
127.0.0.1:6379[3]>
127.0.0.1:6379[3]> set name xkuna
OK
127.0.0.1:6379[3]> get name
"xkuna"
127.0.0.1:6379[3]> keys * ##查看所有键
1) "name"
127.0.0.1:6379[3]> flushdb ## 清空当前库 ;清空所有库 使用flushall
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
127.0.0.1:6379[3]>
Redis为什么单线程还这么快?
redis的数据是写在内存中的
redis是单线程的,多线程CPU上下文切换比较耗时,对于内存系统来说,如果没有上下文切换效率就是最高的!
127.0.0.1:6379> keys * #查询当前库下所有的键
1) "counter:__rand_int__"
2) "mylist"
3) "key:__rand_int__"
4) "name"
127.0.0.1:6379> flushall #清除所有库下的数据
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set name xkuna # 添加string 类型 的键,并赋值
OK
127.0.0.1:6379> set age 21
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> get name #获取string 类型 的键的值
"xkuna"
127.0.0.1:6379> move age 1 #将该键 移动到 索引为 1 的 库
(integer) 1
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> EXISTS name #查看该键是否存在
(integer) 1
127.0.0.1:6379> EXISTS age
(integer) 0
127.0.0.1:6379> EXPIRE name 10 #设置该键 的超时时间为 10 s
(integer) 1
127.0.0.1:6379> ttl name #查看该键剩余的超时时间
(integer) 6
127.0.0.1:6379> ttl name
(integer) 4
127.0.0.1:6379> ttl name
(integer) 4
127.0.0.1:6379> ttl name
(integer) 0
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set name hahaha
OK
127.0.0.1:6379> type name #查看该键的 类型
string
127.0.0.1:6379> get name
"hahaha"
127.0.0.1:6379> del name #删除指定key
(integer) 1
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>
## string基本命令
127.0.0.1:6379> keys * #全部key
(empty list or set)
127.0.0.1:6379> set name xkuna #设置值
OK
127.0.0.1:6379> get name #获取值
"xkuna"
127.0.0.1:6379> APPEND name 2020 #追加值
(integer) 9
127.0.0.1:6379> get name
"xkuna2020"
127.0.0.1:6379> STRLEN name #获取长度
(integer) 9
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> APPEND aoligei aoligeihahaha #如果不存在该 键 直接创建该键 并且赋值
(integer) 13
127.0.0.1:6379> keys *
1) "name"
2) "aoligei"
127.0.0.1:6379> get aoligei
"aoligeihahaha"
127.0.0.1:6379>
# 数值 自增 与 自减
127.0.0.1:6379> set age 21
OK
127.0.0.1:6379> get age
"21"
127.0.0.1:6379> incr age #自增 1
(integer) 22
127.0.0.1:6379> incr age
(integer) 23
127.0.0.1:6379> incr age
(integer) 24
127.0.0.1:6379> get age
"24"
127.0.0.1:6379> decr age #自减 1
(integer) 23
127.0.0.1:6379> decr age
(integer) 22
127.0.0.1:6379> decr age
(integer) 21
127.0.0.1:6379> get age
"21"
127.0.0.1:6379> incrby age 10 #自增 并且设置增加值
(integer) 31
127.0.0.1:6379> get age
"31"
127.0.0.1:6379> decrby age 10 # 自减 并且设置自减值
(integer) 21
127.0.0.1:6379> get age
"21"
#字符串操作
127.0.0.1:6379> set name xkuna
OK
127.0.0.1:6379> getrange name 0 2 #截取字符串 区间[0,2]
"xku"
127.0.0.1:6379> getrange name 0 -1 #截取整个字符串
"xkuna"
127.0.0.1:6379> setrange name 1 ee # 修改从下标 1 开始的字符
(integer) 5
127.0.0.1:6379> get name
"xeena"
127.0.0.1:6379>
# set 高阶操作
# setex 在赋值时设置过期时间
# setnx 该键存在 赋值失败, 该键不存在 赋值成功 (分布式锁中会用到)
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> setex name 30 xkuna #
OK
127.0.0.1:6379> ttl name
(integer) 28
127.0.0.1:6379> ttl name
(integer) 26
127.0.0.1:6379> ttl name
(integer) 26
127.0.0.1:6379> setnx my haha
(integer) 1
127.0.0.1:6379> get my
"haha"
127.0.0.1:6379> setnx my hehe
(integer) 0
127.0.0.1:6379> get my
"haha"
# 多个赋值 mset k v ....
# 多个取值 mget k ....
# msetnx 多个赋值 一个失败 全部失败
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 h1 k4 h4 # msetnx 多个赋值 一个失败 全部失败 ,此时 k1 存在
(integer) 0
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379>
# 先get后set
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongo
"redis"
127.0.0.1:6379> get db
"mongo"
127.0.0.1:6379>
# lpush 在list 左边 即最 前面添加值 可以一次性添加 一个 或者多个值
# rpush 在list 右边 即最 后面添加值 可以一次性添加 一个 或者多个值
# lrange 查看list 区间内的 值
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> lpush list a
(integer) 1
127.0.0.1:6379> lpush list b
(integer) 2
127.0.0.1:6379> lpush list c
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> rpush list r1
(integer) 4
127.0.0.1:6379> rpush list r2
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "c"
2) "b"
3) "a"
4) "r1"
5) "r2"
# lpop 删除第一个元素
# rpop删除最后一个元素
127.0.0.1:6379> lrange list 0 -1
1) "c"
2) "b"
3) "a"
4) "r1"
5) "r2"
127.0.0.1:6379> lpop list # 删除第一个元素
"c"
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "a"
3) "r1"
4) "r2"
127.0.0.1:6379> rpop list #删除最后一个元素
"r2"
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "a"
3) "r1"
#lindex 获取指定下标的 元素
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "a"
3) "r1"
127.0.0.1:6379> lindex list 0
"b"
127.0.0.1:6379> lindex list 1
"a"
#llen list长度
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "a"
3) "r1"
127.0.0.1:6379> llen list
(integer) 3
#list 的值 是可以重复的
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "a"
3) "r1"
127.0.0.1:6379> lpush list b
(integer) 4
127.0.0.1:6379> lpush list b
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "b"
3) "b"
4) "a"
5) "r1"
# list 指定删除 几个 相同的 值 lrem [key] [count] [value]
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "b"
3) "b"
4) "a"
5) "r1"
127.0.0.1:6379> lrem list 2 b # lrem [key] [count] [value]
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "a"
3) "r1"
127.0.0.1:6379>
# ltrim 保留list中的指定区间 元素 ltrim [key] [start] [stop]
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> rpush list hello hello1 hello2 hello3
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "hello1"
3) "hello2"
4) "hello3"
127.0.0.1:6379> ltrim list 1 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello1"
2) "hello2"
# 取出 源list 中的最后一个元素 放到 新list的最前面 rpoplpush [sourceList] [newList]
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> llist 1 2 3 4 5
(error) ERR unknown command `llist`, with args beginning with: `1`, `2`, `3`, `4`, `5`,
127.0.0.1:6379> lpush list 1 2 3 4 5
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> rpoplpush list newList
"1"
127.0.0.1:6379> lrange newList 0 -1
1) "1"
127.0.0.1:6379>
# 给list 指定 下标更新赋值 如果该list 或者 该list的该下标 不存在 会报错 lset [key] [index] [value]
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> exists list
(integer) 0
127.0.0.1:6379> lset list 0 haha
(error) ERR no such key
127.0.0.1:6379> lpush list hello
(integer) 1
127.0.0.1:6379> lrange list 0 - 1
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange list 0 -1
1) "hello"
127.0.0.1:6379> lset list 0 haha
OK
127.0.0.1:6379> lrange list 0 -1
1) "haha"
127.0.0.1:6379>
# 在list指定 值 的前面 或 后面 添加 值 linsert [key] before|after [value] [newValue]
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> lpush list hello1 hello2
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "hello2"
2) "hello1"
127.0.0.1:6379> linsert list before hello1 hello3
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello2"
2) "hello3"
3) "hello1"
127.0.0.1:6379> linsert list after hello2 hello4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello2"
2) "hello4"
3) "hello3"
4) "hello1"
set中的元素是无序且不可重复的
# sadd [key] [values].... 添加一个或者多个元素
# smembers [key] 读取该set所有的元素
# sismember [key] [value] 判断该set 是否含有该 value
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> sadd myset a b c d e f g
(integer) 7
127.0.0.1:6379> smembers myset
1) "c"
2) "a"
3) "g"
4) "b"
5) "d"
6) "f"
7) "e"
127.0.0.1:6379> sadd myset a
(integer) 0
127.0.0.1:6379> smembers myset
1) "g"
2) "b"
3) "d"
4) "a"
5) "c"
6) "f"
7) "e"
127.0.0.1:6379> sismember myset a
(integer) 1
# scard [key] 查询set中元素的数量
# srem [key] [value] 删除set中的 值
127.0.0.1:6379> scard myset
(integer) 7
127.0.0.1:6379> srem myset a
(integer) 1
127.0.0.1:6379> smembers myset
1) "g"
2) "b"
3) "d"
4) "c"
5) "f"
6) "e"
#随机抽出 set 中 指定数目的 元素 srandmember [key] [count]
127.0.0.1:6379> srandmember myset 3
1) "g"
2) "b"
3) "d"
127.0.0.1:6379> srandmember myset 3
1) "d"
2) "e"
3) "f"
127.0.0.1:6379> srandmember myset 3
1) "b"
2) "c"
3) "e"
#随机移除 指定个数 的元素 spop [key] [count]
127.0.0.1:6379> smembers myset
1) "g"
2) "b"
3) "d"
4) "c"
5) "f"
6) "e"
127.0.0.1:6379> spop myset 2
1) "f"
2) "e"
127.0.0.1:6379> smembers myset
1) "g"
2) "b"
3) "d"
4) "c"
#smove [source] [newSet] [value] 将源set中的value 放入 另一个 set
127.0.0.1:6379> smembers myset
1) "g"
2) "b"
3) "d"
4) "c"
127.0.0.1:6379> keys *
1) "myset"
127.0.0.1:6379> smove myset myset2 c
(integer) 1
127.0.0.1:6379> keys *
1) "myset"
2) "myset2"
127.0.0.1:6379> smembers myset
1) "g"
2) "b"
3) "d"
127.0.0.1:6379> smembers myset2
1) "c"
# sdiff [k1] [k2] set集合 k1 中 k2没有的元素
# sinter [k1] [k2] set集合中 k1 和 k2 都有的元素
# sunion [k1] [k2] set集合中 k1 和 k2 全部的元素
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> sadd k1 a b c
(integer) 3
127.0.0.1:6379> sadd k2 c d e
(integer) 3
127.0.0.1:6379> sdiff k1 k2
1) "b"
2) "a"
127.0.0.1:6379> sinter k1 k2
1) "c"
127.0.0.1:6379> sunion k1 k2
1) "d"
2) "a"
3) "c"
4) "b"
5) "e"
hash类型 其实是 key-map 结构
# hset [key] [map-key] [map-value] 添加该键下 一个map中的一对 k v
# hget [key] [map-key] 获取该键下的 该map-key 的map-value
# hmset [key] [map-key] [map-value] [map-key] [map-value] ..... 添加该键下 一个map中的多对 k v
# hmget [key] [map-key] [map-key] .... 获取该键下的 多个map-key 对应的 map-value
# hgetall [key] 获取该键下 所有的 kv 键值对
127.0.0.1:6379> hset user uid 1
(integer) 1
127.0.0.1:6379> hget user uid
"1"
127.0.0.1:6379> hmset user name xuna age 21
OK
127.0.0.1:6379> hmget user name age
1) "xuna"
2) "21"
127.0.0.1:6379> hgetall user
1) "uid"
2) "1"
3) "name"
4) "xuna"
5) "age"
6) "21"
# hlen [key] 该键下 的map 含有多少对 键值对
# hexists [key] [map-key] 该键下 的map 是否含有 该键
# hkeys [key] 该键下 的map中所有的键
# hvals [key] 该键下 的map中所有的值
127.0.0.1:6379> keys *
1) "user"
127.0.0.1:6379> hgetall user
1) "uid"
2) "1"
3) "name"
4) "xuna"
5) "age"
6) "21"
127.0.0.1:6379> hlen user
(integer) 3
127.0.0.1:6379> hexists user name
(integer) 1
127.0.0.1:6379> hkeys user
1) "uid"
2) "name"
3) "age"
127.0.0.1:6379> hvals user
1) "1"
2) "xuna"
3) "21"
#hincrby [key] [map-key] [map-value] [count] 增加值
#hdecrby [key] [map-key] [map-value] [count] 减少值
#hsetnx [key] [map-key] [map-value] 如果不存在该key 或者 map-key 赋值成功,存在 则 赋值失败
127.0.0.1:6379> hincrby user age 1
(integer) 22
127.0.0.1:6379> hsetnx user name xkunaa
(integer) 0
127.0.0.1:6379> hsetnx user username xkunaa
(integer) 1
hash 适合对象存储, string适合存储 字符串
zset 是 有序且不重复的 set集合
# 添加一个会或者多个 元素 zadd [key] [score] [value] [score] [value] ....
# 按照 下标 查询元素 zrange [key] [index1] [index2] 查询出的元素按照 score 升序排列
# 按照 score 升序 且 符合 socre 区间 查询元素 srangebyscore [key] [min] [max] 必须是先 min 后 max ,因为这是区间
# 负无穷到正无穷 -inf +inf
# (a,b) --- (a (b [a,b] --- a b [a,b) a (b
# 按照 score 升序 且 符合 socre 区间 查询元素和对应的 score srangebyscore [key] [min] [max] withscores
127.0.0.1:6379> zadd gongzi 2000 zhangsan 5000 wangwu 3000 lisi
(integer) 3
127.0.0.1:6379> zrange gongzi 0 -1 #查询所有 元素
1) "zhangsan"
2) "lisi"
3) "wangwu"
127.0.0.1:6379> zrangebyscore gongzi -inf +inf #查询 score在 正无穷 到 负无穷 的元素
1) "zhangsan"
2) "lisi"
3) "wangwu"
127.0.0.1:6379> zrangebyscore gongzi 2000 3000 # 查询 score在 [2000, 3000] 的元素
1) "zhangsan"
2) "lisi"
127.0.0.1:6379> zrangebyscore gongzi (2000 (5000 # 查询 score在 (2000, 5000) 的元素
1) "lisi"
127.0.0.1:6379> zrangebyscore gongzi (2000 (5000 withscores # 查询 score在 (2000, 5000) 元素和对应的 score
1) "lisi"
2) "3000"
127.0.0.1:6379>
# zrem [key] [value] 删除zset中的一个值
# zcard [key] 查询该 zset的大小
# zrevrange [key] [index1] [index2] 按照 下标区间 倒叙输出
# zcount [key] [min] [max] 查询 score在 区间内的 元素数量
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> zadd gongzi 3000 zhangsan 5000 wangwu 4000 lisi
(integer) 3
127.0.0.1:6379> zrange gongzi 0 -1
1) "zhangsan"
2) "lisi"
3) "wangwu"
127.0.0.1:6379> zrem gongzi zhangsan
(integer) 1
127.0.0.1:6379> zrange gongzi 0 -1
1) "lisi"
2) "wangwu"
127.0.0.1:6379> zcard gongzi
(integer) 2
127.0.0.1:6379> zrevrange gongzi 0 -1
1) "wangwu"
2) "lisi"
127.0.0.1:6379> zcount gongzi 1000 3000
(integer) 0
127.0.0.1:6379> zcount gongzi 1000 5000
(integer) 2
geoadd 添加一个 或 多个地理位置信息
# geoadd [key] [经度] [纬度] [名称] 添加地理位置 可以一次性 添加多个
127.0.0.1:6379> geoadd cn:city 116.405285 39.904989 beijing
(integer) 1
127.0.0.1:6379> geoadd cn:city 121.472644 31.231706 shanghai
(integer) 1
127.0.0.1:6379> geoadd cn:city 113.280637 23.125178 guangzhou
(integer) 1
127.0.0.1:6379>
geopos 获取一个或者多个地理位置信息
# geopos [key] [名称1] [名称2] 获取key中 一个或多个 地理位置信息
127.0.0.1:6379> geopos cn:city beijing
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
127.0.0.1:6379> geopos cn:city beijing shanghai
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
2) 1) "121.47264629602432251"
2) "31.23170490709807012"
127.0.0.1:6379> geopos cn:city beijing shanghai guangzhou
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
2) 1) "121.47264629602432251"
2) "31.23170490709807012"
3) 1) "113.28063815832138062"
2) "23.12517743834835215"
geodist 获取两个 地理位置之间的距离
单位
# geodist [key] [名称1] [名称2] [单位(不写默认为 m)] 获取key中 两个地理位置之间的 直线距离
127.0.0.1:6379> geodist cn:city shanghai beijing
"1067597.9668"
127.0.0.1:6379> geodist cn:city shanghai beijing km
"1067.5980"
georadius 获取以一个 经纬度 为中心的 半径内的 其他元素
# georadius [key] [经度] [纬度] [半径] [单位] [条件...(可省略)]
127.0.0.1:6379> georadius cn:city 110.21 30.21 1000 km
1) "guangzhou"
127.0.0.1:6379> georadius cn:city 110.21 30.21 4000 km
1) "guangzhou"
2) "shanghai"
3) "beijing"
#条件 withcoord 经纬度
127.0.0.1:6379> georadius cn:city 110.21 30.21 4000 km withcoord
1) 1) "guangzhou"
2) 1) "113.28063815832138062"
2) "23.12517743834835215"
2) 1) "shanghai"
2) 1) "121.47264629602432251"
2) "31.23170490709807012"
3) 1) "beijing"
2) 1) "116.40528291463851929"
2) "39.9049884229125027"
# 条件 withdist 中心的距离
127.0.0.1:6379> georadius cn:city 110.21 30.21 4000 km withdist
1) 1) "guangzhou"
2) "844.9321"
2) 1) "shanghai"
2) "1082.4051"
3) 1) "beijing"
2) "1216.1386"
# 条件 count [number] 查询数量
127.0.0.1:6379> georadius cn:city 110.21 30.21 4000 km withdist withcoord count 1
1) 1) "guangzhou"
2) "844.9321"
3) 1) "113.28063815832138062"
2) "23.12517743834835215"
georadiusbymember 获取以一个 地理位置元素 为中心的 半径内的 其他元素
# georadius [key] [地理位置元素] [半径] [单位] [条件...(可省略)]
127.0.0.1:6379> georadiusbymember cn:city beijing 4000 km withdist withcoord count 2
1) 1) "beijing"
2) "0.0000"
3) 1) "116.40528291463851929"
2) "39.9049884229125027"
2) 1) "shanghai"
2) "1067.5980"
3) 1) "121.47264629602432251"
2) "31.23170490709807012"
其实 geo 底层就是 zset, 我们可以直接适应 zset 直接操作 geo
127.0.0.1:6379> zrange cn:city 0 -1
1) "guangzhou"
2) "shanghai"
3) "beijing"
127.0.0.1:6379> zrem cn:city beijing
(integer) 1
127.0.0.1:6379> zrange cn:city 0 -1
1) "guangzhou"
2) "shanghai"
什么是基数
集合 A{1 , 3 ,5 ,5}, 那么集合A 基数为 3
基数 是 一个集合中不重复元素的个数(可以理解为 set 的size() ),可以接受误差
用途:
举个栗子:假如我要统计网页的UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的解决方案是使用Set来保存用户id,然后统计Set中的元素数量来获取页面UV。但这种方案只能承载少量用户,一旦用户数量大起来就需要消耗大量的空间来存储用户id。我的目的是统计用户数量而不是保存用户,这简直是个吃力不讨好的方案!而使用Redis的HyperLogLog最多需要 12k 就可以统计大量的用户数,尽管它大概有0.81%的错误率,但对于统计UV这种不需要很精确的数据是可以忽略不计的。
# pfadd [key] [value1] [value2] ... 将 多个元素添加到 key中
# pfcount [key]
# pfmerge [distkey] [sourcekey1] [sourcekey2] ..... 将多个 key中的元素 添加到 新的key中
127.0.0.1:6379> pfadd mykey a b c d a
(integer) 1
127.0.0.1:6379> pfcount mykey # 判断该 key中的基数
(integer) 4
127.0.0.1:6379> pfadd mykey2 a e f g h
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 5
127.0.0.1:6379> pfmerge mykey4 mykey mykey2
OK
127.0.0.1:6379> pfcount mykey4
(integer) 8
如果允许容错,一定要使用hyperloglog ,否则使用 set 或者自己定义的数据类型
Redis允许使用二进制数据的Key(binary keys) 和二进制数据的Value(binary values)。Bitmap就是二进制数据的value。Redis的 setbit(key, offset, value)操作对指定的key的value的指定偏移(offset)的位置1或0,时间复杂度是O(1)。
应用场景
统计用户 登录 未登录, 员工打卡 等等,表示两种状态的,都可以使用 bitmaps
bitmaps位图,数据结构,都是操作二进制位来进行记录,就只有 0 和 1两个状态!
模拟打卡
0 - 6周一到周日(offset), 0 未打卡, 1打卡 (value)
# setbit [key] [offset] [value]
# getbit [key] [offset]
# bitcount [key] 获取 value = 1 的数量
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
127.0.0.1:6379> getbit sign 1
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0
127.0.0.1:6379> bitcount sign
(integer) 3
redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行。
一次性,顺序性,排他性
redis事务没有隔离级别的概念!
所有命令在事务中,并没有直接被执行!只有发起执行命令的时候才执行。
redis的单条命令是原子性的,但 redis的事务 不是原子性的。
----入队 set set set 执行----
redis的事务:
正常执行事务
# multi 开始事务
# 命令入队
# exec 执行事务
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 k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v1"
4) "v2"
# discard 放弃事务
127.0.0.1:6379> multi # 开启事务
OK
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 k2 v1
QUEUED
127.0.0.1:6379> getset k1 # 命令错误
(error) ERR wrong number of arguments for 'getset' command
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> 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 # 自增 1
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range #运行时异常
4) "v1"
watch 监视 (可以理解为乐观锁)
在这里我们启用两个客户端 操作我们的redis,客户端 分别命名为 A B
A先执行
127.0.0.1:6379> set money 1000 # A客户端 设置 money为 1000
OK
B再执行
127.0.0.1:6379> watch money # 监控 money
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decrby money 100 # money减 100
QUEUED
A 再执行
127.0.0.1:6379> set money 2000 # B事务未执行前 A修改了money 为 2000
OK
B再执行
127.0.0.1:6379> exec #执行事务 失败, 因为 监视的 money 在事务前后 发生了改变
(nil)
如果此时执行 B 的事务,则需要重新 watch money
B 执行
127.0.0.1:6379> unwatch # 取消监视
OK
127.0.0.1:6379> watch money # 再次监视
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> decrby money 100
QUEUED
127.0.0.1:6379> get money
QUEUED
127.0.0.1:6379> exec # 执行事务
1) (integer) 1900
2) "1900"
什么是Jedis
jedis是redis官方推荐使用的java连接开发工具
在连接我们的linux redis之前,要做两处修改,使得可以远程连接redis
然后创建maven项目
依赖
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.3.0version>
dependency>
创建测试类
import redis.clients.jedis.Jedis;
/**
* @author Xkuna
* @date 2020/9/13 10:51.
*/
public class TestPing {
public static void main(String[] args) {
//连接 redis
Jedis jedis = new Jedis("192.168.52.129", 6379);
//执行命令
jedis.set("k1", "v1") ;
String k1 = jedis.get("k1");
//关闭连接
jedis.close();
System.out.println(k1);
}
}
控制台结果
redis客户端
127.0.0.1:6379> get k1
"v1"
测试成功
jedis的操作redis方法与 我们redis的命令一致!
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
/**
* @author Xkuna
* @date 2020/9/13 11:13.
*/
public class TestTx {
public static void main(String[] args) {
//连接 redis
Jedis jedis = new Jedis("192.168.52.129", 6379);
//开启事务
Transaction multi = jedis.multi();
try{
// 事务命令 入队
multi.set("name", "xkuna") ;
//执行事务 命令
multi.exec() ;
}catch (Exception e){
//执行失败 放弃事务
multi.discard() ;
}finally {
//关闭连接
jedis.close();
}
System.out.println(jedis.get("name"));
}
}
说明:在springboot 2.x 之后,原来的jedis被替换成了 lettuce
两者区别:
jedis: 采用直连,多个线程操作的话,是不安全的,如果是想要避免不安全的,使用jedis pool 连接池! 类似于BIO模式
lettuce: 采用的Netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据! 类似于NIO模式
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
// 默认的redisTemplate没有过多的设置,redis对象都是需要序列化的
// 泛型是
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
//由于 string是redis中最常用的类型,所以单独设置了一个Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
pom依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
配置文件
spring.redis.host=192.168.52.129
spring.redis.port=6379
测试类
package top.xkuna;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Redis02BootApplicationTests {
@Autowired
private RedisTemplate redisTemplate ;
@Test
void contextLoads() {
// opsForValue 操作字符串类型
// opsForList 操作字List类型
// opsForHash 操作Hash类型
// opsForSet 操作Set类型
// opsForZSet 操作Zset类型
//还有其他直接操作key 以及 事务, 监控 的方法 keys() delete() expire() multi() exec() discard() watch() unwatch()等等
// 然后再调用相应的命令api, 跟命令相同
redisTemplate.opsForValue().set("name", "xkuna");
//如果对数据库进行操作,那么需要获取连接
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
}
}
因为我们直接调用 自带的redisTempalte 没有序列化,所以乱码了,但是 写入redis成功
pojo类
package top.xkuna.pojo;
/**
* @author Xkuna
* @date 2020/9/13 16:17.
*/
public class User {
private String name ;
private int age ;
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Test
public void test() throws JsonProcessingException {
User user = new User("xkuna", 21) ;
String s = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user", s);
Object user1 = redisTemplate.opsForValue().get("user");
System.out.println(user1);
}
上面的User类 没有序列化,我们尝试直接存储
@Test
public void test() throws JsonProcessingException {
User user = new User("xkuna", 21) ;
redisTemplate.opsForValue().set("user", user);
Object user1 = redisTemplate.opsForValue().get("user");
System.out.println(user1);
}
直接报 序列化错误
我们需要 User类实现 序列化–Serializable
接口即可
再次运行上面的测试代码
直接存储成功
package top.xkuna.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author Xkuna
* @date 2020/9/13 16:13.
*/
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
此时我们再运行测试代码
@Test
public void test() throws JsonProcessingException {
User user = new User("xkuna", 21) ;
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
客户端
127.0.0.1:6379> keys *
1) "user"
127.0.0.1:6379> get user
"[\"top.xkuna.pojo.User\",{\"name\":\"xkuna\",\"age\":21}]"
完美~~
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
单位
(单位大小写不敏感)
include 包含
网络
bind 0.0.0.0 # 绑定 IP
protected-mode yes # 保护模式
port 6379 # 端口设置
通用配置 GENERAL
daemonize yes # 守护进程(后台) 默认为no
pidfile /var/run/redis_6379.pid # 如果后台运行,则需要指定一个 pid 文件
# 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 # 如果在 900s 内,至少 1 个key进行了操作,那么则会进行持久化操作
save 300 10 # 如果在 300s 内,至少 10 个key进行了操作,那么则会进行持久化操作
save 60 10000 # 如果在 60s 内,至少 10000 个key进行了操作,那么则会进行持久化操作
stop-writes-on-bgsave-error yes # 如果持久化失败 是否关闭持久化
rdbcompression yes # 是否压缩 rdb文件 会消耗 cpu资源
rdbchecksum yes # 是否检查 rdb文件
dir ./ # rdb文件保存的目录
REPLICATION 复制
后面主从复制 再配置
SECURITY 安全
默认 redis 是没有密码的
127.0.0.1:6379> config get requirepass # 获取密码
1) "requirepass"
2) "" # 当前没有设置密码
127.0.0.1:6379>
设置密码(在客户端内设置时, 重新启动会 失效,建议配置文件内修改)
127.0.0.1:6379> config set requirepass 123456 # 设置密码 为 123456
OK
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required. # 未认证
127.0.0.1:6379> keys *
(error) NOAUTH Authentication required. # 未认证
127.0.0.1:6379> auth 123456 # 认证登录
OK
127.0.0.1:6379> config get requirepass # 获取密码
1) "requirepass"
2) "123456" # 当前密码为 123456
此外 还可以 在配置文件中 设置密码
此种方式设置密码 需要 重新通过 配置文件启动 redis服务
客户端限制
客户端最大数量
内存限制
内存最大限制
内存满了 处理策略
APPEND ONLY MODE aof配置
appendonly no # 默认不开启 aof模式
appendfilename "appendonly.aof" # aof 模式 持久化的文件
# appendfsync always # 每次修改都会 sync,消耗性能
appendfsync everysec # 每秒修改都会 sync,容易丢失这 1 秒的数据
# appendfsync no # 不即时同步,由操作系统控制何时刷写到磁盘上,这种模式速度最快
Redis是内存数据库,如果不将内存中的数据库状态 保存到磁盘中,那么一旦服务器进程退出,服务器中的Redis数据库状态也会消失,所以Redis提供了持久化功能!
什么是RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。Redis会单独创建( fork ) 一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是 最后一次持久化后的数据可能丢失。我们默认的就是RDB ,一般情况下不需要修改这个配置!
RDB持久化保存文件是 dump.rdb,在配置文件中的 SNAPSHOTTING(快照配置)
可以设置
持久化规则的修改
配置文件中的 SNAPSHOTTING(快照配置)
dump.rdb文件生成的条件
dump.rdb文件的存放位置
配置文件 内查看修改
默认是 bin
目录
如何恢复rbd文件数据
放在配置文件配置好的 存放位置即可,在redis启动时,会自动恢复 RDB文件的数据
绝大多数情况下,RDB默认的配置已经满足需求
优点:
缺点:
原理
将我们的所有命令都记录下来, 恢复的时候就把这个文件全部在执行一遍!
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录) ,只许追加文件但不可以改写文件, redis
启动之初会读取该文件重新构建数据,换言之, redis重启的话就根据田志文件的内容将写指令从前到后执行一-次以完成数据的恢复
工作
AOF的配置在 配置文件中的 APPEND ONLY MODE
部分
AOF 的文件是 appendonly.aof
AOF开启
此处将no改为yes
测试
127.0.0.1:6379> set name xkuna
OK
127.0.0.1:6379> shutdown
not connected> exit
此时我们已经关机
我们查看 appendonly.aof
文件
vim appendonly.aof
此时发现,appendonly.aof
文件内是我们执行过的命令
我们启动redis查看 数据 是否可以恢复
[root@centos-docker bin] ./redis-server ./redis.conf
2857:C 14 Sep 2020 20:10:01.873 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2857:C 14 Sep 2020 20:10:01.873 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=2857, just started
2857:C 14 Sep 2020 20:10:01.873 # Configuration loaded
[root@centos-docker bin] ./redis-cli
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> get name
"xkuna"
数据恢复了
我们再次将 redis关机,我们手动修改appendonly.aof
文件,看 会有什么结果
修改前
修改后
我们再次启动redis
[root@centos-docker bin] ./redis-server ./redis.conf
2864:C 14 Sep 2020 20:11:49.551 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2864:C 14 Sep 2020 20:11:49.551 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=2864, just started
2864:C 14 Sep 2020 20:11:49.551 # Configuration loaded
[root@centos-docker bin] ./redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused #连接失败
not connected>
此时因为appendonly.aof
文件被修改,redis启动失败
AOF自检工具
为防止上面的情况发生,AOF自带 检测工具 redis-check-aof
输入下面的命令即可 自检恢复
[root@centos-docker bin]# ./redis-check-aof --fix appendonly.aof
0x 39: Expected prefix '*', got: 's'
AOF analyzed: size=82, ok_up_to=57, diff=25
This will shrink the AOF from 82 bytes, with 25 bytes, to 57 bytes
Continue? [y/N]: y
Successfully truncated AOF
此时我们恢复成功了
我们可以查看下appendonly.aof
文件
和修改之前一样,我们再启动redis看是否能正常启动并且数据是否还在
[root@centos-docker bin]# ./redis-server ./redis.conf
2902:C 14 Sep 2020 20:19:35.748 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2902:C 14 Sep 2020 20:19:35.748 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=2902, just started
2902:C 14 Sep 2020 20:19:35.748 # Configuration loaded
[root@centos-docker bin]# ./redis-cli
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> get name
"xkuna"
ok~
规则
# appendfsync always # 每次修改都会 sync,消耗性能
appendfsync everysec # 每秒修改都会 sync,容易丢失这 1 秒的数据
# appendfsync no # 不即时同步,由操作系统控制何时刷写到磁盘上,这种模式速度最快
默认是每秒 保存下执行的命令到 appendonly.aof
文件
如果服务器宕机,那么最后一秒的数据可能会丢失
优缺点
优点:
缺点:
1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2、AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以Redis协
议追加保存每次写的操作到文件末尾, Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。I
RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库( AOF在不断变化不好备份) ,快速重启,而且不会有AOF可能潜在的Bug ,留着作为一个万一的手段。
5、性能建议
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一 次就够了,只保留save 900 1这条规则。
如果Enable AOF , 好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来 了持续的I0 ,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率, AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
如果不Enable AOF,仅靠Master Slave Repllcation实现高可用性也可以,能省掉一 大笔I0 ,也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时宕掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个,微博就是这种架构。
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!
Redis客户端可以订阅任意数量的频道。
订阅/发布消息图:
下图展示了频道channel1,以及订阅这个频道的三个客户端-- client2 、client5 和client1之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
命令
这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。
测试
我们启动两个客户端,一个订阅端,一个发送端
订阅端
127.0.0.1:6379> SUBSCRIBE xkuna #订阅消息通道 xkuna
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "xkuna"
3) (integer) 1
发送端
127.0.0.1:6379> PUBLISH xkuna hello,redis # 在xkuna消息通道内 发送"hello,redis" 消息
(integer) 1
127.0.0.1:6379>
查看 订阅端是否接收到消息
原理
Redis是使用C实现的,通过分析Redis源码里的pubsub.c文件,了解发布和订阅机制的底层实现,籍此加深对Redis的理解。
Redis通过PUBLISH、SUBSCRIBE 和PSUBSCRIBE等命令实现发布和订阅功能。
通过SUBSCRIBE命令订阅某频道后, redis-server 里维护了一个字典,字典的键就是一个个channel , 而字典的值则是一个链
表,链表中保存了所有订阅这个channel的客户端。SUBSCRIBE 命令的关键,就是将客户端添加到给定channel的订阅链表中。
通过PUBLISH命令向订阅者发送消息, redis-server 会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这
个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
Pub/Sub从字面上理解就是发布( Publish )与订阅( Subscribe ) , 在Redis中,你可以设定对某一个key值进行消息发布及消息订
阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系
统,比如普通的即时聊天,群聊等功能。
使用场景
1、 实时消息系统!
2、事实聊天! (频道当做聊天室,将信息回显给所有人即可! )
3、 订阅,关注系统都是可以的!
稍微复杂的场景我们就会使用消息中间件MQ
主从复制,是指将一台Redis服务器的数据 ,复制到其他的Redis服务器。前者称为主节点(master/leader) ,后者称为从节点.
(slave/follower) ;数据的复制是单向的,只能由主节点到从节点。Master以写为主, Slave 以读为主。
默认情况下,每台Redis服务器都是主节点;且-个主节点可以有多个从节点(或没有从节点) ,但一个从节点只能有一个主节点。
主从复制的作用主要包括:
1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接
主节点,读Redis数据时应用连接从节点) , 分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大
大提高Redis服务器的并发量。
4、高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
一般来说 ,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
1、从结构上,单个Redis服务器会发生单点故障,并且一 台服务器需要处理所有的请求负载,压力较大;
2、从容量上,单个Redis服务器内存容量有限,就算一 台Redis服务器内存容量为256G ,也不能将所有内存用作Redis存储内存,
一般来说 ,单台Redis最大使用内存不应该超过20G。
电商网站上的商品, -般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。
对于这种场景,我们可以使如下这种架构:
只配置从库,不配置主库
我们先启动我们的主库,查看主库信息
127.0.0.1:6379> info replication # 查看 当前库的信息
# Replication
role:master # 角色 master
connected_slaves:0 # 没有从机
master_replid:9de388bc2ead191118f3b5f6dff61fe85af60120
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
开始配置从库,此时我们启需要配置2个 redis作为从机,先关闭主机
主机端口 6379, 从机端口 6380, 6381
将三个配置文件 copy到新的文件夹中
[root@centos-docker bin]# mkdir cluster
[root@centos-docker bin]# cp redis.conf ./cluster/redis-6379.conf
[root@centos-docker bin]# cp redis.conf ./cluster/redis-6380.conf
[root@centos-docker bin]# cp redis.conf ./cluster/redis-6381.conf
[root@centos-docker bin]# cd cluster/
[root@centos-docker cluster]# ls
redis-6379.conf redis-6380.conf redis-6381.conf
修改 配置文件
修改 6379配置文件
port 6379 # 端口
pidfile /var/run/redis_6379.pid # pid文件
logfile "redis-6379.log" # 日志文件名称
dbfilename dump-6379.rdb # dump文件名称
6380 6381 配置文件 只需将上面修改位置的6379 分别替换为6380 和 6381 即可
分别启动 三个 redis
[root@centos-docker cluster]# cd ../
[root@centos-docker bin]# ./redis-server cluster/redis-6379.conf
[root@centos-docker bin]# ./redis-server cluster/redis-6380.conf
[root@centos-docker bin]# ./redis-server cluster/redis-6381.conf
[root@centos-docker bin]# ps -ef | grep redis
root 3583 1 0 01:19 ? 00:00:00 ./redis-server 0.0.0.0:6379
root 3588 1 0 01:20 ? 00:00:00 ./redis-server 0.0.0.0:6380
root 3593 1 0 01:20 ? 00:00:00 ./redis-server 0.0.0.0:6381
root 3598 1822 0 01:20 pts/1 00:00:00 grep --color=auto redis
默认情况下,每台Redis服务器都是主节点,我们一般情况下,只用配置从机就好了!
认老大(6379老大,6380 6381 小弟)
配置6380
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380>
配置6381
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380>
查看 6379 主从信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 # 两台从机
slave0:ip=127.0.0.1,port=6380,state=online,offset=84,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=84,lag=0
master_replid:57bfe8e6ebfd266ab1af6a9729112066df146ae6
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84
但是!在客户端用命令配置的主从关系,重启失效,只有在配置文件中配置才会永久生效
在配置文件的REPLICATION
模块配置
注意
主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机保存!
测试:主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息!
复制原理
Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, master将传送
整个数据文件到slave ,并完成一-次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制: Master继续将新的所有收集到的修改命令依次传给slave ,完成同步
但是只要是重新连接master , 一次完全同步(全量复制)将被自动执行I
链路模型
这种模型也可以完成主从复制
主机宕机怎么办?
谋朝篡位
在哨兵模式之前,主机宕机之后,都是手动修改主从关系,比如,从机改为主机 ,命令为 slaveof no one
,此时从机就从slave
变为master
了。如果原来的主机正常启动了,没有修改主从关系,那么 从机 会重新连接到主机。
(自动选举)
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一-种推荐的方式 ,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel (哨兵)架构来解决这个问题。
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程, 作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用:
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一 -次投票,投票的结果由一一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
测试
一般情况下,每个redis都要配置一个sentinel,在这里三台redis只配置了一台sentinel(服务器 配置不
我们新创建一个 sentinel_conf
文件夹 存放sentinel的配置文件
编写sentinel-6379.conf
配置文件
下面是简单配置
port 26379 # sentinel端口
daemonize yes # 是否后台运行
sentinel monitor mymaster 127.0.0.1 6379 1 # 监控的redis信息
## 其他的配置 在启动sentinel之后 自动按照 默认配置 写入到该文件
启动sentinel
[root@centos-docker bin]# ./redis-sentinel sentinel_conf/sentinel-6379.conf
1856:X 15 Sep 2020 10:55:46.229 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1856:X 15 Sep 2020 10:55:46.229 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=1856, just started
1856:X 15 Sep 2020 10:55:46.229 # Configuration loaded
登录sentinel 并查看 监控信息
[root@centos-docker bin]# ./redis-cli -p 26379
127.0.0.1:26379> sentinel master mymaster # 查看监控主机名为 mymaster 的信息
1) "name"
2) "mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6379"
7) "runid"
8) "461c856fa56b91113b2ded3038166913a8ce3271"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "397"
19) "last-ping-reply"
20) "397"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "7240"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "77452"
29) "config-epoch"
30) "0"
31) "num-slaves"
32) "2"
33) "num-other-sentinels"
34) "0"
35) "quorum"
36) "1"
37) "failover-timeout"
38) "180000"
39) "parallel-syncs"
40) "1"
此时我们把 6379的 主机下线
[root@centos-docker bin]# ./redis-cli -p 6379
127.0.0.1:6379> shutdown
not connected> exit
查看 6380 6381 信息
# 6380
[root@centos-docker bin]# ./redis-cli -p 6380
127.0.0.1:6380> info replication
# Replication
role:slave # 角色为从机
master_host:127.0.0.1
master_port:6381
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:36435
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:ee296f79ccb66c1a0e0c56dc15cad8d0f1896d06
master_replid2:fa6f421dce64972a6b8fe40739865eee4a08e9a1
master_repl_offset:36435
second_repl_offset:34922
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:36435
# 6381
[root@centos-docker bin]# ./redis-cli -p 6381
127.0.0.1:6381> info replication
# Replication
role:master # 角色为 主机
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=36981,lag=1
master_replid:ee296f79ccb66c1a0e0c56dc15cad8d0f1896d06
master_replid2:fa6f421dce64972a6b8fe40739865eee4a08e9a1
master_repl_offset:37114
second_repl_offset:34922
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:37114
127.0.0.1:6381>
那么 sentinel 自动选举成功了,6381变为了主机
注意!此时6379再次上线,那么它将变为6381的从机!
优缺点
优点:
缺点:
sentinel全部配置
#sentinel 配置
#端口
port 26379
#目录
dir /tmp
#日志文件
logfile /var/log/redis/redis-sentinel.log
#是否在后台执行,yes:后台运行;no:不是后台运行
daemonize yes
bind 127.0.0.1 #监听的ip地址,根据节点不同进行调整
# sentinel auth-pass
#设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。
# 配置示例:
sentinel auth-pass mymaster 0123passw0rd
#是否开启保护模式,默认开启。开启后,只能根据配置的bind地址和密码进行访问。
protected-mode no
#主节点信息,格式:sentinel ;
# 自定义主节点名称;
# 主节点的ip和端口;
# 多少个主节点检测到主节点有问题就进行故障转移
sentinel monitor mymaster 127.0.0.1 6379 1
#sentinel与master的心跳时间(毫秒),默认30秒。
sentinel down-after-milliseconds mymaster 30000
#故障转移时,最多可以有多少个slave同时对新的master进行数据同步,该值越小,完成故障转移的时间越长,但可用slave数量越多,该值越大,越多slave因为replication而不可用。建议设置为1。
sentinel parallel-syncs mymaster 1
#故障转移超时时间(毫秒),默认180秒。
sentinel failover-timeout mymaster 180000
#master和slaves密码。
#sentinel auth-pass mymaster password
#当sentinel有警告级别的事件发生时执行(也有的资料说failover时触发)的脚本。
#sentinel notification-script
#故障转移之后执行的脚本,并传递7个参数: 。
# 表示 master名字
# 表示的是 每个redis实力的角色,如leader、observer
# 表示状态
# 原来的redis master
#
# 故障迁移后的redis master
#
#sentinel client-reconfig-script
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
另外的一-些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。
概念
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀! ) , 于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
存储空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间 ,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
概述
这里需要注意和缓存击穿的区别,缓存击穿,是指一-个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一 个屏障上凿开了一一个洞。当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据 ,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
概念
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
产生雪崩的原因之一 ,比如在写本文的时候,马上就要到双十二零点,很快就会迎来-波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候 ,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
解决方案
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis ,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一-遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key ,设置不同的过期时间,让缓存失效的时间点尽量均匀。