Redis学习指南

在学习了解Redis面试题之前,需要熟悉redis的基本数据结构、一些常用命令和使用Java操作redis数据库。 尚谷redis教程(提取码:gb11)open in new window

推荐阅读:

  • 《Redis设计与实现》
  • 《Redis实战》

常见问题总结

  • redis的数据是存在内存中的,读写速度快,因此redis被广泛应用于缓冲方向。
  • redis也可用来做分布式锁
  • redis支持事务、持久化、LUA脚本、LRU驱动事件、多种集群方案。

1、为什么要用redis(或问为什么要用缓存)

主要从“高性能”和“高并发”这两点看待问题。

  • 高性能

    • 假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
      Redis 高性能
  • 高并发:

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

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

缓存分为本地缓存和分布式缓存。以Java为例,使用自带的map/guava实现的是本地缓存,最主要的特点就是轻量以及快速,生命周期随着jvm的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。

3、redis和memcached的区别?

  • redis支持更丰富的数据类型(支持更复杂的应用场景):redis不仅仅支持简单的k/v类型,还支持list、set、zset、hash、String等数据结构的存储。memcache支持简单的数据类型,String。
  • redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用,而memcache把数据全部存在内存之中。
  • 集群模式不同,memcache没有原生的集群模式,但redis有。
  • memcache是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路IO复用模型。 区别

4、redis常见数据结构以及使用场景分析

  • (1)String

    • 常用命令:set,get,decr,incr,mget等
    • String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。
    • 常规key-value缓存应用。
    • 常规计数:微博数,粉丝数等。
  • (2)Hash

    • 常用命令:hget,hset,hgettall等
    • hash是一个string类型的field和value的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。如下例(使用hash类型存放个人信息):
key=JavaUser293847  
value={  
    "id" : 1,  
    "name" : "SnilClimb",  
    "age" : 22  
    "location" : "Sanya Hainan"  
}
  • (3) List

    • 常用命令:lpush.rpush,lpop,lrange等
    • list就是链表,redis list的应用场景非常多,也是redis重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的list结构来实现。
    • redis list的实现为一个双向链表,即可以支持反向查好和遍历,方便操作,缺点:带来额外内存开销。
    • 可以通过lrange命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。
  • (4) Set

    • 常用命令:sadd,spop,smembers,sunion等
    • set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。
    • 当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。
    • 应用:将一个用户的所有的关注人存在一个set里,将其所有粉丝存在一个set中,redis可以非常方便实现共同关注、共同粉丝、共同喜好等功能。
sinterstore key1 key2 key3 //将交集存在key1中
  • (5)Sored Set(Zset)
    • 常用命令:zadd,zrange,zrem,zcard等

    • 和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。

    • 举例:直播时,可以用zset来存储数据,以方便排序。

5、redis设置过期时间

Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。

set key时,可以给一个expire time,就是过期时间,来指定key可以存活的时间。

  • 定期删除

    • redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!
  • 惰性删除

    • 定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈!

6、redis内存淘汰机制

redis提供6中淘汰策略:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key 【这个策略最常用】
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。【这个应该没人使用吧!】

4.0版本增加以下两种:

  • volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  • allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key

7、redis持久化机制(怎么保证redis挂掉之后再重启数据可以进行恢复)

很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。

redis持久化方式:

  • 快照(snapshotting, RDB)
  • 追加文件(append-only file,AOF)

快照持久化

Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。

快照持久化是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置:

save 900 1           //在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 300 10          //在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 60 10000        //在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

AOF持久化 与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:

appendonly yes

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

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

appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度

appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘

appendfsync no        #让操作系统决定何时进行同步

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

Redis4.0 对于持久化机制的优化

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。

补充:AOF重写open in new window
补充:Redis持久化open in new window

8、redis事务

Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。

事务具有原子性!

9、缓存雪崩和缓存穿透问题解决方案

10、如何解决redis的并发竞争key问题

  • 分布式锁(zookeeper和redis都实现分布锁)
  • 基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。

11、如何保证缓存与数据库双写时的数据一致性?

你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?

一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。

串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

本文源自小专栏open in new windowredis面试题,算是对redis加深理解的一种方式,只做个人知识库使用。不涉及商业行为!本人也是这个专栏的订阅者!