【问答】Redis

Memcache和Redis的区别?Redis和memcached 的内存管理的区别?

网络IO模型:Memcache是多线程非阻塞IO复用模型,所有操作需要获取全局锁;Redis是单线程IO复用模型,排序、聚合等CPU计算会阻塞IO调度;数据支持类型:Memcache仅支持Key-Value,内存维护巨大的HashTable,查询效率高;Redis支持List、Set、Zset、hash多种数据类型;数据一致性: Memcached提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题;Redis没有提供cas 命令,并不能保证这点,不过Redis提供了事务的功能,可以保证一串命令的原子性,中间不会被任何操作打断;集群管理: Memcached本身不支持分布式,需要客户端通过分布式算法实现; Redis支持分布式存储,分为4096个槽,每个节点上存储多个槽的Key数据;数据存储和持久化: Memcached不支持持久化,所有数据在内存中,Key和Value的最大存储容量较小;Redis支持两种持久化的方式,一种是快照,它可以将存在于某一时刻的所有数据都写入硬盘里面;另一种方法叫AOF,它会在执行写命令时,将被执行的写命令复制到硬盘里面,Key和Value存储容量较大;

内存管理机制:Memcached使用预分配的内存池的方式 ,将内存分割成各种尺寸的块,相同尺寸的块为一组,Item根据大小选择合适的块存储, 省去申请/释放内存的开销,效率高,没有内存碎片,但会浪费空间;Redis使用现场申请内存的方式来存储数据,存在内存碎片,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的;

Redis支持哪几种数据结构

String:key-value,命令:get/set、mset/mget(同时操作多个)、setex(设置过期时间)、setnx(不存在才设置);Hash:类似Map,key-filed-value,命令:hset/hget、hexists(判断field是否存在)、hmset/hmget、hkeys、hvals、hgetall、hdel、hsetnx(field不存在才设置);list:列表List,可重复,命令:lpush、lindex(获取指定序号的值)、lrange、lset(设置指定序号的值)、lpop(删除第一个)、rpop(删除最后一个);set:集合,类似list,无序可去重,使用hash实现,命令:sadd、scard求长度、sdiff求2个集合的差集、sinter求交集、sunion求并集、srem删除指定元素、spop随机移除元素;zset:有序集合,分数可重复,元素不可重复,使用hash实现,命令:zadd、zrange、zcount; 

Redis跳跃表的问题

跳跃表是一种有序数据结构,它通过在每个节点中维持多个/多层指向其他节点的指针,从而达到快速访问节点的目的, 查询复杂度O(logN) 接近平衡树,是zset的底层数据结构; zset是个复合结构,是由一个hash和skiplist组成的,其中hash用来保存value和score对应关系,用链表实现,skiplist用来给score排序,插入和删除时实现近似二分查找的效率,Redis跳表最多64层,即每个节点最多维护64个指向下个节点层次的指针;

Redis压缩表的问题

压缩列表ziplist是列表键和哈希键的底层实现,是为了节约内存而开发的,由一系列特殊编码的连续内存块组成的顺序型数据结构;当一个列表键只包含少量列表项并且每个列表项都是小整数或者长度较短的字符串时,就会使用压缩列表来做列表键的底层实现;

Redis单进程单线程的Redis如何能够高并发?

1.redis是基于内存的,内存的读写速度非常快;2.redis是单线程的,保证了每个操作的原子性,省去了很多上下文切换线程的时间,利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销;3.redis使用非阻塞IO多路复用技术,可以处理并发的连接,非阻塞IO内部实现采用epoll,采用了epoll+自己实现的简单的事件框架,epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间;4. 数据结构的优化,Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度; 

Redis采用多线程会有哪些问题?

Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽,既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了,多线程会带来上下文切换,线程安全等问题;

Redis的线程模型是什么

Redis采用单进程单线程的模型,基于Reactor模式开发了自己的文件事件处理器;文件事件处理器使用IO多路复用监听多个客户端的socket,并根据socket产生的事件选择不同的事件处理器处理客户端连接请求、命令请求和响应请求;文件事件处理器包括多个客户端连接的Socket、IO多路复用程序(监听Socket并放入队列,通过包装select、epoll、evport和kqueue这些I/O多路复用函数库来实现,根据操作系统自动选择)、文件事件分派器(处理队列中的Socket分派到处理器)、事件处理器(应答/命令请求/命令回复/主从复制处理器);

单线程的Redis如何利用多核cpu机器?

Redis是纯内存操作,一般服务器的cpu都不会是性能瓶颈,redis的性能瓶颈主要集中在内存和网络方面,如果确实需要充分使用多核cpu的能力,那么需要在单台服务器上运行多个redis实例(主从部署/集群化部署),并将每个redis实例和cpu内核进行绑定(使用taskset命令),如果需要进行集群化部署,需要对redis进行分片存储;

Redis如何使用Redis实现分布式锁?

分布式锁需考虑互斥性(多个客户端不能同时获取)、安全性(只能被持有者删除)、死锁(超时获取,节点或客户端宕机引起的死锁)、高可用(部分节点宕机锁任然可用)、可重入; 

锁实现:SET key value [EX seconds] [PX milliseconds] [NX|XX]命令代替SETNX实现加锁;GET、DEL命令实现释放锁,释放时需要判断Value避免误释放;通过Lua脚本来避免Check And Set模型的并发问题,因为在释放锁的时候因为涉及到多个Redis操作; 

存在的风险:客户端A在Master上加锁,此时Master宕机,Slave没有完成锁的同步,Slave变为Master,客户端B此时可以完成加锁操作;使用redlock解决,大概意思就是多个master节点同时申请锁; 

Redis分布式锁操作的原子性,Redis内部是如何实现的?

1. setnx+expire分2个命令实现加锁,不能保证原子性,2.6版本后采用set key value(线程ID)ex px nx单个指令加锁;2. 解锁时判断当前线程ID是否等于Value,由于get判断和del删除是两步操作,需要采用LUA脚本实现,保证原子性; 

Redis的缓存淘汰策略?

删除策略:定期删除(每隔100ms随机抽取一部分设置了过期时间的key,若过期则删除)、惰性删除(获取key时检查,过期则删除);

淘汰策略:内存满时的策略,noeviction(默认,不淘汰,写时返回错误)、allkeys-lru/volatile-lru(从所有key/设置了过期时间的key淘汰最近最少使用的)、allkeys-random/volatile-random(随机淘汰)、allkeys-lfu/volatile-lfu(淘汰最近最少访问的)、volatile-ttl(淘汰越早过期的); 

设置:maxmemory(最大使用内存,不设或0则不限制)、maxmemory-policy(内存淘汰策略);Redis实现LRU:采用随机采样法,每次抽取5个key淘汰最近最少使用的,每个key内部增加最近访问时间字段;

如何解决缓存穿透和缓存雪崩?

缓存穿透:请求的数据不存在,缓存中不存在所以每次都请求了数据库;解决:布隆过滤器(将所有可能存在的数据哈希到一个足够大的bitmap中,过滤掉不存在的数据),缓冲空结果; 

缓存击穿:请求的热点数据过期,大量请求同时过来,在缓存还未重新刷新前造成数据库压力;解决:互斥锁(先取缓存再查库再更新缓存)、缓存标识(提前识别过期key更新缓存); 

缓存雪崩:大量缓存key同时失效或宕机导致请求到数据库,解决:redis集群、ehcache本地缓存、分散key过期时间; 

常见的缓存策略有哪些,如何做到缓存(比如Redis)与DB里的数据一致性

缓存策略:1.基于访问时间:按缓存项被访问时间来组织缓存队列,如LRU;2.基于访问频率:按缓存项的被访问频率来组织缓存,如LFU;3.访问时间与频率兼顾:通过自适应参数在访问时间和频率间取得平衡,如LRUF;4.基于访问模式:基于数据访问特点产生相适应的缓存策略; 

由于并发的读写以及可能的读写失败导致缓存和DB数据不一致;解决方法:1.先淘汰缓存,再写数据库,引入分布式锁实现串行写(避免数据库未更新前把旧数据更新到缓存了);写请求:先获取锁,写请求前后双删缓存,再写库;读请求:缓存不存在时获取锁更新缓存;2.先写数据库,再更新缓存; 同一事务中操作数据库并把需要更新的缓存KeyValue入库,定时任务线程轮训库中需要处理的KeyValue更新缓存,或者通过消息队列;3. 基于数据库binlog日志:数据直接写入数据库,开启binlog为row模式,利用阿里的Canal中间件读取binlog日志并将结果发送到MQ,消费MQ消息更新缓存,实现复杂,需要引入三方件;

什么缓存系统,如何设计的

考虑进程缓存Ehcache、分布式缓存redis、多级缓存;考虑解决缓存的问题:缓存击穿、缓存穿透、缓存雪崩、数据不一致、缓存预热、分布式锁;考虑什么数据需要缓存,哪些是热点数据;

缓存数据过期后的更新如何设计

更新缓存方式:淘汰整个缓存和更新缓存具体的Key;淘汰缓存需要重新加载缓存,而且期间会引起缓存不命中,但操作简单;更新缓存需要考虑维护成本;两者的取舍主要取决于更新缓存的复杂度,一般是直接淘汰缓存; 

更新缓存时机:操作缓存和数据库不能保证事务性,所以先写数据库再淘汰缓存和先淘汰缓存再写数据库两种方式出现不一致时哪种对业务影响小就选择那种方式;一般先淘汰缓存再写数据库;  

更新缓存方法:可考虑专门的接口服务或者异步更新缓存,屏蔽业务对缓存和数据库读取的差异,简化业务服务的调用逻辑;

如何看待缓存的使用(本地缓存,集中式缓存),简述本地缓存和集中式缓存和优缺点

本地缓存分布式各个实例中,容易数据不一致,分布式缓存是集中式管理要好些;本地缓存会占用堆内存,影响垃圾回收、影响系统性能,分布式缓存使用专门的机器部署,但存在数据传输;分布式缓存更方便数据扩容,可避免单点故障;本地缓存适用于较小且频率可见的访问场景,尤其适用于不变对象,对于较大且不可预见的访问,最好采用分布式缓存;

Redis如何持久化数据?

RDB:快照保存,默认方式,将内存数据保存到磁盘dump.rdb文件;保存时机:根据规则定期保存(save设置,xx时间内超过xx个Key被修改则保存),手动保存(执行save或bgsave命令);原理:fork子进程,由子进程保存数据到临时rdb文件,再替换原rdb文件;适合做备份但不能实时保存;

AOF(Append Only File):追加写,每个修改命令追加到appendonly.aof文件中;保存时机:执行修改命令时或每秒一次可配置;可实时保存,写较多时会降低性能;支持同时开启RDB和AOF,系统重启后会优先使用AOF来恢复数据,这样丢失的数据会最少;

Redis集群有哪几种形式?

单机模式,单节点读写使用;主从复制:1个master+n个slave,master负责读写,slave从master复制数据,只负责读,master/slave数据相同,slave降低master读的压力,无法保证高可用,没有解决写的压力;哨兵模式:基于主从复制模式增加了n个哨兵服务器(单独进程,定时和主从服务器心跳获取信息),用于监控主从服务器间的连接、通知管理员故障、主服务器故障时转移故障,实现了高可用,但切换要丢数据,没有解决写的压力;集群代理模式:n个master+n个salve+n个哨兵,master层前使用第三方代理工具Twemproxy进行代理,维护成本高,扩展性差;集群模式:数据按Slot分段分别存储在所有机器上,每个Node分配一段Slot(0-16384),Node退出或加入会以Slot为单位迁移数据,分摊了写操作,但节点间互相监听、读和写,任务繁重;

有海量key和value都比较小的数据,在Redis中如何存储才更省内存?

一是考虑多个Key的数据合并存储,如使用hash存储key-field-value;二是Redis本身对List、Hash使用了压缩表压缩存储;

Redis的使用要注意什么

1.冷热数据分离,不要将所有数据全部都放到Redis中; 2.不同的业务数据要分开存储; 3.规范Key的格式,便于查看,统计,排错; 4.存储的Key一定要设置超时时间; 5.谨慎全量操作Hash、Set等集合结构; 6.根据业务场景合理使用不同的数据结构类型; 7.对于必须要存储的大文本数据一定要压缩后存储;

Redis内部通讯机制

Redis客户端和服务端之间采用RESP协议进行通信, RESP是Redis序列化协议,  基于TCP,文本协议,实现简单,性能极好,协议将传输的结构数据分为5种最小单元类型,单元结束时统一加上回车换行符号\r\n;单行字符串(+开头)、多行字符串($开头跟上字符串长度)、整数值(:开头,后跟整数的字符串形式)、错误消息(-开头)、数组(*开头跟上数组长度);

Redis是无中心节点的集群架构,集群内部节点间彼此不断使用Gossip协议通信交换信息,一段时间后所有的节点都会知道集群完整的信息,包括meet(用于通知新节点加入,消息发送者通知接收者加入到当前集群)、ping(集群内每个节点每秒向多个其他节点发送ping消息检测节点是否在线和交换彼此状态信息,ping消息发送封装了自身节点和部分其他节点的状态数据)、pong(当接收到ping、meet消息时作为响应消息回复给发送方确认消息正常通信,封装了自身状态数据,节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新)、fail( 当节点判定集群内另一个节点下线时广播发送,其他节点接手后更新节点信息);

Redis的并发竞争问题如何解决,了解Redis事务的CAS操作吗

解决Redis并发竞争同一Key的问题可以通过分布式锁或者使用消息队列把并发改成串行访问;

Redis的事务:执行事务:MULTI(开始事务)、输入N个命令(命令如FIFO队列,不会马上执行)、EXEC(执行事务,串行执行队列的多个命令);WATCH:CAS乐观锁,执行事务前设置需要观察的多个key,执行事务时判断若观察的key被修改则事务失败;通过watched_keys字典实现,修改每个客户端的REDIS_DIRTY_CAS字段,执行事务时判断该字段来决定是否执行;Redis事务满足ACID:A(执行过程中不被中断)、C(不支持回滚,入队失败则事务失败,单条命令执行错误不影响事务)、I(单线程串行执行)、D(RDB/AOF实现);

Redis的选举算法和流程是怎样的

Redis集群哨兵模式支持主节点挂掉之后故障自动转移,选举过程经过4步: 故障节点主观下线、 故障节点客观下线、 Sentinel集群选举Leader、 Sentinel Leader决定新主节点;

故障节点主观下线:哨兵节点定时向其他所有节点发送心跳,一定时间内没收到消息则认为节点主观下线;

故障节点客观下线:若超过一定数量的哨兵节点都认为该节点主观下线则该节点客观下线,若下线节点是哨兵或从节点则不管,主节点就开始故障转移;

Sentinel集群选举Leader:当哨兵节点判断其他节点主观下线后会请求其他哨兵节点选举自己为Leader,若其他节点没同意过其他请求则同意+1,同意数超过一定数量则被选为Leader;

Sentinel Leader决定新主节点 :哨兵Leader过滤故障节点,选择优先级最大的从节点(若有配置),否则选择偏移量较大(同步比例最大)的节点为Leader,否则选择节点启动时生成的runid最小的节点为leader;

Redis的集群怎么同步的数据的

从节点第一次启动时将进行全量同步: 1:从节点启动时向主数据库发送sync命令;2:主节点接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来;3:当快照完成后主节点会将快照文件和所有缓存的命令发送给从节点;4:从节点收到后会载入快照文件并执行收到的缓存的命令;

运行期间将进行增量同步:1:主节点收到命令执行完后判断是否需要同步给从节点; 2:主节点将命令写入AOF并发送给从节点;3:从节点接收后处理命令;

节点异常恢复后增量拷贝: 由于从节点之前保存了自身已复制的偏移量和主节点的运行ID,恢复连接后从节点发送请求给主节点,主节点核对通过后进行增量拷贝,否则进行全量拷贝;

知道哪些Redis的优化操作

尽量使用短的key、在存到Redis之前先把你的数据压缩下、设置 key 有效期、选择内存回收策略、尽可能地使用hashes哈希存储、当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能、想要一次添加多条数据的时候可以使用管道、 限制redis的内存大小、SLOWLOG 慢查询设置、避免使用keys命令因为是阻塞的;

如何使用Reids和zookeeper实现分布式锁?有什么区别优缺点,会有什么问题,分别适用什么场景

基于缓存(Redis)实现:内存操作,效率最高,利用SETNX key value命令实现;解锁使用Lua脚本实现保证原子性;通过判断锁持有者实现可重入,通过Redis自带的Key超时机制实现锁超时,通过Redis分布式解决单点问题;

基于Zookeeper实现分布式锁:利用Zookeeper创建临时有序节点来实现分布式锁;判断节点序号最小则加锁成功,否则注册Watcher事件等待前面节点(序号小的获取到锁的)通知则获取到锁;通过客户端加锁时将主机和线程信息写入锁中,下一次再来加锁时直接和序列最小的节点对比实现可重入,ZK自动删除会话断开的临时节点实现锁超时;ZK集群解决单点问题;通过curator框架可实现ZK分布式锁;  

区别:ZK:释放锁直接删除节点,实现简单,通过监听机制可以很方便的通知其他客户端,性能开销较小;客户端挂掉之后会自动删除节点释放锁;但需要操作很多节点,所以性能低下,适用于并发小的场景;Redis获取锁失败需要不断尝试,客户端挂掉后需要等待超时时间之后才能重新获取;获取锁只是简单的数据操作,性能较高,适用于并发高的场景;

你可能感兴趣的:(【问答】Redis)