在计算机中,缓存是一个高速数据存储层,其中存储数据的子集,且通常是短暂的存储,这样日后再次请求此数据时,速度要比访问数据的主存储位要快,通过缓存可以高效的重用之前检索或计算的数据。(就是从访问比较慢的地方放到访问比较快的地方)
为什么要用缓存
- 提高应用程序的性能(因为内存的数据读取比磁盘快,所以整体性能也提升了)
- 降低数据库成本(同样的配置,缓存比数据库的读取速度更大)
- 减少后端负载(可以减少与数据库之间的连接)
- 可预测的性能
- 消除数据库热点(将数据库中经常访问的数据加载到缓存中)
- 提高读取吞吐量
读多写少用缓存!!!
Redis
Redis是一个开源的使用C语言编写、支持网络、可基于内存亦可持久化的的日志型、key-value数据库,并提供多种语言的API。
本质时客户端-服务端应用软件程序。
特点是简单、性能强悍、功能使用场景丰富。
通用命令
命令 | 描述 |
---|---|
DEL key | 该命令用于在key存在时删除key |
DUMP key | 序列化给定key,并返回序列化的值 |
EXISTS key | 检查给定key是否存在 |
EXPIRE key seconds | 为指定key设置过期时间,以秒为单位 |
TTL key | 以秒为单位返回给定key的剩余生存时间 TTL (time to live) |
TYPE key | 返回key所储存的值的类型 |
数据结构
String
string 数据结构是简单的key-value类型,value其实不仅是String,也可以是数字。使用场景:微博数,粉丝数(常规计数)
常用命令
命令 | 描述 |
---|---|
Get | 获取指定key的值 |
Set | 设置指定key的值 |
Incr | 将key中存储的数值加一 |
Decr | 将key中存储的数值减一 |
Mget | 获取所有(一个或多个)给定key的值 |
List
List就是链表,使用场景:微博的关注列表,粉丝列表
常用命令
命令 | 描述 |
---|---|
Lpush | 将一个或多个值插入到列表的头部中 |
Rpush | 在列表中添加一个或多个值 |
Lpop | 移出并获取列表的第一个元素 |
Rpop | 移出列表的最后一个元素,返回值为移除的元素 |
Lrange | 获取所有(一个或多个)给定key的值 |
Set
Set就是一个集合,集合的概念就是一堆不重复值的组合,利用Redis提供的Set数据结构,可以存储一些集合行的数据。使用场景:实现共同关注、共同喜好、二度好友。
常用命令
命令 | 描述 |
---|---|
Sadd | 向集合中添加一个或多个成员 |
Spop | 移除并返回集合中一个随机元素 |
Smebers | 返回集合中所有的成员 |
sunion | 返回给定集合的并集 |
Sorted Set
Sorted Set 的使用场景与Se,区别在于Set不是有序的,而Sorted Set 可以通过用户额外提供一个优先级(Score)的参数来为成员排序,并且是插入有序的,即自动排序。使用场景:排行榜、按照用户投票和时间排序。
常用命令
命令 | 描述 |
---|---|
Zadd | 向有序集合添加一个或多个成员,或更新已存在的成员的分数 |
Zrange | 通过索引区间返回有序集合中指定区间内的成员 |
Zrem | 移除有序集合中的一个或多个成员 |
Zcard | 获取有序集合中的成员数 |
Hash
Hash是一个String类型的field和value的映射表,使用场景:存储部分变更的数据,如用户信息。
常用命令
命令 | 描述 |
---|---|
Hget | 获取存储在哈希表中的指定字段 |
HSet | 将哈希表key中的字段field的值设为value |
Hgetall | 获取在哈希表中指定key的所有字段和值 |
GEO
GEO 3.2版本开始对GEO(地理位置)的支持,使用场景LBS应用开发
常用命令
命令 | 描述 |
---|---|
GEOADD | 增加地理位置的坐标,可以批量增加地理位置 |
GEODIST | 获取两个地理位置之间的距离 |
GEOHASH | 获取某个地址位置的geohash值 |
GEOPOS | 获取指定位置的坐标,可以批量获取多个地理位置的坐标 |
GEOREDIUS | 根据地理位置的坐标获取指定范围内的地理位置集合(注意:改命令的中心点由输入和维度决定) |
GEOREDIUSBYMEMBER | 根据给定成员的位置获取指定范围内的位置信息集合(注意:改命令的中心点由输入和维度决定) |
Stream
Stream 5.0版本开始的新的结构"流",使用场景:消费者生产者场景(类似MQ)
常用命令
命令 | 描述 |
---|---|
XADD | 往指定的流中添加消息 |
XLEN | stream流中的消息数量 |
XDEL | 删除流中的消息 |
XRANGE | 返回流中满足给定id范围的消息 |
XREAD | 从一个或多个流中读取消息 |
XINFO | 检索关于流和关联的消费者组的不同的信息 |
Redis的可以做什么
- 缓存:通过把数据放入到缓存中提高查询的效率降低数据库的IO
- 计数器:通过DECR和INCRBY执行增加或减少计数器的值。
- 消息队列:通过LIST,或着Stream可以实现简单的消息队列,Stream是5.0过后更新的其本质是在redis上实现一个消息发布和订阅的组件。
- 好友关系:通过Store Set 取用户的交集,并集,差集,这样方便查询用户之间的好友关系
- 排行榜:通过查找Store Set 起始位置和结束位置,从而实现排行榜。
- session共享:session是存储在服务器中的,在集群的情况下用户登录后可能会分发到不同的机器,这样在另一台机器上没有用户相关session,导致用户可能重复登录的问题。通过把session保存在redis中这样无论用户被分到那一台机器上都可以获取到相关session。
- 分布式锁:使用setnx可以实现redis分布式锁。
缓存一致性
一般我们查询缓存的步骤是:
1.先检查缓存中是否有数据 。
2.没有数据则查询数据库中数据。
3. 把数据库中的查询结果放到缓存中。
但是随着时间的推移和用户的操作可能会导致数据不一致。所以一般有一下几种方案来保证数据的一致性。
先更新数据库再更新缓存
这种方案是先更新数据库,数据库更新成功后再进行更新缓存操作,但是该方案会出现一个ABA的问题,也就是当前有A、B两个用户同时修改数据库中的一条数据,在数据库操作时,先执行A的操作把数据的值改成了1,后执行B操作把数据改成了2,但是当更新缓存的时候,B线程的执行较快先把数据改成了2,后再执行A线程的操作又把数据改成了1。所以就导致缓存中数据和数据库中的数据不一致。(不推荐)
先更新缓存再更新数据库
当出现同上一种方案先后执行的问题时,最终也会导致数据不一致。(不推荐)
先删除缓存再更新数据库
根据上面图片我们可以看出,当两个线程同时执行时,用户A过来先删除缓存中的数据,紧接着B用户又来做查询操作,当缓存中没有数据从数据库中读取后放入缓存,当A用户执行完数据库操作时数据库中的数据又和缓存不一致了。(不推荐)
先更新数据库再删除缓存
如上图所示这种方案可以有效避免数据库和缓存不一致的问题。(推荐)
通过订阅binlog来更新redis
该种方案是mysql开启binlog,然后通过canal解析binlog。canal是阿里巴巴的一个组件,canal可以解析到binlog的日志,当mysql日志变化的时候会将数据发送到canal服务中,canal将数据存放到redis中。这种方案网上有很多操作示例,等有时间写一篇具体的文档。
缓存击穿
查询必然不存在的数据,请求透过Redis直击数据库。这样当查询量大的时候,会导致数据库的压力很大,甚至导致奔溃。
解决思路:在查询之前先判断目标数据是否存在,不存在直接忽略。将流量拦截于缓存和数据库之前。
布隆过滤器
布隆过滤器他实际上是一个很长的二进制数组和一系列hash函数,它可以用于检索一个元素是否在一个集合中,它的优点是空间效率和查询时间都比一般算法要好的多,缺点是有一定的识别率和删除困难。
实现思路:
构建一个二进制数组,将元素hash取模,塞到对应的位置中。查找时根据计算hash并计算出对应的位置。利用redis的特性和命令:bitmaps(setbit设置值,getbit获取值),可以redis中的bit数组长度是2的32次方,所以通过hash值对数组长度进行取模然后找到对应的位置,再用redis中的setbit插入对应位置值的状态,getbit获取获取是否有值。
二进制数据构建过程:
- 加载符合条件的记录
- 计算每条元素的hash值
- 将hash值与二进制数组的长度取模(余数),得到映射位置
- 将对应的位置值改成1
查找元素是否存在的过程
- 计算元素的hash值
- 将hash值与二进制数组的长度取模(余数),得到对应的二进制数组的位置
- 找到数组的对应值,0表示不存在,1表示存在
优点:
内存占用空间小(可存40多亿,但是占用空间只有512M)。
缺点:
- 需要不断的维护
- 并不能精准过滤(判定不存在则100%不存在,但是判定为存在则可能不存在)
理论上Hash计算值是有碰撞(不同的hash可以计算出同样的值),导致不存在的元素可能会被判断为存在,但是布隆过滤器并非是为了拦截所有的请求,是为了将缓存击穿控制在一定的量。
Redis集群
//TODO