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的相关面试总结的主要内容,如果未能解决你的问题,请参考以下文章