文章目录
Redis概述
什么是Redis
Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。
Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。Redis还有三种特殊的数据结构geospatial、Bitmap、Hyperloglog。
与传统数据库不同的是 Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。另外,Redis 也经常用来做分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
Redis为什么这么快
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路 I/O 复用模型,非阻塞 IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
Redis有哪些数据类型
Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分的使用要求,Redis还有三种特殊的数据结构geospatial、Bitmap、Hyperloglog。
常用的set可以实现并集交集的操作,可以来实现共同好友,还有zset可以来实现,topk排行榜,list可以用来实现消息队列等。
数据类型 | 可以存储的值 | 底层实现 | 操作 | 应用场景 |
---|---|---|---|---|
STRING | 字符串、整数或者浮点数 | long,long double,SDS动态字符串 | 从两端压入或者弹出元素对单个或者多个元素进行修剪,只保留一个范围内的元素 | 做简单的键值对缓存 |
LIST | 列表 | ziplist(压缩列表),LinkedList(链表) | 从两端压入或者弹出元素对单个或者多个元素进行修剪,只保留一个范围内的元素 | 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据 |
HASH | 包含键值对的无序散列表 | ziplist,hashtable(字典) | 添加、获取、移除单个键值对获取所有键值对检查某个键是否存在 | 结构化的数据,比如一个对象 |
SET | 无序集合 | intset(整数集合),hashtable | 添加、获取、移除单个元素检查一个元素是否存在于集合中计算交集、并集、差集从集合里面随机获取元素 | 交集、并集、差集的操作,比如交集,可以把两个人的粉丝列表整一个交集 |
ZSET | 有序集合 | ziplist,skiplist(跳跃表) | 添加、获取、删除元素根据分值范围或者成员来获取元素计算一个键的排名 | 去重但可以排序,如获取排名前几名的用户 |
Redis底层数据结构
简单动态字符串
链表
字典
跳跃表
整数集合
压缩列表
Redis底层实现之跳跃表
什么是跳跃表
跳跃表是一种有序的数据结构,它通过在每个节点中维持多个指向其他的几点指针,从而达到快速访问队尾目的。跳跃表的效率可以和平衡树相媲美了,最关键是它的实现相对于平衡树来说,代码的实现上简单很多。
跳跃表用在哪
说真的,跳跃表在 Redis 中使用不是特别广泛,只用在了两个地方。一是实现有序集合键(zset),二是集群节点中用作内部数据结构。
跳跃表的插入删除过程
见上方链接
Redis持久化机制
Redis 是一个支持持久化的内存数据库,通过持久化机制将内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后再将硬盘文件加载到内存,就可以达到恢复数据的目的。
实现:单独fork一个子进程,将父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件,持久化过程就结束了。再由这个临时文件替换上一次的临时文件,持久化就结束了。
RDB
RDB 是 Redis 默认的持久化方式。按照一定的时间周期策略将内存中的数据以快照的形式保存到硬盘的二进制文件。对应产生的数据文件是 dump.rdb,通过配置文件中的 save 参数来定义快照的周期。
AOF
Redis 会将每一个收到的写命令通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启的时候会重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
当两种方式同时开启,Redis 会优先选择 AOF 的方式恢复数据。
缓存异常
缓存雪崩
缓存雪崩是指:缓存在同一时刻出现大面积的过期,导致应该访问缓存的请求都去查询数据库了,从而对数据库CPU和内存造成巨大压力,严重的会导致数据库宕机,从而形成一系列的连锁反应,导致整个系统崩溃。
解决方案
- 当并发量不高的时候,加锁排队
- 缓存数据过期的时间设置随机,防止同一时间缓存大量过期
缓存穿透
缓存穿透是指请求查询缓存和数据库中都没有的数据,导致所有请求都落在数据库上,造成数据库短时间承受大量请求而崩掉。
解决方案
- 业务层增加校验逻辑,不符合规范的查询直接拦截
- 当一个数据在缓存和数据库都没有的时候,可以设置一个key-null的键值对在缓存中,设置比较短过期时间,比如30s。
- 使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层数据库的压力。
布隆过滤器
布隆过滤器的作用是可以判断一个元素是否存在。
它会采用多个不同的哈希函数来将一个元素映射到 bitmap 中。当需要判断一个元素是否存在时,只需要通过这些哈希函数对元素进行映射,如果映射的位置都存在,则可以判断该元素存在;反之只要有一个哈希函数的映射不存在,则该元素不存在。
缓存击穿
缓存击穿是指是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,造成数据库压力过大。与缓存雪崩不同的是,缓存击穿是指并发查一条数据,一个key,而缓存雪崩是发生大面积的缓存过期(很多数据都在缓存中查不到了,从而查数据库)。
解决方案
- 设置热点key永不过期
- 通过 setnx 加互斥锁(一次只能有一个请求访问,访问完后释放锁喉其他请求才能来继续访问)
缓存预热
缓存预热就是在系统上线之后,将相关缓存的数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再更新缓存。用户可以直接查询缓存中预热的数据。
解决方案
- 定时刷新缓存
- 上线后手动操作
缓存更新 / 缓存淘汰策略
Redis自带的缓存失效策略(Redis默认6种)
- volatile-lru 对有过期时间的key通过lru淘汰
- volatitle-random 对有过期时间的key随机淘汰
- allkeys-lru 对所有key通过lru淘汰
- allkeys-random 对所有key随机淘汰
- volatitle-ttl 淘汰最早过期的key
- noeviction 增加新数据时报error,不会删除旧的数据
我们还可以根据具体业务自定义缓存淘汰:
常见的有:
(1)定时清理过期缓存
(2)用户请求过来时,再判断缓存是否过期
缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
热点数据和冷数据
对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。
对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存。
那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们博客的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。
事务
Redis事务的概念
Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
Redis事务的三个阶段
- 事务开始 MULTI
- 命令入队
- 事务执行 EXEC
Redis事务相关命令
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
- redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
- 如果在一个事务中的命令出现错误,那么所有的命令都不会执行(这句话的意思是指在MULTI之后将命令加入到是命令队列的过程中,某条命令出现语法错误,则这次事务不会执行)
- 如果在一个事务中出现运行错误,那么正确的命令会被执行。(这句话是指在命令已经全部加入到命令队列之后,执行EXEC命令开始执行命令队列中的命令时,发生错误,错误的命令不会被执行,而正确的命令依旧还是会被执行)。
- WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
- MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
- EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
- 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
- UNWATCH命令可以取消watch对所有key的监控。
事务的ACID
Redis无法保证事务的原子性,事务中任意命令执行失败,其余的命令仍会被执行。(Redis不支持回滚)
Redis的事务总是具有ACID中的一致性和隔离性,其他特性是不支持的。当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有耐久性。
Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。
Redis分布式问题
分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作。
使用SETNX完成同步锁的流程及事项如下:
使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
为了防止获取锁后程序出现异常(比如获取锁的线程所在服务器宕机了),导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间。
除了可以使用Redis实现分布式锁还可以使用Zookeeper实现分布式锁。
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,当然是从以可靠性为主。所以首推Zookeeper。
什么是 RedLock
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock(Java可以使用Redisson来使用RedLock,底层是通过Lua脚本来实现),此种方式比原先的单节点的方法更安全。它可以保证以下特性:
- 安全特性:互斥访问,即永远只有一个 client 能拿到锁
- 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
- 容错性:只要大部分 Redis 节点存活就可以正常提供服务
常用工具
Redis支持的Java客户端都有哪些?官方推荐用哪个?
Redisson、Jedis、lettuce等等,官方推荐使用Redisson。
Redis和Redisson有什么关系?
Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。
Jedis与Redisson对比有什么优缺点?
Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。