Redis原理(面试用)

Redis 支持哪些数据类型:

主要支持字符串、哈希表、列表、集合、有序集合五种。

 Redis的缓存穿透、缓存崩溃、缓存击穿的理解:

缓存穿透:是指查询一个数据库一定不存在的数据。

正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或   者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。

发生场景:

如果传入的参数为-1,会是怎么样?这个-1,就是一定不存在的对象。就会每次都去查询数据库,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。即便是采用UUID,也是很容易找到一个不存在的KEY,进行攻击。

 

缓存击穿: 是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

发生场景:某一个商品爆款的时候会导致这种情况的产生。

解决方案:

设置缓存永不过期(或者过期时间比较大)。

设置双重缓存备份A和B,当A缓存失效时,使用B缓存。

 

缓存雪崩:缓存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。

解决方案:

也是像解决缓存穿透一样加锁排队;

建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,并且更新A缓存和B缓存;

设置缓存超时时间的时候加上一个随机的时间长度,比如这个缓存key的超时时间是固定的5分钟加上随机的2分钟,酱紫可从一定程度上避免雪崩问题;

 Redis中如何保证缓存数据和数据库数据的一致性

缓存应用和数据库在更新时经常会出现不一致的问题,采用哪种策略,值得去思考。

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存

 

目前最优的解决方案:使用延时双删策略     

策略流程如下图所示:

(1)更新数据库数据

(2)数据库会将操作信息写入binlog日志当中

(3)订阅程序提取出所需要的数据以及key

(4)另起一段非业务代码,获得该信息

(5)尝试删除缓存操作,发现删除失败

(6)将这些信息发送至消息队列

(7)重新从消息队列中获得该数据,重试操作。

 

 

Redis的分布式锁如何实现,有什么优缺点?

分布式锁需要解决的问题

互斥性:任意时刻只能有一个客户端拥有锁,不能同时多个客户端获取

安全性:锁只能被持有该锁的用户删除,而不能被其他用户删除

死锁:获取锁的客户端因为某些原因而宕机,而未能释放锁,其他客户端无法获取此锁,需要有机制来避免该类问题的发生

容错:当部分节点宕机,客户端仍能获取锁或者释放锁。

{



如何通过Redis实现分布式锁:(非完善方法)



SETNX key value :如果key不存在,则创建并赋值



时间复杂度: 0(1)

返回值:设置成功,返回1;设置失败,返回0。

但是此时我们获取的key是长期有效的,所以我们应该如何解决长期有效的问题呢?



如何解决SETNX长期有效的问题?



EXPIRE key seconds



设置key的生存时间,当key过期时(生存时间为0) ,会被自动删除

缺点:原子性得不到满足



//该程序存在危险,如果执行到第二行就崩溃了,则此时key会被一直占用而无法被释放

RedisService redisService = SpringUtils.getBean(Redi sService.class);

long status = redisService.setnx(key, "1");

if(status == 1) {

       redisService.expire(key, expire);

       //执行独占资源逻辑

       doOcuppiedWork();

}

如何通过Redis实现分布式锁:(正确方式)

SET key value [EX seconds] [PX milliseconds] [NX|XX]



EX second :设置键的过期时间为second秒

PX millisecond :设置键的过期时间为millisecond毫秒

NX :只在键不存在时,才对键进行设置操作

XX:只在键已经存在时,才对键进行设置操作

SET操作成功完成时,返回OK ,否则返回nil



RedisService redisService = SpringUtils.getBean(RedisService.class); .

String result = redisService.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

if ("OK".equals(result)) {

       //执行独占资源逻辑

       doOcuppiedWork();

}

大量的key同时过期的注意事项

集中过期,由于清除大量的key很耗时,会出现短暂的卡顿现象

解放方案:在设置key的过期时间的时候,给每个key加上随机值

 

特殊场景1:超时后使用del 导致误删其他线程的锁

又是一个极端场景,假如某线程成功得到了锁,并且设置的超时时间是30秒。

如果某些原因导致线程B执行的很慢很慢,过了30秒都没执行完,这时候锁过期自动释放,线程B得到了锁。

随后,线程A执行完了任务,线程A接着执行del指令来释放锁。但这时候线程B还没执行完,线程A实际上删除的是线程B加的锁。

怎么避免这种情况呢?可以在del释放锁之前做一个判断,验证当前的锁是不是自己加的锁。

至于具体的实现,可以在加锁的时候把当前的线程ID当做value,并在删除之前验证key对应的value是不是自己线程的ID。

,if判断和释放锁是两个独立操作,不是原子性。

 

我们都是追求极致的程序员,所以这一块要用Lua脚本来实现:

 

String luaScript = 'if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end';

 

redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));

这样一来,验证和删除过程就是原子操作了。

 

特殊场景2: 出现并发的可能性

 

还是刚才的场景1,虽然我们避免了线程A误删掉key的情况,但是同一时间有A,B两个线程在访问代码块,仍然是不完美的。

怎么办呢?我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”。

当过去了29秒,线程A还没执行完,这时候守护线程会执行expire指令,为这把锁“续命”20秒。守护线程从第29秒开始执行,每20秒执行一次。

当线程A执行完任务,会显式关掉守护线程。

另一种情况,如果节点1 忽然断电,由于线程A和守护线程在同一个进程,守护线程也会停下。这把锁到了超时的时候,没人给它续命,也就自动释放了。

 

Redisson 实现分布式锁(建议使用):

 

redisson所有指令都通过lua脚本执行,redis支持lua脚本原子性执行

redisson设置一个key的默认过期时间为30s,如果某个客户端持有一个锁超过了30s怎么办?

redisson中有一个watchdog的概念,翻译过来就是看门狗,它会在你获取锁之后,每隔10秒帮你把key的超时时间设为30s

这样的话,就算一直持有锁也不会出现key过期了,其他线程获取到锁的问题了。

redisson的“看门狗”逻辑保证了没有死锁发生。

(如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期了,其他线程可以获取到锁)

看门狗的作用类似上述场景2中的守护线程

Jedis和Redisson 的优缺点

Redisson:

优点:

Redisson实现了分布式和可扩展的Java数据结构。支持的数据结构有:List, Set, Map, Queue, SortedSet, ConcureentMap, Lock, AtomicLong, CountDownLatch。并且是线程安全的,底层使用Netty 4实现网络通信。

封装了redis的分布式锁的实现,使用者只需要调用即可。

缺点:功能较为简单,不支持字符串操作,不支持排序、事务‘管道、分区等Redis特性。

 

Jedis: 与上述相反。

 

8.Redis的数据淘汰策略

当前版本,Redis 3.0 支持的策略包括:

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用 的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数 据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据 淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。

您需要根据系统的特征, 来选择合适的驱逐策略。 当然, 在运行过程中也可以通过命令动态设置驱逐策略, 并通过 INFO 命令监控缓存的 miss 和 hit, 来进行调优。

一般来说:

如果分为热数据与冷数据, 推荐使用 allkeys-lru 策略。 也就是, 其中一部分key经常被读写. 如果不确定具体的业务特征, 那么 allkeys-lru 是一个很好的选择。

如果需要循环读写所有的key, 或者各个key的访问频率差不多, 可以使用 allkeys-random 策略, 即读写所有元素的概率差不多。

假如要让 Redis 根据 TTL 来筛选需要删除的key, 请使用 volatile-ttl 策略。

volatile-lru 和 volatile-random 策略主要应用场景是: 既有缓存,又有持久key的实例中。 一般来说, 像这类场景, 应该使用两个单独的 Redis 实例。

 

值得一提的是, 设置 expire 会消耗额外的内存, 所以使用 allkeys-lru 策略, 可以更高效地利用内存, 因为这样就可以不再设置过期时间了。

Redis 和 Memecache 有什么区别?

Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。

Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。

虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘

过期策略–memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10

分布式–设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从

存储数据安全–memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)

灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复

Redis支持数据的备份,即master-slave模式的数据备份。

10. Redis的内存优化

关闭 Redis 的虚拟内存[VM]功能,即 redis.conf 中 vm-enabled = no

设置 redis.conf 中 maxmemory ,用于告知 Redis 当使用了多少物理内存后拒绝继续写入的请求,可防止 Redis 性能降低甚至崩溃

可为指定的数据类型设置内存使用规则,从而提高对应数据类型的内存使用效率

Hash 在 redis.conf 中有以下两个属性,任意一个超出设定值,则会使用 HashMap 存值

hash-max-zipmap-entires 64 表示当 value 中的 map 数量在 64 个以下时,实际使用 zipmap 存储值

hash-max-zipmap-value 512 表示当 value 中的 map 每个成员值长度小于 512 字节时,实际使用 zipmap 存储值

List 在 redis.conf 中也有以下两个属性

list-max-ziplist-entires 64

list-max-ziplist-value 512

在 Redis 的源代码中有一行宏定义 REDIS-SHARED-INTEGERS = 10000 ,修改该值可以改变 Redis 存储数值类型的内存开销

11. Redis如何进行大数据量更新缓存

Reids是一个cs模式的Tcp服务,类似于http的请求。 当客户端发送一个请求时,服务器处理之后会将结果通过响应报文返回给客户端 。那么当需要发送多个请求时,难道每次都要等待请求响应,再发送下一个请求吗?

当然不是,这里就可以采用Redis的管道技术(pipeline)。举个例子,如果说jedis是:request response,request response,…;那么pipeline则是:request request… response response的方式。

Redis的持久化方式有哪些?各有何利弊?

持久化方式主要分为RDB和AOF两种方式。

RDB:RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

AOF: AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

redis的事务

redis的事务先以MULTI开启事务,将多个命令入队到事务中,然后通过EXEC命令触发执行队伍中的所有命令,如果想取消事务可以执行discard命令.

 

 为啥 Redis 单线程模型也能效率这么高?

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值