Redis
Redis
数据类型
字符串(string),hash(field-value),列表list,集合set,有序集合zset
String
string类型是二进制安全的。意味着redis的string可以包含任何数据。比如jpg图片或者序列化的对象。一个redis中字符串value最多可以是512M。
string的数据结构为简单动态字符串(Simple Dynamic String 缩写SDS)。是可以修改的字符串,内容结构上类似于Java的ArrayList,采用分配冗余空间的方式来减少内存的频繁分配。
List
redis list列表是简单的字符串列表,按照插入顺序排序。可以添加元素到列表的头部或尾部。底层实际是使用双向链表实现的,对两端的操作性能很高,通过索引下标操作中间节点性能会比较差。
list的数据结构为快速链表quickList。首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是压缩列表(ziplist)。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当就比较多的时候才会变成quickList。因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如:这个列表存储的只是int类型的树,结构上还需要两个额外的指针prev和next。
redis将链表和ziplist结合起来组成了quickList。也就是将多个ziplist使用双向指针穿起来使用。这样既满足了快速插入删除性能,又不会出现太大的空间冗余。
Set
redis set对外提供的功能预list类似,是一个列表的功能,特殊指出在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是很好的选择。
redis set是string类型的无序集合,它的底层实际是一个value为null的hash表。添加、删除、查询的复杂度都是O(1)。
set数据结构是字典,字典是用hash表实现的,所有的value都指向同一个内部值
Hash
redis hash是一个键值对集合。
redis hash是一个string类型的fieId和value的映射表,hash特别适合用于存储对象。
hash类型对应的数据结构有两种:ziplist(压缩列表),hashtable(哈希表)。当fieId-value长度较短个数较少时,使用ziplist,否则使用hashtable
Zset(sorted set)
zset与set类似,是没有重复元素的字符串集合。
不同:zset有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从低到最高分的方式排序集合中的成员。
集合的成员是唯一的,但是评分是可以重复的。
内部使用到两种数据结构:
1.hash表:类似于java中的Map<string,score>,key为集合中的元素,value为元素对应的score,可以用来快速定 位元素定义的score,时间复杂度为O(1)
2.skiplist跳表:插入删除查找的复杂度均为O(logN)。
命令
flushdb # 清空当前db
flushall # 清空redis全部数据
keys * # 查看当前库所有的key
exist key # 判断某个key是否存在
type key # 查看key的类型
del key # 删除指定key的信息
unlink key # 根据key删除非阻塞删除,仅仅将keys从keyspacce元素中删除,真正的删除会在后续异步中操作。
expire key 10: # 为key设置过期时间为10秒
ttl key # 查看指定的key还有多少秒过期。-1:永不过期,-2:已过期
select dbindex # 切换数据库0-15,默认0
dbsize # 查看当前数据库key的数量
string字符串
set 【key value】 // 存入键值对
mset 【key1 value1 key2 value2...】 // 批量存入键值对
msetnx【key1 value1 key2 value2...】 // 存入一个不存在的字符串键值对 要么都成功,或者都失败
setnx 【key value】 // 存入一个不存在的字符串键值对
get 【key】 // 获取一个字符串的键值
getrange【key start end】 // 获取[start,end]返回范围内的字符串
setrange 【key 起始位置 value】 // 覆盖指定位置的值
mget 【key1 key2...】 // 批量获取字符串的键值
del 【key key...】 // 删除键
expire 【key seconds】 // 设置一个键的过期时间(秒)
incr 【key】 // 键值加1。只能对数字值操作。如果key不存在则会新建值为1
decr 【jey】 // 键值减1。只能对数字值操作,如果为空,新增值为-1
incrby 【key increment_n】 // 键值加n。若key不存在,则相当于在原值为0的值上递增指定的步 长。
decrby 【key decrement_n】 // 键值减n。若key不存在,则相当于在原值为0的值上递增指定的步 长。
append 【key value】 // 字符串追加(key不存在则相当于set)
setrange 【key 过期时间(秒) value】 // 设置键值&过期时间(秒)
getset 【key value】 // 设置新值,同时返回旧值
List
lpop/rpop【key count】 // 从左侧/右侧弹出多个元素,count可省略,默认为1。操作之后,弹出来的值会从列表中删除 值在键在,值光键亡。
rpoplpush 【source destination】 // 从一个列表右边弹出一个元素放到另外一个列表中。从source的右边弹出一个元素放到destination列表的左边。
lindex 【key index】 // 获取指定索引位置的元素(从左到右)。可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此 类推。
llen 【key】 // 获得列表长度
linsert 【key】before|after【value】【newvalue】 // 在某个值的前或者后面插入一个值。将值 newvalue 插入到列表 key 当中,位于值 value 之前或之后。 当 value 不存在于列表 key 时,不执行任何操作。 当 key 不存在时, key 被视为空列表,不执行任何操作。 如果 key 不是列表类型,返回一个错误。 返回值: 如果命令执行成功,返回插入操作完成之后,列表的长度。 如果没有找到 value ,返回 -1 。 如果 key 不存在或为空列表,返回 0 。
lrem 【key count value】 //根据count的值,移出列表中与参数value相等的元素。(count>0 从表头向表尾搜索,count<0 从表尾向表头搜索,count=0移出表中所有与value相等的值)
lset 【key index value】 // 将列表key下标为index的元素值设置为value
Set
sadd 【key1 value1 value2...】 // 添加一个或多个元素(会自动去重)
smembers 【key】 # 取出所有元素
sismember 【key value】 #判断集合中是否有某个值。1:有,0:无
scard 【key】 # 返回集合中的元素个数
srem 【key member1 member2...】 # 删除多个元素
spop 【key count】 # 随机从key集合中弹出/移除count个元素。count默认1。返回被移除的元素
srandmember 【key count】 # 随机从key集合中取count个元素,不会从集合中删除。
smove 【source destination member】 # 将member元素从source集合移动到destination集合。成功返回1
sinter 【key1 key2】 # 取多个集合的交集
sinterstore 【destination key1 key2...keyn】 # 取多个集合的交集放入一个destination 新集合中。
sunion 【key1 key2...】 # 取多个集合的并集,自动去重。
sunionstore 【destination key1 key2...keyn】 # 取多个集合的并集放入一个destination 新集合中。
sdiff 【key1 key2...keyn】 # 取key1集合和其他集合的差集
sdiffstore【destination key1 key2...keyn】 # 取key1集合和其他集合的差集放入一个destination 新集合中。
Hash
hset 【key fieId1 value1 fieId2 value2 ...】 # 设置多个fieId的值。如果fieId已存在哈希表中,旧值将被覆盖。成功返回1,fieId已存在并且旧值被覆盖返回0.
hget 【key fieId】 # 获取指定fieId的值
hgetall 【key】 # 返回hash表所有的域和值
hexists 【key fieId】 # 判断fieId是否存在,1:存在,0:不存在
hkeys 【key】 # 列出所有的fieId
hvals 【key】 # 列出所有的value
hlen 【key】 # 返回fieId的数量
hincrby 【key fieId increment】 # fieId的值加上指定的增量(可为负数)。key不存在会创建,fieId
不存在执行前值初始化为0。值为字符串则返回错误。
hsetnx 【key fieId value】 # 当fieId不存在时,设置fieId值。fieId存在则设置无效,key不存在则创建。
zset(sortde set)
zadd 【key score1 member1 score2 member2...】添加多个member元素及其score值加入到有序集合key中。
zrange 【key start top withscores】score升序,获取指定索引范围的元素。score相同按字典排序。时间复杂度:O(log(N)+M), N 为有序集的基数,而 M 为结果集的基数。
zrevrange 【key start stop [withscores]】score降序,获取指定索引范围的元素
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]:按照score升序,返回指定score范围内的数据
zrevrangebyscore:按照score降序,返回指定score范围内的数据
zincrby key increment member:为指定元素的score加上指定的增量
zrem key member [member ...] 删除集合中多个元素
zremrangebyrank key start stop:根据索引范围删除元素
zremrangebyscore key min max:根据score的范围删除元素
zcount key min max:统计指定score范围内元素的个数
zrank key member:按照score升序,返回某个元素在集合中的排名
zrevrank:按照score降序,返回某个元素在集合中的排名
zscore key member 返回集合中指定元素member的score值
Redis发布和订阅(pub/sub)
客户端订阅频道channel1、channel2
subscribe 频道1 频道2 ...
打开客户端,给channel1发布消息。返回值表示有几个订阅者
publish 频道 消息
订阅一个/多个符合给定模式的频道,每个模式以 * 作为匹配符。
psubscribe pattern [pattern ...]
Redis事务
Multi:从输入Multi开始,输入的命令都会一次进入队列,直到输入exec后redis回将之前的命令依次执行。组队过程中通过discard放弃组队取消事务。在执行multi之前先执行watch监视在exec之前key是否被更改,则取消执行事务。
unwatch:取消监听
悲观锁:每次都认为数据会被修改。行锁、表锁、读锁、写锁、
乐观锁:每次都认为数据不会被修改。
Redis持久化-RDB(Redis DataBase)
redis持久化方式:RDB(redis DataBase),AOP(Append of File)。
AOF和RBD同时开启,系统默认取AOF数据(数据不会存在丢失)
slave下面也可以挂slave
RDB优点
1.适合大规模数据恢复
2.更适合对数据完整性和一致性要求不高
3.节省磁盘空间
4.恢复速度快
RDB缺点
1.最后一次持久化的数据可能丢失。在备份周期在一定间隔时间做一次备份,如果redis意外down,就会丢失最后一次快照后所有修改。
2.fork时,内存中的数据会被克隆一份,大致2倍的膨胀,需要考虑
3.虽然redis在fork时使用了写时的拷贝技术,但是如果数据庞大时还是比较消耗性能
AOF优点
1.备份机制更稳健,丢失数据概率更低
2.可读的日志文本,通过操作AOF文件,可以处理误操作
AOF缺点
1.比RDB占用更多的磁盘空间
2.备份速度慢
3.每次读写都同步到话,有一定性能压力
3.存在个别bug,造成不能恢复
RDB和AOF的抉择
推荐都启用
对数据不敏感,单独用RDB
不建议单独使用AOF,可能有bug
如果是纯内存缓存,可都不用
RDB是什么?
在指定时间间隔中将内存中的数据集Snapshot快照写入磁盘。
RDB备份执行:redis会单独创建fork一个子进程进行持久化,会将数据写入到一个临时文件中,待持久化过程都结束后,再用这个临时文件替换上次持久化好的文件、整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模恢复,且对数据恢复的完整性不是非常敏感,那RDB比AOF更高效。
FORK:
fork作用是复制一个与当前进程一样的进程,新进程的所有数据数值和原进程一致,它是一个全新的进程,并作为原进程的子进程。
一般情况父进程和子进程会共用一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
redis.conf文件
指定备份文件的名称:redis.conf中修改rdb备份文件名称:dbfilename 文件名.rdb
指定备份文件存放的目录:redis.conf中 dir ./ (默认值:./ ,表示执行redis-server命令启动redis时所在的目录)
触发RDB备份方式
方式1:自动备份,需配置备份规则。redis.conf中
save 秒 写操作次数
save 3600 1(30分钟内修改1次) save 300 100 (5分钟内修改10次) save 60 10000 (1分内钟修改1万次)
方式2:手动执行备份命令(save/bgsave)
save:save时只管保存,其他不管,全部阻塞,手动保存,不建议使用
bgsave:redis在后台异步进行快照操作,快照同时可以响应客户端情况。
lastsave:获取最后一次成功生成快照的时间。
方式3:flushall命令:会产生dump.rdb文件,但里面是空的无意义。
stop-writes-on-bgsave-error yes/no # 磁盘满时,是否关闭redis的写操作,推荐yes
rdbcompression yes/no # rdb备份是否开启压缩,是的话,redis会采用LZF算法进行压缩,推荐yes
rbdchecksum yes/no # 是否检查rdb备份文件的完整性。存储快照后,可以让redis使用CRC64算法进行数据校验,这样做会增加约10%性能消耗。推荐yes
rdb的备份、恢复
1.查询rdb文件目录
config get dir # 查询rdb文件的目录
2.将rdb备份文件*.rdb文件拷贝到其他地方
cp dump.rdb dump2.rdb
3.rdb的恢复
3.1 关闭redis
3.2 把备份的文件拷贝到工作目录 cp dump2.rdb dump.rdb
3.3 启动redis,备份数据直接加载,数据被恢复
rdb的停止
redis-cli config set save "" # 动态停止RDB,save后给空值,表示禁用保存策略
Redis持久化-AOF(Append Only File)
AOF是什么
以日志的形式记录每个写操作(增量保存),将redis执行过的所有写指令记录下来(读操作不记录),只允许追加文件不可改写文件,redis启动之初会读取该文件重新构造函数,换言之,redis重启就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工做。
1.AOF文件是一个只进行追加的日志文件
2.Redis可以在AOF文件体积过大时,自动在后台对AOF文件进行重写
3.AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以redis协议的格式保存,因此AOF文件的内容非常容易被读懂,对文件进行分析也很轻松
4.对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积
5.根据所使用的fsync策略,AOF的速度可能会慢于RDB
AOF持久化流程
1.客户端的请求写命令被append追加到AOF缓冲区内
2.AOF缓冲区会根据AOF持久化策略(always,everysec,no)将操作sync同步到磁盘AOF文件中
3.AOF文件大小超过重写策略或手动重写时,会对AOF文件进行重写(rewrite),压缩AOF文件容量
4.redis服务器重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的
redis.conf文件
appendonly no/yes # 是否开启APF,默认no不开启 appendfilename "appendonly.aof" # aof文件名 dir ./ # aof文件所在目录,默认./表示执行启动命令时所在的目录
reids-server /etc/redis.conf # 启动reids,dir此时就是/opt目录
AOF同步频率
redis.config中配置
appendfsync always # 每次写入立即同;性能较差,数据完整性较好
appendfsync everysec # 每秒同步;更新的命令会放在AOF缓冲区,每秒将缓冲区的命令追加到AOF文件;如果宕机本秒数据可能丢失
appendfsync no # redis不主动同步.把同步交给操作系统
AOF启动/修复/恢复
AOF备份机智和性能虽和RDB不同,但备份和恢复的操作同RDB一样,都是拷贝文件,需要恢复时再拷贝到redis工作目录下,启动系统即加载。
正常恢复:
1.修改默认的appendonly no 改为yes
2.将所有数据的aof文件复制一份保存到对应目录(查看目录:config get dir)
3.恢复:重启redis重新加载
异常恢复:
1.修改默认的appendonly no 改为yes
2.如遇aof文件损坏,通过/usr/loca/bin/redis-check-aof --fix appendonly.aof进行恢复
rewrite压缩(AOF文件压缩)
rewrite压缩是什么?
AOF采用稳健追加形式,为了避免文件越来越大,新增了重写机制。当AOF文件超过锁审定的阈值大小时,redis会启动AOF文件的内容压缩,只保留可恢复数据的最小指令集,可以使用命令bgrewriteaof触发重写。
触发机制?如何实现重写?
auto-aof-rewrite-percentage:设置重写基准值。默认100,当文件达到100%时开始重写(文件是原来重写后文件的2倍时重写)
auto-aof-rewrite-min-size:设置重写基准值。默认64MB,aof文件超过此值开始重写。
手动触发:bgrewiriteaof。redis会记录上次重写aof大小,默认配置是当aof文件大小时上次rewrite后大小的两倍且文件大于64M(auto-aof-rewrite-min-size)时触发。
重写步骤
1.bgrewiiteaof 手动触发重写,判断当前是否有bgfsave/bgrewrireaof在运行,如有需等命令结束后再执行
2.主进程fork出子进程执行重写,保证主进程不会阻塞
3.子进程遍历redis内存中的数据到临时文件,客户端的写请求同事写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整性以及新AOF文件生产期间的数据修改动作不会丢失
4.主进程把aof_rewrite_buf中的数据写入到新的AOF文件
5.使用新的aof文件覆盖旧的aof文件,完成aof重写
Redis主从复制
主机更新后根据配置和策略,自动同步到备机的master/slave机制,master以写为主,slave以读为主。
优点
1.读写分离,性能扩展,降低主服务器的压力。2.容灾,快速恢复,主机挂掉时丛机变为主机
主从复制原理
1.slave启动链接到master后,会给master发送sync数据同步消息
2.master接收到slave发来的数据同步消息后,把主服务器的数据进行持久化到rdb文件,同时手机接收到的用于修改数据的命令,master将传rdb文件发送给slave,完成第一次同步
3.全量复制:slave服务在收到master发来的rdb文件后,将其存盘并加载到内存
4.增量复制:master继续将收集到的修改命令依次传给slave,完成同步
5.只要重新链接master,一次完全同步(全量复制)将会被自动执行
6.主机挂掉后,从机会待命
7.从机挂机重启后,会继续将缺失数据同步过来。
slaveof no noe # 从从属服务器变为主服务器
常用的主从结构
一主二从
配置主从
1.执行命令创建/opt/master-slave工作目录。
mkdir /opt/master-slave # 创建目录
cd /opt/master-slave/ # 打开目录
2.将redis.conf复制到master-slave目录
cp /opt/redis-6.2.1/redis.conf /opt/master-slave/
3.创建master的配置文件:redis-8001.conf
include /opt/master-slave/redis.conf # redis.conf是redis原配置文件,内部包含了很多默认的配置,这里使用include将其引用 daemonize yes bind 【IP:如127.100.100.120】 requirepass 【密码】 # 配置密码 dir /opt/master-slave/ logfile /opt/master-slave/8001.log port 8001 # 端口 dbfilename dump_8001.rdb # rdb文件 pidfile /var/run/redis_8001.pid # pid文件
4.创建slave1的配置文件:redis-8002.conf
include /opt/master-slave/redis.conf # redis.conf是redis原配置文件,内部包含了很多默认的配置,这里使用include将其引用 daemonize yes bind 【IP:如127.100.100.120】 requirepass 【密码】 # 配置密码 dir /opt/master-slave/ port 8002 # 端口 dbfilename dump_8002.rdb # rdb文件 pidfile /var/run/redis_8002.pid # pid文件 logfile /opt/master-slave/8002.log #用来指定主机:slaveof 主机ip 端口 slaveof 【主机IP 端口】127.100.100.120 8001 #主机的密码 masterauth 【主机密码】
5.启动master、slave1
redis-server /opt/master-slave/redis-8001.conf
redis-server /opt/master-slave/redis-8002.conf
PS:如启动异常,可以检查配置,查看日志。启动后悔在/opt/master-slave目录生成日志。
6.查看主机信息
redis-cli -h 【IP】 -p 【端口】 -a 【密码】 # 通过redis-cli命令连接 info Replication # 查看信息
命令
reids-cli -h 服务器IP -p 端口 -a 密码 # 连接服务器 config set masterauth 密码 # 为master设置面 slaveof 服务IP 端口 # 指定做xxx的从机 info replication # 查看主从信息
PS:通过 slaveof 命令指定主从的方式,slave重启之后主从配置会失效,所以,重启后需要在slave 上重新通过 slaveof 命令进行设置
中途通过 slaveof 变更转向,本地的数据会被清除,会从新的master重新同步数据。
哨兵(Sentinel)模式
实现故障自动转移:自动监控master是否发生故障,如果故障根据投票数从slave中挑选一个作为master,其他的slave会自动转向同步新的master。
原理
sentinel会按照指定的频率给master发送ping请求,看master是否宕机,如master在指定时间内未正常响应sentinel发送的ping请求,sentinel则认为master挂掉了。接下来sentinel会进行故障转移,会从slave中投票选出一个服务器,将其升级为新的服务器,并让失效服务器的其他从服务器slaveof指向新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址,使得集群可以使用新主服务器代替失效主服务器。
但这种情况存在误判可能(比如网络不通导致ping失败,master并没挂)。为了避免误判,通常会启动多个sentinel,一般奇数个,那么可以指定当有多个sentinel都认为master挂掉了,才判定master真的挂掉了。通常这个设置为sentinel的一半,比如sentinel的数量是三个,那么这个量就可以设置为2个。
使用
1.创建 /opt/sentinel 目录
# 创建 /opt/sentinel 目录 mkdir /opt/sentinel cd /opt/sentinel/
2.将redis.conf复制到sentinel目录
# 将redis.conf复制到sentinel目录
cp /opt/redis-6.2.1/redis.conf /opt/sentinel/
3.创建master配置文件
include /opt/sentinel/redis.conf daemonize yes bind 服务IP dir /opt/sentinel/
port 端口 dbfilename dump_端口.rdb pidfile /var/run/redis_端口.pid logfile "./端口.log"
4.创建slave配置文件
include /opt/sentinel/redis.conf daemonize yes bind 服务IP dir /opt/sentinel/ port 端口 dbfilename dump_端口.rdb pidfile /var/run/redis_端口.pid logfile "./端口.log"
5.启动主从服务器
redis-server /opt/sentinel/redis-主服务器端口.conf
redis-server /opt/sentinel/redis-从服务器端口.conf
6.配置主从关系
# 1. 连接slave redis-cli -h 服务IP -p 端口 # 2.指定slave作为master从机 slaveof masterIP master端口 # 3.查看主从信息 info replication
7.检查主从复制是否正常
# 1.连接master redis-cli -h masterIP -p master端口 # 2.查看master主从信息 info replication # 3.可以写入数据检查是否正常
8.创建sentinel配置文件
# 配置文件目录 dir /opt/sentinel/ # 日志文件位置 logfile "./sentinel-26379.log" # pid文件 pidfile /var/run/sentinel_26379.pid # 是否后台运行 daemonize yes # 端口 port 26379 # 监控主服务器master的名字:mymaster,IP:192.168.200.129,port:6379,最后的数字2表示当 Sentinel集群中有2个Sentinel认为master存在故障不可用,则进行自动故障转移 sentinel monitor mymaster 192.168.200.129 6379 2 # master响应超时时间(毫秒),Sentinel会向master发送ping来确认master,如果在20秒内,ping 不通master,则主观认为master不可用 sentinel down-after-milliseconds mymaster 60000 # 故障转移超时时间(毫秒),如果3分钟内没有完成故障转移操作,则视为转移失败 sentinel failover-timeout mymaster 180000 # 故障转移之后,进行新的主从复制,配置项指定了最多有多少个slave对新的master进行同步,那可以理 解为1是串行复制,大于1是并行复制 sentinel parallel-syncs mymaster 1 # 指定mymaster主的密码(没有就不指定) # sentinel auth-pass mymaster 123456
9.启动sentinel
# 方式1 redis-server sentinel.conf --sentinel # 方式2 redis-sentinel sentinel.conf
eg:/opt/redis-6.2.1/src/redis-sentinel /opt/sentinel/sentinel-26379.conf
10.查看sentinel信息
redis-cli -p sentinel的端口
info sentinel
11.验证故障自动转移是否成功
- 停止master:shutdown
- 等待2分钟,等待完成故障转移
- 查看slave的主从信息:info replication
- 验证slave之间是否同步
12.旧的master恢复后,会自动挂在新的master下面
Redis集群(Cluster)
redis集群是redis的水平扩容,即启动N个redis节点,将整个数据分布存储在这N个节点中,每个节点存储总数居的1/N。
解决问题:单台redis容量限制,如何扩容?加内存硬件?
单台redis并发写量太大有性能瓶颈,如何解决
Redis缓存穿透
问题描述:系统引入redis缓存后,请求进来会先从redis缓存中查询,查到旧直接返回。缓存没中旧从db中查询,db中有就会将其丢到缓存中。但有些key对应更多数据在db中并不存在,每次针对此key的请求从缓存查不到都会压到db,可能压垮db。
解决方法:
1.对空值缓存:如果一个查询返回的数据为空,仍把null空结果进行缓存,为其设置一个很短的过期时间,最长不超过5分钟。
2.设置可访问的白名单
使用redis中的bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量。每次访问和bitmap里面的id进行比较,如果访问的id不在bitmaps里,则进行拦截,不允许访问。
3.采用布隆过滤器(Bloom Filter)
将所有可能存在是数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉。从而避免了对db的压力。
布隆过过滤器实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)
布隆过滤器可以用于检测一个元素是否在一个集合中,优点:空间效率和查询效率的时间都远超过一般算法。缺点:有一定的误识别率和删除困难
4.进行实时监控
当发现redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合可以设置白名单限制其提供服务(如:IP黑名单)
Redis缓存击穿
问题描述:redis中某热点key过期,此时大量请求同时过来,发现缓存中没有命中,这些请求都打到db上,导致db压力瞬时大增,可能打垮db。
缓存击穿出现的现象:
数据库访问压力瞬间增大
redis中没有出现大量的key国企
redis正常运行
解决方案:
- 预先设置热门数据,适时调整过期时间:再redis高峰前,把热门数据提前存入redis,对缓存中的热门数据进行监控,实时调正过期时间。
- 使用锁:缓存中拿不到数据时,此时不是立即去db中查询,而是去获取分布式锁(如redis中的senx),拿到锁再去db中load数据。没有拿到锁的线程休眠一段时间再重试整个获取数据的方法
Redis缓存雪崩
问题描述:key对应的数据存在,但是极短时间内有大量的key集中过期,此时若有大量并发请求过来,发现缓存没有数据,大量的请求就会落到db上去加载数据,会将db击垮,导致服务崩溃。
缓存雪崩和缓存击穿的区别:前者是大量的key集中过期,后者是某个热点key过期。
解决方案:
- 构建多级缓存:nginx缓存+redis缓存+其他缓存(ehcache等)
- 使用锁或队列:用加锁或者队列的方式保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适用高并发情况
- 监控缓存过期,提前更新
- 将缓存失效时间分散开:可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样缓存的过期时间重复率就会降低,便很难引发集体失效的事件。
Redis分布式锁
由单机部署的系统升级为分布式集群系统后。由于分布式系统多线程、多进程且分布在不同机器上,这样使得原单机部署情况下的并发控制策略失效,
实现方案
- 基于数据库实现分布式锁
- 基于缓存(redis等):性能最高
- 基于zookeeper:最可靠
为了确保分布式锁可用,需确保实现同时满足以下条件
- 互斥性:在任意时刻只能有一个客户端能够持有锁
- 不糊发生死锁,即使有一个客户端在持有锁期间崩溃而没有释放锁,也能保证后续其他客户端能够加锁
- 加锁和解锁必须是同一个客户端:
- 加锁和解锁必须有原子性
lua锁
使用redis实现分布式锁:
# redis上锁 set key value [EX seconds] [PX milliseconds] [NX|XX] EX seconds:设置失效时长,单位秒 PX milliseconds:设置失效时长,单位毫秒 NX:key不存在时设置value,成功返回OK,失败返回(nil) XX:key存在时设置value,成功返回OK,失败返回(nil)
分布式锁
1.获取锁:
setnx product:10001 true //返回1表示取所成功,0表示失败。setnx:set not exist
2.业务逻辑
...省略
3.执行完业务释放锁
del product:10001
4.set product:10001 true ex 10 nx
对象存储:
eg:存入下列用户信息。
id | name | age |
u1 | jon | 29 |
u2 | jan | 28 |
第一种方式,json存储 :
存:mset u1 '{"name":"join","age":29}' u2 '{"name":"jan","age":28}'
取:mget u1 u2
第二种方式,mset存储(推荐):
存:mset user:u1:name join user:u1:age 29
mset user:u2:name jan user:u1:age 28
取:mget user:u1:name user:u1:age
mget user:u2:name user:u2:age