服务器学习心得


基础面试题

<ul> <li><a href="https://www.topgoer.cn/docs/data-structures-questions/data-structures-questions-1d94t2g18u1v9">https://www.topgoer.cn/docs/data-structures-questions/data-structures-questions-1d94t2g18u1v9</a></li> </ul> <h2>Redis为什么快</h2> <ul> <li> <p>完全基于内存,绝大部分请求是基于内存的操作</p> </li> <li>数据结构设计简单,类似于HashMap,方便操作</li> <li>使用单线程,避免了进程或线程的上下文切换相关的消耗,不用考虑锁相关问题消耗。</li> <li>使用多路I/O复用模型,非阻塞IO</li> <li>使用底层模型不同,底层实现方式以及与客户端之间通信的应用协议不一样, <ul> <li>Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求</li> </ul></li> </ul> <h2>什么是多路I/O复用</h2> <ul> <li>利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。</li> <li>“多路”是多个网络连接</li> <li>“复用”是复用同一个线程</li> <li>采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。</li> </ul> <h1>缓存</h1> <ul> <li><a href="https://blog.csdn.net/kongtiao5/article/details/82771694">https://blog.csdn.net/kongtiao5/article/details/82771694</a></li> </ul> <h2>缓存处理流程</h2> <ul> <li>前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。</li> </ul> <h2>缓存和数据库间数据一致性问题</h2> <ul> <li>分布式环境下,非常容易出现缓存和数据库间的数据一致性问题</li> <li>如果项目对缓存要求是强一致性,请不要使用缓存 <ul> <li>只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性</li> </ul></li> <li>合适的策略包括合适的缓存更新策略,更新数据库后要及时更新缓存,缓存失败时增加重试机制,例如MQ模式的消息队列。</li> </ul> <h3>缓存并发问题</h3> <ul> <li>多个redis客户端同时set key引起的并发问题</li> <li>有效的解决方案 <ul> <li>设置操作放在队列中使其串行化,必须一个一个执行,也可加锁</li> </ul></li> </ul> <h2>缓存穿透</h2> <p><strong>描述</strong></p> <ul> <li>缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,</li> <li>如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大</li> </ul> <p><strong>解决方案</strong></p> <ul> <li>接口层增加校验 <ul> <li>如用户鉴权校验,id做基础校验,id&lt;=0的直接拦截;</li> </ul></li> <li>从缓存取不到的数据,在数据库中也没有取到 <ul> <li>可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。</li> <li>可以防止攻击用户反复用同一个id暴力攻击</li> </ul></li> </ul> <h2>缓存击穿</h2> <p><strong>描述</strong></p> <ul> <li> <p>缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力</p> <p><strong>解决方案</strong></p> </li> <li>设置热点数据永远不过期</li> <li>加互斥锁</li> </ul> <h2>缓存雪崩</h2> <p><strong>描述</strong></p> <ul> <li>缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。</li> <li>和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库</li> </ul> <p><strong>解决方案</strong></p> <ul> <li>缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。</li> <li>如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。</li> <li>设置热点数据永远不过期。</li> </ul> <h1>过期、删除</h1> <h2>设置过期时间方式</h2> <ul> <li>expire key seconds <ul> <li>设置 key 在 n 秒后过期</li> </ul></li> <li>pexpire key milliseconds <ul> <li>设置 key 在 n 毫秒后过期</li> </ul></li> <li>expireat key timestamp <ul> <li>设置 key 在某个时间戳(精确到秒)之后过期</li> </ul></li> <li>pexpireat key millisecondsTimestamp <ul> <li>设置 key 在某个时间戳(精确到毫秒)之后过期</li> </ul></li> </ul> <h2>删除策略</h2> <ul> <li>定时删除 <ul> <li>在设置键的过期时间的同时,创建一个定时任务,当键达到过期时间时,立即执行对键的删除操作</li> </ul></li> <li>惰性删除 <ul> <li>放任键过期不管,但在每次从键空间获取键时,都检查取得的键是否过期,如果过期的话,就删除该键,如果没有过期,就返回该键</li> </ul></li> <li>定期删除 <ul> <li>每隔一点时间,程序就对数据库进行一次检查,删除里面的过期键,至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。</li> </ul></li> </ul> <h2>定时删除</h2> <ul> <li>优点 <ul> <li>对内存友好,定时删除策略可以保证过期键会尽可能快地被删除,并释放国期间所占用的内存</li> </ul></li> <li>缺点 <ul> <li>对cpu时间不友好,在过期键比较多时,删除任务会占用很大一部分cpu时间</li> <li>在内存不紧张但cpu时间紧张的情况下,将cpu时间用在删除和当前任务无关的过期键上,影响服务器的响应时间和吞吐量</li> </ul></li> </ul> <h2>定期删除</h2> <ul> <li>由于定时删除会占用太多cpu时间,影响服务器的响应时间和吞吐量以及惰性删除浪费太多内存,有内存泄露的危险,所以出现一种整合和折中这两种策略的定期删除策略。</li> <li>定期删除策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。</li> <li>定时删除策略有效地减少了因为过期键带来的内存浪费。</li> </ul> <h2>惰性删除</h2> <ul> <li>优点 <ul> <li>对cpu时间友好,在每次从键空间获取键时进行过期键检查并是否删除,删除目标也仅限当前处理的键,这个策略不会在其他无关的删除任务上花费任何cpu时间</li> </ul></li> <li>缺点 <ul> <li>对内存不友好,过期键过期也可能不会被删除,导致所占的内存也不会释放。甚至可能会出现内存泄露的现象,当存在很多过期键,而这些过期键又没有被访问到,可能导致它们会一直保存在内存中,造成内存泄露。</li> </ul></li> </ul> <h2>Redis做异步队列</h2> <ul> <li> <p>一般使用list结构作为队列,rpush生产消息,lpop消费消息。</p> <ul> <li>当lpop没有消息的时候,要适当sleep一会再重试。</li> </ul> </li> <li>缺点 <ul> <li>在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等</li> </ul></li> <li>能不能生产一次消费多次呢? <ul> <li>使用pub/sub主题订阅者模式,可以实现1:N的消息队列</li> </ul></li> </ul> <h2>6种淘汰策略</h2> <ul> <li> <p>volatile(不稳定,快过期),lru(选择最近长时间不使用的,一般用作缓存机制),noeviction(不做任何设置),ttl(过期时间设置)</p> </li> <li>volatile-lru <ul> <li>从设置过期时间数据集中,选择最近最久未使用数据释放</li> </ul></li> <li>allkeys-lru <ul> <li>从数据集中(包括设置过期时间以及未设置过期时间的数据集中),选择最近最久未使用数据释放</li> </ul></li> <li>volatile-random <ul> <li>从设置了过期时间的数据集中,随机选择一个数据进行释放</li> </ul></li> <li>allkeys-random <ul> <li>从数据集中(包括了设置过期时间以及未设置过期时间)随机选择一个数据进行入释放</li> </ul></li> <li>volatile-ttl <ul> <li>从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作</li> </ul></li> <li>noeviction() <ul> <li>不删除任意数据(但redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误。</li> </ul></li> </ul> <h2>并发竞争解决</h2> <ul> <li>进程为单线程模式,采用队列模式将并发访问变为串行访问。</li> <li>Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,利用setnx实现锁。</li> </ul> <h2>内存模型</h2> <ul> <li>used_memory(分配器分配的内存总量)</li> <li>used_memory_rss(进程占据操作系统的内存)</li> <li>mem_fragmentation_ratio(内存碎片比率)</li> <li>mem_allocator(内存分配器)</li> </ul> <h2>内存划分</h2> <ul> <li> <p>数据</p> </li> <li>进程本身运行需要的内存</li> <li>缓冲内存</li> <li>内存碎片</li> </ul> <h3>Redis提供高级工具</h3> <ul> <li>慢查询分析、性能测试、Pipeline、事务、Lua自定义命令、Bitmaps、HyperLogLog、发布/订阅、Geo等</li> </ul> <h2>布隆过滤器(bloomfilter)</h2> <ul> <li>就类似于一个hash set,用于快速判某个元素是否存在于集合中,</li> <li>典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。</li> <li>布隆过滤器的关键就在于hash算法和容器大小</li> </ul> <h2>Redis通讯协议</h2> <ul> <li> <p><a href="https://blog.csdn.net/alex_xfboy/article/details/88959064">https://blog.csdn.net/alex_xfboy/article/details/88959064</a></p> </li> <li>RESP <ul> <li>RESP(Redis Serialization Protocol) Redis序列化协议</li> <li>redis客户端和服务端之前使用的一种通讯协议</li> </ul></li> <li>RESP 的特点 <ul> <li>实现简单、快速解析、可读性好</li> </ul></li> </ul> <h2>Redis中海量数据的正确操作方式</h2> <ul> <li>利用SCAN系列命令(SCAN、SSCAN、HSCAN、ZSCAN)完成数据迭代</li> </ul> <h2>SCAN系列命令注意事项</h2> <ul> <li>SCAN的参数没有key,因为其迭代对象是DB内数据</li> <li>返回值都是数组,第一个值都是下一次迭代游标</li> <li>时间复杂度:每次请求都是O(1),完成所有迭代需要O(N),N是元素数量</li> <li>可用版本:version &gt;= 2.8.0</li> </ul> <h2>redis客户端执行一条命令过程</h2> <pre><code>发送命令-〉命令排队-〉命令执行-〉返回结果</code></pre> <ul> <li> <p>这个过程称为Round trip time(简称RTT, 往返时间),</p> </li> <li>mget mset有效节约了RTT,但大部分命令(如hgetall,并没有mhgetall)不支持批量操作,需要消耗N次RTT ,这个时候需要pipeline来解决这个问题</li> </ul> <h3>管道 Pipeline</h3> <ul> <li>在某些场景下一次操作可能需要执行多个命令,如果一个命令一个命令去执行会浪费很多网络消耗时间,如果将命令一次性传输到 Redis中去再执行,则会减少很多开销时间。</li> <li>注意 <ul> <li>pipeline中的命令并不是原子性执行,管道中的命令到达 Redis服务器的时候可能会被其他的命令穿插</li> </ul></li> </ul> <h2>实现“附近的人”</h2> <ul> <li>使用方式</li> </ul> <pre><code>GEOADD key longitude latitude member [longitude latitude member ...]</code></pre> <p>将给定的位置对象(纬度、经度、名字)添加到指定的key。</p> <p>其中,key为集合名称,member为该经纬度所对应的对象。</p> <p>在实际运用中,当所需存储的对象数量过多时,可通过设置多key(如一个省一个key)的方式对对象集合变相做sharding,避免单集合数量过多。</p> <p>成功插入后的返回值:</p> <pre><code>(integer) N</code></pre> <p>其中N为成功插入的个数。</p> <h1>主从、分布式</h1> <h2>主从复制说明</h2> <ul> <li> <p>复制是高可用Redis基础,哨兵和集群都是在复制基础上实现高可用的。</p> </li> <li>复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。</li> <li>缺陷 <ul> <li>故障恢复无法自动化</li> <li>写操作无法负载均衡</li> <li>存储能力受到单机的限制</li> </ul></li> </ul> <h2>主从复制使用情况</h2> <ul> <li>单机故障/容量瓶颈/QPS瓶颈</li> <li>一个主可以有多个从,一个从只能有一个主,数据必须是单流向,从主流向从</li> </ul> <h2>主从故障处理</h2> <ul> <li>master/slave宕机的情况,主从模式没有实现故障的完全自动转移</li> </ul> <h2>主从常见问题</h2> <ul> <li>读写分离 <ul> <li>读流量分摊到从节点,可能遇到复制数据延迟,也可能读到过期的数据,从节点故障怎么办</li> </ul></li> <li>主从配置不一致 <ul> <li>主从最大内存不一致,可能会丢失数据;主从内存不一致</li> </ul></li> <li>规避全量复制 <ul> <li>第一次不可避免;</li> <li>小主节点,低峰处理(夜间)</li> <li>主节点重启后runId发生了变化</li> </ul></li> <li>规避复制风暴 <ul> <li>单机主节点复制风暴,如果是1主N从,当主重启之后,所有从都会全量复制,容易造成redis服务的不可用</li> </ul></li> </ul> <h2>Redis分布式</h2> <ul> <li>主从的模式原则</li> <li>主会将数据同步到从,从不会将数据同步到主,从启动时会连接主来同步数据 <ul> <li>这是一个典型的分布式读写分离模型。可以利用主来插入数据,从提供检索服务</li> <li>可以有效减少单个机器的并发访问数量</li> </ul></li> </ul> <h3>读写分离模型</h3> <ul> <li>通过增加从库数量,读的性能可以线性增长。</li> <li>避免主库的单点故障 <ul> <li>集群一般都会采用两台主库做双机热备,整个集群的读和写的可用性都非常高</li> </ul></li> <li>读写分离架构的缺陷 <ul> <li>不管是主还是从,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力</li> <li>对于写密集型类型应用,读写分离架构并不适合</li> </ul></li> </ul> <h3>数据分片模型</h3> <ul> <li>解决读写分离模型的缺陷,可以将数据分片模型应用进来</li> <li>将每个节点看成独立的主,通过业务实现数据分片</li> <li>将每个主设计成由一个主和多个从组成的模型</li> </ul> <h2>分布式锁实现</h2> <ul> <li>先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。</li> <li>如果在setnx之后执行expire之前进程意外崩溃或者要重启维护了,那会怎么样? <ul> <li>set指令同时把setnx和expire合成一条指令来用</li> </ul></li> </ul> <h2>Redlock 算法</h2> <ul> <li>多节点 Redis 分布式锁</li> <li>获取当前时间(start)。</li> <li>依次向 N 个 Redis节点请求锁。请求锁的方式与从单节点 Redis获取锁的方式一致。</li> <li>为了保证在某个 Redis节点不可用时该算法能够继续运行,获取锁的操作都需要设置超时时间,需要保证该超时时间远小于锁的有效时间。</li> <li>这样才能保证客户端在向某个 Redis节点获取锁失败之后,可以立刻尝试下一个节点。</li> <li>计算获取锁的过程总共消耗多长时间(consumeTime = end - start)。如果客户端从大多数 Redis节点(&gt;= N/2 + 1) 成功获取锁,并且获取锁总时长没有超过锁的有效时间,这种情况下,客户端会认为获取锁成功,否则,获取锁失败。</li> <li>如果最终获取锁成功,锁的有效时间应该重新设置为锁最初的有效时间减去 consumeTime。</li> <li>如果最终获取锁失败,客户端应该立刻向所有 Redis节点发起释放锁的请求。</li> </ul> <h2>复制的配置</h2> <ul> <li>使用slaeof命令,在从redis中执行slave masterip:port使其成为master的从服务器,就能从master拉取数据了; <ul> <li>执行slaveof no one清除掉不成为从节点,但是数据不清楚;</li> </ul></li> <li>修改配置, slaveof ip port / slave-read-only yes(从节点只做都操作); <ul> <li>配置要更改的话,要重启,所以选择的时候谨慎</li> </ul></li> </ul>

页面列表

ITEM_HTML