redis-笔记

redis-笔记

起男 1,176 2021-08-02

redis-笔记

视频: 【尚硅谷】2021 最新 Redis 6 入门到精通 超详细 教程_哔哩哔哩_bilibili

相关知识

数据库

默认16个数据库,类似数组下标从0开始,初始默认使用0号库

使用命令select <dbid>来切换数据库

统一密码管理,所有库同样密码

dbsize查看当前数据库的key的数量

flushdb清空当前库

flushall清空全部库

多路复用io

多路复用是指使用一个线程来检查多个文件描述符(socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)

和memcache的区别

  • 支持多种数据类型
  • 支持持久化
  • 单线程+多路复用io(memcache是多线程+锁)

基本数据类型

键key

命令作用
keys *查看当前库所有key
exists判断某个key是否存在
type查看key是什么类型
del删除指定key
unlink非阻塞删除,仅将key从keyspace元数据中删除,真正的删除会在后续异步操作
expire为key 设定过期时间,单位秒
ttl查看还有多数秒过期,-1表示永不过期,-2表示已过期

字符串string

string是redis最基本的类型,你可以理解成与memcached一模一样的类型,一个key对应一个value

string类型是二进制安全的。意味着redis的string可以包含任何数据。比如图片或者序列化的对象

string类型是redis最基本的数据类型,一个redis字符串value最大可以说512m

常用命令

命令作用
set添加键值对
setnx当key不存在时添加
get查询对应键值
append追加到原值末尾
strlen值的长度
incr自增1
decr自减1
incrby自增指定值
decrby自减指定值
mset同时添加多个值
mget同时获取多个值
msetnx当所有key都不存在时,添加多个值
getrange获得范围的值
setrange覆写范围的值
setex设置值的同时,设置过期时间,单位秒
getset设置新值的同时获得旧值

原子操作

所谓原子操作是指不会被线程调度机制打断的操作

这种操作一旦开始,就一直运行到结束,中间不会有任何contextswitch(切换到另一个线程)

  1. 在单线程中,能够在单条指令中完成的操作都可以认为是“原子操作”,因为中断只能发生于指令之间
  2. 在多线程中,不能被其它进行(线程)打断的操作就叫原子操作

redis单命令的原子性得益于redis的单线程

数据结构

string的数据结构为简单动态字符串(simple dynarnic string,缩写sds)。是可以修改的字符串,内部结构实现上类似于java的arraylist,采用预分配冗余空间的方式来减少内存的频繁分配

redis内部为当前字符串实际分配的空间一般要高于实际长度。当字符串长度小于1m时,扩容是加倍现有空间,如果超过2m,扩容一次最多扩1m的空间。并且字符串的最大长度为512m

列表list

单键多值

redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标操作中间的节点性能较差

常用命令

命令作用
lpush/rpush从左边/右边插入一个或多个值
lpop/rpop从左边/右边弹出一个值
rpoplpush从一个列表左边弹出一个值,插入另一个列表的右边
lrange按照索引下标获取元素(从左到右)
lindex按照下标获得元素
len获取列表的长度
linsert在指定值的前面或后面 添加值
lrem从左边开始删除n个指定的value
lset将指定下标的值替换

数据结构

list的数据结构为快速链表quicklist

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist(压缩列表)

它将所有的元素紧挨着一起存储,分配的是一块连续的内存

当数据量较多时会改成quicklist

因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存储的只是index类型的数据,结构上还需要两个额外的指针prev和next

redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速插入删除性能,又不会出现太大的空间冗余

集合set

set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动去重的,当年需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否存在的接口,这个也是list所不能提供的

redis的set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加、删除、查找的时间复杂度都是O(1)

常用命令

命令作用
sadd将一个或多个元素加入集合,以存在的元素会忽略
smembers取出该集合的所有值
sismember判断集合是否有指定值
scard获取集合元素个数
srem删除集合中的某个元素
spop随机弹出一个值
srandmember随机获取n个值
smove把集合中的指定的值迁移到另一个集合中
sinter返回两个集合的交集
sunion返回两个集合的并集
sdiff返回两个集合的差集

数据结构

set数据结构是dict字典,字典是用哈希表实现的

java中的hashset的内部使用的是hashmap,只不过所有的value都指向同一个对象。redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值

哈希hash

hash是一个键值对集合

hash是一个string类型的field和value的映射表,hash特别适合存储对象。类似java里面的Map<String,Object>

常用命令

命令作用
hset给指定值hash的field赋值
hget取出field的值
hmset批量设置hash的值
hexists指定的field是否存在
hkeys获取所有的field
hvals获取所有的value
hincrby为指定的field自增指定值
hsetnx当field不存在时,为field赋值

数据结构

hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable

有序集合zset

zset和set非常相似,是一个没有重复元素的字符串集合

不同之处是zset的每个成员都关联了一个评分(score),这个评分被用来按照从最低到最高的方式排序集合中的成员。集合的成员是唯一的,但是评分可以重复

因为元素是有序的,所以可以根据评分或者次序来获取一个范围的元素

访问有序集合的中间元素也是非常快的,因此你能够使用有序集合做为一个没有重复成员的智能列表

常用命令

命令作用
zadd将一个或多个元素及评分加入到zset
zrange返回指定范围内所有的元素,可以使用withscores参数获取评分
zrangebyscore获取指定评分范围内的元素,并按照评分从小到大排列
zrevrangebyscore和zrangebyscore一样,只不过从大到小排列
zincrby为元素的评分加上指定值
zrem删除指定的值
zcount统计指定评分范围内的元素个数
zrank返回指定值在集合中的排名,从0开始

数据结构

zset是一个非常特殊的数据结构,一方面它等价于java的数据结构Map<String,Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表

zset底层使用两个数据结构

  • hash:hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值
  • 跳跃表:跳跃表的目的在于给元素value排序,根据score的范围获取元素列表

发布订阅

发布订阅是一种消息的通信模式:发送者发送消息,订阅者接收消息

reids客户端可以订阅任意数量的频道

命令

命令作用
subscribe订阅指定频道
publish向指定频道发布消息

新数据类型

bitmaps

bitmaps本身不是一种数据类型,实际上它是字符串(key-value),但是它可以对字符串的位进行操作

bitmaps单独提供了一套命令,所以redis中使用bitmaps和使用字符串的方法不太相同。可以把bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组 的下标在bitmaps中叫做偏移量

常用命令

命令作用
setbit设置bitmaps中某个偏移量的值(0或1)
getbit获取bitmaps中某个偏移量的值
bitcount统计设置为1的数量,可以指定范围
bitop复合操作,可以做or并集、and交集、not非、xor异或

hyperloglog

hyperloglog是用来做基数统计的算法,优点是,在输入元素的数量或者体积非常大时,计算基数所需的空间总是固定的、并且是很小的

redis中每个hyperloglog只需要花费12kb内存,就可以计算近2^64个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比

但是,hyperloglog只会根据输入元素来计算基数,而不会存储输入元素本身,所以hyperloglog不能像set那样返回输入的各个元素

基数

比如数据集{1,3,5,7,5,7,8},这个数据集的基数集为{1,3,5,7,8},基数为5

基数估计就是在误差可接受范围内,快速计算基数

常用命令

命令作用
pfadd添加指定元素到hyperloglog中
pfcount计算近似的基数
pfmerge合并操作

geospatial

redis3.2中增加了对geo类型的支持。geo地理信息的缩写

该类型,就是元素的二维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置、查询、范围查询、距离查询和经纬度hash等常见操作

常用命令

命令作用
geoadd添加地理位置
geopos获得指定地区的坐标
geodist获取两个位置的直线距离
georadius以给定的经纬度为中心,找出某一半径的内元素

事务

redis事务是一个单独的隔离操作:事务中所有命令都会序列化,按顺序执行。事务在执行的过程中,不会被其他客户端发送来的命令请求打断

redis事务的主要作用就是串联多个命令防止别的命令插队

命令

命令作用
multi开始
exec执行
discard回滚

错误处理

  • 组队时出错:执行时整个队列都会被取消
  • 执行时出错:只有报错的命令不会执行

特性

  • 单独的隔离操作:事务中的所有命令都会序列化、按顺序执行。事务在执行过程中,不会被其他客户端的命令打断
  • 没有隔离基本的概念:队列中的命令没有提交之前都不会被实际执行,因为事务提交前任何指令都不会被实际执行
  • 不保证原子性:事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

悲观锁

每次去拿数据的时候都认为别人会修改,所以每次在拿到数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁

乐观锁

每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制

乐观锁适用于多读的应用类型,这样可以提高吞吐量。redis就是利用这种check-and-set机制实现事务的

命令

命令作用
watch在执行multi之前,先执行watch,可以监视一个或多个key,如果在事务执行之前这个key被其他命令改动,那么事务将被打断
unwatch取消watch命令的监控,如果执行watch命令后,exec或discard被执行的话,就不需要执行unwatch了

持久化

rdb

rdb( Redis DataBase ),每隔一段时间将内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存里

执行流程

redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次被持久化的文件(dump.rdb)

优点

  • 适合大规模的数据恢复
  • 节省磁盘空间
  • 恢复速度快

缺点

  • fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀需要考虑
  • 虽然redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
  • 会丢失最后一次备份后的修改

fork

fork的作用是复制一个与当前进程一样的进程。新进程的所有数据都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

配置文件

  • dir:文件生成的目录
  • dbfilename:rdb文件名,默认dump.rdb
  • save:时间间隔规则
  • stop-writes-on-bgsave-error:当磁盘满了时,是否关闭写操作
  • rdbcompression:是否文件压缩
  • rbdchecksum:检查完整性

手动持久化

命令作用
save只是保存,其他不管,全部阻塞
bgsaveredis会在后台异步进行快照操作,操作时,可以响应客户端请求
lastsave最后一次执行快照的时间

aof

aof( Append Only File ),以日志的形式来纪录每个写操作(增量保存),将redis执行过的所有写指令纪录下来(读操作不纪录),只许追加文件但不可用改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写操作从前到后执行一遍,以完成恢复工作

执行流程

  1. 客户端的请求写命令会被append追加到aof缓冲区内
  2. aof缓冲区根据aof持久化策略将操作sync同步到磁盘的aof文件中
  3. aof文件大小超过重写策略或手动重写时,会对aof文件rewrite重写,压缩aof文件容量
  4. redis重启时,会重新load加载aof文件中的写操作达到数据恢复的目的

优点

  • 备份机制更加稳健,丢失数据概率更低
  • 可读的日志文本,通过操作aof文件,可以处理误操作

劣势

  • 比rdb占用跟多磁盘空间
  • 恢复备份速度更慢
  • 每次写都同步,有一定性能压力

配置

  • appendonly:是否开启aof,默认不开启
  • appendfilename:aof文件名称,默认appendonly.aof
  • dir:aof文件的保存路径,同rdb的路径一致
  • appendifsync:同步频率设置
    • always:始终同步,每次redis的写入都会被立刻记入日志;性能较差但数据完整性好
    • everysec:每秒同步,每秒记入日志一次,如果宕机,本秒数据可能丢失
    • no:不主动进行同步,把同步时机交给操作系统

同时开启rdb和aof,以aof为准

rewrite压缩

aof草原文件追加的方式,文件会越来越大,为避免此种情况,新增了重写机制,当aof文件的大小超过所设的的阈值时,redis就会启动aof文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof

原理:aof文件持续增长而过大时,会fork出一条新进程来将文件重写(先写临时文件最后再rename),redis4后的重写,是指把rdb的快照,以二进制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作

主从复制

主服务数据更新后根据配置和策略,自动同步到从服务的机制

主服务以写为主,从服务以读为主

命令

命令作用
slaveof 主服务ip 端口号在从服务器上执行,设置主服务器
info replication查看状态
slaveof on one将从服务变成主服务

原理

  1. 当从服务器连接上主服务器之后,从服务器向主服务器发送进行数据同步消息
  2. 主服务器接收到从服务器发送过来同步消息,把主服务器数据进行持久化rdb文件,把rdb文件发给从服务器,从服务器拿到rdb进行读取
  3. 每次主服务器进行写操作之后,和从服务器进行数据同步

哨兵模式

能监控主机是否故障,如果故障了根据投票数自动将从服务转为主服务

  1. 建立一个主从环境
  2. 新建配置文件sentinel.conf,名字不能错
  3. 修改配置文件内容:sentinel monitor 监控对象名称 主服务IP地址 主服务端口号 需要几个哨兵同意
  4. 启动:redis-sentinel 配置文件

选举新的主服务策略:

  1. 优先级靠前的,redis.conf中replica-priority,值越小优先级越高
  2. 偏移量最大的,指数据最全的
  3. runid最小的,每个redis启动后随机生成的一个10位的runid

集群

redis集群实现了对redis的水平扩容,即启动n个redis节点,将整个数据库分布存储在这n个节点中,每个节点存储总数的1/n

redis集群通过分区(partition)来提供一定程度的可用性(vavilability):即使集群中有一部分节点失效或者无法进行通信,集群也可以继续处理命令请求

配置

配置文件

  • cluster-enabled:开启集群模式
  • cluster-config-file:当前节点配置文件名称
  • cluster-node-timeout:失联时间(毫秒),集群自动进行主从切换
  • cluster-require-full-coverage:如果一段插槽的主从都挂掉
    • yes:整个集群挂掉
    • no:该插槽数据全都不能使用,其他没有问题

切换到redis的src目录下执行

redis-cli --cluster create --cluster -replicas 1 redis服务IP地址端口号列表

  • 1:以最简单的方式配置集群

集群方式连接 redis-cli -c

命令

命令作用
cluster nodes查看节点信息
cluster keyslot计算key的插槽值
cluster countkeysinslot查看插槽中有几个值(只能看当前服务器的)
cluster getkeysinslot返回插槽中指定数量的值

slots

一个redis集群包含16384个插槽,数据库中的每个键都属于这个16384个插槽中的一个

集群使用公式crc16(key)%16384来计算key属于哪个插槽,其中crc16(key)语句用于计算key的crc16校验和

集群中每个节点负责处理一部分插槽

集群中无法执行如mset这样的批量操作

如果要批量添加需要设置组,如:mset name zhangsan age 20

好处

  • 实现扩容
  • 分摊压力
  • 无中心配置相对简单

缺点

  • 多键操作是不支持的
  • 多键的redis事务是不支持的
  • lua脚本是不支持的

常见问题

缓存穿透

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求会压倒数据源,从而可能压垮数源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库

解决方案

  • 对空值做缓存:如果一个查询返回的数据为空,我们仍然把这个结果缓存,设置空结果的过期时间很短
  • 设置可访问的名单(白名单)使用bitmaps类型定义一个可访问的名单,id作为偏移量,每次访问和bitmap进行比较,如果id不在进行拦截
  • 布隆过滤器:用于检索一个值是否在一个集合中

缓存击穿

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般会从数据库加载并回设到缓存,这个时候大并发的请求可能瞬间把数据库压垮

解决方案

  • 预先设置热门数据:在redis高峰访问之前,把一些数据提前存入到redis里面,加大这些热门数据key的时长
  • 实时调整:现场监控哪些数据热门,实时调整key的过期时长
  • 使用锁
  • 热门数据永不过期

缓存雪崩

key对应的数据存在,但在redis中过期,此时若有大量并且请求过来,这些请求发现缓存过期一般都会从数据库加载数据并回设到缓存,这个时候大并发的请求可能瞬间把数据库压垮

缓存雪崩和缓存击穿的区别在于缓存雪崩针对很多key,缓存击穿则是某一个key

  • 构建多级缓存架构
  • 使用锁或队列
  • 设置过期标志更新缓存:纪录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存
  • 将缓存失效时间分散开:在原有过期时间上加一个随机值

分布式锁

  1. 使用setnx上锁,通过del释放锁
  2. 锁一直没有释放,设置key过期时间,自动释放

可以通过set key value nx ex time命令上锁的时候添加过期时间

这样可以保证操作的原子性

确保分布式锁可用的条件

  • 互斥性:再任意时刻,只有一个客户端能持有锁
  • 不会发生死锁:即使有一个客户端再持有锁的时期崩溃而没有释放锁,也能保证后续其他客户端能加锁
  • 只能释放自己的锁:加锁和解锁必须是同一个客户端,客户端自己不能把别人是锁释放
  • 加锁和解锁必须具有原子性