zoukankan      html  css  js  c++  java
  • 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的替换、。
  • 相关阅读:
    HDU 3911 线段树区间合并
    Memcache启动&amp;存储原理&amp;集群
    剑指Offer面试题27(Java版):二叉搜索树与双向链表
    ORA-12514: TNS: 监听程序当前无法识别连接描写叙述符中请求的服务
    Java.Lang.NoSuchMethod 错误
    poj2524
    特征价格(Hedonic price)
    特征价格(Hedonic price)
    苏州之行
    苏州之行
  • 原文地址:https://www.cnblogs.com/changwenjun-666/p/11388154.html
Copyright © 2011-2022 走看看