学习记录篇:Redis缓存

学习记录篇章之 Redis


前言

Redis,一款用C语言编写的基于内存的NoSQL。在数据量日益增加的今天,Redis等缓存中间件的存在能够很好的帮我们后端数据库的压力。因为如果我们每一次的请求都要流入到数据库中去执行,那么即使性能再优越,效率再高的数据库也无法承担。所以我们可以利用缓存,简单地说就是把一些需要频繁查询的,不经常修改的数据,或者可以把一些频繁查询和修改,但是不需要立刻就存入到数据库中的数据放到缓存中进行操作。


一、Redis的常用数据结构以及使用场景

既然说Redis是一种NoSQL,那作为数据库来存储数据,他也拥有自己的数据结构,下面展示Redis部分主要的数据结构。

1.String

介绍 string 数据结构是简单的 key-value 类型。虽然 Redis 是⽤ C 语⾔写的,但是 Redis
并没有使⽤ C 的字符串表示,⽽是⾃⼰构建了⼀种 简单动态字符串 simple dynamic
string SDS )。相⽐于 C 的原⽣字符串, Redis SDS 不光可以保存⽂本数据还可以保存
⼆进制数据,并且获取字符串⻓度复杂度为 O(1) C 字符串为 O(N) , 除此之外 ,Redis
SDS API 是安全的,不会造成缓冲区溢出。
2. 常⽤命令 : set,get,strlen,exists,dect,incr,setex 等等。
3. 应⽤场景 :⼀般常⽤在需要计数的场景,⽐如⽤户的访问次数、热点⽂章的点赞转发数量等
等。

String的简单使用

127.0.0.1:6379> set key value #设置 key-value 类型的值
OK
127.0.0.1:6379> get key # 根据 key 获得对应的 value
"value"
127.0.0.1:6379> exists key # 判断某个 key 是否存在
(integer) 1
127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的⻓度。
(integer) 5
127.0.0.1:6379> del key # 删除某个 key 对应的值
(integer) 1
127.0.0.1:6379> get key
(nil)
批量设置 :
127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
OK
127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
1) "value1"
2) "value2"
计数器(字符串的内容为整数的时候可以使⽤)
127.0.0.1:6379> set number 1
OK
127.0.0.1:6379> incr number # 将 key 中储存的数字值增⼀
(integer) 2
127.0.0.1:6379> get number
"2"
127.0.0.1:6379> decr number # 将 key 中储存的数字值减⼀
(integer) 1
127.0.0.1:6379> get number
"1"
过期策略
127.0.0.1:6379> expire key 60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56

2.list

介绍 list 即是 链表 。链表是⼀种⾮常常⻅的数据结构,特点是易于数据元素的插⼊和删除
并且且可以灵活调整链表⻓度,但是链表的随机访问困难。许多⾼级编程语⾔都内置了链表
的实现⽐如 Java 中的 LinkedList ,但是 C 语⾔并没有实现链表,所以 Redis 实现了⾃⼰
的链表数据结构。 Redis list 的实现为⼀个 双向链表 ,即可以⽀持反向查找和遍历,更⽅
便操作,不过带来了部分额外的内存开销。
常⽤命令 : rpush,lpop,lpush,rpop,lrange, llen 等。
应⽤场景 : 发布与订阅或者说消息队列、慢查询。

list的简单使用

127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素
(integer) 1
127.0.0.1:6379> rpush myList value2 value3 # 向list的头部(最右边)添加多个元素
(integer) 3
127.0.0.1:6379> lpop myList # 将 list的尾部(最左边)元素取出
"value1"
127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
1) "value2"
2) "value3"
127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第⼀
1) "value2"
2) "value3"

127.0.0.1:6379> rpush myList2 value1 value2 value3
(integer) 3
127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出
"value3"
通过 lrange 查看对应下标范围的列表元素
127.0.0.1:6379> rpush myList value1 value2 value3
(integer) 3
127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
1) "value1"
2) "value2"
127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第⼀
1) "value1"
2) "value2"
3) "value3"

#通过 llen 查看链表⻓度:
127.0.0.1:6379> llen myList
(integer) 3

3.Hash

Rdis中的Hash跟JDK1.8前里面的HashMap很像,内部实现也差不多(数组 + 链表)

Redis hash 做了更多优化。另外, hash 是⼀个 string 类型的 field value 的映射表,
特别适合⽤于存储对象 ,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的
值。 ⽐如我们可以 hash 数据结构来存储⽤户信息,商品信息等等。
常⽤命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。
应⽤场景 : 系统中对象数据的存储。

Hash的简单使用

127.0.0.1:6379> hset userInfoKey name "xiaoming" description "dev" age "24"
OK
127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存
在。
(integer) 1
127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。
"guide"
127.0.0.1:6379> hget userInfoKey age
"24"
127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
   "name"
2) "xiaoming"
3) "description"
4) "dev"
5) "age"
6) "24"
127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表
1) "name"
2) "description"
3) "age"
127.0.0.1:6379> hvals userInfoKey # 获取 value 列表
1) "guide"
2) "dev"
3) "24"
127.0.0.1:6379> hset userInfoKey name "honghong" # 修改某个字段对应的值
127.0.0.1:6379> hget userInfoKey name
"honghong"

4.zset

介绍: set 相⽐, zset 增加了⼀个权重参数 score ,使得集合中的元素能够按 score
进⾏有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java HashMap
TreeSet 的结合体。
常⽤命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。
应⽤场景: 需要对数据根据某个权重进⾏排序的场景。⽐如在直播系统中,实时排⾏信息包
含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息(可以理解为按消息维度的消息排⾏
榜)等信息。
127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
(integer) 1
127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # ⼀次添加多个元素
(integer) 2
127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
(integer) 3
127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
"3"
127.0.0.1:6379> zrange myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元
素1) "value3"
2) "value2"
3) "value1"
127.0.0.1:6379> zrange myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start 1 为
stop
1) "value3"
2) "value2"
127.0.0.1:6379> zrevrange myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start 1
为 stop
1) "value1"
2) "value2"

二、Redis的单线程模型和IO多路复用

Redis 基于 Reactor 模式来设计开发了⾃⼰的⼀套⾼效的事件处理模型 Netty 的线程模型也基
Reactor 模式, Reactor 模式不愧是⾼性能 IO 的基⽯),这套事件处理模型对应的是 Redis
中的⽂件事件处理器( file event handler )。由于⽂件事件处理器( file event handler )是单线程
⽅式运⾏的,所以我们⼀般都说 Redis 是单线程模型。
既然是单线程,那怎么监听⼤量的客户端连接呢?
Redis 通过 IO 多路复⽤程序 来监听来⾃客户端的⼤量连接(或者说是监听多个 socket ),它会
将感兴趣的事件及类型 ( 读、写)注册到内核中并监听每个事件是否发⽣。
这样的好处⾮常明显: I/O 多路复⽤技术的使⽤让 Redis 不需要额外创建多余的线程来监听客户
端的⼤量连接,降低了资源的消耗 (和 NIO 中的 Selector 组件很像)。
Redis 设计与实现》有⼀段话是如是介绍⽂件事件的,我觉得写得挺不错。
Redis 基于 Reactor 模式开发了⾃⼰的⽹络事件处理器:这个处理器被称为⽂件事件处理器
file event handler )。⽂件事件处理器使⽤ I/O 多路复⽤( multiplexing )程序来同时监听
多个套接字,并根据 套接字⽬前执⾏的任务来为套接字关联不同的事件处理器。
当被监听的套接字准备好执⾏连接应答( accept )、读取( read )、写⼊( write )、关 闭
close )等操作时,与操作相对应的⽂件事件就会产⽣,这时⽂件事件处理器就会调⽤套
接字之前关联好的事件处理器来处理这些事件。
虽然⽂件事件处理器以单线程⽅式运⾏,但通过使⽤ I/O 多路复⽤程序来监听多个套接字
⽂件事件处理器既实现了⾼性能的⽹络通信模型,⼜可以很好地与 Redis 服务器中其他同样
以单线程⽅式运⾏的模块进⾏对接,这保持了 Redis 内部单线程设计的简单性。

那么,我们从中得知,文件事件处理器可以分为以下四个部分

1.多个 socket (客户端连接)
2.IO 多路复⽤程序(⽀持多个客户端连接的关键)
3.⽂件事件分派器(将 socket 关联到相应的事件处理器)
4.事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

Rdis中为什么不使用多线程 

对于这个问题,首先这个描述是不准确的,实际上Redis 4.0之后就引入了多线程,主要负责处理一些大键值对数据的删除等操作,大部分对于数据的操作还是单线程的。

那么Redis为什么不使用多线程去操作数据呢。

首先,Redis是基于内存操作的。所以Redis的性能几乎不受CPU的影响,主要与内存的大小以及网络传输方面的因素有关,可以说(多线程是可以有的,但是没有必要)

另外就是,多线程会带来死锁,数据不一致等各方面的问题,如果单线程既能保证效率,又不会带来潜在的危害,那还要什么多线程自行车呀。

这里有一个Redis 6.0的新特性,Redis 6.0引入了多线程操作,只不过不是基于数据读写的,而是在网络层的IO读写引入了多线程,提高了网络数据读写的效率(有兴趣的朋友们可以自行了解)

 

Redis的缓存过期策略

前面我们已经提到过了,Redis是拥有缓存过期特性的,也就是我们可以给Redis中的数据设置一个过期时间,时间到了我们就无法再使用这个数据了,必须要重新设置。

因为Redis是基于内存的,内存资源总是有限且宝贵的嘛,设置过期时间能很好的保证我们内存容量的健康,同时也可以在某些业务中给我们提供帮助(例如用户Token过期,登录验证码,二维码过期等等)。

过期键的删除策略

Redis中的过期键删除策略主要有以下两种,分别是惰性删除和定期删除。

1. 惰性删除 :只会在取出 key 的时候才对数据进⾏过期检查。这样对 CPU 最友好,但是可能会
造成太多过期 key 没有被删除。
2. 定期删除 : 每隔⼀段时间抽取⼀批 key 执⾏删除过期 key 操作。并且, Redis 底层会通过限
制删除操作执⾏的时⻓和频率来减少删除操作对 CPU 时间的影响。
定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采⽤的是 定期
删除 + 惰性 / 懒汉式删除
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉
了很多过期 key 的情况。这样就导致⼤量过期 key 堆积在内存⾥,然后就 Out of memory 了。
怎么解决这个问题呢?答案就是: Redis 内存淘汰机制。

Redis内存淘汰机制

Redis 提供 6 种数据淘汰策略:
1. volatile-lru least recently used :从已设置过期时间的数据集( server.db[i].expires
中挑选最近最少使⽤的数据淘汰
2. volatile-ttl :从已设置过期时间的数据集( server.db[i].expires )中挑选将要过期的数据淘汰
3. volatile-random :从已设置过期时间的数据集( server.db[i].expires )中任意选择数据淘汰
4. allkeys-lru least recently used :当内存不⾜以容纳新写⼊数据时,在键空间中,移除
最近最少使⽤的 key (这个是最常⽤的)
5. allkeys-random :从数据集( server.db[i].dict )中任意选择数据淘汰
6. no-eviction :禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报
错。这个应该没⼈使⽤吧!
4.0 版本后增加以下两种:
7. volatile-lfu least frequently used :从已设置过期时间的数据集 (server.db[i].expires)
挑选最不经常使⽤的数据淘汰
8. allkeys-lfu least frequently used :当内存不⾜以容纳新写⼊数据时,在键空间中,移
除最不经常使⽤的 key

 

总结

  本篇文章就到这里啦,这篇文章首先认识了一下Redis的概念以及基本数据的使用方法。最后还了解了一下Redis的过期策略和内存淘汰策略。由于篇幅原因,Redis的持久化和使用场景我就下一篇再给大家展示咯。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值