当前位置: 首页 > news >正文

有没有发布需求的网站网站连接微信

有没有发布需求的网站,网站连接微信,wordpress动态图片,网站建设延期通知单经过前几天的学习#xff0c;大家已经掌握了微服务相关技术的实际应用#xff0c;能够应对企业开发的要求了。不过大家都知道在IT领域往往都是面试造火箭#xff0c;实际工作拧螺丝。为了更好的应对面试#xff0c;让大家能拿到更高的offer#xff0c;我们接下来就讲讲“造…经过前几天的学习大家已经掌握了微服务相关技术的实际应用能够应对企业开发的要求了。不过大家都知道在IT领域往往都是面试造火箭实际工作拧螺丝。为了更好的应对面试让大家能拿到更高的offer我们接下来就讲讲“造火箭”的事情。接下来的内容主要包括以下几方面Redis高级Redis主从Redis哨兵Redis分片集群Redis数据结构Redis内存回收Redis缓存一致性微服务高级Eureka和Nacos对比Ribbon和SpringCloudLoadBalancerHystix和Sentinel限流算法1.Redis主从单节点Redis的并发能力是有上限的要进一步提高Redis的并发能力就需要搭建主从集群实现读写分离。1.1.主从集群结构下图就是一个简单的Redis主从集群结构如图所示集群中有一个master节点、两个slave节点现在叫replica。当我们通过Redis的Java客户端访问主从集群时应该做好路由如果是写操作应该访问master节点master会自动将数据同步给两个slave节点如果是读操作建议访问各个slave节点从而分担并发压力1.2.搭建主从集群我们会在同一个虚拟机中利用3个Docker容器来搭建主从集群容器信息如下容器名角色IP映射端口r1master192.168.150.1017001r2slave192.168.150.1017002r3slave192.168.150.10170031.2.1.启动多个Redis实例我们利用课前资料提供的docker-compose文件来构建主从集群文件内容如下 version: 3.2services:r1:image: rediscontainer_name: r1network_mode: hostentrypoint: [redis-server, --port, 7001]r2:image: rediscontainer_name: r2network_mode: hostentrypoint: [redis-server, --port, 7002]r3:image: rediscontainer_name: r3network_mode: hostentrypoint: [redis-server, --port, 7003]将其上传至虚拟机的/root/redis目录下执行命令运行集群 docker compose up -d结果查看docker容器发现都正常启动了由于采用的是host模式我们看不到端口映射。不过能直接在宿主机通过ps命令查看到Redis进程1.2.2.建立集群虽然我们启动了3个Redis实例但是它们并没有形成主从关系。我们需要通过命令来配置主从关系 # Redis5.0以前 slaveof masterip masterport # Redis5.0以后 replicaof masterip masterport有临时和永久两种模式永久生效在redis.conf文件中利用slaveof命令指定master节点临时生效直接利用redis-cli控制台输入slaveof命令指定master节点我们测试临时模式首先连接r2让其以r1为master # 连接r2 docker exec -it r2 redis-cli -p 7002 # 认r1主也就是7001 slaveof 192.168.150.101 7001然后连接r3让其以r1为master # 连接r3 docker exec -it r3 redis-cli -p 7003 # 认r1主也就是7001 slaveof 192.168.150.101 7001然后连接r1查看集群状态 # 连接r1 docker exec -it r1 redis-cli -p 7001 # 查看集群状态 info replication结果如下 127.0.0.1:7001 info replication # Replication role:master connected_slaves:2 slave0:ip192.168.150.101,port7002,stateonline,offset140,lag1 slave1:ip192.168.150.101,port7003,stateonline,offset140,lag1 master_failover_state:no-failover master_replid:16d90568498908b322178ca12078114e6c518b86 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:140 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:140可以看到当前节点r1:7001的角色是master有两个slave与其连接slave0port是7002也就是r2节点slave1port是7003也就是r3节点1.2.3.测试依次在r1、r2、r3节点上执行下面命令 set num 123get num你会发现只有在r1这个节点上可以执行set命令写操作其它两个节点只能执行get命令读操作。也就是说读写操作已经分离了。1.3.主从同步原理在刚才的主从测试中我们发现r1上写入Redis的数据在r2和r3上也能看到这说明主从之间确实完成了数据同步。那么这个同步是如何完成的呢1.3.1.全量同步主从第一次建立连接时会执行全量同步将master节点的所有数据都拷贝给slave节点流程这里有一个问题master如何得知salve是否是第一次来同步呢有几个概念可以作为判断依据Replication Id简称replid是数据集的标记replid一致则是同一数据集。每个master都有唯一的replidslave则会继承master节点的replidoffset偏移量随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset说明slave数据落后于master需要更新。因此slave做数据同步必须向master声明自己的replication id 和offsetmaster才可以判断到底需要同步哪些数据。由于我们在执行slaveof命令之前所有redis节点都是master有自己的replid和offset。当我们第一次执行slaveof命令与master建立主从关系时发送的replid和offset是自己的与master肯定不一致。master判断发现slave发送来的replid与自己的不一致说明这是一个全新的slave就知道要做全量同步了。master会将自己的replid和offset都发送给这个slaveslave保存这些信息到本地。自此以后slave的replid就与master一致了。因此master判断一个节点是否是第一次同步的依据就是看replid是否一致。流程如图完整流程描述slave节点请求增量同步master节点判断replid发现不一致拒绝增量同步master将完整内存数据生成RDB发送RDB到slaveslave清空本地数据加载master的RDBmaster将RDB期间的命令记录在repl_baklog并持续将log中的命令发送给slaveslave执行接收到的命令保持与master之间的同步来看下r1节点的运行日志再看下r2节点执行replicaof命令时的日志与我们描述的完全一致。1.3.2.增量同步全量同步需要先做RDB然后将RDB文件通过网络传输个slave成本太高了。因此除了第一次做全量同步其它大多数时候slave与master都是做增量同步。什么是增量同步就是只更新slave与master存在差异的部分数据。如图那么master怎么知道slave与自己的数据差异在哪里呢?1.3.3.repl_baklog原理master怎么知道slave与自己的数据差异在哪里呢?这就要说到全量同步时的repl_baklog文件了。这个文件是一个固定大小的数组只不过数组是环形也就是说角标到达数组末尾后会再次从0开始读写这样数组头部的数据就会被覆盖。repl_baklog中会记录Redis处理过的命令及offset包括master当前的offset和slave已经拷贝到的offsetslave与master的offset之间的差异就是salve需要增量拷贝的数据了。随着不断有数据写入master的offset逐渐变大slave也不断的拷贝追赶master的offset直到数组被填满此时如果有新的数据写入就会覆盖数组中的旧数据。不过旧的数据只要是绿色的说明是已经被同步到slave的数据即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分但是如果slave出现网络阻塞导致master的offset远远超过了slave的offset如果master继续写入新数据master的offset就会覆盖repl_baklog中旧的数据直到将slave现在的offset也覆盖棕色框中的红色部分就是尚未同步但是却已经被覆盖的数据。此时如果slave恢复需要同步却发现自己的offset都没有了无法完成增量同步了。只能做全量同步。repl_baklog大小有上限写满后会覆盖最早的数据。如果slave断开时间过久导致尚未备份的数据被覆盖则无法基于repl_baklog做增量同步只能再次全量同步。1.4.主从同步优化主从同步可以保证主从数据的一致性非常重要。可以从以下几个方面来优化Redis主从就集群在master中配置repl-diskless-sync yes启用无磁盘复制避免全量同步时的磁盘IO。Redis单节点上的内存占用不要太大减少RDB导致的过多磁盘IO适当提高repl_baklog的大小发现slave宕机时尽快实现故障恢复尽可能避免全量同步限制一个master上的slave节点数量如果实在是太多slave则可以采用主-从-从链式结构减少master压力主-从-从架构图简述全量同步和增量同步区别全量同步master将完整内存数据生成RDB发送RDB到slave。后续命令则记录在repl_baklog逐个发送给slave。增量同步slave提交自己的offset到mastermaster获取repl_baklog中从offset之后的命令给slave什么时候执行全量同步slave节点第一次连接master节点时slave节点断开时间太久repl_baklog中的offset已经被覆盖时什么时候执行增量同步slave节点断开又恢复并且在repl_baklog中能找到offset时2.Redis哨兵主从结构中master节点的作用非常重要一旦故障就会导致集群不可用。那么有什么办法能保证主从集群的高可用性呢2.1.哨兵工作原理Redis提供了哨兵Sentinel机制来监控主从集群监控状态确保集群的高可用性。2.1.1.哨兵作用哨兵集群作用原理图哨兵的作用如下状态监控Sentinel 会不断检查您的master和slave是否按预期工作故障恢复failover如果master故障Sentinel会将一个slave提升为master。当故障实例恢复后会成为slave状态通知Sentinel充当Redis客户端的服务发现来源当集群发生failover时会将最新集群信息推送给Redis的客户端那么问题来了Sentinel怎么知道一个Redis节点是否宕机呢2.1.2.状态监控Sentinel基于心跳机制监测服务状态每隔1秒向集群的每个节点发送ping命令并通过实例的响应结果来做出判断主观下线sdown如果某sentinel节点发现某Redis节点未在规定时间响应则认为该节点主观下线。客观下线(odown)若超过指定数量通过quorum设置的sentinel都认为该节点主观下线则该节点客观下线。quorum值最好超过Sentinel节点数量的一半Sentinel节点数量至少3台。如图一旦发现master故障sentinel需要在salve中选择一个作为新的master选择依据是这样的首先会判断slave节点与master节点断开时间长短如果超过down-after-milliseconds * 10则会排除该slave节点然后判断slave节点的slave-priority值越小优先级越高如果是0则永不参与选举默认都是1。如果slave-prority一样则判断slave节点的offset值越大说明数据越新优先级越高最后是判断slave节点的run_id大小越小优先级越高通过info server可以查看run_id。对应的官方文档如下https://redis.io/docs/management/sentinel/#replica-selection-and-priority问题来了当选出一个新的master后该如何实现身份切换呢大概分为两步在多个sentinel中选举一个leader由leader执行failover2.1.3.选举leader首先Sentinel集群要选出一个执行failover的Sentinel节点可以成为leader。要成为leader要满足两个条件最先获得超过半数的投票获得的投票数不小于quorum值而sentinel投票的原则有两条优先投票给目前得票最多的如果目前没有任何节点的票就投给自己比如有3个sentinel节点s1、s2、s3假如s2先投票此时发现没有任何人在投票那就投给自己。s2得1票接着s1和s3开始投票发现目前s2票最多于是也投给s2s2得3票s2称为leader开始故障转移不难看出谁先投票谁就会称为leader那什么时候会触发投票呢答案是第一个确认master客观下线的人会立刻发起投票一定会成为leader。OKsentinel找到leader以后该如何完成failover呢2.1.4.failover我们举个例子有一个集群初始状态下7001为master7002和7003为slave假如master发生故障slave1当选。则故障转移的流程如下1sentinel给备选的slave1节点发送slaveof no one命令让该节点成为master2sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令让这些节点成为新master也就是7002的slave节点开始从新的master上同步数据。3最后当故障节点恢复后会接收到哨兵信号执行slaveof 192.168.150.101 7002命令成为slave2.2.搭建哨兵集群首先我们停掉之前的redis集群 # 老版本DockerCompose docker-compose down# 新版本Docker docker compose down然后我们找到课前资料提供的sentinel.conf文件其内容如下 sentinel announce-ip 192.168.150.101 sentinel monitor hmaster 192.168.150.101 7001 2 sentinel down-after-milliseconds hmaster 5000 sentinel failover-timeout hmaster 60000说明sentinel announce-ip 192.168.150.101声明当前sentinel的ipsentinel monitor hmaster 192.168.150.101 7001 2指定集群的主节点信息hmaster主节点名称自定义任意写192.168.150.101 7001主节点的ip和端口2认定master下线时的quorum值sentinel down-after-milliseconds hmaster 5000声明master节点超时多久后被标记下线sentinel failover-timeout hmaster 60000在第一次故障转移失败后多久再次重试我们在虚拟机的/root/redis目录下新建3个文件夹s1、s2、s3:将课前资料提供的sentinel.conf文件分别拷贝一份到3个文件夹中。接着修改docker-compose.yaml文件内容如下 version: 3.2services:r1:image: rediscontainer_name: r1network_mode: hostentrypoint: [redis-server, --port, 7001]r2:image: rediscontainer_name: r2network_mode: hostentrypoint: [redis-server, --port, 7002, --slaveof, 192.168.150.101, 7001]r3:image: rediscontainer_name: r3network_mode: hostentrypoint: [redis-server, --port, 7003, --slaveof, 192.168.150.101, 7001]s1:image: rediscontainer_name: s1volumes:- /root/redis/s1:/etc/redisnetwork_mode: hostentrypoint: [redis-sentinel, /etc/redis/sentinel.conf, --port, 27001]s2:image: rediscontainer_name: s2volumes:- /root/redis/s2:/etc/redisnetwork_mode: hostentrypoint: [redis-sentinel, /etc/redis/sentinel.conf, --port, 27002]s3:image: rediscontainer_name: s3volumes:- /root/redis/s3:/etc/redisnetwork_mode: hostentrypoint: [redis-sentinel, /etc/redis/sentinel.conf, --port, 27003]直接运行命令启动集群 docker-compose up -d运行结果我们以s1节点为例查看其运行日志 # Sentinel ID is 8e91bd24ea8e5eb2aee38f1cf796dcb26bb88acf # monitor master hmaster 192.168.150.101 7001 quorum 2 * slave slave 192.168.150.101:7003 192.168.150.101 7003 hmaster 192.168.150.101 7001 * sentinel sentinel 5bafeb97fc16a82b431c339f67b015a51dad5e4f 192.168.150.101 27002 hmaster 192.168.150.101 7001 * sentinel sentinel 56546568a2f7977da36abd3d2d7324c6c3f06b8d 192.168.150.101 27003 hmaster 192.168.150.101 7001 * slave slave 192.168.150.101:7002 192.168.150.101 7002 hmaster 192.168.150.101 7001可以看到sentinel已经联系到了7001这个节点并且与其它几个哨兵也建立了链接。哨兵信息如下27001Sentinel ID是8e91bd24ea8e5eb2aee38f1cf796dcb26bb88acf27002Sentinel ID是5bafeb97fc16a82b431c339f67b015a51dad5e4f27003Sentinel ID是56546568a2f7977da36abd3d2d7324c6c3f06b8d2.3.演示failover接下来我们演示一下当主节点故障时哨兵是如何完成集群故障恢复failover的。我们连接7001这个master节点然后通过命令让其休眠60秒模拟宕机 # 连接7001这个master节点通过sleep模拟服务宕机60秒后自动恢复 docker exec -it r1 redis-cli -p 7001 DEBUG sleep 60稍微等待一段时间后会发现sentinel节点触发了failover2.4.总结Sentinel的三个作用是什么集群监控故障恢复状态通知Sentinel如何判断一个redis实例是否健康每隔1秒发送一次ping命令如果超过一定时间没有相向则认为是主观下线sdown如果大多数sentinel都认为实例主观下线则判定服务客观下线odown故障转移步骤有哪些首先要在sentinel中选出一个leader由leader执行failover选定一个slave作为新的master执行slaveof noone切换到master模式然后让所有节点都执行slaveof 新master修改故障节点配置添加slaveof 新mastersentinel选举leader的依据是什么票数超过sentinel节点数量1半票数超过quorum数量一般情况下最先发起failover的节点会当选sentinel从slave中选取master的依据是什么首先会判断slave节点与master节点断开时间长短如果超过down-after-milliseconds * 10则会排除该slave节点然后判断slave节点的slave-priority值越小优先级越高如果是0则永不参与选举默认都是1。如果slave-prority一样则判断slave节点的offset值越大说明数据越新优先级越高最后是判断slave节点的run_id大小越小优先级越高通过info server可以查看run_id。2.5.RedisTemplate连接哨兵集群自学分为三步1引入依赖2配置哨兵地址3配置读写分离2.5.1.引入依赖就是SpringDataRedis的依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId /dependency2.5.2.配置哨兵地址连接哨兵集群与传统单点模式不同不再需要设置每一个redis的地址而是直接指定哨兵地址 spring:redis:sentinel:master: hmaster # 集群名nodes: # 哨兵地址列表- 192.168.150.101:27001- 192.168.150.101:27002- 192.168.150.101:270032.5.3.配置读写分离最后还要配置读写分离让java客户端将写请求发送到master节点读请求发送到slave节点。定义一个bean即可 Bean public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){return clientConfigurationBuilder - clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED); }这个bean中配置的就是读写策略包括四种MASTER从主节点读取MASTER_PREFERRED优先从master节点读取master不可用才读取slaveREPLICA从slave节点读取REPLICA_PREFERRED优先从slave节点读取所有的slave都不可用才读取master3.Redis分片集群主从模式可以解决高可用、高并发读的问题。但依然有两个问题没有解决海量数据存储高并发写要解决这两个问题就需要用到分片集群了。分片的意思就是把数据拆分存储到不同节点这样整个集群的存储数据量就更大了。Redis分片集群的结构如图分片集群特征集群中有多个master每个master保存不同分片数据 解决海量数据存储问题每个master都可以有多个slave节点 确保高可用master之间通过ping监测彼此健康状态 类似哨兵作用客户端请求可以访问集群任意节点最终都会被转发到数据所在节点3.1.搭建分片集群Redis分片集群最少也需要3个master节点由于我们的机器性能有限我们只给每个master配置1个slave形成最小的分片集群计划部署的节点信息如下表格 还在加载中请等待加载完成后再尝试复制3.1.1.集群配置分片集群中的Redis节点必须开启集群模式一般在配置文件中添加下面参数 port 7000 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes其中有3个我们没见过的参数cluster-enabled是否开启集群模式cluster-config-file集群模式的配置文件名称无需手动创建由集群自动维护cluster-node-timeout集群中节点之间心跳超时时间一般搭建部署集群肯定是给每个节点都配置上述参数不过考虑到我们计划用docker-compose部署因此可以直接在启动命令中指定参数偷个懒。在虚拟机的/root目录下新建一个redis-cluster目录然后在其中新建一个docker-compose.yaml文件内容如下 version: 3.2services:r1:image: rediscontainer_name: r1network_mode: hostentrypoint: [redis-server, --port, 7001, --cluster-enabled, yes, --cluster-config-file, node.conf]r2:image: rediscontainer_name: r2network_mode: hostentrypoint: [redis-server, --port, 7002, --cluster-enabled, yes, --cluster-config-file, node.conf]r3:image: rediscontainer_name: r3network_mode: hostentrypoint: [redis-server, --port, 7003, --cluster-enabled, yes, --cluster-config-file, node.conf]r4:image: rediscontainer_name: r4network_mode: hostentrypoint: [redis-server, --port, 7004, --cluster-enabled, yes, --cluster-config-file, node.conf]r5:image: rediscontainer_name: r5network_mode: hostentrypoint: [redis-server, --port, 7005, --cluster-enabled, yes, --cluster-config-file, node.conf]r6:image: rediscontainer_name: r6network_mode: hostentrypoint: [redis-server, --port, 7006, --cluster-enabled, yes, --cluster-config-file, node.conf]注意使用Docker部署Redis集群network模式必须采用host3.1.2.启动集群进入/root/redis-cluster目录使用命令启动redis docker-compose up -d启动成功可以通过命令查看启动进程 ps -ef | grep redis # 结果 root 4822 4743 0 14:29 ? 00:00:02 redis-server *:7002 [cluster] root 4827 4745 0 14:29 ? 00:00:01 redis-server *:7005 [cluster] root 4897 4778 0 14:29 ? 00:00:01 redis-server *:7004 [cluster] root 4903 4759 0 14:29 ? 00:00:01 redis-server *:7006 [cluster] root 4905 4775 0 14:29 ? 00:00:02 redis-server *:7001 [cluster] root 4912 4732 0 14:29 ? 00:00:01 redis-server *:7003 [cluster]可以发现每个redis节点都以cluster模式运行。不过节点与节点之间并未建立连接。接下来我们使用命令创建集群 # 进入任意节点容器 docker exec -it r1 bash # 然后执行命令 redis-cli --cluster create --cluster-replicas 1 \ 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 \ 192.168.150.101:7004 192.168.150.101:7005 192.168.150.101:7006命令说明redis-cli --cluster代表集群操作命令create代表是创建集群--cluster-replicas 1 指定集群中每个master的副本个数为1此时节点总数 ÷ (replicas 1) 得到的就是master的数量n。因此节点列表中的前n个节点就是master其它节点都是slave节点随机分配到不同master输入命令后控制台会弹出下面的信息这里展示了集群中master与slave节点分配情况并询问你是否同意。节点信息如下7001是master节点id后6位是da134f7002是master节点id后6位是862fa07003是master节点id后6位是ad50837004是slave节点id后6位是391f8b认ad50837003为master7005是slave节点id后6位是e152cd认da134f7001为master7006是slave节点id后6位是4a018a认862fa07002为master输入yes然后回车。会发现集群开始创建并输出下列信息接着我们可以通过命令查看集群状态 redis-cli -p 7001 cluster nodes结果3.2.散列插槽数据要分片存储到不同的Redis节点肯定需要有分片的依据这样下次查询的时候才能知道去哪个节点查询。很多数据分片都会采用一致性hash算法。而Redis则是利用散列插槽hash slot的方式实现数据分片。详见官方文档https://redis.io/docs/management/scaling/#redis-cluster-101在Redis集群中共有16384个hash slots集群中的每一个master节点都会分配一定数量的hash slots。具体的分配在集群创建时就已经指定了如图中所示Master[0]本例中就是7001节点分配到的插槽是0~5460Master[1]本例中就是7002节点分配到的插槽是5461~10922Master[2]本例中就是7003节点分配到的插槽是10923~16383当我们读写数据时Redis基于CRC16 算法对key做hash运算得到的结果与16384取余就计算出了这个key的slot值。然后到slot所在的Redis节点执行读写操作。不过hash slot的计算也分两种情况当key中包含{}时根据{}之间的字符串计算hash slot当key中不包含{}时则根据整个key字符串计算hash slot例如key是user则根据user来计算hash slotkey是user:{age}则根据age来计算hash slot我们来测试一下先于7001建立连接 # 进入容器 docker exec -it r1 bash # 进入redis-cli redis-cli -p 7001 # 测试 set user jack会发现报错了提示我们MOVED 5474其实就是经过计算得出user这个key的hash slot 是5474而5474是在7002节点不能在7001上写入说好的任意节点都可以读写呢这是因为我们连接的方式有问题连接集群时要加-c参数 # 通过7001连接集群 redis-cli -c -p 7001 # 存入数据 set user jack结果如下可以看到客户端自动跳转到了5474这个slot所在的7002节点。现在我们添加一个新的key这次加上{} # 试一下key中带{} set user:{age} 21# 再试一下key中不带{} set age 20结果如下可以看到user:{age}和age计算出的slot都是741。3.3.故障转移分片集群的节点之间会互相通过ping的方式做心跳检测超时未回应的节点会被标记为下线状态。当发现master下线时会将这个master的某个slave提升为master。我们先打开一个控制台窗口利用命令监测集群状态 watch docker exec -it r1 redis-cli -p 7001 cluster nodes命令前面的watch可以每隔一段时间刷新执行结果方便我们实时监控集群状态变化。接着我们故技重施利用命令让某个master节点休眠。比如这里我们让7002节点休眠打开一个新的ssh控制台输入下面命令 docker exec -it r2 redis-cli -p 7002 DEBUG sleep 30可以观察到集群发现7002宕机标记为下线过了一段时间后7002原本的小弟7006变成了master而7002被标记为slave而且其master正好是7006主从地位互换。3.4.总结Redis分片集群如何判断某个key应该在哪个实例将16384个插槽分配到不同的实例根据key计算哈希值对16384取余余数作为插槽寻找插槽所在实例即可如何将同一类数据固定的保存在同一个Redis实例Redis计算key的插槽值时会判断key中是否包含{}如果有则基于{}内的字符计算插槽数据的key中可以加入{类型}例如key都以{typeId}为前缀这样同类型数据计算的插槽一定相同3.5.Java客户端连接分片集群选学RedisTemplate底层同样基于lettuce实现了分片集群的支持而使用的步骤与哨兵模式基本一致参考2.5节1引入redis的starter依赖2配置分片集群地址3配置读写分离与哨兵模式相比其中只有分片集群的配置方式略有差异如下 spring:redis:cluster:nodes:- 192.168.150.101:7001- 192.168.150.101:7002- 192.168.150.101:7003- 192.168.150.101:8001- 192.168.150.101:8002- 192.168.150.101:80034.Redis数据结构我们常用的Redis数据类型有5种分别是StringListSetSortedSetHash还有一些高级数据类型比如Bitmap、HyperLogLog、GEO等其底层都是基于上述5种基本数据类型。因此在Redis的源码中其实只有5种数据类型。4.1.RedisObject不管是任何一种数据类型最终都会封装为RedisObject格式它是一种结构体C语言中的一种结构可以理解为Java中的类。结构大概是这样的可以看到整个结构体中并不包含真实的数据仅仅是对象头信息内存占用的大小为44243264 128bit也就是16字节然后指针ptr指针指向的才是真实数据存储的内存地址。所以RedisObject的内存开销是很大的。属性中的encoding就是当前对象底层采用的数据结构或编码方式可选的有11种之多编号编码方式说明0OBJ_ENCODING_RAWraw编码动态字符串1OBJ_ENCODING_INTlong类型的整数的字符串2OBJ_ENCODING_HThash表也叫dict3OBJ_ENCODING_ZIPMAP已废弃4OBJ_ENCODING_LINKEDLIST双端链表5OBJ_ENCODING_ZIPLIST压缩列表6OBJ_ENCODING_INTSET整数集合7OBJ_ENCODING_SKIPLIST跳表8OBJ_ENCODING_EMBSTRembstr编码的动态字符串9OBJ_ENCODING_QUICKLIST快速列表10OBJ_ENCODING_STREAMStream流11OBJ_ENCODING_LISTPACK紧凑列表Redis中的5种不同的数据类型采用的底层数据结构和编码方式如下数据类型编码方式STRINGint、embstr、rawLISTLinkedList和ZipList(3.2以前)、QuickList3.2以后SETintset、HTZSETZipList7.0以前、Listpack7.0以后、HT、SkipListHASHZipList7.0以前、Listpack7.0以后、HT这些数据类型比较复杂我们重点讲解几个面试会问的其它的大家可以查看黑马程序员发布的Redis专业课程暂时无法在飞书文档外展示此内容4.2.SkipListSkipList跳表首先是链表但与传统链表相比有几点差异元素按照升序排列存储节点可能包含多个指针指针跨度不同。传统链表只有指向前后元素的指针因此只能顺序依次访问。如果查找的元素在链表中间查询的效率会比较低。而SkipList则不同它内部包含跨度不同的多级指针可以让我们跳跃查找链表中间的元素效率非常高。其结构如图我们可以看到1号元素就有指向3、5、10的多个指针查询时就可以跳跃查找。例如我们要找大小为14的元素查找的流程是这样的首先找元素1节点最高级指针也就是4级指针起始元素大小为1指针跨度为9可以判断出目标元素大小为10。由于14比10大肯定要从10这个元素向下接着找。找到10这个元素发现10这个元素的最高级指针跨度为5判断出目标元素大小为15大于14需要判断下级指针10这个元素的2级指针跨度为3判断出目标元素为13小于14因此要基于元素13接着找13这个元素最高级级指针跨度为2判断出目标元素为15比14大需要判断下级指针。13的下级指针跨度为1因此目标元素是14刚好于目标一致找到。这种多级指针的查询方式就避免了传统链表的逐个遍历导致的查询效率下降问题。在对有序数据做随机查询和排序时效率非常高。跳表的结构体如下 typedef struct zskiplist {// 头尾节点指针struct zskiplistNode *header, *tail;// 节点数量unsigned long length;// 最大的索引层级int level; } zskiplist;可以看到SkipList主要属性是header和tail也就是头尾指针因此它是支持双向遍历的。跳表中节点的结构体如下 typedef struct zskiplistNode {sds ele; // 节点存储的字符串double score;// 节点分数排序、查找用struct zskiplistNode *backward; // 前一个节点指针struct zskiplistLevel {struct zskiplistNode *forward; // 下一个节点指针unsigned long span; // 索引跨度} level[]; // 多级索引数组 } zskiplistNode;每个节点中都包含ele和score两个属性其中score是得分也就是节点排序的依据。ele则是节点存储的字符串数据指针。其内存结构如下4.3.SortedSet面试题Redis的SortedSet底层的数据结构是怎样的答SortedSet是有序集合底层的存储的每个数据都包含element和score两个值。score是得分element则是字符串值。SortedSet会根据每个element的score值排序形成有序集合。它支持的操作很多比如根据element查询score值按照score值升序或降序查询element要实现根据element查询对应的score值就必须实现element与score之间的键值映射。SortedSet底层是基于HashTable来实现的。要实现对score值排序并且查询效率还高就需要有一种高效的有序数据结构SortedSet是基于跳表实现的。加分项因为SortedSet底层需要用到两种数据结构对内存占用比较高。因此Redis底层会对SortedSet中的元素大小做判断。如果元素大小小于128且每个元素都小于64字节SortedSet底层会采用ZipList也就是压缩列表来代替HashTable和SkipList不过ZipList存在连锁更新问题因此而在Redis7.0版本以后ZipList又被替换为Listpack紧凑列表。Redis源码中zset也就是SortedSet的结构体如下 typedef struct zset {dict *dict; // dict底层就是HashTablezskiplist *zsl; // 跳表 } zset;其内存结构如图5.Redis内存回收Redis之所以性能强最主要的原因就是基于内存存储。然而单节点的Redis其内存大小不宜过大会影响持久化或主从同步性能。我们可以通过修改redis.conf文件添加下面的配置来配置Redis的最大内存 maxmemory 1gb当内存达到上限就无法存储更多数据了。因此Redis内部会有两套内存回收的策略内存过期策略内存淘汰策略5.1.内存过期处理存入Redis中的数据可以配置过期时间到期后再次访问会发现这些数据都不存在了也就是被过期清理了。5.1.1.过期命令Redis中通过expire命令可以给KEY设置TTL过期时间例如 # 写入一条数据 set num 123 # 设置20秒过期时间 expire num 20不过set命令本身也可以支持过期时间的设置 # 写入一条数据并设置20s过期时间 set num EX 20当过期时间到了以后再去查询数据会发现数据已经不存在。5.1.2.过期策略那么问题来了Redis如何判断一个KEY是否过期呢Redis又是何时删除过期KEY的呢Redis不管有多少种数据类型本质是一个KEY-VALUE的键值型数据库而这种键值映射底层正式基于HashTable来实现的在Redis中叫做Dict.来看下RedisDB的底层源码 typedef struct redisDb {dict dict; / The keyspace for this DB , 也就是存放KEY和VALUE的哈希表*/dict *expires; /* 同样是哈希表但保存的是设置了TTL的KEY及其到期时间*/dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/dict *ready_keys; /* Blocked keys that received a PUSH */dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS /int id; / Database ID, 0 ~ 15 /long long avg_ttl; / Average TTL, just for stats /unsigned long expires_cursor; / Cursor of the active expire cycle. */list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */ } redisDb;现在回答第一个问题面试题Redis如何判断KEY是否过期呢答在Redis中会有两个Dict也就是HashTable其中一个记录KEY-VALUE键值对另一个记录KEY和过期时间。要判断一个KEY是否过期只需要到记录过期时间的Dict中根据KEY查询即可。Redis是何时删除过期KEY的呢Redis并不会在KEY过期时立刻删除KEY因为要实现这样的效果就必须给每一个过期的KEY设置时钟并监控这些KEY的过期状态。无论对CPU还是内存都会带来极大的负担。Redis的过期KEY删除策略有两种惰性删除周期删除惰性删除顾明思议就是过期后不会立刻删除。那在什么时候删除呢Redis会在每次访问KEY的时候判断当前KEY有没有设置过期时间如果有过期时间是否已经到期。对应的源码如下 // db.c // 寻找要执行写操作的key robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {// 检查key是否过期如果过期则删除expireIfNeeded(db,key);return lookupKey(db,key,flags); }// 寻找要执行读操作的key robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {robj *val;// 检查key是否过期如果过期则删除if (expireIfNeeded(db,key) 1) {// 略 ...}val lookupKey(db,key,flags);if (val NULL)goto keymiss;server.stat_keyspace_hits;return val; }周期删除顾明思议是通过一个定时任务周期性的抽样部分过期的key然后执行删除。执行周期有两种SLOW模式Redis会设置一个定时任务serverCron()按照server.hz的频率来执行过期key清理FAST模式Redis的每个事件循环前执行过期key清理事件循环就是NIO事件处理的循环。SLOW模式规则① 执行频率受server.hz影响默认为10即每秒执行10次每个执行周期100ms。② 执行清理耗时不超过一次执行周期的25%即25ms.③ 逐个遍历db逐个遍历db中的bucket抽取20个key判断是否过期④ 如果没达到时间上限25ms并且过期key比例大于10%再进行一次抽样否则结束FAST模式规则过期key比例小于10%不执行① 执行频率受beforeSleep()调用频率影响但两次FAST模式间隔不低于2ms② 执行清理耗时不超过1ms③ 逐个遍历db逐个遍历db中的bucket抽取20个key判断是否过期④ 如果没达到时间上限1ms并且过期key比例大于10%再进行一次抽样否则结束5.2.内存淘汰策略对于某些特别依赖于Redis的项目而言仅仅依靠过期KEY清理是不够的内存可能很快就达到上限。因此Redis允许设置内存告警阈值当内存使用达到阈值时就会主动挑选部分KEY删除以释放更多内存。这叫做内存淘汰机制。5.2.1.内存淘汰时机那么问题来了当内存达到阈值时执行内存淘汰但问题是Redis什么时候会执去判断内存是否达到预警呢Redis每次执行任何命令时都会判断内存是否达到阈值 // server.c中处理命令的部分源码 int processCommand(client *c) {// ... 略if (server.maxmemory !server.lua_timedout) {// 调用performEvictions()方法尝试进行内存淘汰int out_of_memory (performEvictions() EVICT_FAIL);// ... 略if (out_of_memory reject_cmd_on_oom) {// 如果内存依然不足直接拒绝命令rejectCommand(c, shared.oomerr);return C_OK;}} }5.2.2.淘汰策略好了知道什么时候尝试淘汰了那具体Redis是如何判断该淘汰哪些Key的呢Redis支持8种不同的内存淘汰策略noeviction 不淘汰任何key但是内存满时不允许写入新数据默认就是这种策略。volatile-ttl 对设置了TTL的key比较key的剩余TTL值TTL越小越先被淘汰allkeys-random对全体key 随机进行淘汰。也就是直接从db-dict中随机挑选volatile-random对设置了TTL的key 随机进行淘汰。也就是从db-expires中随机挑选。allkeys-lru 对全体key基于LRU算法进行淘汰volatile-lru 对设置了TTL的key基于LRU算法进行淘汰allkeys-lfu 对全体key基于LFU算法进行淘汰volatile-lfu 对设置了TTL的key基于LFI算法进行淘汰比较容易混淆的有两个算法LRULeast Recently Used最近最久未使用。用当前时间减去最后一次访问时间这个值越大则淘汰优先级越高。LFULeast Frequently Used最少频率使用。会统计每个key的访问频率值越小淘汰优先级越高。Redis怎么知道某个KEY的最近一次访问时间或者是访问频率呢还记不记得之前讲过的RedisObject的结构回忆一下其中的lru就是记录最近一次访问时间和访问频率的。当然你选择LRU和LFU时的记录方式不同LRU以秒为单位记录最近一次访问时间长度24bitLFU高16位以分钟为单位记录最近一次访问时间低8位记录逻辑访问次数时间就不说了那么逻辑访问次数又是怎么回事呢8位无符号数字最大才255访问次数超过255怎么办这就要聊起Redis的逻辑访问次数算法了LFU的访问次数之所以叫做逻辑访问次数是因为并不是每次key被访问都计数而是通过运算① 生成[0,1)之间的随机数R② 计算 1/(旧次数 * lfu_log_factor 1)记录为P lfu_log_factor默认为10③ 如果 R P 则计数器 1且最大不超过255④ 访问次数会随时间衰减距离上一次访问时间每隔 lfu_decay_time 分钟(默认1) 计数器-1显然LFU的基于访问频率的统计更符合我们的淘汰目标因此官方推荐使用LFU算法。算法我们弄明白了不过这里大家要注意一下Redis中的KEY可能有数百万甚至更多每个KEY都有自己访问时间或者逻辑访问次数。我们要找出时间最早的或者访问次数最小的难道要把Redis中所有数据排序要知道Redis的内存淘汰是在每次执行命令时处理的。如果每次执行命令都先对全量数据做内存排序那命令的执行时长肯定会非常长这是不现实的。所以Redis采取的是抽样法即每次抽样一定数量maxmemory_smples的key然后基于内存策略做排序找出淘汰优先级最高的删除这个key。这就导致Redis的算法并不是真正的LRU而是一种基于抽样的近似LRU算法。不过在Redis3.0以后改进了这个算法引入了一个淘汰候选池抽样的key要与候选池中的key比较淘汰优先级优先级更高的才会被放入候选池。然后在候选池中找出优先级最高的淘汰掉这就使算法的结果更接近与真正的LRU算法了。特别是在抽样值较高的情况下例如10可以达到与真正的LRU接近的效果。这也是官方给出的真正LRU与近似LRU的结果对比你可以在图表中看到三种颜色的点形成三个不同的带每个点就是一个加入的KEY。浅灰色带是被驱逐的对象灰色带是没有被驱逐的对象绿色带是被添加的对象5.3.总结面试题Redis如何判断KEY是否过期呢答在Redis中会有两个Dict也就是HashTable其中一个记录KEY-VALUE键值对另一个记录KEY和过期时间。要判断一个KEY是否过期只需要到记录过期时间的Dict中根据KEY查询即可。面试题Redis何时删除过期KEY如何删除答Redis的过期KEY处理有两种策略分别是惰性删除和周期删除。惰性删除是指在每次用户访问某个KEY时判断KEY的过期时间如果过期则删除如果未过期则忽略。周期删除有两种模式SLOW模式通过一个定时任务定期的抽样部分带有TTL的KEY判断其是否过期。默认情况下定时任务的执行频率是每秒10次但每次执行不能超过25毫秒。如果执行抽样后发现时间还有剩余并且过期KEY的比例较高则会多次抽样。FAST模式在Redis每次处理NIO事件之前都会抽样部分带有TTL的KEY判断是否过期因此执行频率较高。但是每次执行时长不能超过1ms如果时间充足并且过期KEY比例过高也会多次抽样面试题当Redis内存不足时会怎么做答这取决于配置的内存淘汰策略Redis支持很多种内存淘汰策略例如LRU、LFU、Random. 但默认的策略是直接拒绝新的写入请求。而如果设置了其它策略则会在每次执行命令后判断占用内存是否达到阈值。如果达到阈值则会基于配置的淘汰策略尝试进行内存淘汰直到占用内存小于阈值为止。面试题那你能聊聊LRU和LFU吗答LRU是最近最久未使用。Redis的Key都是RedisObject当启用LRU算法后Redis会在Key的头信息中使用24个bit记录每个key的最近一次使用的时间lru。每次需要内存淘汰时就会抽样一部分KEY找出其中空闲时间最长的也就是now - lru结果最大的然后将其删除。如果内存依然不足就重复这个过程。由于采用了抽样来计算这种算法只能说是一种近似LRU算法。因此在Redis4.0以后又引入了LFU算法这种算法是统计最近最少使用也就是按key的访问频率来统计。当启用LFU算法后Redis会在key的头信息中使用24bit记录最近一次使用时间和逻辑访问频率。其中高16位是以分钟为单位的最近访问时间后8位是逻辑访问次数。与LFU类似每次需要内存淘汰时就会抽样一部分KEY找出其中逻辑访问次数最小的将其淘汰。面试题逻辑访问次数是如何计算的答由于记录访问次数的只有8bit即便是无符号数最大值只有255不可能记录真实的访问次数。因此Redis统计的其实是逻辑访问次数。这其中有一个计算公式会根据当前的访问次数做计算结果要么是次数1要么是次数不变。但随着当前访问次数越大1的概率也会越低并且最大值不超过255.除此以外逻辑访问次数还有一个衰减周期默认为1分钟即每隔1分钟逻辑访问次数会-1。这样逻辑访问次数就能基本反映出一个key的访问热度了。6.缓存问题Redis经常被用作缓存而缓存在使用的过程中存在很多问题需要解决。例如缓存的数据一致性问题缓存击穿缓存穿透缓存雪崩6.1.缓存一致性我们先看下目前企业用的最多的缓存模型。缓存的通用模型有三种Cache Aside有缓存调用者自己维护数据库与缓存的一致性。即查询时命中则直接返回未命中则查询数据库并写入缓存更新时更新数据库并删除缓存查询时自然会更新缓存Read/Write Through数据库自己维护一份缓存底层实现对调用者透明。底层实现查询时命中则直接返回未命中则查询数据库并写入缓存更新时判断缓存是否存在不存在直接更新数据库。存在则更新缓存同步更新数据库Write Behind Cahing读写操作都直接操作缓存由线程异步的将缓存数据同步到数据库目前企业中使用最多的就是Cache Aside模式因为实现起来非常简单。但缺点也很明显就是无法保证数据库与缓存的强一致性。为什么呢我们一起来分析一下。Cache Aside的写操作是要在更新数据库的同时删除缓存那为什么不选择更新数据库的同时更新缓存而是删除呢原因很简单假如一段时间内无人查询但是有多次更新那这些更新都属于无效更新。采用删除方案也就是延迟更新什么时候有人查询了什么时候更新。那到底是先更新数据库再删除缓存还是先删除缓存再更新数据库呢现在假设有两个线程一个来更新数据一个来查询数据。我们分别分析两种策略的表现。我们先分析策略1先更新数据库再删除缓存正常情况异常情况异常情况说明线程1删除缓存后还没来得及更新数据库此时线程2来查询发现缓存未命中于是查询数据库写入缓存。由于此时数据库尚未更新查询的是旧数据。也就是说刚才的删除白删了缓存又变成旧数据了。然后线程1更新数据库此时数据库是新数据缓存是旧数据由于更新数据库的操作本身比较耗时在期间有线程来查询数据库并更新缓存的概率非常高。因此不推荐这种方案。再来看策略2先更新数据库再删除缓存正常情况异常情况异常情况说明线程1查询缓存未命中于是去查询数据库查询到旧数据线程1将数据写入缓存之前线程2来了更新数据库删除缓存线程1执行写入缓存的操作写入旧数据可以发现异常状态发生的概率极为苛刻线程1必须是查询数据库已经完成但是缓存尚未写入之前。线程2要完成更新数据库同时删除缓存的两个操作。要知道线程1执行写缓存的速度在毫秒之间速度非常快在这么短的时间要完成数据库和缓存的操作概率非常之低。综上添加缓存的目的是为了提高系统性能而你要付出的代价就是缓存与数据库的强一致性。如果你要求数据库与缓存的强一致那就需要加锁避免并行读写。但这就降低了性能与缓存的目标背道而驰。因此不管任何缓存同步方案最终的目的都是尽可能保证最终一致性降低发生不一致的概率。我们采用先更新数据库再删除缓存的方案已经将这种概率降到足够低目的已经达到了。同时我们还要给缓存加上过期时间一旦发生缓存不一致当缓存过期后会重新加载数据最终还是能保证一致。这就可以作为一个兜底方案。6.2.缓存穿透什么是缓存穿透呢我们知道当请求查询缓存未命中时需要查询数据库以加载缓存。但是大家思考一下这样的场景如果我访问一个数据库中也不存在的数据。会出现什么现象由于数据库中不存在该数据那么缓存中肯定也不存在。因此不管请求该数据多少次缓存永远不可能建立请求永远会直达数据库。假如有不怀好意的人开启很多线程频繁的访问一个数据库中也不存在的数据。由于缓存不可能生效那么所有的请求都访问数据库可能就会导致数据库因过高的压力而宕机。解决这个问题有两种思路缓存空值布隆过滤器6.2.1.缓存空值简单来说就是当我们发现请求的数据即不存在与缓存也不存在与数据库时将空值缓存到Redis避免频繁查询数据库。实现思路如下优点实现简单维护方便缺点额外的内存消耗6.2.2.布隆过滤器布隆过滤是一种数据统计的算法用于检索一个元素是否存在一个集合中。一般我们判断集合中是否存在元素都会先把元素保存到类似于树、哈希表等数据结构中然后利用这些结构查询效率高的特点来快速匹配判断。但是随着元素数量越来越多这种模式对内存的占用也越来越大检索的速度也会越来越慢。而布隆过滤的内存占用小查询效率却很高。布隆过滤首先需要一个很长的bit数组默认数组中每一位都是0.然后还需要K个hash函数将元素基于这些hash函数做运算的结果映射到bit数组的不同位置并将这些位置置为1例如现在k3hello经过运算得到3个角标1、5、12world经过运算得到3个角标8、17、21java经过运算得到3个角标17、25、28则需要将每个元素对应角标位置置为1此时我们要判断元素是否存在只需要再次基于K个hash函数做运算 得到K个角标判断每个角标的位置是不是1只要全是1就证明元素存在任意位置为0就证明元素一定不存在假如某个元素本身并不存在也没添加到布隆过滤器过。但是由于存在hash碰撞的可能性这就会出现这个元素计算出的角标已经被其它元素置为1的情况。那么这个元素也会被误判为已经存在。因此布隆过滤器的判断存在误差当布隆过滤器认为元素不存在时它肯定不存在当布隆过滤器认为元素存在时它可能存在也可能不存在当bit数组越大、Hash函数K越复杂K越大时这个误判的概率也就越低。由于采用bit数组来标示数据即便4,294,967,296个bit位也只占512mb的空间我们可以把数据库中的数据利用布隆过滤器标记出来当用户请求缓存未命中时先基于布隆过滤器判断。如果不存在则直接拒绝请求存在则去查询数据库。尽管布隆过滤存在误差但一般都在0.01%左右可以大大减少数据库压力。使用布隆过滤后的流程如下6.3.缓存雪崩缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机导致大量请求到达数据库带来巨大压力。常见的解决方案有给不同的Key的TTL添加随机值这样KEY的过期时间不同不会大量KEY同时过期利用Redis集群提高服务的可用性避免缓存服务宕机给缓存业务添加降级限流策略给业务添加多级缓存比如先查询本地缓存本地缓存未命中再查询RedisRedis未命中再查询数据库。即便Redis宕机也还有本地缓存可以抗压力6.4.缓存击穿缓存击穿问题也叫热点Key问题就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了无数的请求访问会在瞬间给数据库带来巨大的冲击。由于我们采用的是Cache Aside模式当缓存失效时需要下次查询时才会更新缓存。当某个key缓存失效时如果这个key是热点key并发访问量比较高。就会在一瞬间涌入大量请求都发现缓存未命中于是都会去查询数据库尝试重建缓存。可能一瞬间就把数据库压垮了。如上图所示线程1发现缓存未命中准备查询数据库重建缓存但是因为数据比较复杂导致查询数据库耗时较久在这个过程中一下次来了3个新的线程就都会发现缓存未命中都去查询数据库数据库压力激增常见的解决方案有两种互斥锁给重建缓存逻辑加锁避免多线程同时指向逻辑过期热点key不要设置过期时间在活动结束后手动删除。基于互斥锁的方案如图逻辑过期的思路如图6.5.面试总结面试题如何保证缓存的双写一致性答缓存的双写一致性很难保证强一致只能尽可能降低不一致的概率确保最终一致。我们项目中采用的是Cache Aside模式。简单来说就是在更新数据库之后删除缓存在查询时先查询缓存如果未命中则查询数据库并写入缓存。同时我们会给缓存设置过期时间作为兜底方案如果真的出现了不一致的情况也可以通过缓存过期来保证最终一致。追问为什么不采用延迟双删机制答延迟双删的第一次删除并没有实际意义第二次采用延迟删除主要是解决数据库主从同步的延迟问题我认为这是数据库主从的一致性问题与缓存同步无关。既然主节点数据已经更新Redis的缓存理应更新。而且延迟双删会增加缓存业务复杂度也没能完全避免缓存一致性问题投入回报比太低。面试题如何解决缓存穿透问题答缓存穿透也可以说是穿透攻击具体来说是因为请求访问到了数据库不存在的值这样缓存无法命中必然访问数据库。如果高并发的访问这样的接口会给数据库带来巨大压力。我们项目中都是基于布隆过滤器来解决缓存穿透问题的当缓存未命中时基于布隆过滤器判断数据是否存在。如果不存在则不去访问数据库。当然也可以使用缓存空值的方式解决不过这种方案比较浪费内存。面试题如何解决缓存雪崩问题答缓存雪崩的常见原因有两个第一是因为大量key同时过期。针对问这个题我们可以可以给缓存key设置不同的TTL值避免key同时过期。第二个原因是Redis宕机导致缓存不可用。针对这个问题我们可以利用集群提高Redis的可用性。也可以添加多级缓存当Redis宕机时还有本地缓存可用。面试题如何解决缓存击穿问题答缓存击穿往往是由热点Key引起的当热点Key过期时大量请求涌入同时查询发现缓存未命中都会去访问数据库导致数据库压力激增。解决这个问题的主要思路就是避免多线程并发去重建缓存因此方案有两种。第一种是基于互斥锁当发现缓存未命中时需要先获取互斥锁再重建缓存缓存重建完成释放锁。这样就可以保证缓存重建同一时刻只会有一个线程执行。不过这种做法会导致缓存重建时性能下降严重。第二种是基于逻辑过期也就是不给热点Key设置过期时间而是给数据添加一个过期时间的字段。这样热点Key就不会过期缓存中永远有数据。查询到数据时基于其中的过期时间判断key是否过期如果过期开启独立新线程异步的重建缓存而查询请求先返回旧数据即可。当然这个过程也要加互斥锁但由于重建缓存是异步的而且获取锁失败也无需等待而是返回旧数据这样性能几乎不受影响。需要注意的是无论是采用哪种方式在获取互斥锁后一定要再次判断缓存是否命中做dubbo check. 因为当你获取锁成功时可能是在你之前有其它线程已经重建缓存了。
http://www.zqtcl.cn/news/951556/

相关文章:

  • 北京 网站建设 知乎上海公司买新能源车
  • 成都微网站wordpress 购买
  • 网站开发一般要用到哪些软件软件开发工程师机构
  • dj网站开发建设网站备案 换空间
  • 网站建设哪家最好网站开发怎么报价
  • app 微商城网站建设网站建设流程百科
  • 网站短期培训学校小说网站怎么建设的
  • 最简单的免费网站制作模板电子商务 网站系统
  • 网站域名备案授权书网站建设长春
  • 网站维护主要从哪几个方面做seo营销的概念
  • 北京网站建设营销网站策划案4500
  • 网站建设售后培训wordpress 过滤html
  • 湖北森泰建设集团有限公司网站国外产品设计网站推荐
  • 网站建设与代运营产品介绍排版网页设计教程
  • 音乐网站排名搜索引擎网络推广方法
  • asp.net 企业网站昆明高端seo怎么做
  • 图书馆网站建设需求方案企业网站背景图片
  • 网站app的作用做家教网站资质
  • 资源网站搭建北京app网站建设
  • 天津做宠物饲料的网站wordpress添加注册页面模板
  • 网站建设公司石家庄php网站开发报价
  • 国外免费网站服务器链接cpa自己做网站
  • 手机网站的优缺点2024中国企业500强
  • 黄河道网站建设网站设计标语
  • 企业网站建设范文wordpress 5.1
  • 网站 河北 备案 慢设计一个营销方案
  • 网站建设培训合肥品牌设计案例
  • 建网站注册免费云服务器
  • 可以做网站的公司有哪些聊天软件开发厂家有哪些
  • 正规网站建设公司一般要多少钱婚纱网站有哪些