一篇文章了解Redis数据库

文章原创于公众号:程序猿周先森。本平台不定时更新,喜欢个人文章,欢迎关注个人微信公众号。
fileredis

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:segmentfault

  • 为何要用 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 复用模型。

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

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的两种持久化机制RDB和AOF。
file

快照持久化(RDB)

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

优势:

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

缺点:

  • 数据安全性低。
  • 若是当数据集较大时,可能会致使整个服务器中止服务几百毫秒,甚至是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 还会优雅的放慢本身的速度以便适应硬盘的最大写入速度。

优势:

  • 数据安全性更高,AOF持久化能够配置appendfsync属性
  • 经过append模式写文件,即便中途服务器宕机,能够经过redis-check-aof工具解决数据一致性问题。
  • AOF机制的rewrite模式。

缺点:

  • AOF文件比RDB文件大,且恢复速度慢;数据集大的时候,比rdb启动效率低。
  • 根据同步策略的不一样,AOF在运行效率上每每会慢于RDB。

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

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

Redis 事务

  • 命令:MULTI、EXEC、WATCH等。

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

缓存雪崩

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

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

解决办法:

  • 缓存数据的过时时间设置随机,防止同一时间大量数据过时现象发生。
  • 若是缓存数据库是分布式部署,将热点数据均匀分布在不一样搞得缓存数据库中。
  • 设置热点数据永远不过时。

缓存穿透

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

解决办法:

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

缓存击穿

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

解决方法:

  • 设置热点数据永远不过时。
  • 加互斥锁

解决 Redis 并发竞争 Key 问题

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

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

1.总体技术方案

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

2.时间戳

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

3.什么是分布式锁

分布式锁能够基于不少种方式实现,好比zookeeper、redis等,无论哪一种方式实现,基本原理是不变的:用一个状态值表示锁,对锁的占用和释放经过状态值来标识。

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

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

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

读的时候,先读缓存,缓存没有的话,就读数据库,而后取出数据后放入缓存,同时返回响应。

更新的时候,先更新数据库,而后再删除缓存。

若是喜欢个人文章,欢迎关注个人我的公众号:程序猿周先森。
file**

相关文章
相关标签/搜索