目录:
- 写在前面
- 什么是Redis?
- 为什么要用Redis(缓存)?
- 为什么要用Redis 而不用 map/guava 做缓存?
- Redis与Memcached的区别
- Redis的应用场景
- Redis为什么这么快?
- Redis有哪些数据类型?各自的使用场景?
- Redis 什么是持久机制?
- RDB持久化、AOF持久化的区别
- 如何保证缓存与数据库双写时的数据一致性?
- 缓存穿透是什么?如何解决?
写在前面
大厂都在用Redis?
1、github
2、twitter
3、微博
4、Stack Overflow
5、阿里巴巴
6、百度
7、美团
8、搜狐
9...
什么是Redis?
Redis现在最受欢迎NoSQL数据库之一,Redis是一个使用ANSI C存储数据库具有以下特点:
- 基于内存运行,性能高效(每秒可处理超过) 读写操作10万次)
- 理论上可以无限扩展支持分布式
- key-value存储系统(key按键包括字符串、列表、集合、散列表、有序集合等。
- 开源的使用ANSI C语言编写,遵守BSD协议、支持网络、基于内存或可持续的日志,Key-Value提供多种语言的数据库API
为什么要用Redis(缓存)?
主要从“高性能”和“高并发”这两点来看待这个问题。
如果第一次访问数据库中的一些数据。这个过程会很慢,因为它是从硬盘上读取的。将用户访问的数据存储在数字缓存中,以便在下次访问数据时直接从缓存中获取。操作缓存是直接操作内存,所以速度相当快。如果数据库中的相应数据发生变化,则可以同步更改缓存中的相应数据!
直接操作缓存的要求远远超过直接访问数据库,因此我们可以考虑将数据库中的据库中的一些数据转移到缓存中,以便用户的一些要求将直接访问缓存而不通过数据库。
为什么要用Redis 而不用 map/guava 做缓存?
缓存分为本地缓存和分布式缓存
以 Java 例如,让带 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,而命周期随着 jvm 销毁结束,在多个例子的情况下,每个例子都需要保存缓存,缓存不一致。
使用redis 或 memcached 这被称为分布式缓存。在多个例子中,每个例子共享一个缓存数据,缓存是一致的。缺点是需要保持 redis 或 memcached服务可用性高,整个程序架构复杂。
Redis与Memcached的区别
两者都是非关系内存键值数据库,现在公司通常使用 Redis而且 Redis 越来越强大!Redis 与 Memcached 主要有以下差异:
(1) memcached所有值都是简单的字符串,redis作为替代品,支持更丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis数据可以持久
Redis的应用场景
可以对 String 实现计数器功能的自增自减操作。Redis 这种内存数据库的读写性能很高,非常适合存储频繁读写的计数。
将热点数据放入内存中,设置内存量和淘汰策略,确保缓存命中率。
可以使用 Redis 统一存储多个应用程序服务器的会话信息。当应用程序服务器不再存储用户的会话信息时,用户可以要求任何应用程序服务器,以便更容易实现高可用性和可伸缩性。
除基本会话外token之外,Redis还提供了非常简单的服务FPC平台。以Magento为例,Magento为使用提供插件Redis作为全页缓存后端。另外,对WordPress对于家庭来说,Pantheon有一个很好的插件 wp-redis,这个插件可以帮助你尽快加载你浏览过的页面。
例如 DNS 记录非常适合使用 Redis 存储。搜索表类似于缓存,也被使用 Redis 快速搜索特性。但是,由于缓存不是可靠的数据来源,搜索表的内容不能失效,缓存的内容也不能失效。
List 双向链表可以通过 lpush 和 rpop 写入和读取消息。但是最好用 Kafka、RabbitMQ 等待消息中间件。
在分布式场景中,单机环境下的锁不能用于同步多个节点的过程。Redis 自带的SETNX 此外,还可以使用官方提供的命令实现分布式锁 RedLock 实现分布式锁。
Set 可实现交集、并集等操作,从而实现共同好友等功能。ZSet 可实现有序操作,从而实现排名等功能。
Redis为什么这么快?
1.完全基于内存,大部分要求都是纯内存操作,非常快。数据存储在内存中,类似于HashMap,HashMap 优点是搜索和操作的时间复杂性O(1);
2.数据结构简单,数据操作简单,Redis 中间的数据结构是专门设计的;
3.采用单线程,避免了不必要的上下文切换和竞争条件,也没有多过程或多线程造成的切换 CPU,不需要考虑各种锁,没有锁释放锁操作,没有性能消耗可能导致死锁;
4、使用多路 I/O 复用模型,非阻塞 IO;
5、使用不同的底层模型,它们之间的底层实现和与客户端之间的通信应用协议不同,Redis 直接自己构建 VM 机制,因为如果系统调用系统函数,会浪费一定的时间移动和请求;
Redis有哪些数据类型?各自的使用场景?
Redis有五种数据类型,包括String,List,Set,Zset,Hash,满足大部分使用要求。Redis它还为我们提供了几种先进的数据结构,bitmaps,HyperLogLong、Geo,其中bitmaps,HyperLogLong底层是基于String,Geo则是基于zset。
介绍:
字符串的类型是Redis最基本的数据结构。首先,键是字符串类型,其他数据结构是基于字符串类型,因此字符串类型可以为学习其他四种数据结构奠定基础。字符串类型的值实际上可以是字符串(简单的字符串,复杂的字符串)JSON、XML))、数字(整数、浮点数),甚至二进制(图片、音频、视频),但最大值不得超过512MB。
字符串类型使场景广泛:
Redis 作为缓存层,MySQL作为存储层,绝对部分要求的数据来自Redis获取Redis它具有支持高并发性的特点,因此缓存通常可以加速读写,降低后端压力。
使?Redis 作为计数的基本工具,它可以实现快速计数和查询缓存的功能,同时数据可以异步到其他数据源。
一个分布式Web用户的服务Session信息(如家庭登录信息)保存在各自的服务器中,这将导致一个问题。出于负载平衡的考虑,分布式服务将用户的访问平衡到不同的服务器上,用户可能会发现需要重新登录。
为了解决这个问题,可以使用Redis将用户的Session在这种模式下,只要保证集中管理,Redis它次用户更新或查询登录信息,都是高可用性和可扩展性的Redis集中获取。
例如,出于安全考虑,许多应用程序允许用户在每次登录时输入手机验证码,以确定它们是否是用户本人。但是为了短信?不频繁访问将限制用户每分钟获取验证码的频率,例如每分钟不超过5次。有些网站限制一个IP地址不能在一秒钟内访问n次以上,也可以使用类似的想法。
介绍:
list 即是 链表
列表( list)该类型用于存储多个有序字符串,a、b、c、d、e五个元素从左到右形成有序列表,列表中的每个字符串称为元素(element)
列表类型有两个特点:第一,列表中的元素是有序的,这意味着可以通过索引标记获得某个元素或某个范围内的元素列表。第二、列表中的元素可以是重复的。
消息队列,Redis 的 lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使⽤brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
lpush+lpop = Stack(栈)
lpush +rpop = Queue(队列)
lpsh+ ltrim = Capped Collection(有限集合)
lpush+brpop =Message Queue(消息队列)
介绍:hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。
哈希类型比较适宜存放对象类型的数据,我们可以比较下,如果数据库中表记录为:
set user:1:name king;
set user:1:age 18;
set user:1:sex boy;
优点:简单直观,每个键对应一个值
缺点:键数过多,占用内存多,用户信息过于分散,不用于生产环境
set user:1 serialize(userInfo);
优点:编程简单,若使用序列化合理内存使用率高
缺点:序列化与反序列化有一定开销,更新属性时需要把userInfo全取出来进行反序列化,更新后再序列化到redis
hmset user:1 name King age 18 sex boy
优点:简单直观,使用合理可减少内存空间消耗
缺点:要控制内部编码格式,不恰当的格式会消耗更多内存
介绍:set 类似于 Java 中的 HashSet 。Redis 中的 set 类型是一种无序集合,集合中的元
素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择
使用场景:
set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set轻易实现交集、并集、差集的操作。
集合类型比较典型的使⽤场景是标签( tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。
例如一个电子商务的⽹站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益。除此之外,集合还可以通过生成随机数进行比如抽奖活动,以及社交图谱等等。
介绍:
有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数( score)作为排序的依据。
有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。
有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助我们在实际开发中解决很多问题。
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。
介绍:
现代计算机用二进制(位)作为信息的基础单位,1个字节等于8位,例如“big”字符串是由3个字节组成,但实际在计算机存储时将其用二进制表示,“big”分别对应的ASCII码分别是98、105、103,对应的二进制分别是01100010、01101001和 01100111。
Bitmaps本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作。
Bitmaps单独提供了⼀套命令,所以在Redis中使用Bitmaps和使⽤字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量。
使用场景:适合需要保存状态信息(比如是否签到、是否登录...)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
Redis 的持久化机制是什么?
Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制。
Redis 的持久化机制有两种,第一种是RDB快照,第二种是 AOF日志。
RDB持久化是将某个时间点上Redis中的数据保存到一个RDB文件中,如下所示
该⽂件是⼀个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时Redis中的数据,如下所示:
Redis提供了2个命令来创建RDB文件,一个是SAVE,另一个是BGSAVE。
因为BGSAVE命令可以在不阻塞服务器进程的情况下执行,所以推荐使用BGSAVE命令。
载入RDB文件的目的是为了在Redis服务器进程重新启动之后还原之前存储在Redis中的数据。然后,Redis载入RDB文件并没有专门的命令,而是在Redis服务器启动时自动执行的。而且,Redis服务器启动时是否会载入RDB文件还取决于服务器是否启用了AOF持久化功能,具体判断逻辑为:
- 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据
- 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据。
AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库数据的,如下图所示
默认情况下,AOF持久化功能是关闭的,如果想要打开,可以修改配置。
因为AOF文件包含了重建数据库所需的所有写命令,所以Redis服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原Redis服务器关闭之前的数据。
Redis读取AOF文件并还原数据库的详细步骤如下:
1. 创建一个不带⽹络连接的伪客户端
(因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有⽹络连接的伪客户端来执行AOF文件保存的写命令。伪客户端执行命令的效果和带网络连接的客户端执⾏命令的效果完全一样)
2. 从AOF文件中分析并读取出一条写命令。
3. 使用伪客户端执行被读取出的写命令。
4. 一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被执行完毕。
以上步骤如下图所示:
RDB持久化、AOF持久化的区别
RDB持久化是通过将某个时间点Redis服务器存储的数据保存到RDB文件中来实现持久化的。AOF持久化是通过将Redis服务器执行的所有写命令保存到AOF文件中来实现持久化的。
由上述实现方式可知,RDB持久化记录的是结果,AOF持久化记录的是过程,所以AOF持久化生成的AOF文件会有体积越来越大的问题,Redis提供了AOF重写功能来减小AOF文件体积。
AOF持久化的安全性要⽐RDB持久化的安全性高,即如果发生机器故障,AOF持久化要比RDB持久化丢失的数据要少。
因为RDB持久化会丢失上次RDB持久化后写入的数据,而AOF持久化最多丢失1s之内写入的数据(使用默认everysec配置的话)。
由于上述的安全性问题,如果Redis服务器开启了AOF持久化功能,Redis服务器在启动时会使用AOF文件来还原数据,如果Redis服务器没有开启AOF持久化功能,Redis服务器在启动时会使用RDB文件来还原数据,所以AOF文件的优先级比RDB文件的优先级高。
如何保证缓存与数据库双写时的数据一致性?
只要使用到缓存,无论是本地内存做缓存还是使用 redis 做缓存,那么就会存在数据同步的问题。
这个方案我们一般不考虑。原因是更新缓存成功,更新数据库出现异常了,导致缓存数据与数据库数据完全不一致,而且很难察觉,因为缓存中的数据一直都存在
. 先更新DB,再更新缓存
这个方案也我们一般不考虑,原因跟第一个一样,数据库更新成功了,缓存更新失败,同样会出现数据不一致问题。
这种更新缓存的方式还有并发问题。
同时有请求A和请求B进行更新操作,那么会出现
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存
这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。
、先删除缓存,后更新DB
该方案也会出问题,具体出现的原因如下。
此时来了两个请求,请求 A(更新操作)和请求 B(查询操作)
请求 A 会先删除 Redis 中的数据,然后去数据库进行更新操作;
此时请求 B 看到 Redis 中的数据时空的,会去数据库中查询该值,补录到 Redis 中;
但是此时请求 A 并没有更新成功,或者事务还未提交,请求B去数据库查询得到旧值;
那么这时候就会产生数据库和 Redis 数据不一致的问题。如何解决呢?其实最简单的解决办法就是延时双删的策略。就是
(1)先淘汰缓存
(2)再写数据库
(3)休眠1秒,再次淘汰缓存
这么做,可以将1秒内所造成的缓存脏数据,再次删除。
那么,这个1秒怎么确定的,具体该休眠多久呢?
针对上面的情形,自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
、先更新DB,后删除缓存
这种方式,被称为Cache Aside Pattern,读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。更新的时候,先更新数据库,然后再删除缓存。
这种情况不存在并发问题么?
依然存在。假设这会有两个请求,⼀个请求A做查询操作,⼀个请求B做更新操作,那么会有如下情形产生
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写⼊数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写⼊缓存
然而,发生这种情况的概率又有多少呢?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离⼲嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步(3)耗时比步骤(2)更短,这一情形很难出现。一定要解决怎么办?如何解决上述并发问题?
但是,更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功怎么办?这个问题,在删除缓存类的方案都是存在的,那么此时再读取缓存的时候每次都是错误的数据了。
此时解决方案有两个,一是就是利用消息队列进行删除的补偿。具体的业务逻辑用语言描述如下:
1、请求 A 先对数据库进行更新操作
2、在对 Redis 进行删除操作的时候发现报错,删除失败
3、此时将Redis 的 key 作为消息体发送到消息队列中
4、系统接收到消息队列发送的消息后
5、再次对 Redis 进行删除操作
但是这个方案会有一个缺点就是会对业务代码造成大量的侵入,深深的耦合在一起,所以这时会有一个优化的方案,我们知道对 Mysql 数据库更新操作后再 binlog 日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql 数据库的 binlog 日志对缓存进行操作。
订阅binlog程序在mysql中有现成的中间件叫canal,阿里开源的,大家可以自行查阅官网,可以完成订阅binlog日志的功能。
什么是缓存穿透?怎么解决
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
1970 年布隆提出了一种布隆过滤器的算法,用来判断一个元素是否在一个集合中。
这种算 法由一个二进制数组和一个 Hash 算法组成。
选择多个 Hash 函数计算多个 Hash 值,这样可以减少误判的几率
不定期分享java技术知识,有整理超多java学习资料,如果有需要可以私信回复“大厂”
希望也能在java路上帮助到你!