redis是一个开源的key-value存储系统,支持多种value类型以及丰富的操作,并支持原子性操作,数据可以保存在缓存中,支持数据持久化,周期性写入磁盘记录,单线程多路IO复用,能够实现主从同步,适合高并发、海量数据读写。
最新版7.2.4 redis官网下载
将下载的压缩包在linux下解压安装,安装目录/usr/local/bin,安装目录下
redis-benchmark:性能测试工具
redis-check-aof:修复aof文件
redis-check-dump:修复dump.rdb文件
redis-sentinel:redis集群工具
redis-server:服务器启动命令
例·:redis-server /etc/redis.conf
redis-cli:客户端连接,默认连接本地
默认端口:6379
默认0-15数据库,16个数据库,所有库密码统一
基础命令
前台启动:redis-server、redis-cli
远程执行命令:redis-cli -h host -p port -a password
关闭命令:redis-cli shutdown或进入redis后shutdown
keys *
,参考当前库所有key
exsits key
,判断某个key是否存在,1存在,0不存在
type key
,参考key的类型
del key
,删除指定key的数据
unlink key
,根据key异步删除,即可能不会立刻删除
expire key 10
,为key设置过期时间,10秒后无法取值
ttl key
,查看key还有多少秒过期-1永不过期,-2已过期
dbsize
,查看当前数据库的key数量
set <key> <value>
,设置key-value
get key
,获取value
setnx <key> <value>
,当key不存在时设置键值对。
append <key> <value>
,追加value到指定key的值后,返回长度。
strlen key
,获取值的长度。
incr key
,将key中存贮的数字值加1
decr key
,将key中存贮的数字值减1
incrby key 100
,将key中存贮的数字值加100
decrby key 100
,将key中存贮的数字值减100
mset k1 v1 k2 v2..
,设置多个键值对(原子性)。
msetnx k1 v1 k2 v2..
,同时设置多个键值对,仅当给定key都不存在(原子性)
mget <key1> <key2>..
,获取多个value(原子性)。
getrange key <起始位置> <结束位置>
,根据key获取value的某段范围(前包后包)
setrange key <index> value
,用value覆写key对应的值,从index开始
setex key <过期时间> value
,设置键值的同时,过期时间单位秒
getset key value
,先根据key获取值,然后设置新值value
select <dbid>
,切换数据库
flushdb
,清除当前库
flushall
,清楚全部库
redis设置密码:
config set requirepass "123456"
auth 123456
密码认证
redis获取密码:config get requirepass
五大基本数据类型
字符串String
它是最基本的类型。可以包含任何数据,是二进制安全的。它是一种动态的字符串,采用预分配冗余空间减少内存消耗,当超出分配空间时成倍扩容,一个字符串value最多可以是512M。
列表List
底层为双向链表,按照插入顺序排序,是简单的字符串列表。数据结构为quickList,在元素较少时压缩列表。
lpush/rpush k v1 v2..
,从左边/右边插入一个或多个值。
lpop/rpop key
,从左/右取出(返回并删除)一个值。(当没有值时该键消失)
rpoplpush <key1> <key2>
,从key1列表的右边吐出一个值插入key2列表的左边。
lrange <key> <first> <last>
,根据key按照下标取值,从左到右first-last。例lrange k1 0 -1。
lindex key index
,根据key从左到右,获取下标为index的value值。
llen key
,获取列表长度。
linsert <key> before/after <value> <newvalue>
,从左到右根据key在value前或后插入新值。
lrem <key> n <value>
,从左到右根据key删除列表中前n个value
lset <key> <index> <value>
,从左到右根据key,为下标index的位置设置value
集合Set
数据结构是dict字典,使用哈希表实现,不允许重复元素,是一个无序集合。
sadd key <v1> <v2>..
,将一个或多个值存入key集合。
smembers key
,列出集合key中的所有值。
sismembers key value
,判断集合key中是否有value,有1,没有0.
scard key
,返回集合key元素个数。
srem key <v1> <v2>..
,删除集合key中一个或多个value
spop key
,随机从集合key中返回一个value值。
srandmember key n
,随机从集合中返回个值。
smove k1 k2 value
,将value从k1移动到k2。
sinter k1 k2
,返回两个的交集。
sunion k1 k2
,返回两个集合的并集。
sdiff k1 k2
,返回差集,在k1中,不在k2的
哈希Hash
键值对集合,string类型的field字段和value的映射表,field建赋值value,则redis数据库表示为key:映射表
,其中key为。对应数据结构为ziplist和hashtable。
hset key field value
,给key中field键设置value值。
例:hset user:001 id 1
hsetnx key field value
,当field不存在时,给key中field键设置value值。
hget key field
,获取key中field键的值。
hmset key f1 v1 f2 v2..
,给key中多个field键设置value值。
hexists key field
,查看key集合中field字段是否存在。
hkeys key
,列出key集合中所有field。
hvals key
,列出集合中所有value。
hincrby key field n
,为key中field的value值加n。
有序集合Zset
有序集合,集合中元素不可重复,但是集合中每一个value都有一个score指标,需要设置并以此排序。底层使用了两种数据结构:hash表,value为score;跳跃表,快速获取元素。
zadd key score1 value1 score2 value2..
,将一个或多个value及其score值加入到key中。
zrange key first last [withscores]
,返回有序集合key中,下标在[first,last]间的value,可以带上score。
zrangebyscore key min max [withscores] [limit offset count]
,返回集合中score值在[min.max]区间的value,且默认按从小到大输出。
zrevrangebyscore key max min [withscores] [limit offset count]
,同上,从大到小输出。
zincrby key n value
,给元素value的score值增加n。
zrem key value
,删除key集合下value值。
zcount key smin smax
,统计集合中元素的score在[smin,smax]区间的个数。
zrank key value
,返回集合中值value的下标。
例:
1)zadd level 100 Lv1 80 Lv2 50 Lv3
2)zrange level 0 -1
3)zrangebyscore level 0 80 withscores
新数据类型
Bitmaps
Bitmaps可实现对位bit的操作,但实际是可以操作位的字符串,把存储位的字符串看作动态数组,则数组下标为offset偏移量,数组中的元素为比特0/1,默认全部为0。适合存储大量访问数据,节省空间。
setbit key offset value
,设置名为key的Bitmaps中偏移量offset上的位为value(0或1),偏移量过大影响性能。
getbit key offset
,获取下标为offset上的value值。
bitcount key
,统计该Bitmaps中bit为1的数量,默认全范围,按字节单位统计。
bitcount key start end
,统计该Bitmaps中bit为1的数量,范围为从第start个字节组开始,到第end个字节。
bitop and k1 k2 k3..
,对k2,k3多个Bitmaps做交集运算,结果保存至k1。此外bitop还可以进行or并集、not非、xor异或运算。
例:setbit user:202401027 0 1
HyperLogLog
根据输入的元素计算基数,即集合中不重复的元素,但不存储元素。
pfadd key element1 element2..
,添加元素element1,element2多个元素到集合中。
pfcount key..
,统计一个或多个集合中的基数,为多个key时合并元素再统计基数。
pfmerge dkey skey1 skey2..
,将skey1、skey2等多个集合合并放入dkey集合中。
Geospatial
提供了对地点的二维坐标的存储、查询、hash等操作。
geoadd key 经度 纬度 地点名
,以经纬度添加地点位置,可添加多个地点。
例:geoadd china:city 121.47 31.23 shanghai
geopos key 地点名
,从key集合中获取某地点位置。
geodist key 地点1 地点2 [m/km/ft]
,获取两个地点的直线距离,默认单位m米。
georadius key 经度 纬度 半径 [m/km/ft]
,以给定经纬度的地点为中心,在key中找出在半径内地点城市。
配置文件
1)Units单位
配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit,大小写不敏感。
2)INCLUDES包含
类似jsp中的include,多实例的情况可以把公用的配置文件提取出来
3)网络相关配置bind
默认情况bind=127.0.0.1只能接受本机的访问请求,不写的情况下,无限制接受任何ip地址的访问。生产环境肯定要写你应用服务器的地址,服务器是需要远程访问的,所以需要将其注释掉。如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应。
4)protected-mode
将本机访问保护模式设置no,默认是yes即开启。设置外部网络连接redis服务,设置方式如下:
关闭protected-mode模式,此时外部网络可以直接访问。
开启protected-mode保护模式,需配置bind ip或者设置访问密码。
5)Port
端口号,默认 6379。
6)tcp-backlog
设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。
7)timeout
一个空闲的客户端维持多少秒会关闭,0表示关闭该功能,即永不关闭。
8)tcp-keepalive
对访问客户端的一种心跳检测,每隔n秒检测一次。单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60。默认是300秒检查一次,即如果300秒,没有做任何操作,那么便会释放当前连接。
9)daemonize
是否为后台进程,设置为yes,默认为no,需要手动更改,守护进程,后台启动。
10)pidfile
存放pid文件的位置,每个实例会产生一个不同的pid文件,每次操作,都会有进程号,pidfile负责保存这些进程号。
11)loglevel
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice,四个级别根据使用阶段来选择,生产环境选择notice或者warning。
12)logfile
设置日志的输出文件路径,默认为空。
13)databases 16
设定库的数量默认16,默认数据库为0,可以使用SELECT < dbid >命令在连接上指定数据库id。
14)SECURITY安全
设置密码,# requirepass foobared
默认没有密码。在命令中设置密码,只是临时的,重启redis服务器,密码就还原了。永久设置,需要再配置文件中进行设置。
15)maxclients
设置redis同时可以与多少个客户端进行连接,默认情况下为10000个客户端。如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
16)maxmemory
必须设置,否则将内存占满,造成宕机。设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息。
对于无内存申请的指令,仍然会正常响应,比如GET等。如果redis是主redis,那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在设置的是“不移除”的情况下,才不用考虑这个因素。
17)maxmemory-policy
volatile-lru:使用LRU算法移除key,只针对设置了过期时间的键(最近最少使用)。
allkeys-lru:在所有集合key中,使用LRU算法移除key。
volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键。
allkeys-random:在所有集合key中,移除随机的key。
volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
noeviction:不进行移除,针对写操作,只是返回错误信息。
18)maxmemory-samples
设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。
发布与订阅
发布和订阅是一种消息通信模式,发布者向一个channel频道发布信息,订阅这个频道的订阅者接收信息。
打开一个redis客户端:
subscribe c1
打开另一个客户端:
publish c1 helloredis
订阅的客户端将收到helloredis消息。若消息没有持久化,则订阅的客户端只能收到订阅后的消息。
Jedis操作Redis
Jedis是redis的java版本的客户端实现,使用Jedis提供的Java API对Redis进行操作,并且使用Jedis提供的对Redis的支持灵活全面,但编码复杂度较高。
1)导入依赖
1 | <dependency> |
2)创建对象测试
1 | Jedis jedis = new Jedis("127.0.0.1", 6379);//host port |
3)设置键值
1 | jedis.set("username", "张三疯了"); |
具体可以参考:jedis相关详解
springboot整合redis
SpringBoot 的自动装配机制会自动的创建一些对象来方便我们操作 Redis:
RedisConnectionFactory
,根据指定的配置来获取 Redis 连接的RedisTemplate
、StringRedisTemplate
,用来操作 Redis 存取数据。
1)导入spring-data-redis和commons-pool2连接池依赖:
1 | <dependency> |
从 SpringBoot2.x 开始,默认使用 Lettuce 作为 Spring Data Redis 的内部实现,而不是 Jedis。
2)在核心配置文件中配置redis
1 | spring.redis.host=127.0.0.1 |
3)声明配置类
1 |
|
4)测试
1 |
|
事务操作
redis事务是单独的事务操作,事务中的命令会按顺序执行,且不会被打断。
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
事务开启
1)multi
输入后开启事务,将要执行的命令逐一输入命令行,若出现error,则事务不会执行成功。
2)exec
逐一执行事务的命令,其中一条执行出错不影响其他命令执行成功,期间不被其他命令中断。
3)discard
中止事务,在输入事务命令的期间可以放弃事务。
解决事务冲突方式:悲观锁(操作前先上锁操作后在开锁);乐观锁(给数据添加版本号,每次迭代不同版本,判断数据是否更改,适合读较多的场景)。redis采用check-and-set乐观锁机制解决事务冲突。
watch数据监视
watch key..
,在开启事务multi前,监视一个或多个key,如果事务执行前这些key被其他命令改动则事务将中断。
unwatch key..
,取消所有key的监视。
1 | redis 127.0.0.1:6379> WATCH lock lock_times |
redis事务的三个特性
1)单独的隔离操作
事务中的命令都会序列化,按顺序执行,事务执行中不会其他客户端命令打断。
2)没有隔离级别的操作
事务在提交前,任何指令都不会执行。
3)不保证原子性
事务中有一条命令执行失败,不影响其他命令执行,没有回滚。
Redis 脚本
Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。
语法
Eval 命令的基本语法如下:
1 | redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...] |
实例
以下实例演示了 redis 脚本工作过程:
1 | redis 127.0.0.1:6379> EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second |
脚本命令
下表列出了 redis 脚本常用命令:
序号 | 命令及描述 |
---|---|
1 | EVAL script numkeys key [key …] arg [arg …] 执行 Lua 脚本。 |
2 | EVALSHA sha1 numkeys key [key …] arg [arg …]执行 Lua 脚本。 |
3 | SCRIPT EXISTS script [script …]查看指定的脚本是否已经被保存在缓存当中。 |
4 | SCRIPT FLUSH从脚本缓存中移除所有脚本。 |
5 | SCRIPT KILL杀死当前正在运行的 Lua 脚本。 |
6 | SCRIPT LOAD script将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。 |
数据备份与恢复
SAVE 命令用于创建当前数据库的备份。
备份
Save 命令基本语法如下:
1 | redis 127.0.0.1:6379> SAVE |
该命令将在 redis 安装目录中创建dump.rdb文件。
恢复数据
如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并重新启动服务即可。获取 redis 目录可以使用 CONFIG 命令,如下所示:
1 | redis 127.0.0.1:6379> CONFIG GET dir |
以上命令 CONFIG GET dir
输出的 redis 安装目录为 /usr/local/redis/bin。
bgsave
创建 redis 备份文件也可以使用命令 BGSAVE,该命令在后台执行。
1 | 127.0.0.1:6379> bgsave |
持久化的三种方式
(1)RDB
快照持久化,redis一种数据持久化的方式,默认通过fork子进程创建一个临时文件,把临时文件再替换成持久化文件dump.rdb(在redis启动目录下),在指定的时间间隔内将内存中的数据快照写入磁盘,这种技术为写时复制。
快照持久化是 Redis 默认采用的持久化方式,在 redis.conf
配置文件中默认有此下配置:
1 | save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。 |
关闭rdb命令: config set save ""
Redis 提供了两个命令来生成 RDB 快照文件:
save
: 同步保存操作,会阻塞 Redis 主线程。bgsave
: fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。
这里说 Redis 主线程而不是主进程的主要是因为 Redis 启动之后主要是通过单线程的方式完成主要的工作。
优点:高效,恢复速度快,适合大规模数据操作。
缺点:当服务器宕机时,最后一次备份数据可能丢失。
(2)AOF
以日志形式记录每次的写操作指令,只能追加到aof文件但不可改写文件,redis启动时会读取该文件完成数据恢复。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化(Redis 6.0 之后已经默认是开启了)。
AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir
参数设置的,默认的文件名是 appendonly.aof
。可以在配置文件中通过 appendonly
参数开启或通过config set appendonly "yes"
命令开启:
1 | appendonly yes |
RDB和AOF同时开启默认取AOF文件的数据,两种数据备份和恢复操作相似。
异常恢复:
如果aof文件损坏,可以通过redis-check-aof --fix appendonly.aof
恢复,之后重启redis。
AOF同步频率:
appendfsync always:始终同步,每次写入都会记录日志,性能差但数据完整性好。
appendfsync everysec:每秒记录一次日志,最后一秒的数据可能丢失。
appendfsync no:不主动同步,记录交给主机。
Rewrite压缩
为避免文件越来越大,当文件超出预定大小,redis就会启动aof文件压缩,只保留恢复数据的最小指令集,可以使用bgrewriteaof
命令。重写就是把rdb的快照以二进制附在aof文件头部,作为新数据替换到原来数据,从而覆盖aof文件。
在配置文件中可以设置触发rewrite的值:
auto-aof-rewrite-percentage
:文件大小>=重写前文件的大小basesize + basesize*百分之多少。
auto-aof-rewrite-min-size
:当文件达到这个最小值rewrite。
AOF执行流程:开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 server.aof_buf
中,然后再写入到 AOF 文件中(此时还在系统内核缓存区未同步到磁盘),最后再根据持久化方式的策略来决定何时将系统内核缓存区的数据同步到硬盘中的。只有同步到磁盘中才算持久化保存了,否则依然存在数据丢失的风险。
优点:丢失数据概率低,可以处理误操作
缺点:占用空间,备份慢,每次同步有性能压力。
参考文章:Redis持久化机制 RDB、AOF、混合持久化详解!如何选择?
主从复制
主机数据更新后,根据配置策略自动同步备机中,形成master-slave机制,主机以写操作为主,从机以读为主,达到读写分离。
复制原理:
1)当从服务器连上主服务器后,向主服务器发送数据同步请求
2)主服务器收到请求进行数据持久化操作,把rdb文件发送到从服务器,从服务器进行读取
3)每次主服务器进行写操作后,会主动同步数据到从服务器
一主二从
1)根据主机配置文件创建从机的配置文件,分别为redis6380.conf、redis6381.conf,分别写入:
1 | include /etc/redis.conf |
关闭aof,将6379改为从机的6380、6381。
2)根据配置文件启动redis主机及其从机
1 | redis-server /etc/redis.conf |
此时主机与从机相互独立,没有从属关系。
3)分别进入从机的redis客户端,输入以下命令即可建立关系:
slaveof 主机ip 主机端口
解除关系(反客为主):从机slaveof no one
使用info replication
命令可以看到从属关系,从机上只能读不允许写操作,主机上进行写操作后,从机上也能看到数据。
注意:从机宕机后会变回master主服务器,需要再次绑定关系,而主服务器宕机后再重启还是主服务器,从机还是从机。
代代相传
上一个slave可以是下一个slave的master,从而形成一条数据同步链。同样使用slaveof
命令建立关系,但是中途变更从属关系会失去之前同步的数据而重新拷贝最新的数据。
注意:当某个slave宕机了,后面的slave将无法获取备份数据。
哨兵模式sentinel
自动反客为主,设置一个哨兵服务器后台监控主机状态,若主机故障根据投票将某从机变为主机。
1)配置哨兵服务器
新建一个配置文件sentiel.conf,内容为:
sentinel monitor MyMaster 127.0.0.1 6379 1
其中MyMaster为监控对象名称,即主服务器的别名,127.0.0.1为主服务器ip,6379为端口号,1为同意数据迁移的哨兵数量。
2)启动哨兵
redis-sentinel sentinel.conf
哨兵策略:
从下线的主服务器的从服务器中挑选一个转为主服务器,主机选取策略依次为:选取优先级高的、选取偏移量最大的、选runid最小的。选取主服务器后,向原来的其他从服务器发送slaveof,复制新的master,当已下线的旧主服务器重新上线时,sentinel会发送slaveof命令使其变为从服务器。
注意:配置中replica-priority越小,优先级越高。偏移量最大表示数据越全!
redis集群
实现对redis的水平扩容(启动N个节点,每个节点存储总数据的N分之一),通过分区partition提供可用性,即使节点失效,集群也可以继续处理请求。无中心化集群,任何一个节点都能接收请求,解决容量不够,并发写操作。
集群搭建
1)删除之前的持久化数据rdb,aof
2)制作6个实例,6379,6380,6381,6389,6390,6391,集群至少6个节点!
配置各个实例配置文件,redis6379.conf:
1 | include /etc/redis.conf |
在vim编辑下,快速查找替换命令%s /6379/6380
3)启动6个redis服务器
redis-server redis6379.conf
redis-server redis6380.conf ..
4)将6个节点合并
需要ruby环境,进入redis安装目录src下执行命令
redis-cli --cluster create --cluster-replicas 1 localhost:6379 localhost:6380..
其中localhost可替换为真实ip地址,–cluster集群操作。–replicas 1采用最简单方式配置(1台从节点),一台主机一台从机,将分配三个主节点,三个从节点。
5)此时waiting for the cluster to join…
需要进入各个节点客户端进行集群通信的建立,redis-cli -c -h localhost -p 6379
,进入后执行cluster meet 192.168.56.3 16379 16379
,结果输出ok则成功。其他节点类似,执行cluster meet 节点ip 节点总线端口 公共总线端口
,如cluster meet 192.168.56.3 16380 16379
,注意上述端口一定要能访问,要通过防火墙开放。
6)上述6个节点弄完后集群测试
redis-cli -c -h [ip] -p 6379
查看集群节点信息:cluster nodes
,若集群节点信息输出成功!
7)存入键值
set k1 v1
注意不在一个插槽范围(节点)的键不能使用mset、mget操作,需要{}分组放入:
mset k1{c1} v1 k2{c1} v2 k3{c1} v3
以c1组名作为key放入节点。
slot插槽
一个redis集群会包含16384个插槽,数据库中的每个键都属于其中一个插槽。在存入key-value时,集群使用CRC(key)%16384计算key属于哪个插槽,集群中每个主节点会负责一部分插槽的管理,这样可以让key分配到合适的节点中。
查询集群中的值
cluster keyslot key
,查询集群中key所在的插槽位置。
例:cluster keyslot c1
cluster countkeysinslot 3344
,统计插槽3344中key的个数,只能看所在插槽(节点)的key。
cluster getkeysinslot <slot> <count>
,返回count个在插槽slot中的key,只能看所在插槽(节点)的key。
例:cluster getkeysinslot 3344 520
mget c1
,返回c1的value,只能看所在插槽(节点)的数据。
故障恢复
当某节点的主机故障时,从机将变为主机master,原主机将恢复为从机。若某一主从节点都挂掉,那整个集群能不能继续工作看配置,cluster-require-full-coverage
设为yes则整个集群停止工作。
Jedis操作集群
1 | HostAndPort hostandport = new HostAndPort("192.168.0.1",6379); |
应用问题
缓存穿透
查询一个缓存中和数据库中都不存在的数据,导致每次查询这条数据都会透过缓存,直接查库返回空,造成大量数据请求访问数据库,数据库无法同步缓存,使服务器崩溃。
解决:
1)对空值缓存,设置其过期时间
2)使用bitmaps设置白名单
3)布隆过滤器BloomFilter(底层bitmaps)
4)缓存预热
缓存击穿
指当缓存中某个热点数据过期了,在该热点数据重新载入缓存之前,有大量的查询请求穿过缓存,直接查询数据库。这种情况会导致数据库压力瞬间骤增,造成大量请求阻塞,甚至直接挂掉。
解决:
1)预先设置热门key,将某些热点数据的过期时间设为无限,预加载热点数据。
2)使用分布式锁,在获取数据时,使用分布式锁来控制只有一个是请求可以去后端获取数据,其他请求需要等待锁释放,这种方法可以有效防止多个请求同时穿透到后端存储系统。
缓存雪崩
指当缓存中有大量的key在同一时刻过期或者Redis缓存直接宕机了,导致大量的查询请求全部到达数据库,造成数据库查询压力骤增,甚至直接挂掉。
解决:
1)针对第一种大量key同时过期的情况,只需要将每个key的过期时间打散即可,使它们的失效点尽可能均匀分布。
2)针对第二种redis发生故障的情况,部署redis时可以使用redis的几种高可用方案部署,例如构建多级缓存、集群和哨兵模式。
3)设置过期标志更新缓存,限流。
4)分布式锁。
分布式锁
控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
特征:互斥性、锁超时释放、可重入性、高性能和高可用、原子性‘。
实现的几种方式:
1)setnx+expire
setnx上锁,del释放锁,expire设置锁自动释放时间,缺点是不是原子操作。
1 | setnx users 10 |
2)set key value nx ex seconds
1 | set users 10 nx ex 60 |
- nx : 表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。
- ex seconds : 设定key的过期时间,时间单位是秒。
- px milliseconds: 设定key的过期时间,单位为毫秒
1 | if(jedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1){ //加锁 |
使用UUID解决误删问题:
1 | //uuid表示不同的操作 |
3)使用Lua脚本
使用Lua脚本来保证操作的原子性(包含setnx和expire两条指令),脚本内容如下:
1 | if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then |
加锁代码如下:
1 | String lua_scripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" + |
脚本执行中不能打断,加解锁必须有原子性。
4)使用开源框架Redisson
具体可参考:Redis实现分布式锁的7种方案
ACL访问控制
redis6.0发布了权限管理功能ACL(access control list),可以对不同的用户设置不同的权限,限制用户可使用的命令,可访问的key等。
acl命令
1)acl list
,展示用户权限列表。
1 | "user default on nopass ~* &* +@all" |
default
表示用户名,兼容以前的AUTH命令,如果执行AUTH命令时不写用户名,redis认为是在认证这个default用户
on
表示已启用该用户,off表禁用
nopass
表示没有密码
~*
表示可访问全部的数据Key
&*
表示允许用户访问所有Pub/Sub频道
+@all
表示用户的权限, “+”添加权限;”-“删减权限;@为redis命令分类; 可以通过 ACL CAT 查询相关分类,all表全部的命令集合,最终 +@all 表示拥有所有命令集合的所有权限
2)查看用户
acl users
acl whoami
,查看当前用户
3)创建用户
acl setuser xxx
删除用户:acl deluser xxx
4)启用禁用
acl setuser xxx on >123456
,启用用户xxx并设置密码123456,取消密码<123456。
5)获取命令集合
acl cat
6)增加或减少命令权限
acl setuser xxx ~* +@string
,表示添加string类型的所有命令,多个类型命令用空格隔开。
7)限制key的范围
ACL SETUSER user resetkeys ~mykey:*
,让用户 user 只对以 mykey: 开头的这些 key 有权限。
持久化配置
以上设置都是保存在redis内存中,一旦重启,这些设置就会全部失效,想持久化保存ACL配置,要把ACL相关权限设置都保存到文件中。
1)在redis配置文件中设置要导出的ACL配置文件全路径名,ACL的配置文件名必须以.acl结尾。
2)执行 acl save,上述操作才会保存到该文件。
如果已经有了acl配置文件,直接加载到redis中生效。首先在redis配置文件中设置ACL配置文件全路径名,然后在redis终端上运行acl load
命令即可。
性能测试
Redis 性能测试是通过同时执行多个命令实现的。
redis 性能测试的基本命令如下:
1 | redis-benchmark [option] [option value] |
注意:该命令是在 redis 的目录下执行的,而不是 redis 客户端的内部指令。
以下实例同时执行 10000 个请求来检测性能:
1 | $ redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -n 10000 -q |