Redis 基础详解

码农日常1个月前更新 Trunks
574 0 0

一、Redis介绍

Redis 是用 C 语言开发的一个开源的高性能键值对 ( key-value )数据库,官方提供测试数据,50 个并发执行 100000 个请求,读的速度是 110000 次/s,写的速度是 81000 次/s 且 Redis 通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止 Redlis 支持的键值数据类型如下:

  • 1、字符串类型:String
  • 2、哈希类型:hash,hashmap
  • 3、列表类型:list,linkedList
  • 4、集合类型:set,HashSet
  • 5、有序(排序)集合类型:sortedset(ZSet),TreeSet

Redis 的应用场景:

  • 缓存( 例如:数据查询,短连接,新闻内容,商品内容等 )
  • 聊天室的在线好友列表
  • 任务队列( 例如:秒杀,抢购,抢票等 )
  • 应用排行榜
  • 数据过期处理( 可以精确到毫秒 )
  • 分布式集群架构中的 session 分离,sso 单点登录

二、Redis 安装

三、Redis 常问知识

  • Redis 是单线程机制
  • Redis 默认拥有 16 个数据库,数据库编号从 0 开始,默认使用 0 号数据库
  • Redis 中所有数据库使用同一个密码,默认没有密码,Redis 认为安全层面应该由 Linux来 保证
  • Redis 中所有索引都是从 0 开始
  • Redis 默认端口是 6379
  • Redis 的 ke y和 value 最大可支持 512 M
  • Redis-Cluster 中将分为 16384 个哈希槽( hash slot )

常见命令:

使用 select 数据库编号 可以切换使用的数据库

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

keys * 命令查看当前数据库所有的key

keys ?? 一个问号匹配一个字符串

flushdb 命令清空当前数据库

flushall 命令清空所有数据库

set key value 存储值,设置值时如果key存在就会覆盖

get key 获取值

del key 删除值

type key 查看 key 的 value 类型

strlen key 获取字符串的长度

ttl key 查看剩余时间,如果是 “ -2 ” 这种类型就代表失效了

四、Redis 常见数据结构

Redis 数据存储格式:

Redis 是 Map 类型的存储方式,其中所有的数据是采用 key:value 的形式存储。我们讨论的数据类型指的是存储数据的类型,也就是 value 部分的类型。key 部分永远都是字符串

Redis 存储 key:value 格式数据,key 为字符串,value 有 5 种数据类型。

value 的数据结构:

  • 1、字符串类型:String
  • 2、哈希类型:hash,hashmap
  • 3、列表类型(支持重复元素):list,linkedList
  • 4、集合类型(不允许重复元素):set,HashSet
  • 5、有序(排序)集合类型(不允许重复元素,且元素有顺序):sortedset(ZSet),TreeSet

1、String 类型

字符串类型是 redis 最基本的类型,他允许存储任何形式的字符串,可以存储JSON转换过后的字符串。

一个字符串允许最大的存储容量为 512M。

可存储 “ 对象序列化 ”,“ Java 序列化 ”,“ 二进制的字符串 ”,“ Json 字符串 ”

字符串常用操作

Redis
// 字符串常用操作
// 1、存入字符串键值对
SET key value
// 2、获取一个字符串键值
GET key
// 3、批量存储字符串键值对
MSET key value [key value ...]
// 4、批量过去字符串键值
MGET key [key ...]
// 5、删除一个键或者多个键
DEL key [key ...]
// 6、设置一个键的过期时间(秒)
EXPIRE key seconds

普通的单值存储 key-value

Redis
// 1、单值缓存
SET key value
GET key
// 2、对象缓存
SET user:1 value(JSON格式数据)
MSET user:1:name zs user:1:balance 6666
MGET user:1:name user:1:balance

计数器 incr key 原子加,比如统计文章的阅读量

Redis
// 原子加减
// 1、将key中存储的数字值加1
INCR key
// 2、将key中存储的数字值减1
DECR key
// 3、将key所存储的值加上increment(依次递增指定的数量)
INCRBY key increment
// 4、将key所存储的值减去decrement(依次递减指定的数量)
DECRBY key decrement
// 计数器
INCR article:readcount:{文章id}
GET article:readcount:{文章id}

分布式锁 setnx key value

Redis
// 分布式锁
// 1、返回1代表获取锁成功,返回0代表获取锁失败
SETNX product:1001 true
// 2、执行业务操作
// 3、执行完业务释放锁
DEL product:1001
// 防止程序意外终止导致死锁
SET product:1001 true ex 10 nx
// key设置为多少秒(seconds)过期,value关联key
SETEX key seconds value
// 只有key不存在时设置可以的值
SETNX key value
// 分布式系统全局序列号
// redis批量生成序列号提升性能
INCRBY orderId 1000

分布式 session

redis
// Web集群session共享
spring session + redis实现session共享

适用场景

场景 1、通过微信来进行海选投票时,每个微信号只能每隔四个小时投 1 票

场景 2、推荐的热门商品不能一直处于热门期,每种商品热门期维持 3 天,3 天后自动取消热门。

场景 3、热点新闻最大的特征时对时效性,如何自动控制热点新闻的时效性

场景 4、短信验证码,其它验证码

场景 5、比如点赞商品,视频,文章等的点赞量,点一下加一次。incr item:1

场景 6、分布式锁

场景 7、分布式 id 生成

解决方案( 两种方式 ):

1、在 Redis 中为大 V 用户设定用户信息,以用户主键和属性值作为 key,后台设定时间定时刷新即可。
user:id:10001:focus:2000
user:id:10001:fans:100000
user:id:10001:blogs:4324

2、在 Redis 中以 JSON 格式存储大 V 用户,定时刷新
set user:id:10001{id:10001,focus:2000,fans:100000,blogs:4324}

数据库中的热点数据 key 的命名规则:
表名 : 主键名 : 主键值 : 字段名
eg1: order : id : 29437 : name
eg2: equip : id : 390472 : type
eg3: news : id : 202004 : title

2、Hash类型

为了区别与 Redis 的键值对的称呼,Hash 中的键称为 field,而 key 为 Redis 的键

  • 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
  • 需要的内存结构:一个存储空间保存多少个键值对数据
  • Hash 类型:底层使用哈希表结构实现数据存储

Hash 常用操作

Redis
// Hash常用操作
// 1、存储一个哈希表key的键值(将哈希表key的字段field的值设为value)
HSET key field value
// 2、存储一个不存在的哈希表key的键值(field不存在时,设置哈希表字段的值)
HSETNX key field value
// 3、在一个哈希表key中存储多个键值对
HMSET key field value [field value ...]
// 4、获取哈希表key对应的field键值
HGET key field
// 5、批量获取哈希表key中多个field键值
HMGET key field [field ...]
// 6、删除哈希表key中的field键值(删除指定的field)
HDEL key field [field ...]
// 删除整个hash
DEL key
// 7、返回哈希表key中field的数量
HLEN key
// 8、返回哈希表key中所有的键值
HGETALL key
// 9、为哈希表key中field键的值加上增量increment
HINCRBY key field increment
// 10、有多少个元素
hlen key

其他操作

Redis
// 对象缓存
HMSET user {userId}:name zs {userId}:balance 1888
HMSET user 1:name zs 1:balance 1888
HMGET user 1:name 1:balance

适用场景

场景 1、Hash 实现抢购,限购发放优惠券,激活码等

解决方案:

  • 以商家id作为key
  • 将参与抢购的商品id作为field
  • 将参与抢购的商品数量作为对应的value
  • 抢购时使用这样的方式控制产品数量

场景 2、电商购物车

1、以用户 id 作为 key
2、商品 id 为 field
3、商品数量为 value

购物车操作

1、添加商品 -> hset cart:1001 10006 1
2、增加数量 -> hincrby cart:1001 10006 1
3、商品总数 -> hlen cart:1001
4、删除商品 -> hdel cart:1001 10006
5、获取购物车所有商品 -> hgetall cart:1001

优点:

同类数据归类整合存储,方便数据管理
相比 String 操作消耗内存与 CPU 更小
相比 String 存储更节省空间

缺点:

过期功能不能使用 field 上,只能用在 key 上
Redis 集群架构下不适合大规模使用( 因为某个 key 下是所有 hash 都在一个节点上,可能导致数据倾斜,可以采用拆分 key 的方式 )

Redis 基础详解

3、List 列表类型

  • 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
  • 需要的存储数据:一个存储空间保存多个数据,且通过数据可以体现进入顺序
  • List 类型:保存多个数据,底层使用双向链表存储结构实现
  • LinkedList 有序,可重复

Redis 基础详解

List 常用操作

Redis
// List常用操作
// 1、将一个或多个值value插入到key列表的表头(最左边)
LPUSH key value [value ...]
// 2、将一个或多个值value插入到key列表的表尾(最右边)
RPUSH key value [value ...]
// 3、移除并返回key列表的头元素(删除并展示该元素)
LPOP key
// 4、移除并返回key列表的尾元素
RPOP key
// 5、返回列表key中指定区间内的元素,区间以偏移量start和stop指定 
LRANGE key start stop
// 6、从key列表表头弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
BLPOP key [key ...] timeout
// 7、从key列表表尾弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
BRPOP key [key ...] timeout
// 8、查看有多少元素
llen key

其他操作

Redis
常用数据结构
Stack(栈)= LPUSH + LPOP
Queue(队列)= LPUSH + RPOP
Blocking MQ(阻塞队列)= LPUSH + BRPOP

适用场景

场景 1、可以实现朋友圈点赞 ( 列表来实现队列操作,用 rpush 将依次点赞的人加入进去就可以了 )

场景 2、可以实现展示最近关注的列表( lpush,栈 )

  • 微信公众号订阅的消息( 关注 )
  • 比如我( 张三 )关注的人发布的文章,就推到我的 List 里面, lpush article:张三 id 文章 id
  • 比如要查看我订阅的全部文章,显示 10 条,lrange article:张三 id 0 9
Redis
# 比如我的用户是张三,我关注了李四,李四发表文章的时候,张三是能看见的
# 李四就维护了所有的关注了他的这些list
# 张三关注了李四,在李四这边就有张三list,key为article:zs
# 王五关注了李四,在李四这边就有王五list,key为article:ww
# 比如李四发表了文件,就往关注了他的这些list里面推数据
lpush article:zs 001
lpush article:ww 001

4、Set 类型 ( Hashset,不重复,无序 )

Set 常用操作

Redis
// Set常用操作
// 1、往集合key中存入元素,元素存在则忽略,若key不存在则新建
SADD key member [member ...]
// 2、从集合key中删除元素
SREM key member [member ...]
// 3、获取集合key中所有元素
SMEMBERS key
// 4、获取集合key的元素个数
SCARD key
// 5、判断member元素是否存在于集合key中
SISMEMBER key member
// count表示返回几个随机数;如果count数超过集合元素个数,则返回所有值;如果写的示负数,如-3,表示返回可能重复的三个数
// 6、从集合key中选出count个元素,元素不从key中删除(返回集合中一个或多个随机数)
SRANDMEMBER key [count]
// 7、从集合key中选出count个元素,元素从key中删除
SPOP key [count]
// 移除并返回集合中的一个随机元素
SPOP key

Set 运算操作

Redis
// 1、交集运算
SINTER key [key ...]
// 2、将交集结果存入新集合 destination 中
SINTERSTORE destination key [key ...]
// 3、并集运算
SUNION key [key ...] 
// 4、将并集结果存入新集合 destination 中
SUNIONSTORE destination key [key ...]
// 5、差集运算
SDIFF key [key ...] 
// 6、将差集结果存入新集合 destination 中
SDIFFSTORE destination key [key ...]

适用场景

场景1、抽奖活动

  • 用户 ID,立即参与按钮,sadd key 用户 ID(添加改用户信息进入)
  • 显示已经多少人参与了,SCARD key
  • 从 set 中任意选取 N 个中奖人,SRANDMEMBER key 2 为随机抽奖 2 个人,元素不重复
  • SPOP key 3 为随机抽奖 3 个人,元素会删除

场景2、点赞,收藏,标签

  • 点赞:SADD like:{消息ID}{用户ID}
  • 取消点赞:SREM like:{消息ID}{用户ID}
  • 检查用户是否点过赞:SISMEMBER like:{消息ID}{用户ID}
  • 获取点赞的用户列表:SMEMBERS like:{消息ID}
  • 获取点赞用户数:SCARD like:{消息ID}

集合操作

Redis
SINTER set1 set2 set3->{c}
SUNION set1 set2 set3->{a,b,c,d,e}
SDIFF set1 set2 set3->{a}
1、张三关注的人:
zsSet -> {lisi,wangwu}
2、李四关注的人:
lisiSet -> {zs,wangwu,zhaosi,wangqi}
3、王五关注的人:
wangwuSet -> {zs,lisi,zhaosi,lisan}
4、张三和李四的共同关注:
SINTER zsSet lisiSet --> {wangwu}

5、SortedSet 类型

ZSet 常用操作

Redis
// ZSet常用操作
// 1、往有序集合key中加入带分值元素(或者更新已存在成员的分数)
ZADD key score member [score member ...]
// score是得分的意思,例如:
zadd df 90 lisi
// 2、从有序集合key中删除元素
ZREM key member [member ...]
// 3、返回有序集合key中元素member的分值
ZSCORE key member
// 4、为有序集合key中元素member的分值加上increment
ZINCRBY key increment member
// 5、返回有序集合key中的元素个数
ZCARD key
// 6、正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop [WITHSCORES]
// 7、倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]

ZSet 集合操作

Redis
// 并集计算
ZUNIONSTORE destkey numkeys key [key ...]
// 交集计算
ZINTERSTORE destkey numkeys key [key ...]

Redis 基础详解

适用场景

场景 1、热搜榜:

  • 点击一次新闻,ZINCRBY hotNews:20190819 1 新闻1号或者是 ZINCRBY hotNews:20190819 1 新闻1号 2 新闻2号
  • 展示当日排行前十,ZREVRANGE hotNews:20190819 0 9 WITHSCORES
  • 七日搜索榜单计算,ZUNIONSTORE hotNews:20190813-20190816 7 hotNews:20190813 hotNews:20190814 hotNews:20190815 hotNews:20190816
  • 展示七日排行前十,ZREVRANGE hotNews:20190813-20190819 0 9 WITHSCORES

场景2、根据商品销售对商品进行排序显示:

  • 定义商品销售排行榜(sortedset集合),key 为 goods:sellsort,分数为商品销售数量
  • 商品编号 1001 的销量是 9,商品编号 1002 的销量是 15,zadd goods:sellsort 9 1001 15 1002
  • 有一个客户又买了 2 件商品 1001,商品编号 1001 销量加 2,zincrby goods:sellsort 2 1001
  • 求商品销量前 10 名,zrange goods:sellsort 0 10 withscores

五、Redis 的单线程和高性能

1、Redis 是单线程吗?

Redis 的单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

2、Redis单线程为什么还能这么快?

因为它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。正因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些耗时的指令 ( 比如 keys ),一定要谨慎使用,一不小心就可能会导致 Redis 卡顿。

3、Redis 单线程如何处理那么多的并发客户端连接?

Redis 的 I0 多路复用:redis 利用 epoll 来实现 IO 多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。

Redis 基础详解

Shell
# 查看redis支持的最大连接数,在redis.conf文件中可修改,# maxclients 10000
127.0.0.1:6379> config get maxclients
##1) "maxclients"
##2) "10000"

六、Redis 持久化

持久化概念:意外断电或重启之后,内存的数据会丢失,所以内存的数据要保存到磁盘中。利用磁盘等将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制就叫做持久化

持久化方式:

  • 快照( RDB ):将某个时间点的工作状态保存下来,恢复时可直接恢复指定时间点的工作状态
  • 日志( AOF ):将对数据的所有操作过程记录下来,恢复数据时重新执行这些操作

1、RDB 快照

基本概念

在默认情况下,Redis 将内存数据库快照保存到名字为 dump.rdb 的二进制文件中。

你可以对 Redis 进行设置,让他在 “ N 秒内数据集至少有 M 个改动 ” 这一条件被满足时,自动保存一次数据集。

例如:以下设置会让 Redis 在满足 “ 60 秒内有之少 1000 个键被改动 ” 这一条件时,自动保存一次数据集

save

Shell
# save 60 1000 // 关闭RDB只需要将所有的save保存策略注释掉即可

还可以手动执行命令生成 RDB 快照,进入 Redis 客户端执行命令 save 或 bgsave 可以生成 dump.rdb 文件,每次命令执行都会将所有 Redis 内存快照到一个新的 RDB 文件里,并覆盖原有 RDB 快照文件。

bgsave

bgsave 的写时复制( COW )机制

Redis 借助操作系统提供的写时复制技术 ( Copy-On-Write,COW ),在生成快照的同时,依然可以正常处理写命令。简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入RDB文件。此时,如果主线程对这些数据也都是读操作,那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据,那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。

Shell
	  |--1、发送指令-->|		 |--2、调用fork函数,生成子进程-->|      |--3、创建rdb文件-->|	
bgsave| 			  |	redis|							     |redis|				  |.rdb
	  |<--2、返回消息--|		 |<--------2、返回消息------------|	   |				  |

save 与 bgsave 对比

命令 save bgsave
IO 类型 同步 异步
是否阻塞redis其它命令 否( 在生成子进程执行调用 fork 函数时会有短暂阻塞 )
复杂度 O(n) O(n)
优点 不会消耗额外内存 不阻塞客户端命令
缺点 阻塞客户端命令 需要 fork 子进程,消耗内存

配置自动生成rdb文件后台适用的是bgsave方式

2、AOF( Append-Only File )

快照功能并不是非常耐久 ( durable ):如果 Redis 因为某些原因而造成故障停机,那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。

从1.1版本开始,Redis 增加了一种完全耐久的持久化方式:AOF持久化,将修改的每一条指令记录进文件 appendonly.aof 中 ( 先写入 os cache,每隔一段时间 fsync 到磁盘 )

AOF 以日志的方式记录每次操作的命令,重启之后执行 AOF 中保存的命令恢复数据,较为主流

比如执行命令 set zs 666,AOF 文件里会记录如下数据

Shell
*3
$3
set
$2
zs
$3
666

这是一种resp协议格式数据,星号后面的数字代表命令有多少个参数,$ 号后面的数字代表这个参数有几个字符

注意:如果执行带过期时间的 set 命令,AOF 文件里记录的并不是执行的原始命令,而是记录 key 过期的时间戳

比如执行 set zs 888 ex 1000,对应 AOF 文件里记录如下

Shell
*3
$3
set
$2
zs
$3
888
*3
$9
PEXPIREAT
$2
zs
$13
1604249786301

可以通过修改 redis.conf 配置文件来打开 AOF 功能:

Shell
# appendonly yes

从现在开始,每当 Redis 执行一个改变数据集的命令时 ( 比如 SET ),这个命令就会被追加到 AOF 文件的末尾。

这样的话,当 Redis 重新启动时,程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。

你可以配置 Redis 多久才将数据 fsync 到磁盘一次。有三个选项:

Shell
appendfsync always:每次有新命令追加到AOF文件时就执行一次fsync,非常慢,也非常安全。
appendfsync everysec:每秒fsync 一次,足够快,并且在故障时只会丢失1秒钟的数据。[推荐]
appendfsync no:从不fsync,将数据交给操作系统来处理。更快,也更不安全的选择

推荐 ( 并且也是默认 ) 的措施为每秒 fsync 一次,这种 fsync 策略可以兼顾速度和安全性。

AOF 文件里可能有太多没用指令,所以 AOF 会定期根据内存的最新数据生成 aof 文件

例如,执行了如下几条命令:

Shell
127.0.0.1:6379> incr readcount
(integer) 1
127.0.0.1:6379> incr readcount
(integer) 2
127.0.0.1:6379> incr readcount
(integer) 3
127.0.0.1:6379> incr readcount
(integer) 4
127.0.0.1:6379> incr readcount
(integer) 5

重写后 AOF 文件里变成

Shell
*3
$3
SET
$2
readcount
$1
5

如下两个配置可以控制 AOF 自动重写频率

Shell
# auto-aof-rewrite-min-size 64mb   //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就很快,重写的意义不大
# auto-aof-rewrite-percentage 100  //aof文件自上一饮次重写后文件大小增长了100%则再次触发重写

当然 AOF 还可以手动重写,进入 Redis 客户端执行命令 bgrewriteaof 重写 AOF

注意:AOF 重写 Redis 会 fork 出一个子进程去做( 与 bgsave 命令类似 ),不会对 Redis 正常命令处理有太多影响

3、RDB 和 AOF,该怎么选?

命令 RDB AOF
启动优先级
体积
恢复速度
数据安全性 容易丢数据 根据策略决定

生产环境可以都启用,Redis 启动时如果既有 RDB 文件又有 AOF 文件则优先选则 AOF 文件恢复数据,因为 AOF 一般来说数据更安全一点

七、Redis 4.0 混合持久化

1、为什么使用混合持久化?

重启 Redis 时,我们很少使用 RDB 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。

通过如下配置可以开启混合持久化( 必须先开启 aof ):

Shell
# aof-use-rdb-preamble yes

如果开启了混合持久化,AOF 在重写时,不再是单纯将内存数据转换为 RESP 命令写入 AOF 文件,而是将重写这一刻之前的内存做 RDB 快照处理,并且将 RDB 快照内容和增量的 AOF 修改内存数据的命令存在一起,都写入新的 AOF 文件,新的文件一开始不叫 appendonly.aof,等到重写完新的 AOF 文件才会进行改名,覆盖原有的 AOF 文件,完成新旧两个 AOF 文件的替换。

于是在 Redis重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。

混合持久化 AOF 文件结构如下:

Redis 基础详解

2、Redis数据备份策略
写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
每次copy备份的时候,都把太旧的备份给删了
每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏

原文链接:https://blog.csdn.net/m0_62288910/article/details/156688249

© 版权声明

相关文章

暂无评论

暂无评论...