社招后端21连问(三年工作经验一面)(二)

tony_ybliu
发布于 2022-5-30 17:30
浏览
0收藏

 

12. Redis 过期策略和内存淘汰策略 
 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区
12.1 Redis的过期策略
我们在set key的时候,可以给它设置一个过期时间,比如expire key 60。指定这key60s后过期,60s后,redis是如何处理的嘛?我们先来介绍几种过期策略哈:

一般有定时过期、惰性过期、定期过期三种。

  • 定时过期
    每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期
    只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期
    每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。

Redis中同时使用了惰性过期和定期过期两种过期策略。

  • 假设Redis当前存放30万个key,并且都设置了过期时间,如果你每隔100ms就去检查这全部的key,CPU负载会特别高,最后可能会挂掉。
  • 因此,redis采取的是定期过期,每隔100ms就随机抽取一定数量的key来检查和删除的。
  • 但是呢,最后可能会有很多已经过期的key没被删除。这时候,redis采用惰性删除。在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除。
    但是呀,如果定期删除漏掉了很多过期的key,然后也没走惰性删除。就会有很多过期key积在内存内存,直接会导致内存爆的。或者有些时候,业务量大起来了,redis的key被大量使用,内存直接不够了,运维小哥哥也忘记加大内存了。难道redis直接这样挂掉?不会的!Redis用8种内存淘汰策略保护自己~

12.2 Redis 内存淘汰策略

  • volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰;
  • allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰。
  • volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU(最少访问算法)进行删除key。
  • allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰;
  • volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据;。
  • allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰;
  • noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。
    13. Hashmap 是怎样实现的?为什么要用红黑树,而不用平衡二叉树?为什么在1.8中链表大于8时会转红黑树?HashMap是线性安全的嘛?如何保证安全? 
    13.1 Hashmap 是怎样实现的?
  • JDK1.7 Hashmap的底层数据结构是数组+链表
  • JDK1.8 Hashmap的底层数据结构是数组+链表+红黑树
    数据元素通过映射关系,即散列函数,映射到桶数组对应索引的位置,插入该位置时,如果发生冲突,从冲突的位置拉一个链表,把冲突元素放到链表。如果链表长度>8且数组大小>=64,链表转为红黑树 如果红黑树节点个数<6 ,转为链表。

13.2 为什么要用红黑树,为什么不用二叉树?为什么不用平衡二叉树?
为什么不用二叉树?

红黑树是一种平衡的二叉树,其插入、删除、查找的最坏时间复杂度都为 O(logn),避免了二叉树最坏情况下的O(n)时间复杂度。

为什么不用平衡二叉树?

平衡二叉树是比红黑树更严格的平衡树,为了保持保持平衡,需要旋转的次数更多,也就是说平衡二叉树保持平衡的效率更低,所以平衡二叉树插入和删除的效率比红黑树要低。

13.3 为什么在1.8中链表大于8时会转红黑树?
红黑树的平均查找长度是log(n),如果长度为8,平均查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,而log(6)=2.6,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。

13.4 HashMap是线性安全的嘛?如何保证安全?

HashMap不是线程安全的,多线程下扩容死循环。可以使用HashTable、Collections.synchronizedMap、以及 ConcurrentHashMap 可以实现线程安全。

  • HashTable 是在每个方法加上 synchronized 关键字,粒度比较大;
  • Collections.synchronizedMap 是使用 Collections 集合工具的内部类,通过传入 Map 封装出一个 SynchronizedMap 对象,内部定义了一个对象锁,方法内通过对象锁实现;
  • ConcurrentHashMap 在jdk1.7中使用分段锁,在jdk1.8中使用CAS+synchronized。
    14. select 和 epoll的区别 
    14.1 IO多路复用之select
    应用进程通过调用select函数,可以同时监控多个fd,在select函数监控的fd中,只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时应用进程再发起recvfrom请求去读取数据。

社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区

 非阻塞IO模型(NIO)中,需要N(N>=1)次轮询系统调用,然而借助select的IO多路复用模型,只需要发起一次询问就够了,大大优化了性能。

但是呢,select有几个缺点:

  • 监听的IO最大连接数有限,在Linux系统上一般为1024。
  • select函数返回后,是通过遍历fdset,找到就绪的描述符fd。(仅知道有I/O事件发生,却不知是哪几个流,所以遍历所有流)
  • 因为存在连接数限制,所以后来又提出了poll。与select相比,poll解决了连接数限制问题。但是呢,select和poll一样,还是需要通过遍历文件描述符来获取已经就绪的socket。如果同时连接的大量客户端,在一时刻可能只有极少处于就绪状态,伴随着监视的描述符数量的增长,效率也会线性下降。
    14.2 IO多路复用之epoll
    为了解决select/poll存在的问题,多路复用模型epoll诞生,它采用事件驱动来实现,流程图如下:

 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区
epoll先通过epoll_ctl()来注册一个fd(文件描述符),一旦基于某个fd就绪时,内核会采用回调机制,迅速激活这个fd,当进程调用epoll_wait()时便得到通知。这里去掉了遍历文件描述符的坑爹操作,而是采用监听事件回调的机制。这就是epoll的亮点。

一下select、poll、epoll的区别

社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区

大家可以看我这篇文章哈:看一遍就理解:IO模型详解

15. http与https的区别,https的原理,如何加密的? 
http与https的区别

思路: 这道题实际上考察的知识点是HTTP与HTTPS的区别,这个知识点非常重要,可以从安全性、数据是否加密、默认端口等这几个方面去回答哈。其实,当你理解HTTPS的整个流程,就可以很好回答这个问题啦。

HTTP,即超文本传输协议,是一个基于TCP/IP通信协议来传递明文数据的协议。HTTP会存在这几个问题:

  • 请求信息是明文传输,容易被窃听截取。
  • 没有验证对方身份,存在被冒充的风险
  • 数据的完整性未校验,容易被中间人篡改
    为了解决Http存在的问题,Https出现啦。

HTTPS= HTTP+SSL/TLS,可以理解Https是身披SSL(Secure Socket Layer,安全套接层)的HTTP。

HTTP + HTTPS的区别

 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区
https的原理,如何加密的

 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区

  • 客户端发起Https请求,连接到服务器的443端口。
  • 服务器必须要有一套数字证书(证书内容有公钥、证书颁发机构、失效日期等)。
  • 服务器将自己的数字证书发送给客户端(公钥在证书里面,私钥由服务器持有)。
  • 客户端收到数字证书之后,会验证证书的合法性。如果证书验证通过,就会生成一个随机的对称密钥,用证书的公钥加密。
  • 客户端将公钥加密后的密钥发送到服务器。
  • 服务器接收到客户端发来的密文密钥之后,用自己之前保留的私钥对其进行非对称解密,解密之后就得到客户端的密钥,然后用客户端密钥对返回数据进行对称加密,酱紫传输的数据都是密文啦。
  • 服务器将加密后的密文返回到客户端。
  • 客户端收到后,用自己的密钥对其进行对称解密,得到服务器返回的数据。
    16. Raft算法原理 
    Raft 算法是分布式系统开发首选的共识算法,它通过“一切以领导者为准”的方式,实现一系列值的共识和各节点日志的一致。Raft 算法一共涉及三种角色(Follower、Candidate、Leader)和两个过程(Leader选举和日志复制)。

16.1  Raft 角色
跟随者(Follower):,默默地接收和处理来自Leader的消息,当等待Leader心跳信息超时的时候,就主动站出来,推荐自己当候选人(Candidate)。

候选人(Candidate):向其他节点发送投票请求,通知其他节点来投票,如果赢得了大多数(N/2+1)选票,就晋升领导(Leader)。

领导者(Leader):负责处理客户端请求,进行日志复制等操作,每一轮选举的目标就是选出一个领导者;领导者会不断地发送心跳信息,通知其他节点“我是领导者,我还活着,你们不要发起新的选举,不用找个新领导者来替代我。”

16.2 领导选举过程
1.在初始时,集群中所有的节点都是Follower状态,都被设定一个随机选举超时时间(一般150ms-300ms):

社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区

 2. 如果Follower在规定的超时时间,都没有收到来自Leader的心跳,它就发起选举:将自己的状态切为 Candidate,增加自己的任期编号,然后向集群中的其它Follower节点发送请求,询问其是否选举自己成为Leader:

 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区
3.其他节点收到候选人A的请求投票消息后,如果在编号为1的这届任期内还没有进行过投票,那么它将把选票投给节点A,并增加自己的任期编号:
 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区
4.当收到来自集群中过半节点的接受投票后,A节点即成为本届任期内 Leader,他将周期性地发送心跳消息,通知其他节点我是Leader,阻止Follower发起新的选举:
 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区
16.2 日志复制
当有了leader,系统可以对外工作期啦。客户端的一切请求来发送到leader,leader来调度这些并发请求的顺序,并且保证leader与followers状态的一致性。Leader接收到来自客户端写请求后,处理写请求的过程其实就是一个日志复制的过程。

日志项长什么样呢?如下图:

 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区
请求完整过程:

  1. 当系统leader收到一个来自客户端的写请求,就会添加一个log entry(日志项)到本地日志。
  2. Leader通过日志复制(AppendEntries)RPC 消息,将日志项并行复制到集群其它Follower节点。
  3. 如果Leader接收到大多数的“复制成功”响应后,它将日志项应用到自己的状态机,并返回成功给客户端。相反没有收到大多数的“复制成功”响应,那么就返回错误给客户端;
  4. 当Follower接收到心跳信息,或者新的AppendEntries消息后,如果发现Leader已经提交了某条日志项,而自己还没应用,那么Follower就会将这条日志项应用到本地的状态机中。


社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区 Raft算法,Leader是通过强制Follower直接复制自己的日志项,来处理不一致日志,从而最终实现了集群各节点日志的一致。

大家有兴趣可以看这篇文章哈:分布式一致性:Raft算法原理[1](https://www.tpvlog.com/article/66)

17. 消息中间件如何做到高可用 
消息中间件如何保证高可用呢?单机是没有高可用可言的,高可用都是对集群来说的,一起看下kafka的高可用吧。

Kafka 的基础集群架构,由多个broker组成,每个broker都是一个节点。当你创建一个topic时,它可以划分为多个partition,而每个partition放一部分数据,分别存在于不同的 broker 上。也就是说,一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。

有些伙伴可能有疑问,每个partition放一部分数据,如果对应的broker挂了,那这部分数据是不是就丢失了?那还谈什么高可用呢?

Kafka 0.8 之后,提供了复制品副本机制来保证高可用,即每个 partition 的数据都会同步到其它机器上,形成多个副本。然后所有的副本会选举一个 leader 出来,让leader去跟生产和消费者打交道,其他副本都是follower。写数据时,leader 负责把数据同步给所有的follower,读消息时, 直接读 leader 上的数据即可。如何保证高可用的?就是假设某个 broker 宕机,这个broker上的partition 在其他机器上都有副本的。如果挂的是leader的broker呢?其他follower会重新选一个leader出来。
18. 消息队列怎么保证不丢消息的 
一个消息从生产者产生,到被消费者消费,主要经过这3个过程:

 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区
因此如何保证MQ不丢失消息,可以从这三个阶段阐述:

  • 生产者保证不丢消息
  • 存储端不丢消息
  • 消费者不丢消息
    18.1 生产者保证不丢消息
    生产端如何保证不丢消息呢?确保生产的消息能到达存储端。

如果是RocketMQ消息中间件,Producer生产者提供了三种发送消息的方式,分别是:

  • 同步发送
  • 异步发送
  • 单向发送
    生产者要想发消息时保证消息不丢失,可以:
  • 采用同步方式发送,send消息方法返回成功状态,就表示消息正常到达了存储端Broker。
  • 如果send消息异常或者返回非成功状态,可以重试。
  • 可以使用事务消息,RocketMQ的事务消息机制就是为了保证零丢失来设计的
    18.2 存储端不丢消息
    如何保证存储端的消息不丢失呢?确保消息持久化到磁盘。大家很容易想到就是刷盘机制。

刷盘机制分同步刷盘和异步刷盘:

  • 生产者消息发过来时,只有持久化到磁盘,RocketMQ的存储端Broker才返回一个成功的ACK响应,这就是同步刷盘。它保证消息不丢失,但是影响了性能。
  • 异步刷盘的话,只要消息写入PageCache缓存,就返回一个成功的ACK响应。这样提高了MQ的性能,但是如果这时候机器断电了,就会丢失消息。
    Broker一般是集群部署的,有master主节点和slave从节点。消息到Broker存储端,只有主节点和从节点都写入成功,才反馈成功的ack给生产者。这就是同步复制,它保证了消息不丢失,但是降低了系统的吞吐量。与之对应的就是异步复制,只要消息写入主节点成功,就返回成功的ack,它速度快,但是会有性能问题。

18.3 消费阶段不丢消息
消费者执行完业务逻辑,再反馈会Broker说消费成功,这样才可以保证消费阶段不丢消息。

19. Redis如何保证高可用?聊聊Redis的哨兵机制 
主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址。显然,多数业务场景都不能接受这种故障处理方式。Redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。

哨兵模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监视所有的Redis主节点和从节点,并在被监视的主节点进入下线状态时,自动将下线主服务器属下的某个从节点升级为新的主节点。但是呢,一个哨兵进程对Redis节点进行监控,就可能会出现问题(单点问题),因此,可以使用多个哨兵来进行监控Redis节点,并且各个哨兵之间还会进行监控。

 社招后端21连问(三年工作经验一面)(二)-鸿蒙开发者社区
简单来说,哨兵模式就三个作用:

  • 发送命令,等待Redis服务器(包括主服务器和从服务器)返回监控其运行状态;
  • 哨兵监测到主节点宕机,会自动将从节点切换成主节点,然后通过发布订阅模式通知其他的从节点,修改配置文件,让它们切换主机;
  • 哨兵之间还会相互监控,从而达到高可用。
    故障切换的过程是怎样的呢

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行 failover 过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover 操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
哨兵的工作模式如下:

  1. 每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他Sentinel实例发送一个 PING命令。
  2. 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel标记为主观下线。
  3. 如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
  4. 当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线。
  5. 在一般情况下, 每个 Sentinel 会以每10秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。
  6. 当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次
  7. 若没有足够数量的 Sentinel同意Master已经下线, Master的客观下线状态就会被移除;若Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
    20. 无重复字符的最长子串 
    给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

这道题可以使用滑动窗口来实现。滑动窗口就是维护一个窗口,不断滑动,然后更新答案。

滑动窗口的大致逻辑框架,伪代码如下:

int left =0,right = 0;
while (right < s.size()){
  //增大窗口
  window.add(s[right]);
  right++;
  
  while (window needs shrink){
    //缩小窗口
    window.remove (s[left]);
    left ++;
  }
}

 

解法流程如下:

  • 首先呢,就是获取原字符串的长度。
  • 接着维护一个窗口(数组、哈希、队列)
  • 窗口一步一步向右扩展
  • 窗口在向右扩展滑动过程,需要判断左边是否需要缩减
  • 最后比较更新答案
    完整代码如下:
int lengthOfLongestSubstring(String s){
     //获取原字符串的长度
     int len = s.length();
     //维护一个哈希集合的窗口
     Set<Character> windows = new HashSet<>();
     int left=0,right =0;
     int res =0;

     while(right<len){
       char c = s.charAt(right);
       //窗口右移
       right++;

       //判断是否左边窗口需要缩减,如果已经包含,那就需要缩减
       while(windows.contains(c)){
          windows.remove(s.charAt(left));
           left++;
       }
       windows.add(c);
       //比较更新答案
       res = Math.max(res,windows.size());
      }
     return res;
}

 

之前写过一篇滑动窗口解析,大家有兴趣可以看下哈:

leetcode必备算法:聊聊滑动窗口

参考与感谢 

  • 分布式理论之分布式一致性:Raft算法原理[2]
  • 一文搞懂Raft算法[3]
    参考资料
    [1]分布式一致性:Raft算法原理: https://www.tpvlog.com/article/66

[2]分布式理论之分布式一致性:Raft算法原理: https://www.tpvlog.com/article/66

[3]一文搞懂Raft算法: https://www.cnblogs.com/xybaby/p/10124083.html

 

文章转自公众号:小白debug

分类
标签
已于2022-5-30 17:30:14修改
收藏
回复
举报
回复
    相关推荐