1.Redis的定义
先上一段官方定义:Redis 是开源免费(遵守BSD协议)、高性能的key-value数据库。
是不是觉得上面的一句话既熟悉又陌生,作者怎么想的为啥开源免费?BSD协议又是什么?key-value数据库是什么,与传统的mysql、oracle有什么区别?
别急,欲知详情,请看下面分解:
Redis是由意大利人Salvatore Sanfilippo(网名:antirez)开发的一款内存高速缓存数据库。Redis全称为:Remote Dictionary Server(远程字典服务),该软件使用C语言编写,Redis是一个key-value存储系统,它支持丰富的数据类型,如:string、list、set、zset(sorted set)、hash。众多语言都支持Redis。
那么BSD协议是什么呢?简单来说就是一个开源协议,具体请看https://www.runoob.com/note/13176。
key-value数据库?就是像python中的dict类型一样的数据存储模式,传统的mysql、oracle是关系型数据库,key-value模式的数据库是非关系型的数据库(NoSQL)。
2.Redis的用途
作为公共缓存:Redis交换数据速度快,在架构中常用来存储一些需要频繁调取的数据,这样可以大大节省系统直接读取磁盘来获得数据的I/O开销,并且可以极大提升速度。
以大型网站来举个例子,比如a网站首页一天有100万人访问,其中有一个板块为推荐新闻。要是直接从数据库查询,那么一天就要多消耗100万次数据库请求。上面已经说过,Redis支持丰富的数据类型,所以这完全可以用Redis来完成,将这种热点数据存到Redis(内存)中,要用的时候,直接从内存取,极大的提高了速度和节约了服务器的开销。
3.Redis的特点
1)支持数据的持久化:可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
2)支持丰富的数据类型:不仅支持简单的key-value类型,同时还支持string、list,set,zset(sorted set),hash等数据结构的存储。
3)支持数据的备份:即master-slave模式的数据备份。
这个理解起来有点深,跟服务器集群、数据的队列访问、程序代码对内存的控制有关。就像一般存储数据都是用mysql、oracle等数据库,而不是使用excel一样。Redis是标准的数据存储和管理方式,只不过与关系型数据库不同的是,Redis的数据存在内存中,效率比关系型数据库高很多。但如果我们自己给服务器添加内存条,我们就要自己写数据的管理方式等,非常繁琐,有现成的为什么不直接用呢?
场景 | 描述 |
---|---|
缓存热数据 | 热数据(进入市场时经常被查询,但未被修改或删除的数据),首选使用redis缓存,redis的性能非常优越。 |
计数器 | 由于具有单个线程,因此可以计算诸如单击,访问,喜欢,评论,视图等的计数,以避免并发问题,确保数据的准确性和100%毫秒的性能,同时启用Redis持久性为了促进数据的持久性。 |
单线程机制 | 验证上一段中的重复请求,并可以自由扩展类似情况。可以通过redis进行过滤。例如,每个请求都使用请求IP,参数和接口的哈希值作为存储Redis的键,并设置有效期限。然后,下一次请求结束时,现在将检索redis中是否存在此密钥,然后验证它是否为“在一定时间内不是重复提交”。例如,要限制用户登录的数量,最好不要超过一天中错误登录的数量。基于redis的Spike系统是单线程功能,可防止数据库超卖,全局增量ID生成等。 |
排行榜 | 得分最高的人排名第一,例如高点击率,活跃度,最高销售数量,投票数最高的前10名,等等。 |
分布式锁 | Redis可用于实现分布式锁。为了确保分布式锁可用,至少锁的实现必须同时满足以下条件:互斥在任何时候,只有一个客户端可以持有该锁。不会发生死锁。即使一个客户在锁定保持期间崩溃而没有主动解锁,也可以确保其他客户以后可以锁定。本的容错能力只能在大多数Redis节点上正常运行。然后,客户可以锁定和解锁。锁定和解锁必须是同一客户端,并且该客户端无法解锁他人添加的锁。 |
会话存储 | 使用Redis进行会话缓存是很常见的情况。使用Redis在其他存储上缓存会话的优点是Redis提供了持久性。目前,很多解决方案都采用Redis作为会话存储解决方案。 |
Redis 内部使用一个 RedisObject 对象来表示所有的 key 和 value。RedisObject 最主要的信息如上图所示:type 表示一个 value 对象具体是何种数据类型,encoding 是不同数据类型在 Redis 内部的存储方式。
比如:type=string 表示 value 存储的是一个普通字符串,那么 encoding 可以是 raw 或者 int。
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
String(字符串) | 1.Redis 最基本的类型 2.二进制安全 |
1. 最基本的类型:可以理解成与 Memcached一模一样的类型,一个 Key 对应一个 Value 2.二进制安全:可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M |
/ |
Hash(字典) | 键值对集合,即编程语言中的Map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删改,提供了操作某一段元素的API | 1.最新消息排行等功能(比如朋友圈的时间线) 2.消息队列 |
Set(集合) | 哈希表实现,元素不重复 | 1.添加、删除,查找的复杂度都是O(1) 2.为集合提供了求交集、并集、差集等操作 |
1.共同好友 2.利用唯一性,统计访问网站的所有独立ip 3.好友推荐时,根据tag求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将Set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行天然排序 | 1.排行榜 2.带权重的消息队列 |
暂时不写
缓存数据与数据库不一致的情况大概有以下三种:
1. 数据库有数据,缓存没有数据:采用先读缓存在读数据库,且读数据库时把数据加载到缓存的策略会消除此种不一致。
2. 数据库有数据,缓存也有数据,数据不相等:无法解决,只能等下次更新数据
3. 数据库没有数据,缓存有数据:删除数据库成功但删除缓存失败了会导致这种情况的发生。
缓存读取策略:
先读缓存数据,如果读到数据则直接返回;如果读不到,就读数据库,并将数据会写到缓存,并返回。
缓存更新策略:
首先先来看下缓存的使用策略,一种常规的用法叫Cache Aside Pattern:
1)读数据:先读缓存数据,如果读到数据则直接返回;如果读不到,就读数据库,并将数据会写到缓存,并返回。
2)写数据:需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)。
对于读数据,应该是毫无疑问肯定要先读缓存数据的,不然增加redis干嘛呢?那么思考一下,写数据的时候,如果不是先更新数据再删除缓存,而是先删除缓存再更新数据库会怎样呢?看下A、B两个线程同时要更新数据时会发生哪些情况:
1)A、B都完成了删除缓存且A、B更新数据库的中间有其他线程读取数据:假设A线程先更新数据库。此时C线程介入,C线程要读取数据,由于缓存没有则查数据库,并把A更新的数据写入了缓存,最后B线程更新数据库,以后D、E、F等线程读取数据的时候都是从缓存读取且B线程写入数据库的数据永远不会加载到缓存,由此形成了缓存与数据库数据的永久不一致,直至下次缓存数据被删除。。。
2)A、B都完成了删除缓存且A、B更新数据库的中间没有其他线程读取数据:这种情况要看A和B谁先更新了数据库,假设A线程先更新了数据,那么下次其他线程读取数据的时候便会把A线程的数据加载到缓存。这种情况无论A、B谁先更新了数据库,都不会导致缓存与数据库不一致。
另外有人会问,如果采用你提到的方法,为什么最后是把缓存的数据删掉,而不是把更新的数据写到缓存里?
这么做引发的问题是,如果A,B两个线程同时做数据更新,A先更新了数据库,B后更新数据库,则此时数据库里存的是B的数据。而更新缓存的时候,是B先更新了缓存,而A后更新了缓存,则缓存里是A的数据。这样缓存和数据库的数据也不一致。
并发不高的情况:
读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
写: 写mysql->成功,再写redis;
并发高的情况:
读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
写:异步话,先写入redis的缓存,就直接返回;定期或特定动作将数据保存到mysql,可以做到多次更新,一次保存;
缓存删除策略:
1. 删除失败则重试:删除缓存失败的话需要进行重试,数据的一致性要求越高,重试得越快。
2. 定期全量更新:简单地说,就是定期把缓存全部清掉,然后再全量加载。
3. 给所有的缓存一个失效期:一个大杀器,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定。
分布式环境下非常容易出现缓存和数据库间数据一致性问题,针对这一点,如果项目对缓存的要求是强一致性的,那么就不要使用缓存。只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。合适的策略包括上述的缓存更新策略:更新数据库后及时更新缓存、缓存失败时增加重试机制。
参考文章:
搞懂这些Redis知识点,吊打面试官!
Redis 数据类型
Redis怎么保持缓存与数据库一致性?