专栏首页Node开发一篇文章了解Redis数据库

一篇文章了解Redis数据库

redis是一个key-value存储系统。它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了主从同步。简单来说 Redis 就是一个数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的,所以存写速度非常快,因此 Redis 被广泛应用于缓存方向。Redis 也经常用来做分布式锁。Redis 提供了多种数据类型来支持不同的业务场景。除此之外,Redis 支持事务 、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。

本篇文章将从下列几个方向讲解 Redis

为什么要用 Redis实现缓存?
为什么要用 Redis 而不用 map/guava 做缓存
Redis 和 Memcached 的区别
Redis 常见数据结构以及使用场景分析
Redis 设置过期时间
Redis 内存淘汰机制
Redis 持久化机制
Redis 事务
缓存雪崩和缓存穿透问题解决方案
如何解决 Redis 的并发竞争 Key 问题
如何保证缓存与数据库数据一致性

为什么要用 Redis 做缓存?

第一个问题先抛出来,既然选择使用Redis作缓存,其实主要从“高性能”和“高并发”来进行理解。

高性能

1.用户首次访问数据,从数据库读取,效率较低
2.将用户访问数据保存缓存中,二次读取则可以直接从缓存中读取,效率更高
3.数据库若数据发生改变,则同步更新缓存中数据即可。

因为从数据库读取数据,是从硬盘中读取数据,所以效率较低。如果将数据存入缓存中,二次读取从缓存读取,从缓存读取数据是直接操作内存,所以效率非常之高。

高并发

直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求可以不用操作数据库,提高高并发能力。

为什么要用 Redis 而不用 map/guava 做缓存

缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map /guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 JVM 的销毁而结束。而且在多实例状态下缓存不具有唯一性。使用 Redis 作缓存称为分布式缓存,在多实例状态下共用一份缓存数据,缓存具有一致性。

Redis 和 Memcached 的区别

  • Redis支持常见数据类型:Redis 不仅仅支持简单的 key/value 类型的数据,同时还提供string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)等数据结构的存储。而Memcache 只支持简单的数据类型 String
  • Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。
  • 集群模式:Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 Cluster 模式的。
  • Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。

贴一张对比图可能看起来更加明显:

Redis 常见数据结构以及使用场景分析

String

常用命令:set、get、decr、incr、mget 等。

String 数据结构是简单的 Key-Value 类型,Value 可以是string或者数字。常规 Key-Value 缓存应用;常规计数:博客数,阅读数等。

Hash

常用命令:hget、hset、hgetall 等。

Hash 特别适合用于存储对象。

List

常用命令:lpush、rpush、lpop、rpop、lrange 等。

链表是 Redis 最重要的数据结构之一,Redis List 为一个双向链表,支持反向查找和遍历,更方便操作,不过带来了额外的内存开销。

Set

常用命令:sadd、spop、smembers、sunion 等。

Set 其实和List都是列表的选项,Set 是可以自动去重的。当需要存储一个不出现重复数据的列表数据,Set 是一个最好的选择。你可以基于 Set 轻易实现交集、并集、差集的操作。

Sorted Set

常用命令:zadd、zrange、zrem、zcard 等。

Sorted Set 相比Set增加了一个权重参数 Score,使得集合中的元素能够按 Score 进行有序排列。

Redis 设置过期时间

Redis可以对存储在缓存中的数据设置过期时间。作为一个缓存数据库,这是非常实用的功能。之前写过一篇前后端交互的文章讲过,Token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。而有一个好的方案其实就是将这些验证信息存入Redis设置过期时间,如果设置了存活时间30分钟,那么半小时之后这些数据就会从Redis中进行删除。那说到删除,Redis是如果做到对这些数据进行删除的呢:

  • 定期删除:Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 Key,检查其是否过期,如果过期就删除。为什么是随机抽取而不是检查所有key?因为你如果设置的key成千上万,每100毫秒都将所有存在的key检查一遍,会给CPU带来比较大的压力。
  • 惰性删除 :定期删除可能会导致很多过期 Key 到了时间并没有被删除掉。用户在获取key的时候,redis会检查一下,这个key如果设置过期时间那么是否过期了,如果过期就删除这个key

但是只是使用定期删除 + 惰性删除的删除机制还是存在一个致命问题:如果定期删除漏掉了很多过期 Key,而且用户长时间也没有使用到这些过期key,就会导致这些过期key堆积在内存里,导致Redis内存块被消耗殆尽。所以有了Redis内存淘汰机制的诞生。

Redis 内存淘汰机制

Redis 提供 6 种数据淘汰策略:

  • volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
  • volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
  • volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
  • allkeys-lru:当内存不足以容纳新写入数据时移除最近最少使用的key。
  • allkeys-random:从数据集中任意选择数据淘汰。
  • no-enviction:当内存不足以容纳新写入数据时,新写入操作会报错。

Redis 持久化机制

怎么保证 Redis 宕机之后再重启Redis后数据可以进行恢复?很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面。Redis持久化支持两种不同的持久化操作。接下来,我们来简单聊聊Redis的两种持久化机制RDBAOF

快照持久化(RDB)

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。RDBRedis默认的持久化方式,会在对应的目录下生产一个dump.rdb文件,重启会通过加载dump.rdb文件恢复数据。

优点:

1.只有一个文件dump.rdb,方便持久化;
2.容灾性好,一个文件可以保存到安全的磁盘;
3.性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化(使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能) ;
4.如果数据集偏大,RDB的启动效率会比AOF更高。

缺点:

1.数据安全性低。
2.如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

快照持久化是 Redis 默认采用的持久化方式,在 redis.conf 配置文件中已经进行配置:

save 900 1:在15分钟内,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 300 10:在5分钟内,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 60 10000:在1分钟之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

AOF持久化

AOF持久化是以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,文件中可以看到详细的操作记录。她的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。 默认情况下 Redis 没有开启 AOF持久化,可以通过设置 appendonly 参数开启:

appendonly yes

开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件。AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。

Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

appendfsync always:每次有数据修改发生时都会写入AOF文件
appendfsync everysec:每秒钟同步一次,将多个写命令同步到硬盘
appendfsync no:让操作系统决定何时进行同步

用户可以使用appendfsync everysec选项 ,让 Redis 每秒同步一次 AOF 文件,这样Redis性能几乎不会受到影响,而且这样即使出现宕机,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

优点:

1.数据安全性更高,AOF持久化可以配置appendfsync属性
2.通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题。
3.AOF机制的rewrite模式。

缺点:

1.AOF文件比RDB文件大,且恢复速度慢;数据集大的时候,比rdb启动效率低。

2.根据同步策略的不同,AOF在运行效率上往往会慢于RDB。

Redis 4.0 对于持久化机制的优化

1.Redis 4.0支持 RDB 和 AOF 的混合持久化,不过默认是关闭状态。
2.开启混合持久化,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。
3.AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

Redis 事务

命令:MULTI、EXEC、WATCH等。

事务提供了一种按顺序地执行多个命令的机制。并且在事务执行期间,服务器会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。事务总是具有原子性、一致性和隔离性,并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性。

缓存雪崩

缓存处理过程:接收到请求请求,先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。

缓存雪崩:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。

解决办法:

1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。

3.设置热点数据永远不过期。

缓存穿透

简介:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决办法:

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置30秒

缓存击穿

简介: 缓存击穿是指缓存中没有但数据库中有的数据,这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大

解决方法:

  1. 1.设置热点数据永远不过期。
  2. 2.加互斥锁

解决 Redis 并发竞争 Key 问题

问题描述:多客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了。或者是多客户端同时获取一个key,修改值之后再写回去,只要顺序错了,数据就错了。一个key的值是1,本来按顺序修改为2,3,4,最后是4,但是顺序变成了4,3,2,最后变成了2.

我个人认为比较好的方案是分布式锁+时间戳:

1.整体技术方案

这种情况,主要是准备一个分布式锁,大家去抢锁,加锁的目的实际上就是把并行读写改成串行读写的方式,从而来避免资源竞争。利用SETNX非常简单地实现分布式锁。

2.时间戳

由于key的操作需要顺序执行,所以需要保存一个时间戳判断顺序。假设系统B先抢到锁,将key1设置为{ValueB 7:05}。接下来系统A抢到锁,发现自己的key1的时间戳早于缓存中的时间戳(7:00<7:05),那就不做set操作了。

3.什么是分布式锁

分布式锁可以基于很多种方式实现,比如zookeeperredis等,不管哪种方式实现,基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

保证缓存与数据库双写时的数据一致性

可能对大部分来说最先想到的方案就是读请求和写请求串行化,串到一个内存队列里去。但是这个方案有着特别大的缺点:它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

最经典的缓存+数据库读写的模式。

  • 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先更新数据库,然后再删除缓存。

本文分享自微信公众号 - 程序猿周先森(zhanyue_org),作者:逆月翎

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Redis缓存击穿、缓存穿透、缓存雪崩

    上篇文章谈到了Redis分布式锁,实际上就是为了解释为什么做缓存采用Redis而不使用map/guava。缓存分为本地缓存和分布式缓存。以 Java 为例,使...

    逆月翎
  • Redis与数据库数据一致性

    可能谈到保持Redis与Mysql双库的数据一致性,可能很多人最先想到的方案就是读请求和写请求串行化,串到一个内存队列里去。但是这个方案有着一个致命的缺点:读请...

    逆月翎
  • Redis系列总结--这几点你会了吗?

    前面几篇已经对Redis中几个关键知识点做了介绍,本篇主要对Redis系列做一下总结以及对Redis中常见面试题简单进行介绍一下。首先我们对前面几篇谈到的Red...

    逆月翎
  • 2019秋招:460道Java后端面试高频题答案版【模块十二:Redis】

    Redis 对于 2020 届后端开发秋招面试中已经是一个常规的模块了。毕竟现在是互联网时代,各大公司维护的系统的并发量都是很高的,对性能都是有很高的要求,所以...

    乔戈里
  • 如何合理的在项目中运用Redis

    “ 在昨天推送的文章中,我们能够明显的看到访问Redis存储的数据,比访问MySQL中存储的数据要快很多,但是我们也强调了Redis的一些缺点,那么在实际的项目...

    每天学Java
  • Redis入门总结(大神慎入)

    “ 从Redis的安装到项目集成的两篇文章中,我们已经简单的了解到何如去用Redis的,再然后通过Redis和Mysql的查询性能对比和项目中如何合理运用Red...

    每天学Java
  • Redis 缓存问题(13) 原

    因为这些数据是很少修改的,所以在绝大部分的情况下可以命中缓存。但是,一旦被缓存的数据发生变化的时候,我们既要操作数据库的数据,也要操作Redis的数据,所以问题...

    兜兜毛毛
  • 缓存淘汰、缓存穿透、缓存击穿、缓存雪崩、数据库缓存双写一致性

    为什么需要缓存淘汰?你需要缓存30G的数据,但是Redis本身只能使用10G的内存,那你就得做个取舍了,毕竟鱼与熊掌不可兼得。为了利益最大化肯定要保留最重要的1...

    Java学习录
  • Redis缓存击穿、缓存穿透、缓存雪崩

    上篇文章谈到了Redis分布式锁,实际上就是为了解释为什么做缓存采用Redis而不使用map/guava。缓存分为本地缓存和分布式缓存。以 Java 为例,使...

    逆月翎
  • Redis 真得那么好用吗?

    Redis是一个开源的底层使用C语言编写的Key-Value存储数据库。可用于缓存、事件发布订阅、高速队列等场景。而且支持丰富的数据类型:string(字符串)...

    猿哥

扫码关注云+社区

领取腾讯云代金券