1.为什么Redis是单线程的?为什么单线程这么快?单线程可以避免线程切换和竞争条件带来的消耗,单线程可以简化数据结构和算法的实现。至于单线程,更快是因为Redis是基于内存的数据库,内存响应速度非常快,并且使用了epoll作为I/O多路复用技术,再加上Redis自带的事件处理模型,连接,epoll中的读写、关闭都转化为事件,以免在网络I/O上浪费太多时间。epoll是为解决Linux内核处理大量文件描述符而提出的解决方案,属于Linux下多通道I/O多路复用接口中select/poll的增强。常用于Linux下的高并发服务程序,尤其是大量并发连接中只有一小部分处于活动状态时。在某些情况下,可以提高CPU利用率。epoll是事件驱动的,只需要遍历被内核IO事件异步唤醒的描述符集,然后加入就绪队列返回用户空间即可。epoll提供了电平触发(LT)和边沿触发(ET)两种触发方式,目前效率最高的IO操作方案是:epoll+ET+非阻塞IO模型2.redis用的最多的是缓存,其他的可以用于排行榜、计数器、消息队列等。3、Redis淘汰策略Redis3.0版本支持策略volatile-lru:从设置过期的数据集(server.db[i].expires)中选择最近最少使用的数据时间。没有过期时间的key不会被淘汰,这样在增加内存空间的同时也不会丢失需要持久化的数据。volatile-ttl:除淘汰机制采用LRU外,策略与volatile-lru基本相似。将过期的数据是从数据集(server.db[i].expires)中选出的,过期时间设置为淘汰。ttl值越大,优先级越高。废弃。volatile-random:从数据集(server.db[i].expires)中随机选择数据淘汰,设置了过期时间。当内存达到限制,无法写入非过期时间的数据集时,可以通过这种淘汰策略从主键空间中随机删除一个键。allkeys-lru:从数据集(server.db[i].dict)中选择最近最少使用的数据进行淘汰。该策略要淘汰的密钥是针对所有密钥集的,而不是过期密钥集。allkeys-random:从数据集(server.db[i].dict)中选择任意数据进行淘汰。no-enviction:禁止数据驱逐,即当内存不足以容纳新的数据时,新的写操作会报错,请求可以继续,在线任务不能继续。no-enviction策略可以保证数据不会丢失,这也是系统默认的淘汰策略。四、Redis持久化机制1、RDB持久化:将当前进程数据的快照保存到硬盘的过程。触发方式包括手动触发和自动触发。手动触发命令:save和bgsave命令save命令:阻塞当前Redis服务,直到RDB进程执行完毕,在线环境不建议使用bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化进程负责子进程,完成后自动结束。阻塞只发生在fork阶段,通常会在短时间内自动触发:使用Save相关配置,如“savemn”。表示当数据集在m秒内被修改n次时,会自动触发。如果从节点执行全量复制操作,主节点会自动执行bgsave生成RDB文件发送给从节点执行debugreload命令重新加载Redis,同时也会自动触发保存操作默认,执行shutdown命令时,如果没有启动AOF持久化,会自动执行bgsaveRDB文件,保存在dir配置指定的目录下。文件名由dbfilename配置指定。您可以执行configsetdir{newDir}和configsetdbfilename{newFileName}它是在运行时动态执行的。下次运行时将RDB文件保存到新的目录,当磁盘损坏或已满时,可以通过configsetdir在线修改文件路径为可用的磁盘路径,然后执行bgsave切换磁盘。Redis默认以LZF算法压缩生成的RDB文件。压缩后,文件元素小于内存大小。默认情况下启用。可以通过参数configsetrdbcompression{yes|no}动态修改RDB。优点:RDB是一个紧凑的二进制文件,代表Redis在某个时间点的Data快照,适用于备份、全量复制等场景。Redis加载RDB和恢复数据的速度比AOFRDB快很多。缺点:无法实现实时持久化/秒级持久化。RDB文件存储在特定的二进制中,存在兼容性问题2、AOF持久化以独立日志的形式记录每条写命令。写入的内容直接采用文本协议格式。重启时重新执行AOF文件中的命令恢复数据,解决了数据持久化的实时性问题。启用AOF:appendonlyyes,默认不启用AOF文件名:appendfilename配置,默认appendonly.aof保存路径:同RDB,通过dir配置AOF工作流:所有写入命令追加到aof_buf(buffer)AOFbuffer根据对应同步操作到硬盘的策略随着AOF文件越来越大,AOF文件需要周期性的重写和压缩。父进程执行fork创建子进程。子进程根据内存快照进行AOF改写,父进程继续响应后面的命令,子进程完成改写后,父进程将新添加的写命令写入新的AOF文件。重启Redis服务,加载AOF文件进行数据恢复。为什么AOF直接使用文本协议格式呢?文本协议具有良好的兼容性。开启AOF后,所有写命令都包含append操作,直接使用协议格式,避免二次处理开销文本协议可读性好,方便AOF直接修改处理。为什么在aof_buf中加入命令写入缓冲区aof_buf可以提升性能,而Redis提供了多种缓存同步硬盘策略重写AOF文件为什么可以更小?过程中超时的数据不再写入文件。旧AOF文件包含无效命令,rewrite直接使用进程内数据生成。新的AOF文件只保留了最终数据的写入命令。多个写命令可以合并为一个。为了防止溢出,以64个元素为界,分为多个AOF缓冲区同步策略。可配置值由参数appendfsync控制,表示其他always命令写入aof_buf后调用系统fsync操作同步到AOF文件。fsync完成后线程返回,每次写入都要同步AOF文件,在一般的SATA硬盘上很难做到高性能。everysec命令写入aof_buf后,调用系统写操作,写完成后线程返回。fsync同步操作由线程每秒调用一次(推荐策略)。默认的同步策略是没有命令写入aof_buf,然后调用系统写操作。不对AOF文件进行fsync同步。同步硬盘的操作由操作系统处理。通常,最大同步周期为30秒。系统每次同步AOF文件的周期是不可控的,每次都会增加硬盘的数据量。虽然性能有所提升,但数据安全性得不到保证。手动触发AOF:调用bgrewriteaof命令自动触发:有两个参数auto-aof-rewrite-min-size:表示运行时AOF重写时文件的最小大小,默认64MB自动触发时机=aof_current_size>auto-aof-rewrite-min-size&&(aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewrite-percentageRDB和AOF同时开启,且AOF文件存在,优先加载AOF文件AOF文件错误,可以通过redis-check-aof-fix修复3.Redis数据类型stringkey为string类型,string类型的value可以是字符串,数字,二进制(最多512MB),动态字符串,内部传递预分配冗余空间,减少频繁分配内存命令解释备注setsetvaluekeyvalue[exseconds]setsecond-levelexpirationtimeforkey[pxmilliseconds]setmillisecond-levelexpirationtimeforkey[nx\xx]nxkey必须不存在才能设置成功。xx键必须存在才能设置成功。mset批量设置值keyvalue[keyvalue...]mget批量获取值key[key...]incr对值进行自增操作且值不是整数,返回错误值为整数,自增后返回结果key不存在,值为0自增,返回结果1decr自减增量浮点数键incrementappend附加值键valuestrlenstringlengthkeygetset设置并返回原始值keyvaluesetrange设置指定位置key偏移的字符valuegetrange获取部分字符串keystartendhashes一个键值对结构,内部结构同Java的HashMap,array+链表结构,取值只能存储字符串,编码为ziplist或hashtable另外,rehash时,使用定时任务逐步迁移内容命令解释说明hsetsetvaluekeyfieldvaluehsetnx类似于setnx-hgetgetvaluekeyfieldhdeldeleteoneormorefieldkeyfield[field...]hlencalculatethe字段数-hmget批量获取key字段[field...]hmset批量设置key字段值[fieldvalue...]hexists判断字段是否存在keyfieldhkeys获取所有fieldkeyhvals获取所有valuekeyhgetall获取所有field-valuekey如果元素较多,会造成阻塞,建议使用类似incrby/incrbyfloatkey字段incrementhstrlen的hscanhincrbyhincrbyfloat来计算值字符串的长度。关键字段列表用于存储多个有序字符串。一个列表最多可以存储232-1个元素。列表中的元素是可以重复的,这一点相当Java中的LinkedList是链表而不是数组。底层采用quicklist结构。当数据量较小时,使用ziplist来压缩列表。当数据量较大时,使用quicklist命令来解释备注。rpush从右边插入元素键值[value...]lpush从左边插入元素键值[value...]linsert在元素之前或之后插入元素键before\afterpivotvalue找到等于的元素pivot从列表中取出,在其前后插入一个元素新元素valuelrange获取指定范围内的元素列表keystartendindex下标从左到右为0到N-1,从右到左为-1至-N;end包含自己的lindex获取列表key指定索引下标的元素indexllen获取列表的长度keylpop从列表左侧弹出元素keyrpop从列表右侧弹出元素keylrem删除指定元素keycountvalue从列表中找到等于value的元素并删除,count>0,从左到右,删除最多count个元素。count<0,从右到左,删除至多count个绝对值元素。count=0,删除所有ltrim根据索引范围keystartendlset修改指定索引下标keyindex的元素newValueblpopbrpop弹出元素阻塞版本key[key...]multiplekeystimeount阻塞时间(秒))set(集合)用于保存多个字符串元素,但与列表元素不同的是,集合中不允许有重复的元素,集合中的元素是乱序的,不能通过索引下标获取元素。一个集合最多可以存储232-1个元素当set元素全部为整数且元素个数小于set-max-intset-entries配置(默认512)时,使用intset编码减少内存占用,否则使用hashtableencoding相当于Java中的HashSet,allvalue全部为一个值NULL命令解释备注saddaddelementkeyelement[element...]sremdeleteelementkeyelement[element...]scard计算元素个数keyismember判断是否元素在集合中keyelementsrandmember从集合中随机返回指定元素个数key[count]spop从集合中随机弹出元素,删除元素key[count]smembers获取所有元素keysinter求多个集合key[key...]sunion求多个集合的并集key[key...]sdiff求多个集合的差集key[key...]sinterstoresuionstoresdiffstore保存交集、并集、差集的结果key[key...]destinationnameofthetargetset集合之间的操作在元素中,更多情况下会比较耗时。有序集合不能重复,但是集合中的元素可以按照分数score排序,从小到大排序。当有序集合中的元素个数小于zset-max-ziplist-entries(默认128),且每个元素的值小于zset-max-ziplist-value配置(默认64字节)时,使用ziplist作为内部编码,否则使用skiplist作为内部编码,相当于Java的SortedSet和HashMap的结合,数据结构skiplist内部使用zset实现'skiplist'编码的有序集合对象该结构作为底层实现,zset结构包括字典和跳表(根据成员查找分值和范围操作效率最高)命令说明备注zaddaddmemberkey[NX\XX][CH][INCR]scoremember[scoremember...][NX\XX]NX:只有当元素不存在时才添加新元素;XX:更新已有元素,不添加元素CH返回有序集合中的元素个数和分数变化的个数incr对score做增加,相当于zincrbyzcard计算成员个数keymemberzrank计算会员排名,分数从低到高keymemberzrevrank计算会员排名,分数从高到低keymemberzrem删除会员keymember[member...]zincrby增加会员分数keyincrementmemberzrangezrevrangereturn指定排名范围的成员keystartend[withscores]显示分数zrangebyscorezrevrangebyscore返回成员keyminmax[withscores][limitoffsetcount]keymaxmin[withscores][limitoffsetcount]min和max支持开区间(小括号)和闭区间(括号),-inf和+inf无限小和无限大zcount返回指定分数范围key内的成员个数minmaxzremrangebyrank删除指定rankingkey内的升序元素startendzremrangebyscore删除指定分数范围key内的成员minmaxzinterstoreintersectiondestinationnumkeyskey[key...][weightsweight[weight...]][aggregatesum\min\max]destinationintersection计算结果保存到这个keynumkeys需要做intersection计算key的个数key[key...]需要计算交集权重的keyweight[weight...]每个key的权重。在做交集计算时,每个key中的每个成员都会给自己打分乘以这个权重,每个key的权重默认为1aggregatesum\min\max计算成员的交集后,可以根据sum、min、max对score进行聚合。默认是sumzunionstroe联合参数与zinterstore4相同。一个RTT传输给Redis,然后依次将这组Redis命令的执行结果返回给客户端Pipeline支持多条命令Redis服务端支持原生的批处理命令,Pipeline需要服务端和客户端共同实现5.事务Redis简单的事务功能,把一组需要一起执行的命令放到两个命令中:multi和exec,multi之间的命令代表事务的开始,exec命令代表事务的结束。它们之间的命令以原子顺序执行。如果要停止事务的执行,使用discard命令代替exec命令。watch命令用于确保事务中的key在执行事务前没有被其他客户端修改,否则不执行(类似于乐观锁)。6.两个Lua方法:evalevalscriptcontentkeynumberkeylistparameterlisteval'return"helo"..KEYS[1]..ARGV[1]'1redisworld也可以使用redis-cli--eval直接执行文件evalshaevalshascriptsha1valuekeyNumberkeylistParameterlist加载Lua脚本到Redis服务器,获取脚本的SHA1校验和,evalsha以SHA1为参数执行对应的Lua脚本,避免每次都发送脚本,脚本常驻并加载内存中的脚本Redis:redis-cliscriptload"$(cat~/lua_test.lua)"7.Rediskey过期删除策略key过期,内部保存在expires字典中。Redis采用惰性删除和定时任务删除机制;惰性删除用于当客户端读取一个带有超时属性的key时,如果已经过了设置的过期时间,则执行删除并返回null。但是,如果密钥过期并且没有通过这种方式重新访问,则该密钥将一直存在。定时任务删除:为了解决懒删除的问题,Redis内部维护了一个每秒运行10次的定时任务。根据密钥的过期比例,采用快慢两种速率方式回收密钥。缺点是占用CPU时间。当过期key较多时,会影响服务器的响应时间和吞吐量。定期删除:每隔一段时间删除过期的密钥。通过限制删除操作的执行时间和频率来降低??影响。缺点是需要合理设置执行时间和频率。生成RDB文件时,过期的key不会被保存。加载RDB文件时,主服务器将检查密钥,过期的key不会被加载,slave服务会加载所有的key,直到master服务删除并通知AOF写入。如果一个key过期了,AOF中会增加一个DEL命令;AOFrewrites会不时检查key。当过期键不会被写入和复制时,主服务器会发送删除通知,只有从服务器收到删除通知后才会删除过期键。8、Redis高可用方案的主从模式:一主两从redis.conf,从节点配置slaveof127.0.0.16379确认主从关系:redis-cli-h127.0.0.1-p6379inforeplicationsentinel模式:configureredis-sentinel.conf,sentinelmonitormymaster127.0.0.163791最后一位是选举master需要的票数。启动哨兵:redis-sentinelredis-sentinel.conf或redis-serverredis-sentinel.conf--sentinelredis-cluster:每个节点保存数据和整个集群状态,每个节点与其他节点连接通信。哈希函数用于将数据映射到一组具有固定范围的整数,并将整数定义为槽。所有key根据hash函数映射到0~16383整数槽,公式slot=CRC16(key)&16383Codis集群限制:key批量操作支持限制,目前只支持槽值相同的key批量操作Key交易操作支持有限key是数据分区的最小粒度,hash、list等大key-value对象不能映射到不同节点。只能使用数据库编号0。从节点只能复制主节点,不支持嵌套树状复制结构。主从复制过程从节点执行slaveof后,从节点保存主节点的地址信息。从节点通过每秒运行的定时任务维护复制相关逻辑。当定时任务发现有新的主节点时,会尝试与该节点建立网络连接。连接建立成功后,从节点发送第一次通信的ping请求,目的是检测主从之间的网络套接字是否可用,以及主节点当前是否接受处理命令,如果是主节点节点配置了密码认证,从节点必须配置相同的密码才能通过认证,复制同步验证通过后,主从可以正常通信,主节点会继续向从节点发送数据.同步方式包括全同步和部分同步。刚建立时,会进行全同步。同步完成后,进行部分同步。主节点和从节点同步完当前数据后,主节点会继续向从节点发送后续新的命令进行同步。sentinel模式最低配置为1主2从3哨兵,3个哨兵可以监控每个master。和salve9。缓存问题缓存穿透缓存穿透是指key的数据在缓存中不存在,于是去数据库查询,数据库中不存在该数据,导致循环查询数据优化:缓存空对象为不存在的数据,仍然缓存空值。但是这样会造成内存空间的浪费,可以为这类数据加上过期时间。为了缓存和存储层的数据一致性,可以在缓存过期时请求存储层,或者通过消息系统更新缓存Bloomfilter,让所有存在的数据都变成一个Bloomfilter,可以使用Bitmaps来实现。数据量占用空间少。当有新的请求时,先去Bloomfilter检查是否存在,如果数据不存在,直接返回;如果数据存在,则查询缓存并查询数据库。Redis4.0及以上使用插件集成,https://github.com/RedisBloom...原理:在redis中,是一个大位数组。通过计算key的hash,然后对数组的长度取模,得到一个位置并写入;判断是否存在时,判断位数组中的几个位置是否全为1,只要有一位为0,就说明该key不存在。布隆过滤器也有一定的误判。如果位数组比较稀疏,则概率高,反之则降低。Cachebreakdown缓存击穿是指某个key突然变成了hotkey,大量对该key的请求碰巧再次失败,导致从数据库中查询数据。优化:通过互斥锁setnx请求数据库前设置,查询数据库并更新缓存后,删除setnx缓存雪崩意味着缓存因大量请求而挂起,大量请求直接命中存储层,导致存储层挂掉优化:使用master-slave、sentinel、cluster模式保证缓存的高可用依赖隔离组件限流后端降级预演,做好备份打算同步更新缓存,先写入数据库,写入成功后再更新缓存。异步更新,通过消息中间件触发更新。无效更新,当获取不到缓存时,从数据库中取数据,然后更新缓存。定时更新,通过定时任务更新缓存缓存不一致通过加入重试机制,补偿任务,实现最终一致性热点key重建优化使用互斥锁,只允许一个线程重建缓存使用逻辑缓存过期,存储一个key在valuetoexpire时间,获取key时根据逻辑时间判断10.redis锁实现与zookeeper锁实现不同redis锁通过setnxkeyvalue或setkeyvaluepxmillsecondsnx返回1时表示获取到锁,而当它返回0时,表示获取锁失败,通过Redis的key超时机制释放锁PS:在业务逻辑执行完之前,Redis锁可能会超时释放,所以释放锁时,其他线程可能已经重新持有了锁,所以在释放锁的时候要验证key对应的值,这个值是在创建缓存的时候随机生成的。或者使用redisson作为分布式锁ZK锁通过在server端创建一个临时有序节点,哪个client成功创建了第一个临时有序节点,就说明client已经拿到了锁,后续节点的client会在监听状态,当锁被释放时,服务器会删除第一个临时节点。此时第二个临时节点可以监听到前一个节点的释放事件,从而使第二个节点成为第一个节点,此时client2表示已经获取到了锁。如果client的session关闭,临时节点会被删除,锁会被释放《三种分布式锁的优缺点及解决方案》
