Redis和Thread相关面试题

小群,一起交流飞向架构师

《Redis和Thread相关面试题》 群号:264698630

1. MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?
这道题有很多的实现思路:我这里只写一两种。
第一种:
比如用户数据。数据库有2000w条。
活跃用户:
redis sortSet里 放两天内(为方便取一天内活跃用户)登录过的用户,登录一次ZADD一次,
如set已存在则覆盖其分数(登录时间)。键:login:users,值:分数 时间戳、value userid。
设置一个周期任务,比如每天03:00:00点删除sort set中前一天3点前的数据(保证set不无序增长、
留近一天内活跃用户)。
取时,拿到当前时间戳(int 10位),再减1天就可按分数范围取过去24h活跃用户。
===================================================
第二种:
提供一种简单实现缓存失效的思路: LRU(最近少用的淘汰)
即redis的缓存每命中一次,就给命中的缓存增加一定ttl(过期时间)(根据具体情况来设定, 比如10分钟).
一段时间后, 热数据的ttl都会较大, 不会自动失效, 而冷数据基本上过了设定的ttl就马上失效了.
2. Redis常见性能问题和解决办法?
1.主线程(master)写内存快照,save命令调度rdbSave函数,此时会阻塞主线程的工作,当快照比较大
的时候对性能影响非常严重,会出现间断性暂停服务。
解决方法:主线程不做任何的持久化工作包括RDB和AOF操作,尤其是不能使用内存快照做持久化
2. 主线程(master)做AOF持久化操作,如果不重写AOF文件这个持久化方式对性能的影响是最小的,但
是AOF文件过大会影响主线程重启的恢复速度。
解决方法:如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒钟同步一次。
3. 主线程调用bgrewriteaof 重写AOF文件,AOF在重写的时候会栈大量的cpu和内存的资源,导致服务
load过高,出现短暂服务暂停现象。
解决方法:为了主从复制的速度和链接稳定性,Slave和Master最好在同一个局域网中。
4. Redis主从复制的性能问题,第一次Slave向Master同步的实现是:Slave向Master发出同步请求,
Master先dump出rdb文件,然后将rdb文件全量传输给slave,然后Master把缓存的命令转发给Slave,
初次同步完成。第二次以及以后的同步实现是:Master将变量的快照直接实时依次发送给各个Slave。
不管什么原因导致Slave和Master断开重连都会重复以上过程。Redis的主从复制是建立在内存快照的持久
化基础上,只要有Slave就一定会有内存快照发生。虽然Redis宣称主从复制无阻塞,但由于磁盘io的限制,
如果Master快照文件比较大,那么dump会耗费比较长的时间,这个过程中Master可能无法响应请求,也就
是说服务会中断,对于关键服务,这个后果也是很可怕的。
解决方法:尽量避免在压力较大的主库上增加从库的
5.单点故障问题,由于目前Redis的主从复制还不够成熟,所以存在明显的单点故障问题,这个目前只能自
己做方案解决,如:主动复制,Proxy实现Slave对Master的替换等,这个也是Redis作者目前比较优先的
任务之一
解决办法:为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:
Master<--Slave1<--Slave2<--Slave3.......,这样的结构也方便解决单点故障问题,实现Slave
对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。
3Redis持久化机制都有哪些,各有哪些优缺点
 RDB持久化是指用数据集快照的方式记录redis数据库的所有键值对。

  两个命令:
  SAVE命令会阻塞主进程来完成写文件
  BGSAVE命令会创建子进程来完成写文件,主进程会继续处理命令。

  优点:
  1.只有一个文件dump.rdb,方便持久化。
  2.容灾性好,一个文件可以保存到安全的磁盘。
  3.性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。
  4.相对于数据集大时,比AOF的启动效率更高。

  缺点:
  1.数据安全性低,通过配置save参数来达到定时的写快照,比如 每900 秒有1个键被修改就进行一次
快照,每600秒至少有10个键被修改进行快照,每30秒有至少10000个键被修改进行记录。所以如果当服务
器还在等待写快照时出现了宕机,那么将会丢失数据。
  2.fork子进程时可能导致服务器停机1秒,数据集太大。

====================================================
  AOF持久化是指所有的命令行记录以redis命令请求协议的格式保存为aof文件。

  优点:
  1.数据安全,aof持久化可以配置appendfsync属性,有always,每进行一次命令操作就记录到aof
文件中一次;everySec,就是每秒内进行一次文件的写操作;no就是不进行aof文件的写操作。
  2.通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性
问题。
  3.AOF机制的rewrite模式,用来将过大的aof文件缩小,实现原理是将所有的set 通过一句set 命令
总结,所有的SADD命令用总结为一句,这样每种命令都概括为一句来执行,就可以减少aof文件的大小了。
(注意,在重写的过程中,是创建子进程来完成重写操作,主进程每个命令都会在AOF缓冲区和AOF重写缓冲
区进行保存,这样旧版aof文件可以实现数据最新,当更新完后将重写缓冲区中的数据写入新的aof文件中然
后就可以将新的文件替换掉旧版的文件。
  缺点:
  1.文件会比RDB形式的文件大。
  2.数据集大的时候,比rdb启动效率低。
4. 请介绍一下redis watch命令?
def incr($key)
    $value = GET $key
    if not $value
           $value = 0
    $value = $value + 1
    SET $key, $value
    return $value
在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能。假设我们通过WATCH命令在事务执
行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,
同时返回Null multi-bulk应答以通知调用者事务执行失败。
5.请说一下redis主从复制的原理?
Redis复制工作原理:
1. 如果设置了一个Slave,无论是第一次连接还是重连到Master,它都会发出一个SYNC命令;
2. 当Master收到SYNC命令之后,会做两件事:
  a) Master执行BGSAVE,即在后台保存数据到磁盘(rdb快照文件);
  b) Master同时将新收到的写入和修改数据集的命令存入缓冲区(非查询类);
3. 当Master在后台把数据保存到快照文件完成之后,Master会把这个快照文件传送给Slave,而Slave则
把内存清空后,加载该文件到内存中;
4. 而Master也会把此前收集到缓冲区中的命令,通过Reids命令协议形式转发给Slave,Slave执行这些
命令,实现和Master的同步;
5. Master/Slave此后会不断通过异步方式进行命令的同步,达到最终数据的同步一致;
6. 需要注意的是Master和Slave之间一旦发生重连都会引发全量同步操作。但在2.8开始,当Master和
Slave之间的连接断开之后,他们之间可以采用持续复制处理方式代替采用全量同步。
Master端为复制流维护一个内存缓冲区(in-memory backlog),记录最近发送的复制流命令;同时,
Master和Slave之间都维护一个复制偏移量(replication offset)和当前Master服务器ID(Master
 run id)。当网络断开,Slave尝试重连时:
  a. 如果MasterID相同(即仍是断网前的Master服务器),并且从断开时到当前时刻的历史命令依然在
Master的内存缓冲区中存在,则Master会将缺失的这段时间的所有命令发送给Slave执行,然后复制工作
就可以继续执行了;
  b. 否则,依然需要全量复制操作;
Redis 2.8 的这个部分重同步特性会用到一个新增的 PSYNC 内部命令, 而 Redis 2.8 以前的旧版本
只有 SYNC 命令, 不过, 只要从服务器是 Redis 2.8 或以上的版本, 它就会根据主服务器的版本来
决定到底是使用 PSYNC 还是 SYNC :
如果主服务器是 Redis 2.8 或以上版本,那么从服务器使用 PSYNC 命令来进行同步。
如果主服务器是 Redis 2.8 之前的版本,那么从服务器使用 SYNC 命令来进行同步。

《Redis和Thread相关面试题》 Redis 复制机制

6.请用Redis实现分布式锁,用伪代码并注释。
用SETNX实现分布式锁

利用SETNX非常简单地实现分布式锁。例如:某客户端要获得一个名字foo的锁,客户端使用下面的命令进
行获取:
SETNX lock.foo <current Unix time + lock timeout + 1>
 如返回1,则该客户端获得锁,把lock.foo的键值设置为时间值表示该键已被锁定,该客户端最后可以通
过DEL lock.foo来释放该锁。如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试
等对方完成或等待锁超时。

伪代码

上面的锁定逻辑有一个问题:如果一个持有锁的客户端失败或崩溃了不能释放锁,该怎么解决?我们可以通
过锁的键对应的时间戳来判断这种情况是否发生了,如果当前的时间已经大于lock.foo的值,说明该锁已
失效,可以被重新使用。

发生这种情况时,可不能简单的通过DEL来删除锁,然后再SETNX一次,当多个客户端检测到锁超时后都会
尝试去释放它,这里就可能出现一个竞态条件,让我们模拟一下这个场景:

 C0操作超时了,但它还持有着锁,C1和C2读取lock.foo检查时间戳,先后发现超时了。
 C1 发送DEL lock.foo
 C1 发送SETNX lock.foo 并且成功了。
 C2 发送DEL lock.foo
 C2 发送SETNX lock.foo 并且成功了。
这样一来,C1,C2都拿到了锁!问题大了!

幸好这种问题是可以避免D,让我们来看看C3这个客户端是怎样做的:

C3发送SETNX lock.foo 想要获得锁,由于C0还持有锁,所以Redis返回给C3一个0
C3发送GET lock.foo 以检查锁是否超时了,如果没超时,则等待或重试。
反之,如果已超时,C3通过下面的操作来尝试获得锁:
GETSET lock.foo <current Unix time + lock timeout + 1>
通过GETSET,C3拿到的时间戳如果仍然是超时的,那就说明,C3如愿以偿拿到锁了。
如果在C3之前,有个叫C4的客户端比C3快一步执行了上面的操作,那么C3拿到的时间戳是个未超时的值,
这时,C3没有如期获得锁,需要再次等待或重试。留意一下,尽管C3没拿到锁,但它改写了C4设置的锁的
超时值,不过这一点非常微小的误差带来的影响可以忽略不计。
注意:为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,
再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得,
这时就不必解锁了。
根据上面的代码,我写了一小段Fake代码来描述使用分布式锁的全过程:

# get lock
lock = 0
while lock != 1:
    timestamp = current Unix time + lock timeout + 1
    lock = SETNX lock.foo timestamp
    if lock == 1 or (now() > (GET lock.foo) and now() > (GETSET lock.foo timestamp)):
        break;
    else:
        sleep(10ms) 
# do your job
do_job() 
# release
if now() < GET lock.foo:
    DEL lock.foo
7.怎样保证对外开放的接口的安全性?
前端请求:
为保证数据传输的安全性,在进行移动端与后台的数据校验的时候(通过URL的形式传递校验数据)。
后端分配给移动端的秘钥是不进行网络传输的。
  ① 把参数进行排序如a=entrr&b=123&token=weekfh2376428,
  ② 将秘钥与排序进行拼接,如果秘钥为:
    %#$¥askjdflj 那么就将密码与排序好的参数进行拼接如:
    %#$¥askjdflja=entrr&b=123&token=weekfh2376428
  ③ 把含有秘钥的接着进行大写转换
  ④ 对添加秘钥之后的参数进行 MD5转换 。生成签名字段
    sign= (%#$¥askjdflja=entrr&b=123&token=weekfh2376428)的MD5加密之后的字段。
后台验证:
  1.判断是否有timestamp token sign(签名字段) 是否有这些请求参数,如果没有直接返回错误。
  2.判断此URL是否过期。如果过期,返回错误。
  3.判断sign值,把除了sign的参数进行排序并大写,然后把秘钥拼接在排序之后的参数的前面,然后
对其进行MD5转换。将MD转换之后生成的签名字段与传过来的sign进行比对,如果不一致,返回错误。
  4.根据token取出userId ,如果取不到,说明请求超时。token过期,返回json数据。让用户再次登录
8.ReadWriteLock是什么?
 读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应
的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数
据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
  ● ReentrantReadWriteLock会使用两把锁来解决问题,一个读锁,一个写锁
      ○ 线程进入读锁的前提条件:
          ■ 没有其他线程的写锁,
          ■ 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
  ● 线程进入写锁的前提条件:
          ■ 没有其他线程的读锁
          ■ 没有其他线程的写锁
9.CyclicBarrier和CountDownLatch的区别?
作用原理:拦截所有的线程,直到所有的线程都完成之后才能准许通过。
1.  CyclicBarrier初始化时规定一个数目,然后计算出调用CyclicBarrier.await()方法进入等待状
态中的线程数。]当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
2. CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个
障碍。
3.  CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable的任是在CyclicBarrier的数
目达到后,在所有其它线程被唤醒前被执行。
CountDownLatch在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
1. 需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后
面的操作。
2. 用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达
零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即
返回。这种现象只出现一次——计数无法被重置。
10.ThreadLocal有什么作用?
线程的本地变量,每一个线程都有的对象。容器,每一个线程都有的一个容器。如果多线程需要共用一个
session,可以session放在这里,不影响线程。提升效率和线程安全。操作完成之后清空ThreadLcoal
,但是链接却不会关闭。
    ● ThreadLcoal中session的来历:
    ● 当线程使用到session的时候,会先去ThreadLocal中寻找,如果找不到就会调用方法获取
session或者是创建session,然后返回session供使用。
    原文作者:小炉炉
    原文地址: https://www.jianshu.com/p/13281d10c1e7
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞