[十万个为什么]- 为什么使用Redis

Java基础

浏览数:100

2019-9-2

AD:资源代下载服务

Redis是什么?

Redis (REmote DIctionary Server)是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列,是一个高性能的key-value数据库。
Redis与其他key-value缓存产品有以下三个特点:

  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

为什么要用Redis

  • 性能极高 – Redis读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,即要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持publish/subscribe, key过期等特性。

Redis的数据类型及使用场景

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。每种数据类型有其适合的使用场景,下面具体介绍.

String(字符串)

string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

使用方法

SET key value   设置指定 key 的值  
GET key     获取指定 key 的值。
SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。

使用场景

1.会话缓存
用户登录系统后,使用Redis保存用户的Session信息,每次用户查询登录信息都直接从Redis中获取。

2.计数器

  • 比如登录系统会限制密码错误次数,当一个用户在一定时间内连续输入密码错误,就不能登录,需要一段时间后才能登录,我们可以使用redis,把username作为key,错误的次数作为value,同时设置过期时间即可.
  • 手机验证码限收到短信的次数
  • 统计其他计数

3.定时器
redis的key可以设置过期时间,我们基于此特性设置一个定时器.

4.对象
我们把对象序列化后,可以使用redis保存该对象,然后在获取对象信息的时候,反序列化value

5.分布式锁
redis提供了setnx()方法,即SET IF NOT EXIST,只有在key不存在的时候才能set成功,这就意味着同一时间多个请求只有一个请求能保存成功,这块的可以自行搜索redis的分布式锁

Hash(哈希)

Redis hash 是一个键值(key=>value)对集合,即编程语言中的Map类型.
Redis hash 是一个 string 类型的 field 和 value 的映射表.

使用方法

HSET key field value 
将哈希表 key 中的字段 field 的值设为 value 。

HGET key field 
获取存储在哈希表中指定字段的值。  

HKEYS key 
获取所有哈希表中的字段  

HMSET key field1 value1 [field2 value2 ] 
同时将多个 field-value (域-值)对设置到哈希表 key 中。

使用场景

hash 特别适合用于存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去)

List(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

使用方法

LPUSHX key value 
将一个值插入到已存在的列表头部  

LPUSH key value1 [value2] 
将一个或多个值插入到列表头部  

LPOP key 
移出并获取列表的第一个元素  

BLPOP key1 [key2 ] timeout 
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

使用场景

1.消息队列
Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的”抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。

2.类目/文章/活动等列表
最常见的就是各个系统的首页数据,包括电商系统的商品类目,拼团活动列表,博客园的首页文章列表等

3.其他
根据push和pop的方式不同,有以下组合方式

    lpush + lpop = Stack(栈)

    lpush + rpop = Queue(队列)

    lpush + ltrim = Capped Collection(有限集合)

    lpush + brpop = Message Queue(消息队列)

Set(集合)

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

使用方法

SADD key member1 [member2] 
向集合添加一个或多个成员  

SDIFF key1 [key2] 
返回给定所有集合的差集  

SINTER key1 [key2] 
返回给定所有集合的交集  

SMEMBERS key 
返回集合中的所有成员

使用场景

1.标签(tag)
比如在点餐评价系统中,用户给某商家评价,商家会有多个评价标签,但是不会重复的,如果100万人给某商家评价打了标签,如果使用MySQL数据库获取大数据量去重后的评价标签,会影响数据库的性能和系统的并发量.

2.相同点/异同点
利用交集、并集、差集等操作,可以计算两个人的共同喜好,全部的喜好,自己独有的喜好等功能。

zset(sorted set:有序集合)

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

使用方法

ZADD key score1 member1 [score2 member2] 
向有序集合添加一个或多个成员,或者更新已存在成员的分数  

ZCARD key 
获取有序集合的成员数  

ZREM key member [member ...] 
移除有序集合中的一个或多个成员  

使用场景

1.排行榜
例如博客园需要对用户发表的文章做排行榜,榜单的维度可能是多个方面的:按照时间、按照点赞数、按照热度,浏览数等

Redis持久化

Redis 提供了不同级别的持久化方式:

  • RDB持久化,该方式能够在指定的时间间隔能对数据进行快照存储.
  • AOF持久化,该方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
  • 不持久化,如果你只希望数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
  • RDB+AOF模式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

RDB的优点

  • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.

RDB的缺点

  • 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
  • RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.

AOF优点

  • 使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.

AOF缺点

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。

如何选择持久化方式

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化,但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快
一般来说,如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能.

使用Redis出现的问题

在一个高频访问的应用系统中,每次用户的请求需要去DB中获取数据,会对数据库造成很大的压力、容易导致数据库的奔溃。所以才会出现缓存来分担一部分的数据库的压力。 但是使用缓存也带来了一系列问题:

1.缓存一致性问题

当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。这就比较依赖缓存的过期和更新策略。一般会在数据发生更改的时,主动移除对应的缓存。 所以需要通过事物机制来保证缓存的一致性。

2.缓存雪崩问题

在高并发场景下,有多个请求去共同请求一份相同的业务数据。有可能多个请求先去从缓存中获取数据、获取不到的并发的去从数据库获取数据,对后端数据库造成极大的冲击,甚至导致 “雪崩”现象。

  • 方案一: 可以做一个随机的等待、错峰去访问缓存的信息。这样就能保证同一时刻高并发的访问、经过时间离散之后只有小部分的请求访问数据库、大部分的请求去命中缓存。
  • 方案二: 可以按照比例限制有部分数据直接访问数据库然后更新缓存、大部分的数据直接请求缓存。

按照实际的场景去做判断例如 1%的场景直接访问数据库,99%的可以通过缓存获取到数据。

3.缓存击穿的问题

在系统设计的的时候预期是通过缓存来减轻数据库的压力、防止数据奔溃的情况。在某个实际发生的场景中、大量的请求并没有命中缓存而导致了大量请求达到数据库、从而导致数据库有巨大冲击和压力。

3.1缓存中没有数据

在某个大促活动中有大量的热点数据,互动一开始需要访问这些数据。由于活动开始的时候洪峰流量到来,所有的请求缓存、缓存直接击穿,访问数据库导致数据库直接cpu 100%,业务系统直接奔溃。
对于这种场景可以提前对数据进行预热,开活动开始前先将数据推送到缓存中。

3.2缓存集中失效

由于我们在缓存使用的过程中会设置缓存的失效时间、如果设置的不合理可能会导致数据集中失效的情况。由于缓存集中失效会导致系统缓存穿透、在同一时刻高并发的访问数据,造成数据雪崩。
解决这种场景的可以将失效的时间由固定值+随机值来构成。EXPIRETIME=FIXTIME+RUND_TIME 例如你想保证整个EXPIRETIME是5S 左右,可以 通过EXPIRETIME=4000+Random(1000)

Redis的过期策略

通常Redis keys创建时没有设置相关过期时间,他们会一直存在,除非使用显示的命令移除,例如,使用DEL命令。

EXPIRE一类命令能关联到一个有额外内存开销的key。当key执行过期操作时,Redis会确保按照规定时间删除他们。

key的过期时间和永久有效性可以通过EXPIRE和PERSIST命令(或者其他相关命令)来进行更新或者删除过期时间。

Redis keys过期有两种方式:定期删除和惰性删除。

1.定期删除

定时删除,用一个定时器来负责监视key,当key过期则自动删除key。
虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,会影响redis的性能.

2.惰性删除

客户端尝试访问key时,key会被发现并主动的过期. 但是这样是不够的,因为有些过期的keys,永远不会访问他们,那么他们就永远不会被删除,而占用内存,导致redis内存被过期的key占用.

3.定期删除+惰性删除

redis默认每100ms检查一次,是否有过期的key,有过期key则删除。需要说明的是,redis不是每100ms将所有的key检查一次,而是随机抽取20个keys进行过期检查,同时删除已经过期的keys,如果有多于25%的keys过期,重复抽取。直到过期的keys的百分比低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys

那么问题来了,采用定期删除+惰性删除就能保证过期的key会全部删除掉么?

内存淘汰机制

如果定期删除没删除key。然后也没去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
在redis.conf中有配置

maxmemory-policy volatile-lru

内存淘汰策略如下:

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,不建议使用
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除

作者:iceblow