无知的我正在复盘Redis。。。
笔记特点是
- 重新整理了涉及资料的一些语言描述、排版而使用了自己更容易理解的描述。。
- 提升了总结归纳性
- 同样是回答了一些常见关键问题。。
文章目录
Redis
Redis 是什么
是什么
- 目前最受欢迎的NoSQL数据库之一
- 使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库
特点
- 基于内存运行,所以性能高效(每秒可以处理超过10万次读写操作)
- 支持分布式,理论上可以无限扩展
- key-value 存储系统。
- key 是字符串
- value 可以是 字符串、列表、集合、散列表、有序集合等等
- 开源地使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存持久化的日志型、key-value 数据库;提供多种语言的API
Redis 数据类型
数据类型 导读
- 基本数据结构 都是基于 String 的
- bitMap、HyperLog 的底层是基于 String 的
- GEO 是基于 zset 的
数据类型 String
是什么
- 是 redis 最基础的数据结构
- 键都是字符串类型
- 其他集中基本数据结构都是在 String 的基础上构建的
- 值实际上可以是字符串(编码方式为编为字节码;占用内存更多)、数字(编码方式是直接编为二进制)、二进制
- 字符串包括 简单的、复杂的(如 JSON、XML)
- 数字包括 整数、浮点数
- 二进制包括 图片、音频、视频
- 值最大不能超过512MB
如图
使用场景
缓存功能
- MySQL作为存储层绝大部分的数据都是从Redis这个缓存层获取的。
- 缓存能够 加速读写、降低后端压力。这是因为Redis具有支撑高并发的特性
计数
- 能够 快速计数、查询缓存
- 可以 让数据异步落地到其他数据源
共享Session
- 为了解决“分布式服务器负载均衡而使得用户每次访问Session都需要重新登陆”问题
- 具体实现是 将用户的Session做集中管理,在保证Redis高可用、扩展性下,用户每次更新或者查询登录的信息都可以从 Redis 中集中获得
限速
- 限制用户访问接口的频率
数据类型 List
是什么
- 就是链表
- 存储多个有序的字符串(如 a、b、c 从左到右组成了一个有序的列表)
- 列表中的每个字符串被称为元素
特点
- 列表中的元素是有序的
- 列表中的元素是可重复的
使用场景
消息队列
Redis的lpush + brpop命令组合可以实现阻塞队列
- 生产者客户端使用lrpush从列表左侧插入元素
- 多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡、高可用性
文章列表
实现其他数据结构
- lpush + lpop = Stack
- lpush + rpop = Queue
- lpush + ltrim = Capped Colleciton(有限集合)
- lpush + brpop = Message Queue(消息队列)
数据类型 has
是什么
类似于 JDK1.8 前的HashMap,内部实现也差不多(数组 + 链表)
使用场景
比较适宜存放对象类型的数据
举例说明——使用场景
使用String类型,需要三条语句
缺陷是 键数过多,占用内存多,用户信息过于分散,不用于生产环境
对象序列化存入redis
序列化、反序列化有一定的开销。更新属性时需要userInfo全取出来进行反序列化,更新后再序列化到redis
使用hash类型
缺陷是 要控制内部编码格式,不适当的格式会消耗更多的内存
数据类型 set
是什么
类似于java中Hashset。是一种无序集合、没有先后顺序
使用场景
提供了判断某个成员是否在set集合内的接口中(这个是list不能提供的)
可以基于set轻易实现交集、并集、差集的操作
应用在 标签
应用在 生成随机数进行抽奖活动、社交图谱等等
数据结构 Zset
是什么
保留了集合不能有重复成员的特性,不同的是 可以排序有序集、不像列表一样使用索引下标作为排序依据,而是为每个元素设置一个分数(score)作用排序的依据
特点是
元素不能重复,但是score能重复
提供了获取指定分数、元素范围查询、计算成员排名等功能
使用场景
排行版系统
数据结构 bitmap
是什么
“big”字符串由三个字符组成
分别对应ASCII码为 98、105、103
对应的二进制分别是01100010、01101001、011001111
本身不是一种数据结构,而是字符串。但可以对字符串的位进行操作
Redis使用bitmaps和使用字符串的方法不同。
可以把Bitmaps想象成一个以位为单位的数组,数组的每个单位只能存储0、1。数组的下标在Bitmaps中叫做偏移量
使用场景
需要保存状态信息(如是否签到、是否登录)并需要对其进行进一步分析的场景
构建布隆过滤器
Redis 使用原因
这是因为实现了高性能、高并发
Redis 与Memcached
Memcached | Redis |
---|---|
数据类型单一。所有的值都是简单的字符串 | 数据类型丰富 |
速度慢 | 速度快 |
不可持久化数据 | 可持久化数据 |
都是非关系型内存键值数据库
Redis 应用场景
计数器
对String进行自增自减运算
缓存
将热点数据放到内存中
会话缓存
统一存储多台应用服务器的会话信息
全页缓存(FPC)
除了基本的会话token之外,Redis还提供了很简便的FPC平台来加速加载曾访问过的页面
查找表
DNS记录很适合使用Redis来存储
消息队列
这是因为List是一个双向链表。可以通过lpush、rpop写入和读取信息
但是推荐使用RabbitMQ、Kafka等消息中间件
分布式锁实现——在分布式场景下,无法使用在单机环境下的锁来对多个节点上的进程进行同步
使用Redis自带的SETNX命令实现分布式锁
使用官方提供的 RedLock 分布式锁实现
Redis 快的原因
- 完全基于内存。类似于HashMap的优势是查找和操作的时间复杂度都是O(1)
- 数据结构简单,数据操作简单
- 采用单线程。避免了不必要的上下文切换、竞争条件;不存在多线程、多进程导致的切换而消耗CPU;不存在加锁释放锁操作;不会出现死锁
- 使用多路I/O复用模型,非阻塞IO
- 使用的底层模型是Redis自己构建的 VM 机制。避免了系统调用系统函数会浪费一定时间去移动和请求的问题。
Redis 不用map/guava作缓存的原因
缓存分为本地的、分布式的缓存
Java自带的 map、guava 实现的是本地缓存
- 生命周期随着 JVM 的销毁而结束
- 缓存不具有一致性(主要原因)。比如 在多实例情况下,每个实例都需要各自保存一份数据
- 适应场景是 快速、轻量
Redis、Memcached 是分布式缓存
- 缓存具有一致性。比如 在多实例情况下,每个实例共用一份缓存数据
- 虽然缺点是需要保持它们服务的高可用性、整个程序架构较为复杂
Redis 持久化机制
Redis 持久化是什么 防止当突然宕机时,Redis的数据因保存在内存而导致全部消失
Redis 持久化机制方式
- RDB 快照
- AOF 日志
Redis 数据库双写问题
为了确保数据是一致的方法
首先排除两种方法
- 先更新缓存,再更新DB
- 先更新DB,再更新缓存
- 原因是 难以确保更新的内容是一致的,这是难以察觉的
原因是 存在并发问题导致更新出了脏数据。如图
先删除缓存,再更新DB
引出问题 并发问题导致的数据不一致问题
这是因为当进程A想要删除缓存而更新数据库、B同时想要查询数据时
- 进程A删除缓存的速度比进程B进度慢
- 进程B判断到缓存为空
- 去数据库查询
- 自动将数据库查询到的数据更新到原本的空缓存中
- 此时进程A更新数据库(没有更新缓存)
- =>这就意味者缓存中的是旧数据(就是脏数据)
解决办法是 使用“延时双删”策略
进程A在最后更新数据库时,等待其他进程执行完成后(比如先休眠进程A一秒),重新再次更新缓存
Redis 缓存穿透问题
定义及结构图
当用户发送访问 缓存中没有的数据 的请求时,这个请求会继续向数据库进行访问,如果有大量的这种请求,会导致数据库在短时间内因承受大量请求而崩掉。如下图
解决方案
两种常规方案
- 在接口层添加校验。
- 在第一次接收到 访问缓存没有的、数据库也没有的数据 的请求时,将key-value对写成key-null来返回,那么在下一个接收到同样的请求时,可以直接从缓存将设定好的数据再次返回。(但是还要考虑到用户反复用同一个id暴力攻击的情况,解决办法是 将这种数据的缓存有效期设置短一些)
布隆过滤器
是什么
- 本质上还是在接口层添加校验
- 只不过校验的方式是通过一个二进制数组、Hash 算法组成的
- 作用是 判断一个元素是否存在一个集合中
实现机制
- 将 所有可能存在的数据 哈希到 一个足够大的bitmap中
- 在bitmap的值中 1 代表存在;0代表不存在
- 一个一定不存在的数据一定会被bitmap拦截
- //从而避免对底层存储系统的查询压力过大
校验机制 举例说明
- A、B、C数据形成了一个集合 并且 哈希到 了一个bitmap中
- 当用户查询D、F数据时,将它们哈希到 bitmap 中,如果哈希到的bitmap的值为0,则返回false
- 如下图
引出问题 当访问哈希的bitmap值为1,无法确定其存在哪个集合中
解决办法是 作两次哈希。如下图