【备战秋招冲击大厂】Java面试题系列—Redis

int free;

// 字符数组

char[] buf;

};

Redis的字符串也会遵守C语言的字符串的实现规则,即最后一个字符为空字符。然而这个空字符不会被计算在len里头。

  • Redis动态扩展步骤:

  • 计算出大小是否足够

  • 开辟空间至满足所需大小

  • 开辟与已使用大小len相同长度的空闲free空间(如果len < 1M),开辟1M长度的空闲free空间(如果len >= 1M)

  • Redis字符串的性能优势

  • 快速获取字符串长度:直接返回len

  • 避免缓冲区溢出:每次追加字符串时都会检查空间是否够用

  • 降低空间分配次数提升内存使用效率:(1)空间预分配;(2)惰性空间回收

6. Redis持久化

  • RDB(redis database)在指定时间间隔内将内存中的数据集快照写入磁盘,也就是snapshot快照,恢复时是将快照文件直接读到内存里。Redis会单独创建(fork:复制一个与当前进程一样的进程)一个子进程来进行持久化,会先将数据写入到临时文件,待持久化过程结束,再替换上次持久化好的文件(dump.rdb)。主进程不进行IO操作。如果需要进行大规模数据的恢复,且对数据完整性不敏感,那么RDB比AOF更高效。缺点就是最后一次持久化的数据可能丢失。

  • 默认:1分钟改了一万次,5分钟改了10次,15分钟改了一次

  • AOF以日志的形式来记录每个写操作,将redis执行过的所有写的指令记录下来(读操作不记录),只需追加文件当不可以改写文件,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

  • 同步策略:always一直同步、everysec每秒同步、no不同步

  • AOF的优点:

  • 备份机制更稳健,丢失数据概率低

  • 可读的日志文本,可以处理误操作

  • AOF的缺点:

  • 比RDB占用更多的磁盘空间

  • 恢复备份速度慢

  • 每次读写同步的话有一定的性能压力

  • 存在个别的bug,造成不能恢复

  • AOF重写机制:当aof文件的大小超过所设定的阈值时,redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof,fork出一条新进程来将文件重写,redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的1倍且文件大于64M时触发。

7. Redis的事务

  • 定义:可以一次执行多个命令,部分支持事务

  • 命令:MULTI开启事务、EXEC执行事务、DISCARD放弃事务、WATCH监视一个或多个key,如果在事务执行之前这个key被其他命令所改动,那么事务将被打断、UNWACTH一旦执行了EXEC之前加的所有的监控锁都会被取消。

  • 特性:

  • 单独的隔离操作:事务中所有的命令都会序列化、按顺序地执行。事务执行过程中,不会被其他客户端发送过来的命令请求所打断。

  • 没有隔离级别的概念

  • 不保证原子性:只要有一条命令执行失败,其他的命令仍然会执行,不支持回滚

8. LUA脚本

  • 定义:LUA是一种小巧的脚本语言,可以很容易地被C/C++调用,也可以调用C/C++函数,一个完整的LUA解释器不超过200k,适合作为嵌入式脚本语言。

  • 在redis中的优势

  • 将复杂或者多步的redis操作,写为一个脚本,一次性提交给redis执行,减少反复连接redis的次数,提升性能。

  • LUA脚本类似redis事务,有一定的原子性,不会被其他命令插队

9. Redis内存淘汰策略

  • 这八种大体上可以分为4种,lru、lfu、random、ttl。
  1. volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。

  2. volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。

  3. volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。

  4. volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。

  5. allkeys-lru:从数据集中挑选最近最少使用的数据淘汰

  6. allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。

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

  8. no-enviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失。

10. 秒杀常见问题

  • 连接超时

  • 使用连接池

  • 超卖问题

  • 使用事务

  • 库存遗留

  • 使用LUA脚本

11. Redis发布订阅

  • SUBSCRIBE c1 c2 c3

  • PUBLISH c2 hello-redis

12. Redis主从复制

  • 配从不配主:slaveof 主库IP 主库端口,每次与master断开之后,都需要重新连接,除非配置redis.conf文件。

  • 配置文件细节操作:

  • 拷贝多个redis.conf文件

  • 开启daemonize yes

  • Pid文件名字

  • 指定端口

  • Log文件名字

  • Dump.rdb名字

  • 常用招式

  • 一主二仆

  • Info replication:查看信息

  • SLAVEOF 127.0.0.1 6379:配置从库

13. 哨兵模式

  • 定义:反客为主(slaveof no one)自动化,能够监控主机是否故障,如果故障根据投票数自动将从库转为主库

  • 使用步骤:

  • 调整结构,6379带着80、81

  • 自定义的/myredis目录下新建sentinel.conf文件

  • 配置哨兵,填写内容:Sentinel monitor host637(被监控数据库名字(自己起名字))127.0.0.1 6379 1(多于1票则设为主机)

  • 启动哨兵:Redis-sentinel /myredis/sentinel.conf

更多Java学习资料、面试真题获得,请【点击此处

14. Java使用redis

  • 连接:Jedis jedis = new Jedis(“127.0.0.1”,6379);

  • 插入:jedis.set(“k1”,”v1”);

  • 事务:

Transaction transaction = jedis.multi();

transaction.set(“k2”,”v2”);

transaction.set(“k3”,”v3”);

transaction.exec();

  • 加锁:

public class TestTransaction {

public boolean transMethod() {

Jedis jedis = new Jedis(“127.0.0.1”, 6379);

int balance;// 可用余额

int debt;// 欠额

int amtToSubtract = 10;// 实刷额度

jedis.watch(“balance”);

//jedis.set(“balance”,“5”);//此句不该出现。模拟其他程序已经修改了该条目

balance = Integer.parseInt(jedis.get(“balance”));

if (balance < amtToSubtract) {

jedis.unwatch();

System.out.println(“modify”);

return false;

} else {

System.out.println(“***********transaction”);

Transaction transaction = jedis.multi();

transaction.decrBy(“balance”, amtToSubtract);

transaction.incrBy(“debt”, amtToSubtract);

transaction.exec();

balance = Integer.parseInt(jedis.get(“balance”));

debt = Integer.parseInt(jedis.get(“debt”));

System.out.println(“*******” + balance);

System.out.println(“*******” + debt);

return true;

}

}

/**

  • 通俗点讲,watch命令就是标记一个键,如果标记了一个键, 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中重新再尝试一次。

  • 首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 足够的话,就启动事务进行更新操作,

  • 如果在此期间键balance被其它人修改, 那在提交事务(执行exec)时就会报错, 程序中通常可以捕获这类错误再重新执行一次,直到成功。

*/

public static void main(String[] args) {

TestTransaction test = new TestTransaction();

boolean retValue = test.transMethod();

System.out.println("main retValue-------: " + retValue);

}

}

  • 主从复制

  • 配置从库:Jedis jedis_s = new Jedis(“127.0.0.1”,6380);

  • Jedis_s.slaveof(“127.0.0.1”,6379);

  • JedisPool

JedisPoolConfig poolConfig = new JedisPoolConfig( );

poolConfig.setMaxActive ( 1000);

poolconfig.setMaxIdle ( 32);

poolconfig. setMaxwait (100*1000);poolconfig.setTestOnBorrow(true);

jedisPool = new JedisPool(poolConfig, “127.0.0.1”,6379);

  • maxActive:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted。

  • maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;

  • whenExhaustedAction:表示当pool中的jedis实例都被allocated完时,pool要采取的操作:默认有三种。

  • WHEN_EXHAUSTED_FAIL -->表示无jedis实例时,直接抛出NoSuchElementException;

  • WHEN_EXHAUSTED_BLOCK -->则表示阻塞住,或者达到maxWait时抛出JedisConnectionException;

  • WHEN_EXHAUSTED_GRoW -->则表示新建一个jedis实例,也就说设置的maxActive无用;

  • maxWait:表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛JedisConnectionException;

  • testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的:

15. 解决session存储问题

  • 方案一:存在cookie里

  • 不安全

  • 网络负担效率低

  • 方案二:存在文件服务器或数据库里

  • 大量的IO效率问题

  • 方案三:session复制

  • Session数据冗余

  • 节点越多浪费越大

  • 方案四:缓存数据库

  • 完全存在内存中,速度快数据结构简单

16. 单线程+多路IO复用

多路复用是指用一个线程来检查多个文件描述符(socket)的就绪状态,比如调用select、poll、epoll函数进行监视,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。

多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。

  • Select:每一个请求都进行询问,最多1024个

  • Poll:每一个请求都进行询问不限制数量

  • Epoll:监视请求时为每个请求设置标识符,不需要一一询问

17. Select、poll、epoll

  • select的几大缺点:

1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

3)select支持的文件描述符数量太小了,默认是1024

  • 为什么epoll比select和poll更高效?

1)减少了用户态和内核态之间文件描述符的拷贝

2)减少了对就绪文件描述符的遍历

3)select和poll只支持LT模式,而epoll支持高效的ET模式,并且epoll还支持EPOLLONESHOT事件。

  • 无论哪种情况下,epoll都比select和poll高效吗?

  • epoll适用于连接较多,活动数量较少的情况。

1)epoll为了实现返回就绪的文件描述符,维护了一个红黑树和好多个等待队列,内核开销很大。如果此时监听了很少的文件描述符,底层的开销会得不偿失;

2)epoll中注册了回调函数,当有事件发生时,服务器设备驱动调用回调函数将就绪的fd挂在rdllist上,如果有很多的活动,同一时间需要调用的回调函数数量太多,服务器压力太大。

  • select和poll适用于连接较少的情况。

1)当select和poll上监听的fd数量较少,内核通知用户现在有就绪事件发生,应用程序判断当前是哪个fd就绪所消耗的时间复杂度就会大大减小。

18. REDIS缓存穿透,缓存击穿,缓存雪崩原因+解决方案

  • 缓存穿透:key对应的数据在数据库和缓存并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据库,从而可能压垮数据库。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

  • 解决方案:

1)最常见的则是采用布隆过滤器,在写入数据库时,将数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

2)简单粗暴的方法:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

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

  • 解决方案

1)使用互斥锁:就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

2)设置永不过期

  • 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。

  • 大量数据同时过期解决方案

1)用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。

2)将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

3)双key策略,主key设置过期时间,备key设置永久,主key过期时,返回备key内容

4)后台缓存更新,定时更新、消息队列通知更新

服务器宕机解决方案

1)服务熔断:在分布式系统中,我们往往需要依赖下游服务,不管是内部系统还是第三方服务,如果下游出现问题,我们不在盲目地去请求,在一个周期内失败达到一定次数,不在请求,及时失败。过一段时间,在逐步放开请求,这样既能防止不断的调用,使下游服务更坏,保护了下游方,还能降低自己的执行成本,快速的响应,减少延迟,增加吞吐量。

2)服务降级:降级就是为了解决资源不足和访问量增加的矛盾,在有限的资源情况下,为了能抗住大量的请求,就需要对系统做出一些牺牲,有点“弃卒保帅”的意思。放弃一些功能,保证整个系统能平稳运行。比如:抢购可以占时限流评论,将流量让给秒杀业务

3)请求限流:通过对并发访问进行限速。最简单的方式,把多余的请求直接拒绝掉,可以根据一定的用户规则进行拒绝策略

4)构建redis高可靠集群

19. 布隆过滤器

布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。

布隆过滤器会通过 3 个操作完成标记:

  • 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;

  • 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置。

  • 第三步,将每个哈希值在位图数组的对应位置的值设置为 1;

举个例子,假设有一个位图数组长度为 8,哈希函数 3 个的布隆过滤器。

在数据库写入数据 x 后,把数据 x 标记在布隆过滤器时,数据 x 会被 3 个哈希函数分别计算出 3 个哈希值,然后在对这 3 个哈希值对 8 取模,假设取模的结果为 1、4、6,然后把位图数组的第 1、4、6 位置的值设置为 1。当应用要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数组的第 1、4、6 位置的值是否全为 1,只要有一个为 0,就认为数据 x 不在数据库中。

布隆过滤器由于是基于哈希函数实现查找的,高效查找的同时存在哈希冲突的可能性,比如数据 x 和数据 y 可能都落在第 1、4、6 位置,而事实上,可能数据库中并不存在数据 y,存在误判的情况。

所以,查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。

20. 为什么要用redis而不用map做缓存?

  • Redis 可以用几十 G 内存来做缓存,Map 不行,一般 JVM 也就分几个 G 数据就够大了

  • Redis 的缓存可以持久化,Map 是内存对象,程序一重启数据就没了

  • Redis 可以实现分布式的缓存,Map 只能存在创建它的程序里

  • Redis 可以处理每秒百万级的并发,是专业的缓存服务,Map 只是一个普通的对象

  • Redis 缓存有过期机制,Map 本身无此功能

  • Redis 有丰富的 API,Map 就简单太多了

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

下面的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)

在这里插入图片描述

最新整理电子书

在这里插入图片描述

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
%以上Java开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

下面的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)

[外链图片转存中…(img-NVlTc3YN-1713546063076)]

最新整理电子书

[外链图片转存中…(img-uYuP8sud-1713546063078)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值