导图社区 redis缓存数据库
redis缓存数据库学习笔记,讲述了磁盘及内存、redis集群、缓存常见问题、redis进阶、redis原理等,希望对你有所帮助!
编辑于2021-11-15 22:42:14redis缓存数据库
常识介绍-磁盘、内存、IO
1. 磁盘
寻址:ms
带宽:G/M
2. 内存
寻址:ns
带宽:
秒>毫秒>微秒>纳秒,磁盘比内存在寻址上满了 10W 倍
3. I/O buffer
数据存储发展进程
redis安装实操
redis原理
单进程、单线程、单实例
并发量很多的时候,如何保证访问速度?
redis五大数据类型
String help @string
常用命令
字符串
set
summary: Set the string value of a key since: 1.0.0 grammar: set key value
e.g.: set k1 hello
get
summary: Get the value of a key since: 1.0.0 grammar: get key
e.g.: get k1
setnx
summary: Set the value of a key, only if the key does not exist since: 2.0.0 grammar: setnx key value
e.g.: setnx k5 abcdefg
msetnx
summary: Set multiple keys to multiple values, only if none of the keys exist(仅当所有键都不存在时,才将多个键设置为多个值) since: 1.0.1 grammar: msetnx key value [key value ...]
e.g.: msetnx k1 a k2 1 k3 c 说明:该操作为原子性操作,仅当key: k1、k2、k3 不存在redis中,才会全部设置成功,否则全部设置失败
setex
summary: Set the value and expiration of a key since: 2.0.0 grammar: setex key seconds value
e.g.: setex k6 5 abcdefg
psetex
summary: Set the value and expiration in milliseconds of a key(以毫秒为单位设置键的值和过期时间) since: 2.6.0 grammar: psetex key milliseconds value
e.g.: psetex k1 5000 hello
mset
summary: Set multiple keys to multiple values since: 1.0.1 grammar: mset key value [key value ...]
e.g.: mset k1 hello k2 9
mget
summary: Get the values of all the given keys since: 1.0.0 grammar: mget key [key ...]
e.g.: mget k1 k2 k3
append
summary: Append a value to a key since: 2.0.0 grammar: append key value
e.g.: append k1 abcdefg
setrange
summary: Overwrite part of a string at key starting at the specified offset since: 2.2.0 grammar: setrange key offset value
e.g.: setrange k1 0 abcdef 说明:当起始坐标大于字符长度时,会在中间插入 \x00 空字符串的 ASCII 码值
getrange
summary: Get a substring of the string stored at a key since: 2.4.0 grammer: getrange key start end
e.g.: getrange k1 0 5 e.g.: getrange k1 0 -1(查询value值内容,-1表示倒序的第一个位置) 说明:当 start 超过 value 值的实际长度时,返回空字符串;redis支持正反序索引。
strlen
summary: Get the length of the value stored in a key since: 2.2.0 grammar: strlen key
e.g.: strlen k1
getset
summary: Set the string value of a key and return its old value since: 1.0.0 grammar: getset key value
e.g.: getset k1 2021 说明:为了减少通信的 IO 次数
数值
指令
incr
summary: Increment the integer value of a key by one since: 1.0.0 grammar: incr key
e.g.: incr k1
incrby
summary: Increment the integer value of a key by the given amount since: 1.0.0 grammar: incrby key increment
e.g.: incrby k1 90
decr
summary: Decrement the integer value of a key by one since: 1.0.0 grammar: decr key
e.g.: decr k1
decrby
summary: Decrement the integer value of a key by the given number since: 1.0.0 grammar: decrby key decrement
e.g.: decr k1 90
应对需求
抢购、秒杀、详情页、点赞、评论等 规避并发下,对数据库的事务操作,完全由redis内存操作代替
bitmap
指令
setbit
summary: Sets or clears the bit at offset in the string value stored at key since: 2.2.0 grammar: setbit key offset value
e.g.: setbit k1 1 1
bitpos
summary: Find first bit set or clear in a string since: 2.8.7 grammar: bitpos key bit [start] [end] 注意: start、end 为字节 byte 的坐标位置,而 bit 是指字节 byte 中的位坐标
e.g.: bitpos k1 0 0 -1
bitcount
summary: Count set bits in a string since: 2.6.0 grammar: bitcount key [start end]
e.g.: bitcount k1 0 -1
bitop
summary: Perform bitwise operations between strings since: 2.6.0 grammar: bitop operattion destkey key [key ...]
e.g.: bitop and andKey k1 k2
应对需求
1. 有用户系统,需统计用户登录天数,且窗口随机 setbit john 1 1 (用户 John 在第 2 天登录) setbit john 7 1 setbit john 363 1 setbit john 364 1 strlenn john (46B=368bit,48B就可以存下一个人一年的到岗情况) bitcount john 0 -1 (统计 John 全年到岗的天数) bitcount john 2 -1 (统计 John 全年-2*8 到岗的天数)
2. 京东618活动,送礼物,假设有2亿用户,仓库中需要备多少礼物? 用户类别:僵尸用户、冷热用户、忠诚用户。 需要做活跃用户的统计! 随机窗口,比如 1号-3号,连续登录要去重 setbit 20210101 1 1 (1号用户在20210101日期登录) setbit 20210102 1 1 setbit 20210102 6 1 bitop or activeUser 20210101 20210102 20210103 bitcount activeUser 0 -1
二进制、安全 存的字节流而非字符流
List help @list
栈 同向命令(lpush、lpop;rpush、rpop)
队列 反向命令(lpush、rpop;rpush、lpop)
lpush、lpop、rpush、rpop lrange
lpush
grammar: lpush key element [element ...] summary: Prepend one or multiple elements to a list since: 1.0.0
lpop
grammar: lpop key [count] summary: Remove and get the first elements in a list since: 1.0.0
rpush
grammar: rpush key element [element ...] summary: Append one or multiple elements to a list since: 1.0.0
rpop
grammar: rpop key [count] summary: Remove and get the last elements in a list since: 1.0.0
lrange
grammar: lrange key start stop summary: Get a range of elements from a list since: 1.0.0
数组
lindex
grammar: lindex key index summary: Get an element from a list by its index since: 1.0.0
lset
grammar: lset key index element summary: Set the value of an element in a list by its index since: 1.0.0
lrem
grammar: lrem key count element summary: Remove elements from a list since: 1.0.0
e.g.: lrem k1 2 a(移除 k1 list中前 2 个 a) e.g.: lrem k1 -2 a(移除 k1 list中后 2 个 a)
linsert
grammar: linsert key before|after pivot element summary: Insert an element before or after another element in a list since: 2.2.0
e.g.: linsert k1 before aaa 20(在 k1 对应的 value 第一个为 aaa 的前面插入数字 20,若有多个 aaa 的值时,只会在第一个前面插入;未找到时,返回-1,否则返回 list 长度) e.g.: linsert k1 after aaa 20(在 k1 对应的 value 第一个为 aaa 的后面插入数字 20,若有多个 aaa 的值时,只会在第一个后面插入;未找到时,返回-1,否则返回 list 长度)
阻塞,单播队列 FIFO
blpop
grammar: blpop key [key ...] timeout(timeout 为 0 时,处于一直监听状态) summary: Remove and get the first element in a list, or block until one is available since: 2.0.0
e.g.: blpop k1 0(当向 key=k1 的键中放入 list 类型元素时,才会实时返回监听到的键值对,若未设置k1键值对,或向 k1 放入 string 或其他类型时,始终阻塞); e.g.: blpop k1 5(5 秒后,没有监听到 list 类型的键值对时,返回空)
brpop
grammar: brpop key [key ...] timeout summary: Remove and get the last element in a list, or block until one is available since: 2.0.0
Hash map(k-v) help @hash
常用指令
hset
hmset
hsetnx
hget
hmget
hkeys
grammar: hkeys key summary: Get all the fields in a hash since: 2.0.0
hvals
grammar: hvlas key summary: Get all the values in a hash since: 2.0.0
hgetall
grammar: hgetall key summary: Get all the fields and values in a hash since: 2.0.0
hincrby
grammar: hincrby key field increment summary: Increment the integer value of a hash field by the given number since: 2.0.0
e.g.:hincrby john age -1(将用户John的年龄-1)
hincrbyfloat
grammar: hincrbyfloat key field increment summary: Increment the float value of a hash field by the given number since: 2.0.0
hdel
grammar: hdel key field [field ...] summary: Delete one or more hash fields since: 2.0.0
hlen
grammar: hlen key summary: Get the number of fields in a hash since: 2.0.0
hstrlen
grammar: hstrlen key field summary: Get the length of the value of a hash field since: 3.2.0
对 field 进行数值计算(hincrby、hincrbyfloat) 应用场景:点赞、收藏、详情页
Set help @set
无序 去重
sadd
grammar: sadd key member [member ...] summary: Add one or more members to a set since: 1.0.0
smembers
grammar: smembers key summary: Get all the members in a set since: 1.0.0
srem
grammar: srem key member [member ...] summary: Remove one or more members from a set since: 1.0.0
集合操作(交集、并集、差等)
sinter
grammar: sinter key [key ...](交集) summary: Intersect multiple sets since: 1.0.0
sinterstore
grammar: sinterstore destination key [key ...] summary: Intersect multiple sets and store the resulting set in a key since: 1.0.0
sdiff
grammar: sdiff key [key ...] summary: Subtract multiple sets since: 1.0.0
sdiffstore
grammar: sdiffstore destination key [key ...] summary: Subtract multople sets and store the resulting set in a key since: 1.0.0
随机事件
srandmember
grammar: srandmember key [count] summary: Get one or multiple random members from a set since: 1.0.0 正数:去除一个去重的结果集(不能超过已有集) 负数:取出一个带重复的结果集,一定满足所设置的 count 数量,当count数量大于key对应的value数量时,可能存在某些value值未取出的情况 0:不返回
应用场景
抽奖
有100个人,10个中奖名额,用正数,中奖的10个人不会出现重复情况
有10个人,100个奖项,用负数,存在某些人一个奖都没中的情况
做随机决策(抽签)
spop
grammar: spop key [count] summary: Remove and return one or multiple random members from a set since: 1.0.0
抽奖(中奖后,从将箱子里取出号码,不再参与后续抽奖)
zSet(sorted set) help @sorted_set
物理内存左小右大 不随命令发生变化
zrange
zrevrange
集合操作 并集、交集 权重/聚合指令
排序是怎么实现的? 增删改查的速度如何?
skip list 跳跃表
redis进阶使用
redis中的管道 |
redis默认每次执行请求都会创建和断开一次连接池的操作,如果想执行多条命令的时候会在这件事情上消耗过多的时间,因此我们可以使用redis的管道来一次性发送多条命令并返回多个结果,节约发送命令和创建连接的时间以提升效率。
pub/sub:发布/订阅 help @pubsub
publish
grammar: publish channel message summary: Post a message to a channel since: 2.0.0
subscribe
grammar: subscribe channel [channel ...] summary: Listen for message published to the given channels since: 2.0.0
注意:在订阅 channels 之前发布的所有信息,都是收不到的,只有订阅开始以后发布的所有信息才能收到
redis事务 help @transactions
multi
grammar: multi summary: Mark the start of a transaction block since: 1.2.0
exec
grammar: exec summary: Execute all commands issued after MULTI since: 1.2.0
watch
grammar: watch key [key ...] summary: Watch the given keys to determine execution of the MULTI/EXEC block since: 2.2.0
说明:在开启事务之前监控某些 key 值,若有变化,则执行事务的时候失败,不予执行事务内的语句
redis中的module
bloom布隆过滤器
解决的问题是缓存穿透
安装与使用步骤: 1. 访问 redis.io 官网 2. Modules 板块 3. 访问 RedisBloom 的 GitHub:https://github.com/RedisBloom/RedisBloom 4. Linux 中下载RedisBloom压缩包 wget *.zip 5. 安装解压缩工具 yum install unzip 6. 解压缩RedisBloom压缩包:unzip *.zip 7. 进入解压缩包后,里面有 Makefile 文件,可执行 make 命令编译(若是下载 master 版本,编译可能报错,换成 tag 标签的稳定发行版本) 8. 编译完成后,拷贝编译后生成的 redisbloom.so 到相应文件夹:cp redisbloom.so /usr/local/bin/redis6/ 9. 扩展布隆过滤器的方式启动redis服务:redis-server /etc/redis/6380.conf --loadmodule /usr/local/bin/redis6/redisbloom.so (注意 --loadmodule 位置) 10. redis-cli --raw -a password bf.add bfk1 abc bf.exits bfk1 abc (成功返回:1) bf.exits bfk1 www (失败返回:0) 11. cf.add cfk1 def #布谷鸟过滤器
情形一:若值不存在,但穿透了bloom过滤器,可在 client 端增加 redis 中的 key-value 标记,再次穿透的时候,可根据 key-value 判断是否有值; 情形二 :数据库增加了元素的时候,必须完成元素对 bloom 的添加。
redis作为数据库/缓存的区别
缓存
缓存的数据是“不重要”的,缓存数据可以丢,不是全量数据,急速! 缓存应该随着访问的情况变化,更多的只缓存热数据,因为内存大小是有限的
数据库
数据绝对不能丢的!速度+持久性
持久化
RDB(快照)
在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是 fork 一个子进程(只复制一份内存中的地址引用,数据不复制,复制的引用指向当时的数据),先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。 说明:Linux 中的父子进程 1. 常规思想:进程数据是隔离的; 2. 进阶思想:父进程其实可以让子进程看到数据; 3. export 的环境变量,子进程的修改不会破坏父进程的数据;父进程的修改也不会破坏子进程的数据。
优点: 1. 一旦采用该方式持久化,那么 redis 数据库将只包含一个文件 dump.rdb,这对于文件备份而言是非常完美的; 2. 对于灾难恢复而言,rdb 是非常不错的选择,因为我们可以非常轻松的讲一个单独的文件压缩后再转移到其它存储介质上; 3. 性能最大化:对于 redis 的服务进程而言,在开始持久化时,它唯一需要做的只是 fork 出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行 IO 操作了; 4. 类似于 Java 中的序列化,恢复的速度相对快,相比于 aof 机制,如果数据集很大,rdb 的启动效率会更高。
缺点: 1. 如果想保证数据的高可用性,即最大限度的避免数据丢失,那么 rdb 将不是一个很好的选择,因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失;或者在 rdb 写入过程中宕机,数据也将有所丢失; 2. 由于 rdb 是通过 fork 子进程来协助完成数据持久化工作的,因此,如果数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
AOF(追加日志)
以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
优点:丢失数据少 1. 该机制可以带来更高的数据安全性,即数据持久性。redis 中提供了 3 种同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中,可以预见,这种方式在效率上是最低的。至于无同步,就是不做数据的同步备份,高效但是不会持久化数据; 2. 由于该机制对日志文件的写入操作采用的是 append 模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容;然而如果本次操作知识写入了一半数据就出现了系统崩溃问题,也不用担心,在redis下一次启动之前,我们可以通过 redis-check-aof 工具来帮助我们解决数据一致性的问题; 3. 如果日志过大,redis 可以自动启用rewrite机制;即redis以append模式不断的将修改数据写入到老的磁盘文件中,同事redis还会创建已给新的文件用于记录此期间有哪些修改命令被执行,因此在进行rewrite切换时可以更好的保证数据安全性; 4. aof 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作,事实上,也可以通过改文件完成数据的重建。
缺点:体量无限变大、恢复慢 1. 对于相同数量的数据集而言,aof 文件通常要大于 rdb 文件,rdb 在恢复大数据集时的速度要比 aof 快; 2. 根据同步策略的不同,aof 在运行效率上往往会慢于 rdb,总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和 rdb 一样高效。
redis中,rdb 和 aof 可以同时开启 如果开启了 aof,只会用 aof 恢复 且redis4.0以后 aof 中包含 rdb 全量及rdb后续新的增量写、删除操作,此时的 aof 是一个混合体,利用了 rdb 的恢复快,也保证了日志的全量。
redis集群
redis缓存之 AKF (X-Y-Z)问题
单机、单节点、单实例模式问题
单点故障
解决方案:主从复制
容量有限
访问压力
redis cluster
redis自带集群尝试
使用redis提供的脚本启动集群cluster: 1. 进入到redis安装目录下的 utils/create-cluster/ 目录下; 2. 执行脚本启动redis集群:./redis-cluster start; 3. 为redis集群分配槽点(slot)及主从关系:./redis-cluster create; 4. 连接redis集群的其中一个节点:redis-cli -c -p 30001 ...... 5. 停止redis集群:./redis-cluster stop; 6. 清除刚刚测试的集群日志、配置等内容(在 utils/create-cluster/ 目录下):./redis-cluster clean
使用命令自己在启动的时候灵活配置redis集群: 1. 执行脚本启动redis集群:./redis-cluster start; 2. 查看redis集群的帮助命令:redis-cli --cluster help; 3. 使用命令启动 redis cluster:redis-cli --cluster create 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006 --cluster-replicas 1; 4. 使用 reshard 解决数据倾斜的问题:redis-cli --cluster reshard 127.0.0.1:30001;输入以上命令后,再按照指令更换槽点到其他的主节点(master)上,不可更换槽点到从节点(slave)上; 5. 使用 info 查看槽点更改情况:redis-cli --cluster info 127.0.0.1:30001; 6. 使用 check 查看各节点(主从关系、槽点数)情况:redis-cli --cluster check 127.0.0.1:3000。
X 轴
主从复制的全量同步解决了单点故障问题,也就是 AKF 理论中的 X 轴(读写分离能解决部分访问压力,但不能完全解决访问压力问题)
Y 轴
按照不同的业务使用不同的redis服务;订单、用户、支付等都是用不同的服务;也就是 AKF 理论中的 Y 轴
Z 轴
解决单业务中的数据膨胀问题,使用分片模式;使用哈希取模等散列算法进行散列;例如用户信息、订单信息、商品详情等日请求数据量特别大的问题。 同业务分治需要解决的问题:客户端散列、代理层散列(Twemproxy)、redis集群散列(包含散列路由、无主模型) hash取模弊端:取模的数必须固定,影响分布式下的扩展性。
数据一致性(主从复制原理)
强一致性
所有节点同步阻塞,直到数据全部一致。
引发问题: 如果其中一台slave挂掉或者网络波动引起超时,即使其他salve都返回写入成功给master,master也会认为所有从机都写入失败,进而都进行回撤,最终客户端会认为写入失败。写入失败代表服务不可用,所以数据强一致性会破坏可用性!
问题:为什么我们要把单机一边多为集群? 就是单点故障,解决可用性的问题。但是强一致性中,只要其中一台机器出现异常,就会导致整个模块不可使用,问题又回到了原点; 解决办法 : 给强一致性降级,采用异步的方式,容忍丢失一部分数据,就是弱一致性。
弱一致性
1. master收到Client命令直接返回给客户端ok; 2. master会异步的通知两个slave写操作,如果两个slave挂了导致写入失败,master也挂了。再重启之后slave就拿不到之前的master的写操作了,等于丢了一批数据。 注:redis使用默认的异步复制,其特点是低延迟和高性能。
问题:如何解决数据丢失问题? 答:最终一致性
最终一致性
1. 在master和slave之间,添加一个可靠、响应速度够快 的 集群(比如:kafka),master到kafka之间为同步阻塞状态。master在写入的时候并没有直接通知两个slave,而是通知kafka,由kafka通知两个slave。如果master和kafka之间能够足够快的写入响应成功的话,就可以直接给Client返回OK了; 2. 只要最终两个slave从kafka中取到数据,那么最终两个slave就会和master的数据达成一致,数据就不会丢失。
问题:最终一致性:master和slave之间维护一个可靠的集群。 但是一个客户端从一个黑盒化的集群中取数据。有可能会从master取到,也有可能从slave中取到,在slave和master数据最终达成一致之前,有可能取到不一致的数据。
redis实现主从复制命令
slaveof(redis 5及以后过时)
grammar: slaveof host port summary: Make the server a replica of another instance, or promote it as master.Deprecated starting with Redis 5. Use replicaof instead. since: 1.0.0 group: server
replicaof
grammar: replicaof host port summary: Make the server a replica of another instance, or promote it as master. since: 5.0.0 group: server
redis 配置文件: 1. 配置主节点redis的ip、port,若主节点redis有密码,还需要配置 masterauth: replicaof 127.0.0.1 6379 masterauth password01 2. 配置从节点 redis 仅可读,不能写: replica-read-only yes 3. slave-serve-stale-data参数设置成yes,主从复制中,从服务器可以响应客户端请求,所请求的数据有可能是过时的; slave-serve-stale-data参数设置成no,主从复制中,从服务器将阻塞所有请求,有客户端请求时返回“SYNC with master in progress”。 redis默认配置为:replica-serve-stale-data yes
sentinel(哨兵)
解决问题:解决主从复制中的主节点挂了之后,需要人为的通过命令更换从节点为主节点,且其他节点跟从新的主节点
启动命令:redis-server /etc/redis6/26379.conf --sentinel
注意:redis客户端有设置密码的,必须要配置认证密码,且主从密码应该设置为一致;sentinel需要的其他配置,可参考源文件中的:/usr/local/src/soft/redis6.2.6/sentinel.conf
缓存常见问题,面试回答思路
击穿
前提条件: 1. redis 当作缓存用; 2. 某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况; 3. redis 中的这个 key 失效(redis 的缓存过期或被主动删除); 4. 大量的请求击穿缓存,直接请求数据库。
穿透
前提条件: 1. redis 当作缓存用; 2. redis 缓存及 DB 中均查不到(如黑客发起的攻击);
雪崩
前提条件: 1. redis 当作缓存用; 2. 大批量热点数据过期后系统涌入大量查询请求; 3. 大量的请求渗透到数据库层,引起数据库压力造成查询堵塞甚至宕机。
redis 分布式锁
1. 加锁:执行 setnx; 2. 若成功再执行 expire 添加过期时间; 3. 多线程(守护进程),开启另一个线程监听是否从数据库查询到数据并返回,若没有返回则延长过期时间; 4. 解锁:执行 delete 命令。