文章目录
- [一. 为什么要使用Redis](#一. 为什么要使用Redis)
- [二. 简单认识Redis特性](#二. 简单认识Redis特性)
-
- [1. 快](#1. 快)
- [2. 其它特性](#2. 其它特性)
- [三. 分布式与微服务](#三. 分布式与微服务)
-
- [1. 分布式](#1. 分布式)
- [2. 微服务](#2. 微服务)
- [四. Redis中的通用命令](#四. Redis中的通用命令)
-
- [1. 进入Redis客户端](#1. 进入Redis客户端)
- [2. get与set命令](#2. get与set命令)
- [3. keys](#3. keys)
- [4. exists](#4. exists)
- [5. del](#5. del)
- [6. expire](#6. expire)
- [7. ttl](#7. ttl)
- [8. 常见的过期策略](#8. 常见的过期策略)
-
- [(1) Redis中key的过期策略](#(1) Redis中key的过期策略)
- [(2) 定时删除策略](#(2) 定时删除策略)
- [(3) 定时器原理](#(3) 定时器原理)
-
- 1) 基于优先级队列(堆)实现的定时器 基于优先级队列(堆)实现的定时器)
- 2) 基于时间轮实现的定时器 基于时间轮实现的定时器)
- [9. type](#9. type)
一. 为什么要使用Redis
我们之前学的MySQL这种关系型数据库, 其实是能适应大部分场景的, 但随着业务的越来越复杂, 对于性能要求越来越高, MySQL这种储存到硬盘上的数据库有时不能满足这种高性能需求, 于是Redis这种存储在内存中的非关系型数据库大展拳脚
现在业务的复杂程度, 单体架构已经不能满足, 于是需要引入多个主机, 将业务拆分,分布式系统就诞生了, 而分布式恰恰就是Redis的主场, Redis又是一个客户端-服务器结构的程序, 通过网络通信来操作内存
二. 简单认识Redis特性
1. 快
Redis最主要的特性就是快, 比MySQL快的多得多, 啥?你问我有多快?
你可能会问, Redis为什么这么快? 下面我来为你一一分析
1.最关键的一点, 存储在内存中, 肯定要比那些存储硬盘的数据库快
2. Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行
3.Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象, 核心功能是操作内存中的数据结构, CPU开销小
4.在网络传输上, Redis使用了IO多路复用的方式即一个线程管理多个socket
5.Redis使用的是单线程模型, 减少了多线程之间不必要的竞争开销
2. 其它特性
1. Redis是客户端-服务器结构, 这就意味着在客户端产生的一系列请求需要通过网络传输到达服务器, 服务器再进行响应
2. 当然Redis同样知道自己的毛病, 因此大部分操作都支持批量操作, 例如exists和del等等...
三. 分布式与微服务
1. 分布式
谈到分布式, 我们很多人觉得高大尚和难, 但其实只不过是把业务模块进行了拆分而已, 下面我们对分布式进行一个简单梳理
1.应用服务与储存服务分离: 单体架构中应用服务器与储存服务器在一台主机上, 随着业务发展, 处理业务与储存数据一台主机已经不能满足, 这时就需要分离
2.应用服务器分布集群:负载均衡器对客户端发送过来的请求进行转发, 平衡流量, 不至于当遇到流量高峰期时, 导致处理业务的应用服务器挂掉,处理业务的服务器分配到多台主机上, 每台主机都可以完成总业务的处理
3.读写分离: 读数据库与写数据库分离, 这是因为上面处理业务的应用服务器引入了多台, 可以处理大量业务, 所以对数据读与写也要跟上, 单一的数据库服务器同时进行读与写已经显露疲态, 这时我们就需要将读与写服务器分离
4.冷热分离: 在实际业务中, 对数据库的读操作是远远大于写操作的, 同时一些数据的读取率是远远大于的其他数据的, 例如用户的信息等等, 被称为热点数据, 这时我们就可以将这些热点信息抽离出来,存储到Redis中, 因为热点数据往往占据总数据的一小部分, 所以Redis只需要存储20%左右的数据就能满足80%左右的查询操作, 这又被称为二八原则
5.垂直分库:随着业务的发展, 数据量越来越多, 所有数据存储在同一个的数据库中已经不行了, 这时我们可以将数据库进行分库分表, 例如用户数据有一个主和从数据库, 订单也有一个主和从数据库
2. 微服务
1. 微服务就是微小的服务, 多小呢? 并没有一个明确的规定, 可以小到一个服务只对应一个功能, 只做一件事, 可以单独部署运行,
2. 将上面各个业务各个集群拆分为更细的微服务, 每个微服务都是一个独立可运行的模块, 由不同团队运行和维护, 最后通过以写公共组件如服务注册/发现中心, 网关等实现相互之间的调用关联
3. 微服务可以理解为经过良好设计的分布式架构, 微服务拆到不能再拆了
微服务一般都有以下几个组件:⬇️
1.Distributed/versioned configuration 分布式版本配置: 如果发布一些新版本拓展了一些新功能, 不再通过发布方式就可以让服务拿到这些配置
2.Service registration and discover 服务注册和服务发现: 如果有服务器突然挂掉要及时发现, 新增的服务器后确保流量能分到这些新的服务器上
3.Routing 路由: 发送的请求路由到相关服务上
4.Service-to-service calls 服务调用: 服务和服务之间通过服务调用来建立联系, 用更优雅更简单方式来调用
5.Load balancing 负载均衡: 把流量更均衡分配到所有机器上
6.Circuit Breakers 断路器: 服务有问题时, 线中断服务, 让访问方有更多时间做其他准备, 回复访问时, 第一时间能投入生产
四. Redis中的通用命令
1. 进入Redis客户端
redis-cli
首先,要使用Redis命名那么,就要进入Redis客户端
2. get与set命令
get与set可以说是Redis最基础最重要的命令,毕竟Redis是个数据库,最重要的就是存储数据与取出数据的能力
set key value
①这里的key与value都是字符串类型
②key与value虽然是字符串类型,但是不需要加单/双引号(当然你想加也可以)
③Redis中的命令不区分大小写,但一般都是用的小写(比较顺眼)
1.get命令后面直接加上key即可,同样的加不加单/双引号都可以
2.get后面如果跟上一个不存在的key, 那么会返回nil, 与Java中的null一个意思,都表示空/0
3.当然value的数据类型不止一种,常见的有字符串,哈希表,列表,集合等等,相反的key只有字符串类型,类型不同的value对应的存储与查询命令也是不同的(这里我们后面一一阐述)
3. keys
keys命令常见使用场景是来查询符合条件key,当然我们同事也要注意keys在自己的环境上学习知道就可以了,工作中keys命令是禁止使用的,因为这个命令的时间复杂度是O(N)级别的,意味着会把生成环境的所有key都遍历一边去比对,而Redis操作又是单线程的, 在数据量过于庞大的时候, 这就会导致Redis服务发生阻塞, 从而导致依赖Redis的那些服务也发生阻塞, 这时因为Redis在生成环境中常用于作为二级缓存, 为MySQL一类的关系型数据库遮风挡雨, 这时Redis响应超时会导致查询请求打到MySQL上, 这些请求通常是大量的且重复的, 又因为MySQL操作硬盘资源比Redis慢了好几个数量级, 这时请求太多就会导致MySQL服务宕机, 进而导致整个系统崩溃, 年终奖甚至工作可能不保~
是不是有点熟悉的感觉?在我们学习MySQL的时候同时也是禁止在生成环境中使用select * 的
keys [pattern]
上述的pattern意思是匹配条件的意思,可以从历史存储的key中查询到符合匹配条件[pattern]的key
下面是我们从官方文档copy过来的示例,相信大家也是一眼能看懂下方其中的含义,官方文档给的还是很详细的~
h?llo matches hello, hallo and hxllo
h*llo matches hllo and heeeello
h[ae]llo matches hello and hallo, but not hillo
h[^e]llo matches hallo, hbllo, ... but not hello
h[a-b]llo matches hallo and hbllo
1. ? 单字符匹配,意味着这里必须得有一个字符,而是什么字符不重要,都可以匹配上
2. * 0到多字符匹配,这里可以没有,也可以有多个字符,范围比上面 ? 大的很多
3. [ae] 指定匹配, 这里相当于已经给出了固定选项,只能从a/e中选出一个
4. [^e] 排除匹配, 这里除了e这个字符,其他字符都可以匹配上
5. [a-e] 闭区间范围匹配, 这里a到e字符都可以匹配正确
4. exists
exists key [key...]
1.获取key存在的个数, 我们需要注意, Redis中key与value是一一对应的, 当exists后面只跟一个key时, 这里所说的获取存在的个数非1即0, 当后面跟着n个key时, 存在个数值的范围就是[0, n], 可不敢理解为存在多个相同的key
2.exists查询的时间复杂度是O(1), 底层基于哈希表这种数据结构
exists key1
exists key2
与
exists key1 key2
上面两者的区别都可以获取存活个数, 不同点在于上面的两个命令需要通过Redis客户端向服务器发送请求, 进行两次网络通信的过程, 而下方命令只需要一次, 要知道在计算机中读写速度一般是内存>硬盘>网卡的(当然万兆网卡这种氪金怪除外), 因此下面的命令执行速度是大于上方两个命令的
5. del
del key [key...]
1. 删除数据, 返回值是删除的个数, 同样的, 该删除操作也是O(1)的, 也支持同时删除多个数据2. 相比于MySQL中的删除操作的十分危险, Redis中的删除操作危险性就下降了许多, 这时因为在实际工作中Redis通常作为缓存, 注意:我们这里说的是作为缓存, 如果使用Redis作为数据库的话, 删除操作就十分危险了...
使用Redis来存储数据库中的热点数据(访问概率高且频繁的数据), 热点数据通常又占整个数据库的小部分, 因此缺失几个缓存数据影响不大, 当然没有确实更好, 但如果直接把Redis中的大部分或者所有缓存都删除后, 那么影响就大的多了, 大部分访问Redis的服务因为超时从而转发到MySQL上, 就会导致数据库宕机, 进而整个服务崩溃
6. expire
1. expire key seconds
2. pexpire key
1. expire命令作用是给key设置一个过期时间, 超过这个指定的时间key就会自动删除, 单位是s(秒), 返回值1表示成功, 0表示失败
2. pexpire命令设置的过期时间是毫秒(ms)级别
3. 设置过期时间这个操作在获取验证码, 令牌等等场景很常见, 同时在基于Redis实现的分布式锁中, 为了避免出现一些特殊情况导致不能正确解锁, 通常会给key设置一个过期时间
7. ttl
1. ttl key
2. pttl key
1. 上述命令用来获取key的过期时间, ttl获取的是秒级单位, pttl获取的是毫秒级单位, 一般是与上面expire.pexpire配套使用
2. 两个特殊值: -1 表示没有设置过期策略, 一般指永久存在, -2 表示key不存在
8. 常见的过期策略
(1) Redis中key的过期策略
使用Redis作为缓存时, 我们可能有很多的key都设置了过期时间, 那么Redis如何确保已经过期的会被删除, 没过期的key保留呢? 下面我们来为大家讲解Redis中的过期策略
1. 惰性删除: 在Redis中, 如果一个key过期时间到了之后, 不会立即删除, 暂时会存在一段时间, 后续如果再有操作需要用到这个key的时候, 会检查一下当前的过期时间, 如果已经到了过期时间, 就会触发del删除操作, 并且返回一个 nil 值
2. 定期删除: 只有上面的惰性删除肯定是不够的, 因为一个key到过期时间之后, 如果该key的访问频率很小, 那这个已经过期的key不就会一直存在, 一直占用内存空间么, 于是就引入了定期删除的策略, 同时定期删除也不是说一次性将所有的可以都扫描一遍, 而是从可以key中抽取一部分进行检查是否过期的操作, 然后再次等待下一次的检查时间, 这也是因为Redis的核心操作是单线程, 如果一次遍历所有key就可能占用很大一部分资源, 导致挂机, 因此要确保定期检查的操作是足够快的
3. 上述两种策略还是不能保证所有已过期的key能被及时删除, 从而释放内存资源, 因此Redis还有一系列内存淘汰策略(后面详细介绍)
(2) 定时删除策略
在网上还有一种比较流行的删除策略-定时删除: 意思是给所有持有过期时间的key建一个定时器, 等定时时间到后进行删除操作, 当然如果给每个有过期时间的key都建立一个定时器的话, 那么肯定会导致资源的严重浪费, 而如果引入那种高效定时器的话, 例如堆/时间轮等实现的定时器, 就势必会引入多线程, 这就和Redis单线程的初衷又有违和, 这大概也是Redis没有将定时删除策略加入的原因吧(当然以上内容是听老湿说的)
(3) 定时器原理
1) 基于优先级队列(堆)实现的定时器
队列的特点是先进先出, 而堆的特点就是优先级高的先出, 而堆的优先级策略又是我们程序员自己决定的, 因此在这里我们让过期时间最短的作为堆顶, 建立一个小根堆, 指定一个线程来扫描堆顶, 等到过期时间到时, 将堆顶元素移除, 重新排序, 需要注意的是线程的扫描不是时时刻刻的, 而是先获取堆顶元素的过期时间, 然后进行相同时间的wait等待, 等时间到了之后, 由系统进行唤醒, 执行删除操作, 当然在休眠期间有新的key加入时, 可以先解除休眠, 将元素添加到堆中进行排序, 重复上述休眠操作, 这样就极大的节省了CPU开销
2) 基于时间轮实现的定时器
时间轮的概念就是将一段时间划分为很多小段(具体划分的粒度看实际需求), 每一小段时间后面连接是一个定时任务组成的链表, 然后设置一个定期的轮转间隔, 时间到了之后指针会从一个时间段调到下一个时间段, 然后一个线程会扫描该时间段挂载的整个链表, 尝试执行其中的定时任务, 之所以是'尝试'执行是因为有可能有的key过期时间过长, 轮转完一圈没有它的位置, 这时候就需要过期时间减去轮转时间继续向后寻找找到它应该待的位置, 这就可能导致同一个时间段中同一个链表中可能包含过期时间差距较大的任务, 指针每隔固定的间隔就轮转一次, 不停旋转
9. type
type key
查询key对应val的数据类型, 时间复杂度为O(1), 已知在Redis中key的数据类型只有string, 但是val的数据类型就有很多了:如string字符串, list列表, set集合, zset有序集合, hash哈希表, stream流(Redis作为消息队列的时候会使用这个类型)






















