redis的相关面试总结

Posted changwenjun-666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis的相关面试总结相关的知识,希望对你有一定的参考价值。

1.redis和memcached比较?

相同点:二者都是将数据缓存到内存中。
区别:

redis支持list,Set,zSet,hash,string五种类型
memcached只支持字符窜数据的存储

redis支持RDb和AOF两种持久化的存储
memcached存储基于LRU,不支持持久化,出现宕机数据丢失。

redis采用点线程服务,意味着较多的阻塞,单线程下io复用模型
memcached采用的是多线程,阻塞较少,非阻塞i复用模型

2.redis中数据库默认是多少个db 及作用?

默认存在16个db,可以在连接的时候去指定db.



3.python操作redis的模块?
直接导入redis模块,当然了,在设置里需要作出相应的修改!


4.如果redis中的某个列表中的数据量非常大,如果实现循环显示每一个值?

基于增量迭代来实现,本质上还是用到了yield生成器


import redis
conn = redis.Redis(host=127.0.0.1,port=6379,password=12345)
conn.lpush = (test,*[1,2,3,4,5,6,])
def scan_list(name,num=3):
    index = 0
    while True:
        data_list = conn.lrange(name,index,index+num-1)
        if not data_list:
            return 
        index += count
        for item in data_list:
            yield item
for item in scan_list(name,5):
    print(item)




5.redis如何实现主从复制?以及数据同步机制?

什么是主从复制:
为了保证数据的高可用性,一般使用多台数据库服务器做集群,选择其中一台作为master,其余的作为slave.master上数据根性后按照文件配置的策略,自动同步到slave上,这就是主从复制。

如何实现主从复制:
先配置redis.conf的配置文件:
1)、将redis的配置文件复用多份
2)、设置为后台进程
3)、配置不同的pidfile(文件名)
4)、配置不同的port
5)、配置不同的logfile
6)、配置不同的dbfilename(rdb文件名)

启动多个redis-client客户端

将其中一个redis服务作为master,其他的 作为slave

再执行主从复制


缺陷;slaver需要同步到master上时,发送的数据量很大的时候,存在一定的时延。



实现数据同步:
1、从服务器发送sync命令给主服务器
2、接收到sync的主服务器调用bgsave命令,创建一个RDB文件,使用缓冲区去记录接下来所有的写命令。
3、主服务器执行完bgsave后,向从服务器发送RDB文件,而从服务器接收并载入这个文件。
4、主服务器将缓冲区的所有写命令发送给从服务器执行





6.redis中的sentinel的作用?

一旦主节点宕机,从节点可以作为主节点的备份随时顶上来。拓展主节点的读能力,分担主节点读压力。当然了,这种背景下,需要人为的修改应用方所有的主节点的地址,还需要命令所有从节点复制新的主节点!
这个问题呗redis-setinel很好的解决。本身就是一个独立的进程,用来监控多个master-slave集群,自动发现master宕机,进行自动切换slave > mster.

7.如何实现redis集群?

这种场景一般是由于单个服务器的内存不大,一般只有16-256G,如果业务需求大于这个时,核心思想是将数据分片储存在多个redis实例中,每一片就是一个rediis实例。

现有的企业集群方案:
twemproxy  由 twitteer 开源
codis 由豌豆荚开发,基于go和c语言开发
redis-cluster官方3.0版本后的集群方案


8.redis中默认有多少个哈希槽?

内置了16384个哈希槽,redis会对key进行crc16算法算出一个结果,让后把结果对16384取余数。
redis集群没有使用一致性哈希算法,而是引入了哈希槽的概念。


9.简述redis的有哪几种持久化策略及比较?

持久化策略:
RDB持久化:
在指定的时间间隔内将内存里数据通过save命令生成RDB快照文件!这种文件是二进制文件,文件保存到硬盘,redis可以通过该文件还原数据库当时的状态。比起AOF,在数据量比较大时,RDB的启动速度更快!
缺陷:容易造成数据的丢失。

AOF持久化:
以日志的形式记录服务器所处理的每一个写、删除操作,却不会记录查询操作,生成AOF文件,重启redis时,AOF的命令被重新执行一次,重建数据!保证数据的安全,类似于mysql的binlog!
比RDB可靠,可以制定不同的fsync策略。
缺陷:在相同的数据集下,AOF文件的大小一般都比RDB的 大。


10.列举redis支持的过期策略。


六种过期策略:
volatile-lru:已设置过期时间的数据集中选择最近最少使用的数据淘汰掉  
volatile-ttl;已设置过期时间的数据集中选择将要过期的数据淘汰掉
volatile-random;已设置过期时间的数据集里任意挑选数据淘汰
allkey-lru;从数据集中选择最近最少使用的数据淘汰掉
allkey-random:从数据集中选择任意数据淘汰掉
no-enviction:禁止驱逐数据,达到最大内存限制后
,如果还需要更多内存,则直接返回错误信息!



11.MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中都是热点数据? 

依据redis的数据过期策略,计算20万数据大致需要的内存,然后设置redis的内存限制,将淘汰策略设置为volatile-lru或者allkeys-lru即可!


12.写代码,基于redis的列表实现 先进先出、后进先出队列、优先级队列。

不想写


13.如何基于redis实现消息队列?

消息队列是基于redis的发布于订阅功能区实现的,
发布者消息到频道,频道就是一个队列。除了这种原生的python里自带的发布订阅功能所具备的缺陷外,我们还注意到,业务上应该避免过度的去复用redis,因为平常主要用来做计算,做缓存,在用来做任务队列,压力太大,因此不建议使用!


14.如何基于redis实现发布和订阅?以及发布订阅和消息队列的区别?

发布端:
import redis
pool = redis.ConnectionPool(host=127.0.0.1,port=6379,password=12345)
conn = redis.Redis(connection_pool=pool)
while True:
    msg = input(publish:>>>)
    conn.publish(spub, msg)
    if msg == over:
        print(停止发布)
        break


订阅端:
import redis
pool = redis.ConnectionPool(host=127.0.0.1,port=6379,password=12345)
conn = redis.Redis(connection_pool=pool)
pub = conn.pubsub()
pub.subscribe(spub,cctv1)

for item in pub.listen():
    print("Listen on channel : %s " % item[channel].decode())
    if item[type] == message:
        data = item[data].decode()
        print("From %s get message : %s" % (item[channel].decode(), item[data].decode()))
        if item[data].decode() == over:
            print(item[channel].decode(), 停止发布)
            break
pub.unsubscribe(spub)
print("取消订阅")



遗憾之处:
虽然这种发布和订阅非常有用,但是如果一个客户端订阅了某个或某些频道,但它读取消息的速度却不够快的话,那么不断积压的消息会使得Redis输出缓冲区的体积变得越来越大,这可能会导致Redis的速度变慢,甚至直接崩溃。
另外一个原因与数据传输的可靠性有关。可能会在传输的过程中出现断线的情况,断线产生的连接错误通常使得网络连接两端中的其中一端重新连接。在python中redis客户端可以自动连接,也会自动处理连接池。虽然如此,但是发布者发布的消息会在断线期间丢失。因此,依靠频道来接受消息的用户会对redis提供的publish和subscribe命令的语义感到失望!

有改善的方法,但是平常不怎么去使用它!








15.什么是codis及作用?

由豌豆荚的中间件团队开发,作为集群的一种方案来使用。对于上层的应用来说,连接codis proxy 和连接原生的redis server没有明显 的区别,但是上层应用可以像单机一样去使用redis,codis底层会去处理请求的转发,不停机的数据迁移等工作。可以简单认为后面连接的是一个内存无限大的redis服务!!



16.什么是twemproxy及作用?

是twtter开源的一个redis和memcached的 代理服务器,主要用来管理redis和memcached集群,减少cache与服务器直接连接的数量。





17.写代码实现redis事务操作。

需要明确redis的事物是简单事务,因为关系型数据库具有ACID特性,redis可以保证A(原子性)和I(隔离性),D(取决于是否配置了RDB或者AOF持久化操作),
但是无法保证一致性,因为redis事物不支持回滚。

事务与管道pipeline的异同:
事务和管道都是不支持回滚;中间命令出错不会影响之前执行成功的命令,也不会影响后续的任务,本身会返回error.
事务可以实现原子性和隔离性,而pipeline中的命令虽然是以队列形式发送发送到redis中执行的,但是其他redis连接在此时发送了命令,那么pipeline中的多个命令可能会被其他命令插队。具体实现没有思路!!!

只能通过任务的执行结果去证明,不过也没用实际的应用场景!!!  
redis任务在执行的时候,是单线程的,因此再有在等待 执行的时候才出现插队的现象。
解决:开启redis的事务,可以保证一个事务内的 所有命令依次执行而不被其他的命令插入!


redis事务的简单实现:
import redis 
pool = redis.ConnectionPool(host=127.0.0.1,port=6379,password=12345)
r = redis.Redis(connection_pool=pool)

pipe = r.pipeline(transaction=True)
pipe.multi()  #开启事务
pipe.set(name,alex)
pipe.set(age)
pipe.execute()  # 提交



18.redis中的watch的命令的作用?

回忆:在vue里,watch是一个监听函数,只要里面的监听的数据发生变化就会调用该函数,去实现内部的了逻辑代码。

redis里watch的命令的作用:
watch命令提供了在开始事务前监视一个或者多个键,在这些被监视的键中,只要有一个在执行事务前发生
了改变,那么整个事务就会被取消并抛出watcherror异常。
像这种情况就属于乐观锁了,key所对应的数据被其他客户端修改,通知执行watch命令的客户端。

对于关系型数据库而言,执行的加锁为悲观锁,持有锁的客户端 运行越慢,等待解锁的客户端被阻塞的时间越长。


如果在watch之后数据发生了改变那有如何?
待续!!!!!!!!!!!




19.基于redis如何实现商城商品数量计数器?


import redis
pool = redis.ConnectionPool(host=127.0.0.1,port=6379,password=12345)
r = redis.Redis(connection_pool=pool)
   #  r.set(‘num‘,100)   先设置好
with r.pipeline() as pipe:
    r.watch(num)
    pipe.multi()
    old_num = r.get(num)
    num = int(old_num)
    if num > 0:
        pipe.decr(num,amount=1)
        # 或者 pipe.set(‘num‘,num-1)
    pipe.execute()
    
    
    
    
20.简述redis分布式锁和redlock的实现机制。

具体的实现机制待续........



分布式锁应该具备的条件:
1、在分布式环境下,一个方法在同一时间内只能被一个机器的一台线程执行
2、高可用的 获取锁与释放锁
3、高性能的获取锁和释放锁
4、具备锁失效机制,防止死锁
5、具备非阻塞锁特性,没有获取到锁将直接返回,此次获取锁失败


三种实现方式:
基于数据库的 
基于缓存的(redis)
基于zookeeper实现


基于缓存(redis):

import redis

import uuid
import time
from threading import Thread,current_thread

pool = redis.ConnectionPool(host=127.0.0.1,port=6379,password=12345)

redis_client = redis.Redis(connection_pool=pool)


# 获取一个锁:

def acquire_lock(lock_name,acquire_time=10,time_out=10):
    ‘‘‘

    :param lock_name: 锁名
    :param acquire_time: 等待获取的时间
    :param time_out: 锁的超时时间
    :return:
    ‘‘‘

    identifier = str(uuid.uuid4())
    # print(identifier)
    end = time.time() + acquire_time
    lock = string:lock: + lock_name
    while time.time() < end:
        if redis_client.setnx(lock,identifier):
            #  setnx的用法是增加一个key-value,如果 key存在,则直接返回0,不存在,创建且 返回1
            redis_client.expire(lock,time_out)
            # 设置超时时间,防止进程崩溃导致其他进程无法获取 锁
            print(current_thread().name,当前的进程拿到了锁)
            # 下面进行缓存里商品数据的数量的改变
            redis_client.decr(num,amount=1)

            return identifier


        # 下面的这部分存在的意义何在????
        elif not redis_client.ttl(lock): # 当超时时间到时:
            #  ttl:  返回键“名称”将过期前的秒数
            redis_client.expire(lock,time_out)
            # expire  将键为lock,设置过期时间

        time.sleep(0.001)

    return False


‘‘‘
获取锁是为了对数据进行操作,所以此处设置一个商品的库存为100,来一个线程就将其库存减少一
用redis的字符串类型的decr来实现
‘‘‘




# 释放一个锁

def release_lock(lock_name,identifier):
    """通用的锁释放函数"""
    lock = string:lock: + lock_name
    pip = redis_client.pipeline(transaction=True)
    while True:
        try:
            pip.watch(lock)
            lock_value = redis_client.get(lock)
            if not lock_value:
                return True

            if lock_value.decode() == identifier:

                pip.multi()  # 开启事务
                pip.delete(lock)  # 删除字段lock
                pip.execute()  # 提交
                print(current_thread().name,释放了锁,等待下一个)
                return True

            pip.unwatch()   # 关闭watch
            break
        except redis.exceptions.WatchError:  # 捕获错误
            pass
    return False


# 测试自己编写的分布式锁
# 例子中使用50个线程模拟秒杀一个商品,使用–运算符来实现商品减少,
# 从结果有序性就可以看出是否为加锁状态。
def seckill():
    # print(Thread.getName)
    identifier = acquire_lock(resource)
    # print(current_thread().name, "获得了锁")
    release_lock(resource,identifier)


for i in range(50):
    t = Thread(target=seckill)
    t.start()





21.什么是一致性哈希?Python中是否有相应模块?

一致性哈希:为了解决热点问题,存在四个定义:
平衡性,单调性,分散型,负载

对应的模块为 hash_ring




22.如何高效的找到redis中所有以oldboy开头的key?

python里的redis里存在这样一个语法:
keys(pattern=xx)  用来匹配符合条件的key值

支持正则:
 ?   h?llo  匹配hello,hallo等单个字符
 *      h*llo    匹配多个字符,例 heeeggllo
 []   h[]llo 匹配指定模式的区间  h[ae]llo 匹配在a到e之间的字符。
 需要注意,可以用/ 来转义特殊的字符


如果说,被匹配到的数据集里数据太大,内存可能会在一瞬间崩掉。如果在一个数据集里查找特定的key,最好还是用redis的get去代替。


需要注意,redis里获取到的数据都是字节类型的,同时,keys(pattern=xx) 获取的是一个列表类型,取值时可以用mget来实现!




23、redis的缓存穿透:
概念:查询一个数据库中一定不存在的数据,真长采用缓存查找的流程,先查询缓存,不存在或者过期,则对数据库进行查询,如果查询对象为空,则不放进缓存

危害:每次查询为空,每次又进行缓存,存在恶意攻击,利用漏洞,对数据库造成压力。

解决:将查询的对象即使为空,也放入缓存,只是设定的时间较短。


24、reddis的缓存雪崩

概念:在某一个时间段,缓存集中过期失效

危害:在双11时,将商品的数据放入缓存,设置时间为1个小时,当过期后,对数据的访问就就给数据库,因此产生周期性的压力波峰!

解决:采取不同的分类,缓存不同的周期,在同一分类的商品,加上一个随机因子。尽可能的分散缓存的过期 时间。




25、redis的缓存击穿

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

正常下,大部分爆款很难对数据库造成压垮性的压力。在这个级别的公司没几家。
对于主打商品早早做准备,让缓存永不过期就ok.


26、项目中遇到的性能问题
自己的项目里没有遇到,不过在学习redis的时候,注意到了一些方面的小点:
1)、master最好 不要做任何持久化操作,例如 rdb和aof日志文件
2)、数据比较重要 。摸个slave开启aof备份数据时,策略设置为1秒一次
3)、为了主从复制的速度和连接的稳定性,master和slave最好在同一局域网内
4)、主从复制不要用图装结果,用单向链表结构更稳定。
这样的一些设计,仿版解决单点故障问题,能够很好的实现slave读master的替换、。

 

以上是关于redis的相关面试总结的主要内容,如果未能解决你的问题,请参考以下文章

分布式技术高质量面试总结

分布式技术高质量面试总结

Java相关面试题总结

我见过最详细的Redis解析:Java开发经验的有效总结

Java面试题总结 14Redis面试题总结(附答案)

Redis面试总结