Java高级开发高频面试题(七)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: Java高级开发高频面试题

🍊 主从架构下的数据同步

🎉 主从复制/数据同步

master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB文件生成完毕之后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。

🎉 主从架构下的数据部分复制(断点续传)

当redis是主从架构时,主节点同步数据到从节点进行持久化,这个过程可能会因为网络/IO等原因,导致连接中断,当主节点和从节点断开重连后,一般都会对整份数据进行复制,这个过程是比较浪费性能的。从redis2.8版本开始,redis改用可以支持部分数据复制的命令去主节点同步数据,主节点会在内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,主节点和它所有的从节点都维护复制的数据下标和主节点的进程id,当网络连接断开后,从节点会请求主节点继续进行数据同步,从记录数据的下标开始同步数据。如果主节点进程id变化了,或者从节点数据下标太旧,不在主节点的缓存队列里,会进行一次全量数据的复制。

🎉 数据丢失发生的场景以及解决方案

  • 异步复制导致的数据丢失:主节点到从节点的复制是异步的,主节点有部分数据还没复制到从节点,主节点就宕机了。
  • 脑裂导致的数据丢失:脑裂导致的数据丢失:某个 主节点 所在机器突然脱离了正常的网络,跟其他从节点机器不能连接,但是实际上 主节点还运行着,这个时候哨兵可能就会认为 主节点 宕机了,然后开启选举,将其他从节点切换成了 主节点,集群里就会有两个主节点 ,也就是所谓的脑裂。虽然某个从节点被切换成了 主节点,但是可能 client 还没来得及切换到新的主节点,还继续向旧的主节点写数据,当旧的主节点再次恢复的时候,会被作为一个从节点挂到新的 主节点上去,自己的数据会清空,从新的主节点复制数据,新的主节点并没有后来 client写入的数据,这部分数据也就丢失了。

解决方案:

  • 针对异步复制导致的数据丢失,可以通过控制复制数据的时长和ack的时间来控制,一旦从节点复制数据和 ack 延时太长,就认为可能主节点宕机后损失的数据太多了,那么就拒绝写请求,这样可以把主节点宕机时由于部分数据未同步到从节点导致的数据丢失降低的可控范围内。
  • 针对脑裂导致的数据丢失:如果一个主节点出现了脑裂,跟其他从节点断了连接,如果不能继续给从节点发送数据,而且从节点超过10 秒没有给自己ack消息,那么就直接拒绝客户端的写请求,这样即便在脑裂场景下,最多就丢失10 秒的数据。在redis的配置文件里面有二个参数,min-slaves-to-write 3表示连接到master的最少slave数量,min-slaves-max-lag 10表示slave连接到master的最大延迟时间,通过这二个参数可以把数据丢失控制在承受范围以内。

🎉 主从/哨兵/集群区别

📝 主从架构

主数据库可以进行读写操作,当写操作导致数据变化的时候,会自动将数据同步给从数据库,从数据库一般是只读的,接受主数据库同步过来的数据。

📝 哨兵

当主数据库遇到异常中断服务后,需要通过手动的方式选择一个从数据库来升格为主数据库,让系统能够继续提供服务,难以实现自动化。 Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能,哨兵的作用就是监控redis主、从数据库是否正常运行,主数据库出现故障,自动将从数据库转换为主数据库。

📝 集群

即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存,有木桶效应。为了最大化利用内存,可以采用集群,就是分布式存储,每台redis存储不同的内容,Redis集群共有16384个槽,每个redis分得一些槽,客户端请求的key,根据公式,计算出映射到哪个分片上。

🎉 高可用/哨兵集群/主备切换

Redis哨兵集群实现高可用,哨兵是一个分布式系统,可以在一个架构中运行多个哨兵进程,这些进程使用流言协议来接收关于主节点是否下线的信息,并使用投票协议来决定是否进行自动故障迁移,选择哪个备节点作为新的主节点。每个哨兵会向其它哨兵、主节点、备节点定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间内未回应,则暂时认为对方已挂。若“哨兵群”中的多数哨兵,都报告某一主节点没响应,系统才认为该主节点"彻底死亡",通过算法,从剩下的备节点中,选一台提升为主节点,然后自动修改相关配置,比如主节点名称,IP,端口号,选举次数,主服务器的密码,心跳检测毫秒数,做多少个节点等。

🍊 Redis调优

🎉 绑定CPU内核

现代计算机的CPU都是多核心多线程,例如i9-12900k有16个内核、24个逻辑处理器、L1缓存1.4MB、L2缓存14MB、L3缓存30MB,一个内核下的逻辑处理器共用L1和L2缓存。

Redis的主线程处理客户端请求、子进程进行数据持久化、子线程处理RDB/AOF rewrite、后台线程处理异步lazy-free和异步释放fd等。这些线程在多个逻辑处理器之间切换,所以为了降低Redis服务端在多个CPU内核上下文切换带来的性能损耗,Redis6.0版本提供了进程绑定CPU 的方式提高性能。

在Redis6.0版本的redis.conf文件配置即可:

server_cpulist:RedisServer和IO线程绑定到CPU内核

bio_cpulist:后台子线程绑定到CPU内核

aof_rewrite_cpulist:后台AOF rewrite进程绑定到CPU内核

bgsave_cpulist:后台RDB进程绑定到CPU内核

🎉 使用复杂度过高的命令

Redis有些命令复制度很高,复杂度过高的命令如下:

MSET、MSETNX、MGET、LPUSH、RPUSH、LRANGE、LINDEX、LSET、LINSERT、HDEL、HGETALL、HKEYS/HVALS、SMEMBERS、SUNION/SUNIONSTORE、SINTER/SINTERSTORE、SDIFF/SDIFFSTORE、ZRANGE/ZREVRANGE、ZRANGEBYSCORE/ZREVRANGEBYSCORE、ZREMRANGEBYRANK/ZREMRANGEBYSCORE、DEL、KEYS

具体原因有以下:

在内存操作数据的时间复杂度太高,消耗的CPU资源较多。

一些范围命令一次返回给客户端的数据太多,在数据协议的组装和网络传输的过程就要变长,容易延时。

Redis虽然使用了多路复用技术,但是复用的还是同一个线程,这一个线程同一时间只能处理一个IO事件,像一个开关一样,当开关拨到哪个IO事件这个电路上,就处理哪个IO事件,所以它单线程处理客户端请求的,如果前面某个命令耗时比较长,后面的请求就会排队,对于客户端来说,响应延迟也会变长。

解决方案:分批次,每次获取尽量少的数据,数据的聚合在客户端做,减少服务端的压力。

🎉 大key的存储和删除

当存储一个很大的键值对的时候,由于值非常大,所以Redis分配内存的时候就会很耗时,此外删除这个key也是一样耗时,这种key就是大key。开发者可以通过设置慢日志记录有哪些命令比较耗时,命令如下:

命令执行耗时超过10毫秒,记录慢日志

CONFIG SET slowlog-log-slower-than 10000

只保留最近1000条慢日志

CONFIG SET slowlog-max-len 1000

后面再通过SLOWLOG get [n]查看。

对于大key可以通过以下命令直接以类型展示出来,它只显示元素最多的key,但不代表占用内存最多,命令如下:

#-h:redis主机ip
#-p: redis端口号
#-i:隔几秒扫描
redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.01

对于这种大key的优化,开发者事先在业务实现层就需要避免存储大key,可以在存储的时候将key简化,变成二进制位进行存储,节约redis空间,例如存储上海市静安区,可以对城市和区域进行编码,上海市标记为0,静安区标记为1,组合起来就是01,将01最为key存储起来比上海市静安区作为key存储起来内存占比更小。

可以将大key拆分成多个小key,整个大key通过程序控制多个小key,例如初始阶段,业务方只需查询某乡公务员姓名。然而,后续需求拓展至县、市、省。开发者未预见此增长,将数据存储于单个键中,导致键变成大键,影响系统性能。现可将大键拆分成多个小键,如省、市、县、乡,使得每级行政区域的公务员姓名均对应一个键。

根据Redis版本不同处理方式也不同,4.0以上版本可以用unlink代替del,这样可以把key释放内存的工作交给后台线程去执行。6.0以上版本开启lazy-free后,执行del命令会自动地在后台线程释放内存。

使用List集合时通过控制列表保存元素个数,每个元素长度触发压缩列表(ziplist)编码,压缩列表是有顺序并且连续的内存块组成的一种专门节约内存的存储结构,通过在redis.conf(linux系统)或者redis.windows.conf(windows系统)文件里面修改以下配置实现:

list-max-ziplist-entries 512
list-max-ziplist-value 64

🎉 数据集中过期

在某个时段,大量关键词(key)会在短时间内过期。当这些关键词过期时,访问Redis的速度会变慢,因为过期数据被惰性删除(被动)和定期删除(主动)策略共同管理。惰性删除是在获取关键词时检查其是否过期,一旦过期就删除。这意味着大量过期关键词在使用之前并未删除,从而持续占用内存。主动删除则是在主线程执行,每隔一段时间删除一批过期关键词。若出现大量需要删除的过期关键词,客户端访问Redis时必须等待删除完成才能继续访问,导致客户端访问速度变慢。这种延迟在慢日志中无法查看,经验不足的开发者可能无法定位问题,因为慢日志记录的是操作内存数据所需时间,而主动删除过期关键词发生在命令执行之前,慢日志并未记录时间消耗。因此,当开发者感知某个关键词访问变慢时,实际上并非该关键词导致,而是Redis在删除大量过期关键词所花费的时间。

(1)开发者检查代码,找到导致集中过期key的逻辑,并设置一个自定义的随机过期时间分散它们,从而避免在短时间内集中删除key。

(2)在Redis 4.0及以上版本中,引入了Lazy Free机制,使得删除键的操作可以在后台线程中执行,不会阻塞主线程。

(3)使用Redis的Info命令查看Redis运行的各种指标,重点关注expired_keys指标。这个指标在短时间内激增时,可以设置报警,通过短信、邮件、微信等方式通知运维人员。它的作用是累计删除过期key的数量。当指标突增时,通常表示大量过期key在同一时间被删除。

🎉 内存淘汰策略

当Redis的内存达到最大容量限制时,新的数据将先从内存中筛选一部分旧数据以腾出空间,从而导致写操作的延迟。这是由内存淘汰策略所决定的。

常见的两种策略为淘汰最少访问的键(LFU)和淘汰最长时间未访问的键(LRU)。

LRU策略可能导致最近一段时间的访问数据未被访问而突然成为热点数据。

LFU策略可能导致前一段时间访问次数很多,但最近一段时间未被访问,导致冷数据无法被淘汰。

尽管LFU策略的性能优于LRU策略,但具体选择哪种策略需要根据实际业务进行调整。对于商品搜索和热门推荐等场景,通常只有少量数据被访问,大部分数据很少被访问,可以使用LFU策略。对于用户最近访问的页面数据可能会被二次访问的场景,则适合使用LRU策略。

除了选择淘汰策略外,还可以通过拆分多个实例或横向扩展来分散淘汰过期键的压力。

如果效果仍不理想,开发者可以编写淘汰过期键功能,设置定时任务,在凌晨不繁忙时段主动触发淘汰,删除过期键。

🎉 碎片整理

Redis存储在内存中必然会出现频繁修改的情况,而频繁的修改Redis数据会导致Redis出现内存碎片,从而导致Redis的内存使用率减低。

通常情况下在4.0以下版本的Redis只能通过重启解决内存碎片,而4.0及以上版本可以开启碎片自动整理解决,只不过碎片整理是在主线程中完成的,通用先对延时范围和时间进行评估,然后在机器负载不高同时业务不繁忙时开启内存碎片整理,避免影响客户端请求。

开启内存自动碎片整理配置如下:

# 已启用活动碎片整理
activedefrag yes
# 启动活动碎片整理所需的最小碎片浪费量
active-defrag-ignore-bytes 100mb
# 启动活动碎片整理的最小碎片百分比
active-defrag-threshold-lower 10
# 使用最大努力的最大碎片百分比
active-defrag-threshold-upper 100
# 以CPU百分比表示的碎片整理工作量最小
active-defrag-cycle-min 5
# 以CPU百分比表示的碎片整理最大工作量
active-defrag-cycle-max 75
# 将从主字典扫描中处理的集合/哈希/zset/列表字段的最大数目
active-defrag-max-scan-fields 1000

🎉 内存大页

自Linux内核2.6.38版本起,Redis可申请以2MB为单位的内存,从而降低内存分配次数,提高效率。然而,由于每次分配的内存单位增大,处理时间也相应增加。在进行RDB和AOF持久化时,Redis主进程先创建子进程,子进程将内存快照写入磁盘,而主进程继续处理写请求。数据变动时,主进程将新数据复制到一块新内存,并修改该内存块。读写分离设计允许并发写入,无需加锁,但在主进程上进行内存复制和申请新内存会增加处理时间,影响性能。大key可能导致申请更大的内存和更长的处理时间。根据项目实际情况,关闭Redis部署机器上的内存大页机制以提高性能是一种不错的选择。

🎉 数据持久化与AOF刷盘

Redis提供三种持久化方式:RDB快照、AOF日志和混合持久化。默认使用RDB快照。

(1)RDB快照:周期性生成dump.rdb文件,主线程fork子线程,子线程处理磁盘IO,处理RDB快照,主线程fork线程的过程可能会阻塞主线程,主线程内存越大阻塞越久,可能导致服务暂停数秒。

(2)AOF日志:每条写入命令追加,回放日志重建数据。文件过大时,会去除没用的指令,定期根据内存最新数据重新生成aof文件。默认1秒执行一次fsync操作,最多丢失1秒数据。在AOF刷盘时,如果磁盘IO负载过高,fsync可能会阻塞主线程,主线程继续接收写请求,把数据写到文件内存里面,写操作需要等fsync执行完才可以继续执行。

(3)混合持久化:RDB快照模式恢复速度快,但可能丢失部分数据。AOF日志文件通常比RDB数据快照文件大,支持的写QPS较低。将两种持久化模式混合使用,AOF保证数据不丢失,RDB快速数据恢复,混合持久化重写时,将内存数据转换为RESP命令写入AOF文件,结合RDB快照和增量AOF修改。新文件一开始不叫appendonly.aof,重写完成后改名,覆盖原有AOF文件。先加载RDB,再重放AOF。

三种持久化方式都存在问题:fork操作可能阻塞主线程;磁盘IO负载过大时,fork阻塞影响AOF写入文件内存。

原因:fork创建的子进程复制父进程的空间内存页表,fork耗时跟进程总内存量有关,OPS越高耗时越明显。

解决方案:

(1)可以通过info stats命令查看latest_fork_usec指标观察最近一次fork操作耗时进行问题辅助定位。

(2)减少fork频率,根据实际情况适当地调整AOF触发条件

(3)Linux内存分配策略默认配置是vm.overcommit_memory=0,表示内存不足时,不会分配,导致fork阻塞。改成1,允许过量使用,直到内存用完为止。

(4)评估Redis最大可用内存,让机器至少有20%的闲置内存。

(5)定位占用磁盘IO较大的应用程序,将该应用程序移到其他机器上去,减少对Redis影响。

(6)资金充足情况下,更换高性能的SSD磁盘,从硬件层面提高磁盘IO处理能力。

(7)配置no-appendfsync-on-rewrite none表示AOF写入文件内存时,不触发fsync,不执行刷盘。这种调整有一定风险,如果Redis在AOF写入文件内存时刚好挂了,存在数据丢失情况。

🎉 丢包/中断/CPU亲和性

网络因素有以下问题:

(1)网络宽带和流量是否瓶颈、数据传输延迟和丢包情况、是否频繁短连接(如TCP创建和断开)

(2)数据丢包情况:数据丢包通常发生在网卡设备驱动层面,网卡收到数据包,将数据包从网卡硬件缓存转移到服务器内存中,通知内核处理,经过TCP/IP协议校验、解析、发送给上层协议,应用程序通过read系统调用从socket buffer将新数据从内核区拷贝到用户区读取数据。TCP能动态调整接收窗口大小,不会出现由于socket buffer接收队列空间不足而丢包的情况。

然而在高负载压力下,网络设备的处理性能达到硬件瓶颈,网络设备和内核资源出现竞争和冲突,网络协议栈无法有效地处理和转发数据包,传输速度受限,而Linux使用缓冲区来缓存接收到的数据包,大量数据包涌入内核缓冲区,可能导致缓冲区溢出,进而影响数据包的处理和传输,内核无法处理所有收到的数据包,处理速度跟不上收包速度,导致数据包丢失。

(3)Redis的数据通常存储在内存中,通过网络和客户端进行交互。在这个过程中,Redis可能会受到中断的影响,因为中断可能会打断Redis的正常执行流程。当CPU正在处理Redis的调用时,如果发生了中断,CPU必须停止当前的工作转而处理中断请求。在处理中断的过程中,Redis无法继续运行,必须等待中断处理完毕后才能继续运行。这会导致Redis的响应速度受到影响,因为在等待中断处理的过程中,Redis无法响应其他请求。

(4)在NUMA架构中,每个CPU内核对应一个NUMA节点。中断处理和网络数据包处理涉及多个CPU内核和NUMA节点。Linux内核使用softnet_data数据结构跟踪网络数据包的处理状态,以实现更高效的数据处理和调度。在处理网络数据包时,内核首先在softnet_data中查找相关信息,然后根据这些信息执行相应操作,如发送数据包、重新排序数据包等。

网络驱动程序使用内核分配的缓冲区(sk_buffer)存储和处理网络数据包,当网络设备收到数据包时,会向驱动程序发送中断信号,通知其处理新数据包。驱动程序从设备获取数据包,并将其添加到sk_buffer缓冲区。内核会继续处理sk_buffer中的数据包,如根据协议类型进行分拣、转发或丢弃等。

softnet_data和sk_buffer缓冲区都可能跨越NUMA节点,在数据接收过程中,数据从NUMA节点的一个节点传递到另一个节点时,由于数据跨越了不同的节点,不仅无法利用L2和L3缓存还需要在节点之间进行数据拷贝,导致数据在传输过程中的额外开销,进而增加了传输时间和响应时间,性能下降。

(5)Linux的CPU亲和性特性也会影响进程的调度。当一个进程唤醒另一个的时候,被唤醒的进程可能会被放到相同的CPU core或者相同的NUMA节点上。当多个NUMA node处理中断时,可能导致Redis进程在CPU core之间频繁迁移,造成性能损失。

解决方案:

(1)升级网络设备或增加网络设备的数量,以提高网络处理能力和带宽。

(2)适当调整Linux内核缓冲区的大小,以平衡网络处理能力和数据包丢失之间的关系。

(3)将中断都分配到同一NUMA Node中,中断处理函数和Redis利用同NUMA下的L2、L3缓存、同节点下的内存,降低延迟。

(4)结合Linux的CPU亲和性特性,将任务或进程固定到同一CPU内核上运行,提高系统性能和效率,保证系统稳定性和可靠性。

注意:在Linux系统中NUMA亲和性可以指定在哪个NUMA节点上运行,Redis在默认情况下并不会自动将NUMA亲和性配置应用于实例部署,通常情况下通过使用Kubernetes等容器编排工具,调整节点亲和性策略或使用pod亲和性和节点亲和性规则来控制Redis实例在特定NUMA节点上运行。或者在手动部署Redis实例时,使用Linux系统中的numactl命令来查看和配置NUMA节点信息,将Redis实例部署在某个NUMA节点上。如果是在虚拟化环境中,使用NUMA aware虚拟机来部署Redis实例,让它在指定的NUMA节点上运行。

(5)添加网络流量阈值预警,超限时通知运维人员,及时扩容。

(6)编写监控脚本,正确配置和使用监控组件,使用长连接收集Redis状态信息,避免短连接。

(7)为Redis机器分配专用资源,避免其他程序占用。

🎉 操作系统Swap与主从同步

Redis突然变得很慢,需要考虑Redis是否使用操作系统的Swap以缓解内存不足的影响,它允许把部分内存数据存储到磁盘上,而访问磁盘速度比访问内存慢很多,所以操作系统的Swap对Redis的延时是无法接受的。

解决方案:

(1)适当增加Redis服务器的内存

(2)对Redis的内存碎片进行整理

(3)同时当Redis内存不足或者使用了Swap时,通过邮件、短信、微信等渠道通知运维人员及时处理

(4)主从架构的Redis在释放Swap前先将主节点切换至新主节点,旧主节点释放Swap后重启,待从库数据完全同步后再行主从切换,以避免影响应用程序正常运行。

在主从架构数据同步过程中,可能因网络中断或IO异常导致连接中断。建议使用支持数据断点续传的2.8及以上版本,以避免对整份数据进行复制,降低性能浪费。

🎉 监控

在Redis的监控中,有两种推荐的体系:ELK和Fluent + Prometheus + Grafana。

ELK体系通常使用metricbeat作为指标采集,logstash作为收集管道,并通过可视化工具kibana来呈现数据。ElasticSearch用于存储监控数据。

Fluent + Prometheus + Grafana体系则使用redis-eport作为指标采集,fluentd作为采集管道,并通过可视化工具Grafana来展示数据。Prometheus用于存储监控数据。

这两种监控体系都可以获取Redis的各项指标,并对数据进行持续化存储和对比。可视化工具使得开发者和运维人员能够更清晰地观察Redis集群的运行状况,如内存消耗、集群信息、请求键命中率、客户端连接数、网络指标、内存监控等。此外,它们都支持预警机制,例如设置慢查询日志阈值来监控慢日志个数和最长耗时,超出阈值则通过短信、微信、邮件等方式进行报警通知。这样,有了监控系统后,就可以快速发现问题、定位故障,并协助运维人员进行资源规划、性能观察等操作。

🎉 高可用

上述提到的主从同步和哨兵机制可以保证Redis服务的高可用,还有多级缓存、冷热分离可以保证高可用。

商品详情页在电商平台的秒杀场景中,涉及商品信息的动态展示和高并发访问,需要通过一系列手段保证系统的高并发和高可用,通过采用Nginx+Lua架构、CDN缓存、本地应用缓存和分布式缓存等多种技术手段,实现了商品详情页的动态化和缓存优化,提高用户访问商品详情页的速度和体验。同时,通过开关前置化和缓存过期机制,确保了缓存数据的有效性,降低了对后端数据库的访问压力。

7.12.1主从同步和哨兵机制

主从复制通常采用异步方式,可能导致主节点数据尚未完全复制至从节点,主节点便已故障,导致数据丢失。因此,需要控制复制数据的时长和ACK延迟,降低数据丢失风险。

主从切换过程通常使用哨兵机制。但在主节点正常运行时,可能因与某从节点连接中断,哨兵误判主节点已故障。在此情况下,哨兵可能启动选举,将某从节点升级为主节点,导致集群出现两个主节点,发生脑裂。旧主节点恢复网络后,将被升级为从节点并挂载至新主节点,导致自身数据丢失,并需从新主节点复制数据。而新主节点并未包含后续客户端写入的数据,导致这些数据丢失。为降低数据丢失风险,可设置连接主节点最少的从节点数量和从节点连接主节点最大的延迟时间,若主节点与从节点断开连接,且从节点超过阈值时间未收到ACK消息,则拒绝客户端的写请求,将数据丢失控制在可控范围。

7.12.2多级缓存

Java多级缓存是一种常见的优化策略,可以有效地提高系统的性能和响应速度。

1.浏览器缓存

在页面间跳转时,从本地缓存获取数据;或在打开新页面时,根据Last-Modified头来CDN验证数据是否过期,减少数据传输量。

CDN缓存当用户点击商品图片或链接时,从最近的CDN节点获取数据,而非回源到北京机房,提升访问性能。

2.服务端应用本地缓存

采用Nginx+Lua架构,通过HttpLuaModule模块的shared dict或内存级Proxy Cache来减少带宽。

3.一致性哈希

在电商场景中,使用商品编号/分类作为哈希键,提高URL命中率。

4.mget优化

根据商品的其他维度数据(如分类、面包屑、商家等),先从本地缓存读取,如不命中则从远程缓存获取。这个优化减少了一半以上的远程缓存流量。

5.服务端缓存

(1)将缓存存储在内存、SSD和JIMDB中,实现读写性能和持久化的平衡。

(2)对热门商品和访问量较大的页面进行缓存,降低数据库压力。

(3)使用Nignx缓存:存储数据量少但访问量高的热点数据,例如双11或者618活动。

(4)使用JVM本地缓存:存储数据量适中访问量高的热点数据,例如网站首页数据。

(5)使用Redis缓存:存储数据量很大,访问量较高的普通数据,例如商品信息。

6.商品详情页数据获取

(1)用户打开商品详情页时,先从本地缓存获取基本数据,如商品ID、商品名称和价格等。

(2)根据用户浏览历史和搜索记录,动态加载其他维度数据,如分类、商家信息和评论等。

7.Nginx+Lua架构

(1)使用Nginx作为反向代理和负载均衡器,将请求转发给后端应用。

(2)使用Lua脚本实现动态页面渲染,并对商品详情页数据进行缓存。

(3)重启应用秒级化,重启速度快,且不会丢失共享字典缓存数据。

(4)需求上线速度化,可以快速上线和重启应用,减少抖动。

(5)在Nginx上做开关,设置缓存过期时间,当缓存数据过期时,强制从后端应用获取最新数据,并更新缓存。

7.12.3冷热分离

冷热分离的具体步骤:

(1)分析现有系统的数据类型和访问模式,了解各类数据的冷热程度。

(2)确定合适的冷热分离策略和方案,以优化数据存储和管理。

(3)设计冷热分离架构,为热数据和冷数据选择合适的存储介质、存储策略以及数据同步机制。

(4)将冷数据从热存储介质迁移到冷存储介质,可以采用全量迁移和增量迁移的方式。

(5)对热数据进行有效管理,包括访问控制、数据安全、性能监控等,以确保数据的安全性和可用性。

(6)对冷数据进行持久化、备份、归档等操作,以防止数据丢失并确保数据的可恢复性。

(7)设计合适的故障转移和恢复策略,如主从复制、多副本存储、故障检测与恢复等,以确保系统在故障或恢复时的稳定运行。

(8)在冷热分离后对系统性能进行优化,包括优化热存储介质的性能监控、调整存储结构、调整缓存策略等。

(9)持续监控数据同步、性能指标、故障排查与修复,确保系统的稳定运行。

以实际案例进行说明:

案例1:在线购物网站的商品库存管理系统

(1)热数据:用户频繁访问的商品信息,如商品名称、价格、库存量等,需要快速响应和低延迟。

(2)冷数据:用户访问较少的商品信息,对响应速度要求较低,但对数据安全和完整性要求较高。如商品的详细描述、评价、历史价格等。

案例2:在线音乐平台的曲库管理系统

(1)热数据:用户经常访问的热门歌曲,如排行榜前10名、新上架的歌曲等,存储在高速且高可靠性的SSD硬盘Redis缓存中,以确保快速的数据访问和响应速度。

(2)冷数据:用户较少访问的歌曲,如过时的经典歌曲、小众音乐等,存储在低成本且大容量的存储介质(HDFS、Ceph、S3)中,以节省成本并存储大量历史数据。

案例3:在线求职招聘网站的职位信息管理系统

(1)热数据:用户经常访问的热门职位信息,如招聘需求高的职位、高薪职位、职位信息的基本描述、薪资范围、投递人数等。

(2)冷数据:用户较少访问的职位信息,如停招职位的详细描述、过期职位、历史招聘情况等。

小结:

在冷数据(如历史数据、归档数据等)存储场景中,使用RocksDB作为Key-Value分布式存储引擎,存储大量数据,进行数据备份和恢复,以确保在故障或系统恢复时能够快速恢复数据,节省成本并提高存储空间利用率。

在热数据(如实时更新的数据、用户操作日志等)存储场景中,使用Redis缓存支持各种高并发场景,提升响应速度。

通过以上步骤,可以有效地对冷热数据进行分离,从而实现更高效、更安全的数据存储和管理。

🎉 缓存雪崩、穿透、击穿、热点缓存重构、缓存失效

从前,有一个叫做小明的程序员,他的网站被越来越多的用户访问,于是他决定使用Redis缓存来提高网站性能。

一天,大雪纷飞,小明的服务器突然停机了。当服务器重新启动后,所有的缓存都失效了。这就是Redis缓存雪崩的场景。

为了避免Redis缓存雪崩,小明决定使用多级缓存和缓存预热等技术手段。他设置了多个Redis实例,同时监听同一个缓存集群。当一个实例出现问题时,其他实例可以顶替它的功能。并且,他在低访问时间段主动向缓存中写入数据,以提前预热缓存。

然而,小明并没有想到缓存穿透的问题。有些用户在请求缓存中不存在的数据时,会频繁地向数据库查询,从而拖慢服务器响应时间。这就是Redis缓存穿透的场景。

为了避免Redis缓存穿透,小明决定使用布隆过滤器等技术手段。布隆过滤器可以高效地过滤掉不存在的数据,从而减少数据库查询次数。

不久之后,小明又遇到了缓存击穿的问题。某一个热门商品被多个用户同时请求,导致缓存无法承受压力,最终请求直接打到了数据库。这就是Redis缓存击穿的场景。

为了避免Redis缓存击穿,小明决定使用分布式锁等技术手段。分布式锁可以保证同一时间只有一个用户请求数据库,避免了缓存被高并发压垮的情况。

最后,小明遇到了缓存热点重构的问题。某一个商品的热度突然升高,导致缓存集中在这个商品上,其他商品的缓存无法承受压力。这就是缓存热点重构的场景。

为了避免缓存热点重构,小明决定使用数据预热等技术手段。他在缓存中设置过期时间,同时在低访问时间段主动重构热点商品的缓存,以避免缓存集中在某一个商品上。

技术解决方案和手段:

(1)多级缓存和缓存预热:适用于缓存雪崩场景,可以提前将数据存储到缓存中,避免缓存雪崩。

(2)布隆过滤器:适用于缓存穿透场景,可以高效地过滤不存在的数据,减少数据库查询次数。

(3)分布式锁:适用于缓存击穿场景,可以保证同一时间只有一个用户请求数据库,避免了缓存被高并发压垮的情况。

(4)数据预热:适用于缓存热点重构场景,可以在低访问时间段主动重构热点商品的缓存,避免缓存集中在某一个商品上。

优缺点对比:

(1)多级缓存和缓存预热:优点是能够提前将数据存储到缓存中,避免缓存雪崩;缺点是需要占用更多的内存空间,同时预热时间过长可能会拖慢服务器响应速度。

(2)布隆过滤器:优点是可以高效地过滤不存在的数据,减少数据库查询次数;缺点是无法完全避免缓存穿透,同时需要占用一定的内存空间。

(3)分布式锁:优点是可以保证同一时间只有一个用户请求数据库,避免了缓存被高并发压垮的情况;缺点是会增加系统的复杂度,可能引入单点故障等问题。

(4)数据预热:优点是可以避免缓存热点重构的问题;缺点是需要占用更多的内存空间,同时需要在低访问时间段主动重构缓存。

总之,不同的技术解决方案和手段都有其优缺点。程序员需要根据实际情况选择适合自己的方案,并且不断地优化和改进,以提高系统的性能和稳定性。


相关文章
|
2天前
|
Java
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
12 0
|
2天前
|
安全 Java 程序员
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
6 0
|
3天前
|
存储 Java 数据库连接
使用Java开发桌面应用程序
使用Java开发桌面应用程序
14 0
|
3天前
|
Java API 开发工具
java与Android开发入门指南
java与Android开发入门指南
11 0
|
3天前
|
分布式计算 负载均衡 Java
构建高可用性Java应用:介绍分布式系统设计与开发
构建高可用性Java应用:介绍分布式系统设计与开发
8 0
|
3天前
|
前端开发 安全 Java
使用Spring框架加速Java开发
使用Spring框架加速Java开发
7 0
|
3天前
|
前端开发 JavaScript Java
Java与Web开发的结合:JSP与Servlet
Java与Web开发的结合:JSP与Servlet
8 0
|
3天前
|
设计模式 算法 Java
设计模式在Java开发中的应用
设计模式在Java开发中的应用
15 0
|
3天前
|
监控 Java Maven
揭秘Java Agent技术:解锁Java工具开发的新境界
作为JDK提供的关键机制,Java Agent技术不仅为Java工具的开发者提供了一个强大的框架,还为性能监控、故障诊断和动态代码修改等领域带来了革命性的变革。本文旨在全面解析Java Agent技术的应用场景以及实现方式,特别是静态加载模式和动态加载模式这两种关键模式。
22 0
|
4天前
|
存储 Java 开发者
探索Java开发中触发空指针异常的场景
作为一名后端开发者在Java编程的世界中,想必大家对空指针并不陌生,空指针异常是一种常见而又令人头疼的问题,它可能会在我们最不经意的时候突然出现,给我们的代码带来困扰,甚至导致系统的不稳定性,而且最可怕的是有时候不能及时定位到它的具体位置。针对这个问题,我们需要深入了解触发空指针异常的代码场景,并寻找有效的方法来识别和处理这些异常情况,而且我觉得空指针异常是每个Java开发者都可能面临的挑战,但只要我们深入了解它的触发场景,并采取适当的预防和处理措施,我们就能够更好地应对这个问题。那么本文就来分享一下实际开发中一些常见的触发空指针异常的代码场景,并分享如何有效地识别和处理这些异常情况。
19 1
探索Java开发中触发空指针异常的场景