<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Lhy&#039;s blog</title>
	<atom:link href="https://blog.lhyshome.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.lhyshome.com</link>
	<description>welcome</description>
	<lastBuildDate>Thu, 18 Jun 2026 10:01:02 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>
<site xmlns="com-wordpress:feed-additions:1">219834889</site>	<item>
		<title>【Java面试】场景题</title>
		<link>https://blog.lhyshome.com/2026/06/18/562/</link>
					<comments>https://blog.lhyshome.com/2026/06/18/562/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 07:18:55 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=562</guid>

					<description><![CDATA[线上故障排查类 线上服务突然CPU飙升到100%，但GC次数正常，如何快速定位？ 系统频繁发生FGC，但堆内存… <span class="read-more"><a href="https://blog.lhyshome.com/2026/06/18/562/">Read More: 【Java面试】场景… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">线上故障排查类</h1>



<h2 class="wp-block-heading"><strong>线上服务突然CPU飙升到100%，但GC次数正常，如何快速定位？</strong></h2>



<ul class="wp-block-list">
<li><strong>核心排查思路（黄金5分钟）</strong>&nbsp;：
<ol start="1" class="wp-block-list">
<li><strong><code>top -H -p &lt;pid&gt;</code></strong>&nbsp;查看进程中哪个<strong>线程（LWP）</strong>&nbsp;消耗CPU最高，记录线程ID（十进制）。</li>



<li><code>printf "%x\n" &lt;线程ID&gt;</code>&nbsp;转换为十六进制。</li>



<li><strong><code>jstack &lt;pid&gt; | grep -A 20 &lt;十六进制线程ID&gt;</code></strong>&nbsp;查看该线程栈。</li>



<li><strong>根因分析（四种常见情况）</strong>&nbsp;：
<ul class="wp-block-list">
<li><strong>死循环/空轮询</strong>：如&nbsp;<code>while(true)</code>&nbsp;未设置休眠，多见于Netty的NioEventLoop bug或业务自旋锁。</li>



<li><strong>频繁Full GC（但你说GC正常，排除）</strong>。</li>



<li><strong>正则表达式回溯</strong>：如&nbsp;<code>(a+)+b</code>&nbsp;匹配超长字符串，导致CPU飙高，通过<code>jstack</code>定位到Pattern匹配行。</li>



<li><strong>序列化/反序列化</strong>：如Jackson解析超大JSON，占用大量CPU计算。</li>
</ul>
</li>
</ol>
</li>



<li><strong>补充工具</strong>：<code>arthas</code>&nbsp;的&nbsp;<code>thread -n 3</code>&nbsp;一键抓取最忙的3个线程，比手动<code>jstack</code>效率高10倍。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>系统频繁发生FGC，但堆内存（Heap）占用很低，可能是什么原因？</strong></h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>面试官陷阱</strong>：绝大多数人只会看堆内存，忽视了<strong>非堆内存（Metaspace/DirectBuffer）</strong>。</p>
</blockquote>



<ul class="wp-block-list">
<li><strong>根因排查清单</strong>：
<ol start="1" class="wp-block-list">
<li><strong>Metaspace（元空间）膨胀</strong>：大量动态生成类（CGLIB代理、Groovy脚本、JSP编译）导致Metaspace阈值触发FGC。使用&nbsp;<code>jstat -gc &lt;pid&gt;</code>&nbsp;查看&nbsp;<code>MC</code>（当前Metaspace容量）和&nbsp;<code>MU</code>（已使用），若接近&nbsp;<code>MaxMetaspaceSize</code>&nbsp;则确证。</li>



<li><strong>Direct Memory（直接内存）泄漏</strong>：NIO（Netty）使用&nbsp;<code>ByteBuffer.allocateDirect</code>&nbsp;未释放，虽然不在Heap内，但FGC会尝试回收DirectMemory。使用&nbsp;<code>jcmd &lt;pid&gt; VM.native_memory</code>&nbsp;查看。</li>



<li><strong><code>System.gc()</code>&nbsp;显式调用</strong>：某些框架（RMI、NIO）会主动触发FGC，加&nbsp;<code>-XX:+DisableExplicitGC</code>&nbsp;屏蔽（但谨慎）。</li>



<li><strong>堆外内存不足</strong>：<code>-XX:MaxDirectMemorySize</code>&nbsp;设置过小。</li>
</ol>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>线上数据库连接池突然爆满（Active Connections飙高），应用无响应，如何排查？</strong></h2>



<ul class="wp-block-list">
<li><strong>排查步骤</strong>：
<ol start="1" class="wp-block-list">
<li><strong>先摘流量</strong>（网关切流或K8s删Pod），防止故障蔓延。</li>



<li><strong><code>jstack</code>&nbsp;导出线程栈</strong>，搜索&nbsp;<code>getConnection</code>&nbsp;或&nbsp;<code>datasource</code>&nbsp;关键字，看哪些线程在等待获取连接。</li>



<li><strong>检查慢SQL</strong>：是否在事务中调用了第三方RPC接口（如HTTP超时），导致数据库连接一直不释放。</li>



<li><strong>检查连接池配置</strong>：
<ul class="wp-block-list">
<li><code>maxActive</code>&nbsp;是否合理（通常50~100）。</li>



<li><code>maxWait</code>&nbsp;是否配置（若为-1无限等待，线程全部阻塞）。</li>



<li><strong>是否忘了在finally中关闭Connection</strong>（虽然Druid/Hikari有泄露检测，需开启&nbsp;<code>leakDetectionThreshold</code>）。</li>
</ul>
</li>
</ol>
</li>



<li><strong>紧急止损</strong>：重启应用（瞬时释放连接），然后临时调大&nbsp;<code>maxActive</code>，事后优化慢SQL或拆分长事务。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">高并发与分布式架构类</h1>



<h2 class="wp-block-heading"><strong>设计一个“百万级QPS”的签到/点赞计数器，如何保证数据绝对不丢且延迟小于5ms？</strong></h2>



<ul class="wp-block-list">
<li><strong>方案设计（多级缓存 + 异步刷盘）</strong>&nbsp;：
<ol start="1" class="wp-block-list">
<li><strong>L1（本地内存）</strong>&nbsp;：每个JVM实例内使用&nbsp;<code>ConcurrentHashMap</code>&nbsp;+ 定时聚合（如每1秒）。写请求直接操作内存中的原子变量（<code>LongAdder</code>），响应在微秒级。</li>



<li><strong>L2（Redis）</strong>&nbsp;：本地聚合后的增量数据，使用<strong>Lua脚本</strong>原子累加到Redis（<code>INCRBY</code>）。</li>



<li><strong>异步落库</strong>：Redis中的计数器值，通过<strong>定时任务</strong>（每5秒）或<strong>MQ</strong>&nbsp;异步写入DB（或HBase）。</li>
</ol>
</li>



<li><strong>防丢机制</strong>：
<ul class="wp-block-list">
<li>本地内存聚合时，记录<strong>批次ID（BatchId）</strong>。若JVM突然宕机，重启后对比Redis中的最新值，未落库的批次从Redis反查补偿（依赖Redis AOF持久化）。</li>



<li>使用<strong>双缓冲队列</strong>（一个写，一个刷），刷盘时交换指针，避免加锁。</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>RocketMQ/Kafka消费时，因下游DB抖动导致消费积压，且已有大量消息在队列中，如何快速恢复？</strong></h2>



<ul class="wp-block-list">
<li><strong>止损三步走</strong>：
<ol start="1" class="wp-block-list">
<li><strong>临时扩容消费者实例</strong>（前提是Topic的分区数 &gt; 消费者数）。若分区数固定无法扩容，启动<strong>临时应急消费者</strong>，只做消息<code>ack</code>，将消息<strong>转发至新的临时Topic</strong>，用更多的分区数并行处理。</li>



<li><strong>开启批量处理</strong>：消费者改为拉取一批消息（如一次拉取1000条），使用<strong>批量SQL（如Batch Insert/Update）</strong>，极大减少DB交互次数。</li>



<li><strong>降级非核心逻辑</strong>：如发送短信、写入日志ES等操作暂时关闭或设置为异步丢弃。</li>
</ol>
</li>



<li><strong>事后根治</strong>：分析下游DB慢的原因（索引缺失/锁竞争），增加DB从库读写分离。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>分布式锁（Redis）在极端情况下（主从切换）失效，导致超卖，如何兜底？</strong></h2>



<ul class="wp-block-list">
<li><strong>问题复现</strong>：线程A在Master获取锁（<code>SET key value NX EX 30</code>），Master宕机未同步到Slave，Slave升级为Master，线程B获取到了相同的锁，导致超卖。</li>



<li><strong>兜底方案（DB层面最终防御）</strong>&nbsp;：
<ol start="1" class="wp-block-list">
<li><strong>数据库乐观锁</strong>是最后一道防线。扣库存SQL必须加条件：<code>UPDATE inventory SET amount = amount - #{buyNum} WHERE product_id = #{id} AND amount &gt;= #{buyNum}</code>。</li>



<li>若影响行数为0，抛异常回滚业务，人工补偿或触发重试。</li>
</ol>
</li>



<li><strong>关于RedLock的讨论（加分项）</strong>&nbsp;：
<ul class="wp-block-list">
<li>面试官若问及RedLock（红锁），需指出Martin Kleppmann的批判：<strong>红锁强依赖系统时钟同步（NTP）</strong>&nbsp;，且跨机房网络延迟会导致活锁。在大厂实践中，<strong>绝大多数场景下单Redis主从 + DB乐观锁</strong>&nbsp;已足够，引入RedLock成本远大于收益。</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">数据库与缓存一致性类</h1>



<h2 class="wp-block-heading"><strong>缓存（Redis）和数据库（MySQL）双写一致性，如何保证？延迟双删策略存在什么缺陷？</strong></h2>



<ul class="wp-block-list">
<li><strong>经典方案</strong>：<strong>Cache-Aside（旁路缓存）</strong>&nbsp;模式 +&nbsp;<strong>延迟双删</strong>。
<ul class="wp-block-list">
<li>写操作：更新DB → 删除Redis缓存（等待500ms后再次删除，防止并发读将旧数据写回）。</li>
</ul>
</li>



<li><strong>延迟双删缺陷</strong>：
<ul class="wp-block-list">
<li>第二次删除延迟时间<strong>难以预估</strong>（业务SQL执行时间飘忽不定），若时间太短，第二次删除来不及，脏数据依然存在。</li>



<li>若第二次删除失败（Redis超时），没有任何补偿机制。</li>
</ul>
</li>



<li><strong>终极方案（大厂主流）</strong>&nbsp;：
<ul class="wp-block-list">
<li>订阅&nbsp;<strong>Canal（MySQL Binlog增量解析）</strong>，将变更事件发送至MQ，消费者异步删除/更新Redis。</li>



<li><strong>优点</strong>：解耦、保证最终一致性，且不会污染业务主流程（主流程只管更新DB和发送MQ即可）。</li>



<li><strong>注意</strong>：缓存读取逻辑需容忍短暂不一致（如设置过期时间兜底，例如5分钟自动失效）。</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>分库分表后，如何生成全局唯一的分布式ID？雪花算法（Snowflake）遇到时钟回拨怎么办？</strong></h2>



<ul class="wp-block-list">
<li><strong>常见方案</strong>：雪花算法（64位：符号位1 + 时间戳41 + 机器ID10 + 序列号12）。</li>



<li><strong>时钟回拨处理（重点）</strong>&nbsp;：
<ol start="1" class="wp-block-list">
<li><strong>记录上次生成ID的时间戳（lastTimestamp）</strong>&nbsp;。若当前时间 &lt; lastTimestamp，判定为时钟回拨。</li>



<li><strong>保守策略</strong>：抛出异常，通知运维人工校准时钟（大厂标准做法）。</li>



<li><strong>等待策略</strong>：<code>Thread.sleep(差值)</code>，等待时钟追上（仅适用于回拨毫秒级，若回拨数秒则不可行）。</li>



<li><strong>备用方案</strong>：使用<strong>ZooKeeper持久化序列号</strong>或<strong>数据库号段模式（Leaf-segment）</strong>&nbsp;作为兜底。</li>
</ol>
</li>



<li><strong>面试加分</strong>：美团的&nbsp;<strong>Leaf</strong>&nbsp;方案（号段双Buffer），每次获取一个号段（如1000-2000），用完再取，不依赖时钟，性能极高且绝对不重复。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">微服务治理类</h1>



<h2 class="wp-block-heading"><strong>线上Feign调用频繁超时，如何快速定位是网络问题、服务端处理慢还是GC问题？</strong></h2>



<ul class="wp-block-list">
<li><strong>分层排查法（网络 → 服务端 → JVM）</strong>&nbsp;：
<ol start="1" class="wp-block-list">
<li><strong>网络层</strong>：在调用方执行&nbsp;<code>telnet &lt;ip&gt; &lt;port&gt;</code>&nbsp;或&nbsp;<code>ping</code>，确认网络连通性。使用&nbsp;<code>tcpdump</code>&nbsp;抓包分析SYN重传。</li>



<li><strong>服务端线程池</strong>：查看服务端&nbsp;<code>jstack</code>，是否有大量线程在&nbsp;<code>WAITING</code>（如数据库连接池耗尽）。</li>



<li><strong>服务端GC</strong>：<code>jstat -gcutil</code>&nbsp;查看GC频率，若FGC频繁，STW导致响应变慢。</li>



<li><strong>RPC层监控</strong>：查看Skywalking/Zipkin的Trace，定位具体哪一步耗时最长（如下游调下游）。</li>
</ol>
</li>



<li><strong>临时止损</strong>：调大Feign的超时时间（<code>connectTimeout</code>&nbsp;/&nbsp;<code>readTimeout</code>），开启<strong>Feign重试</strong>（注意幂等性），或快速熔断返回降级数据。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Spring Cloud Gateway网关通过Sentinel限流，如何做到“根据用户等级（VIP/普通）”差异化限流？</strong></h2>



<ul class="wp-block-list">
<li><strong>核心思路</strong>：使用Sentinel的<strong>热点参数限流（ParamFlow）</strong>&nbsp;。
<ol start="1" class="wp-block-list">
<li>自定义&nbsp;<code>RequestOriginParser</code>&nbsp;解析请求头中的&nbsp;<code>userId</code>，查询用户等级（VIP/普通）。</li>



<li>将用户等级作为参数传递给Sentinel（<code>SphU.entry("resourceName", EntryType.IN, 1, userLevel)</code>）。</li>



<li>配置<strong>热点规则</strong>：对参数&nbsp;<code>userLevel</code>&nbsp;分别设置阈值，VIP用户QPS=1000，普通用户QPS=100。</li>
</ol>
</li>



<li><strong>进阶</strong>：若用户量极大，本地缓存用户等级（Caffeine），每5分钟同步一次，避免每次限流都查DB/Redis造成性能损耗。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Spring Boot 3.x 使用AOT编译和GraalVM原生镜像后，哪些功能会受限？</strong></h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">*这是2025-2026年的新兴热点。*</p>
</blockquote>



<ul class="wp-block-list">
<li><strong>受限功能</strong>：
<ol start="1" class="wp-block-list">
<li><strong>动态代理（CGLIB/JDK Proxy）</strong>&nbsp;：GraalVM静态编译时无法提前确定代理类，需在&nbsp;<code>reflect-config.json</code>&nbsp;中预声明。</li>



<li><strong>反射/内省</strong>：所有反射调用必须在编译时注册（如Jackson序列化、MyBatis实体类）。</li>



<li><strong>Resource文件加载</strong>：<code>ClassPathResource</code>&nbsp;需显式在&nbsp;<code>resource-config.json</code>&nbsp;中配置。</li>
</ol>
</li>



<li><strong>解决方案</strong>：Spring Boot 3.x提供了&nbsp;<strong><code>@Hint</code></strong>&nbsp;注解和&nbsp;<strong>AOT Agent</strong>（运行测试时捕获动态调用），生成配置文件。迁移成本较高，适合云原生Serverless场景（启动毫秒级），传统微服务慎用。</li>
</ul>



<h2 class="wp-block-heading"><strong>设计一个秒杀系统的库存扣减方案。</strong></h2>



<ul class="wp-block-list">
<li><strong>架构分层</strong>：
<ol start="1" class="wp-block-list">
<li><strong>流量削峰</strong>：前端秒杀按钮置灰 + 验证码（防止脚本刷）；网关层限流（令牌桶，如 Sentinel）。</li>



<li><strong>库存预扣（Redis）</strong>：
<ul class="wp-block-list">
<li>使用&nbsp;<strong>Lua 脚本</strong>&nbsp;保证原子性：<code>if (redis.call('get', key) &gt; 0) then redis.call('decr', key) return 1 else return 0 end</code>。</li>



<li>此处为<strong>预扣（逻辑库存）</strong>，不直接操作 DB，响应毫秒级。</li>
</ul>
</li>



<li><strong>异步落库</strong>：
<ul class="wp-block-list">
<li>预扣成功后，生成订单消息发送至&nbsp;<strong>RocketMQ/Kafka</strong>，返回客户端“排队中”。</li>



<li>消费者拉取消息，执行 DB 扣减真实库存（使用&nbsp;<strong>乐观锁</strong>：<code>update inventory set amount=amount-? where id=? and amount&gt;=?</code>，若影响行数为0则记录失败）。</li>
</ul>
</li>



<li><strong>防超卖兜底</strong>：
<ul class="wp-block-list">
<li>Redis 扣减失败直接返回“已售罄”。</li>



<li>数据库扣减失败触发&nbsp;<strong>补偿回滚</strong>（将 Redis 库存 +1），通过消息重试或定时任务保证最终一致性。</li>
</ul>
</li>
</ol>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>分库分表后如何实现分页查询？</strong></h2>



<ul class="wp-block-list">
<li><strong>挑战</strong>：<code>limit 10, 10</code>&nbsp;在分库分表下需要每个分片都查询 20 条数据，然后归并排序取第 10~20 条，数据量越大性能越差（ShardingSphere 默认支持但伴随性能瓶颈）。</li>



<li><strong>解决方案</strong>：
<ol start="1" class="wp-block-list">
<li><strong>禁止跳页（游标滚动）</strong>：业务上改为“上一页/下一页”，使用&nbsp;<code>where id &gt; lastId order by id limit 10</code>。每条 SQL 在各分片查询，归并取最小 10 条即可，性能极高，适合瀑布流。</li>



<li><strong>ES 宽表冗余</strong>：将需要分页展示的字段同步到 ElasticSearch，依靠 ES 的分布式分页（<code>from/size</code>&nbsp;或&nbsp;<code>search_after</code>）完成。</li>



<li><strong>二次查询法（ShardingSphere 默认优化）</strong>：先查询到目标数据的主键 ID（例如 limit 1000,10，实际每条 SQL 只查 ID），再根据 ID 回表查询完整数据。</li>



<li><strong>数仓/汇总库</strong>：将分表数据按小时同步到一个只读的汇总库（单库），在该库进行分页（适合报表类慢查询）。</li>
</ol>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>突发流量导致订单接口超时，如何10分钟内止损？</strong></h2>



<p class="wp-block-paragraph"><strong>止损黄金十分钟（止血 &gt; 排查）</strong>：</p>



<ol start="1" class="wp-block-list">
<li><strong>扩容</strong>：快速增加 Pod 实例（K8s HPA 或手动调整副本数），或临时增加物理机/容器资源。</li>



<li><strong>限流与熔断</strong>：
<ul class="wp-block-list">
<li>在网关（如 Sentinel / Hystrix）配置<strong>快速失败</strong>（<code>QPS</code>&nbsp;限流，超出返回友好提示“系统繁忙”）。</li>



<li>开启<strong>熔断</strong>（错误率 &gt; 50% 自动熔断，走&nbsp;<code>fallback</code>&nbsp;兜底返回缓存数据）。</li>
</ul>
</li>



<li><strong>降级</strong>：非核心逻辑（如发送短信、更新积分）改为异步 MQ 处理，或直接关闭这些功能。</li>



<li><strong>慢查询定位</strong>：
<ul class="wp-block-list">
<li>连接 APM（Skywalking/Pinpoint）查看 Trace 链路，定位慢 SQL 或第三方 RPC 调用。</li>



<li>若发现慢 SQL，快速执行&nbsp;<strong>执行计划优化</strong>（加索引）或&nbsp;<strong>限流 SQL</strong>（SQL 网关层拦截）。</li>
</ul>
</li>



<li><strong>JVM 紧急处理</strong>：若 CPU 飙升，<code>jstack</code>&nbsp;查看是否大量 GC；若内存高，考虑&nbsp;<code>jmap</code>&nbsp;导出并调整&nbsp;<code>-Xmx</code>&nbsp;参数重启（临时方案）。</li>



<li><strong>消息队列解压</strong>：若下游处理慢，将请求直接投入 MQ 排队，立即返回“处理中”，异步通知结果。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>如何设计一个日500万次搜索的商品检索系统？</strong></h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">*注：500万次/天 ≈ 58 QPS，高峰期（10倍）约 580 QPS，属于中等规模。*</p>
</blockquote>



<ul class="wp-block-list">
<li><strong>技术选型</strong>：<strong>Elasticsearch 集群</strong>（倒排索引 + 分词器，如 IK）。</li>



<li><strong>架构设计</strong>：
<ol start="1" class="wp-block-list">
<li><strong>索引拆分</strong>：按商品类目或时间分索引（<code>index-{date}</code>），便于冷热数据分离。</li>



<li><strong>分片策略</strong>：设置合理的&nbsp;<code>number_of_shards</code>（约&nbsp;<code>数据总量 / 单分片建议大小(10~50GB)</code>），副本数至少 1 个（保证高可用）。</li>



<li><strong>写入链路</strong>：商品变更通过&nbsp;<strong>Canal</strong>&nbsp;监听 MySQL Binlog → 发送至 MQ → 消费者增量更新 ES（保证最终一致性）。</li>



<li><strong>查询链路</strong>：
<ul class="wp-block-list">
<li><strong>L1 本地缓存（Caffeine）</strong>：缓存高热度搜索词（Top 1000 关键词）的返回结果，TTL 5min。</li>



<li><strong>L2 Redis 缓存</strong>：缓存次热词，TTL 1min。</li>



<li><strong>L3 ES 查询</strong>：使用&nbsp;<code>bool</code>&nbsp;组合查询 +&nbsp;<code>function_score</code>&nbsp;按销量/评分自定义相关度。</li>
</ul>
</li>



<li><strong>性能优化</strong>：
<ul class="wp-block-list">
<li>禁用&nbsp;<code>*</code>&nbsp;通配符查询，使用精确字段（<code>keyword</code>）聚合。</li>



<li>开启&nbsp;<code>routing</code>&nbsp;路由，确保相同类目的商品落在同一分片，减少跨分片查询。</li>



<li>配置&nbsp;<code>refresh_interval</code>&nbsp;调大（如 30s），降低写入压力。</li>
</ul>
</li>
</ol>
</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/06/18/562/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">562</post-id>	</item>
		<item>
		<title>【Java面试】常用中间件</title>
		<link>https://blog.lhyshome.com/2026/03/17/511/</link>
					<comments>https://blog.lhyshome.com/2026/03/17/511/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 10:50:13 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=511</guid>

					<description><![CDATA[Zookeeper zookeeper 是什么？ zookeeper 是一个分布式的，开放源码的分布式应用程序… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/17/511/">Read More: 【Java面试】常用… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading"><strong>Zookeeper</strong></h1>



<h2 class="wp-block-heading" id="34j2n">zookeeper 是什么？</h2>



<p class="wp-block-paragraph">zookeeper 是一个分布式的，开放源码的分布式应用程序协调服务，是 google chubby 的开源实现，是 hadoop 和 hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件，提供的功能包括：配置维护、域名服务、分布式同步、组服务等。</p>



<h2 class="wp-block-heading" id="2gvtm">zookeeper 都有哪些功能？</h2>



<ul class="wp-block-list">
<li>集群管理：监控节点存活状态、运行请求等。</li>



<li>主节点选举：主节点挂掉了之后可以从备用的节点开始新一轮选主，主节点选举说的就是这个选举的过程，使用 zookeeper 可以协助完成这个过程。</li>



<li>分布式锁：zookeeper 提供两种锁：独占锁、共享锁。独占锁即一次只能有一个线程使用资源，共享锁是读锁共享，读写互斥，即可以有多线线程同时读同一个资源，如果要使用写锁也只能有一个线程使用。zookeeper可以对分布式锁进行控制。</li>



<li>命名服务：在分布式系统中，通过使用命名服务，客户端应用能够根据指定名字来获取资源或服务的地址，提供者等信息。</li>
</ul>



<h2 class="wp-block-heading" id="6drt1">zookeeper 有几种部署模式？</h2>



<p class="wp-block-paragraph">zookeeper 有三种部署模式：</p>



<ul class="wp-block-list">
<li>单机部署：一台集群上运行；</li>



<li>集群部署：多台集群运行；</li>



<li>伪集群部署：一台集群启动多个 zookeeper 实例运行。</li>
</ul>



<h2 class="wp-block-heading" id="euilc">zookeeper 怎么保证主从节点的状态同步？</h2>



<p class="wp-block-paragraph">zookeeper 的核心是原子广播，这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 zab 协议。zab 协议有两种模式，分别是恢复模式（选主）和广播模式（同步）。当服务启动或者在领导者崩溃后，zab 就进入了恢复模式，当领导者被选举出来，且大多数 server 完成了和 leader 的状态同步以后，恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态。</p>



<h2 class="wp-block-heading" id="61r4p">集群中为什么要有主节点？</h2>



<p class="wp-block-paragraph">在分布式环境中，有些业务逻辑只需要集群中的某一台机器进行执行，其他的机器可以共享这个结果，这样可以大大减少重复计算，提高性能，所以就需要主节点。</p>



<h2 class="wp-block-heading" id="124ln">集群中有 3 台服务器，其中一个节点宕机，这个时候 zookeeper 还可以使用吗？</h2>



<p class="wp-block-paragraph">可以继续使用，单数服务器只要没超过一半的服务器宕机就可以继续使用。</p>



<h2 class="wp-block-heading" id="6tj6a">说一下 zookeeper 的通知机制？</h2>



<p class="wp-block-paragraph">客户端端会对某个 znode 建立一个 watcher 事件，当该 znode 发生变化时，这些客户端会收到 zookeeper 的通知，然后客户端可以根据 znode 变化来做出业务上的改变。</p>



<h1 class="wp-block-heading"><strong>Elasticsearch</strong></h1>



<h2 class="wp-block-heading">解释一下Elasticsearch中的<code>text</code>和<code>keyword</code>字段类型的区别。</h2>



<ul class="wp-block-list">
<li><strong><code>text</code></strong>：该类型会进行<strong>分词</strong>处理，将长文本拆分为多个词项，然后建立倒排索引。它主要用于<strong>全文检索</strong>场景，例如搜索文章内容、商品描述等<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong><code>keyword</code></strong>：该类型<strong>不会进行分词</strong>，会将整个字段内容作为一个完整的词项存入倒排索引。它主要用于<strong>精确匹配、聚合（Aggregation）和排序</strong>，例如订单状态、用户ID、邮箱地址等<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。实际应用中，常用<code>multi-fields</code>特性为一个字段同时定义<code>text</code>和<code>keyword</code>类型，以应对不同场景<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>
</ul>



<h2 class="wp-block-heading">Elasticsearch是如何实现Master选举的？如何处理脑裂问题？</h2>



<ul class="wp-block-list">
<li><strong>选举流程</strong>：ES的ZenDiscovery模块负责选主。当一个节点发现集群中不存在活跃主节点时，它会发起选举。所有具备主节点资格（<code>node.master: true</code>）的节点会对所有已知的有资格的节点进行排序（通常按节点ID），并倾向于选举自己知道的第一个节点。如果一个节点获得了超过<strong>半数</strong>（<code>discovery.zen.minimum_master_nodes</code>配置的值）的选票，且该节点也选举了自己，那么它就成为新的主节点<a href="https://cloud.tencent.com.cn/developer/article/2405245?from=15425" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>脑裂预防</strong>：脑裂是指集群中出现多个主节点的现象。核心解决方法是<strong>正确配置 <code>discovery.zen.minimum_master_nodes</code></strong>，其值应设置为<strong>有资格成为主节点的节点数 <code>n/2 + 1</code></strong>（即超过半数）<a href="https://cloud.tencent.com.cn/developer/article/2405245?from=15425" target="_blank" rel="noreferrer noopener"></a>。这样，任何时刻最多只有一个分区拥有足够的节点数来选举出主节点，从而避免脑裂。</li>
</ul>



<h2 class="wp-block-heading">Elasticsearch有哪些不同的查询类型？请举例说明。</h2>



<ul class="wp-block-list">
<li><strong>精确查询</strong>：如<code>term</code>、<code>terms</code>、<code>range</code>查询，不对查询词进行分析，用于精确匹配条件<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>全文检索</strong>：如<code>match</code>、<code>match_phrase</code>、<code>multi_match</code>查询，会对查询文本进行分析（分词），然后去倒排索引中查找<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>复合查询</strong>：如<code>bool</code>查询，通过<code>must</code>、<code>should</code>、<code>filter</code>、<code>must_not</code>等子句组合多个查询条件。其中<code>filter</code>子句不计算相关性得分，且结果可被缓存，性能更好<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>聚合分析</strong>：如<code>terms</code>（分组统计）、<code>avg</code>（平均值）、<code>date_histogram</code>（时间直方图）等，用于数据分析<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a><a href="https://blog.csdn.net/qq_41377858/article/details/150592750" target="_blank" rel="noreferrer noopener"></a>。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/17/511/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">511</post-id>	</item>
		<item>
		<title>【Java面试】消息队列</title>
		<link>https://blog.lhyshome.com/2026/03/17/508/</link>
					<comments>https://blog.lhyshome.com/2026/03/17/508/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 10:46:53 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=508</guid>

					<description><![CDATA[RabbitMQ RabbitMQ 的使用场景有哪些？ RabbitMQ 有哪些重要的角色？ RabbitMQ… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/17/508/">Read More: 【Java面试】消息… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading" id="d60ul"><strong>RabbitMQ</strong></h1>



<h2 class="wp-block-heading" id="cdseg"><strong>RabbitMQ 的使用场景有哪些？</strong></h2>



<ul class="wp-block-list">
<li>抢购活动，削峰填谷，防止系统崩塌。</li>



<li>延迟信息处理，比如 10 分钟之后给下单未付款的用户发送邮件提醒。</li>



<li>解耦系统，对于新增的功能可以单独写模块扩展，比如用户确认评价之后，新增了给用户返积分的功能，这个时候不用在业务代码里添加新增积分的功能，只需要把新增积分的接口订阅确认评价的消息队列即可，后面再添加任何功能只需要订阅对应的消息队列即可。</li>
</ul>



<h2 class="wp-block-heading" id="c9b8o"><strong>RabbitMQ 有哪些重要的角色？</strong></h2>



<p class="wp-block-paragraph">RabbitMQ 中重要的角色有：生产者、消费者和代理：</p>



<ul class="wp-block-list">
<li>生产者：消息的创建者，负责创建和推送数据到消息服务器；</li>



<li>消费者：消息的接收方，用于处理数据和确认消息；</li>



<li>代理：就是 RabbitMQ 本身，用于扮演“快递”的角色，本身不生产消息，只是扮演“快递”的角色。</li>
</ul>



<h2 class="wp-block-heading" id="51v7u"><strong>RabbitMQ 有哪些重要的组件？</strong></h2>



<ul class="wp-block-list">
<li>ConnectionFactory（连接管理器）：应用程序与Rabbit之间建立连接的管理器，程序代码中使用。</li>



<li>Channel（信道）：消息推送使用的通道。</li>



<li>Exchange（交换器）：用于接受、分配消息。</li>



<li>Queue（队列）：用于存储生产者的消息。</li>



<li>RoutingKey（路由键）：用于把生成者的数据分配到交换器上。</li>



<li>BindingKey（绑定键）：用于把交换器的消息绑定到队列上。</li>
</ul>



<h2 class="wp-block-heading" id="7l0st"><strong>RabbitMQ 中 vhost 的作用是什么？</strong></h2>



<p class="wp-block-paragraph">vhost：每个 RabbitMQ 都能创建很多 vhost，我们称之为虚拟主机，每个虚拟主机其实都是 mini 版的RabbitMQ，它拥有自己的队列，交换器和绑定，拥有自己的权限机制。</p>



<h2 class="wp-block-heading" id="at35u"><strong>RabbitMQ 的消息是怎么发送的？</strong></h2>



<p class="wp-block-paragraph">首先客户端必须连接到 RabbitMQ 服务器才能发布和消费消息，客户端和 rabbit server 之间会创建一个 tcp 连接，一旦 tcp 打开并通过了认证（认证就是你发送给 rabbit 服务器的用户名和密码），你的客户端和 RabbitMQ 就创建了一条 amqp 信道（channel），信道是创建在“真实” tcp 上的虚拟连接，amqp 命令都是通过信道发送出去的，每个信道都会有一个唯一的 id，不论是发布消息，订阅队列都是通过这个信道完成的。</p>



<h2 class="wp-block-heading" id="6qvd3"><strong>RabbitMQ 怎么保证消息的稳定性？</strong></h2>



<ul class="wp-block-list">
<li>提供了事务的功能。</li>



<li>通过将 channel 设置为 confirm（确认）模式。</li>
</ul>



<h2 class="wp-block-heading" id="307js"><strong>RabbitMQ 怎么避免消息丢失？</strong></h2>



<ul class="wp-block-list">
<li>把消息持久化磁盘，保证服务器重启消息不丢失。</li>



<li>每个集群中至少有一个物理磁盘，保证消息落入磁盘。</li>
</ul>



<h2 class="wp-block-heading" id="r201"><strong>要保证消息持久化成功的条件有哪些？</strong></h2>



<ul class="wp-block-list">
<li>声明队列必须设置持久化 durable 设置为 true.</li>



<li>消息推送投递模式必须设置持久化，deliveryMode 设置为 2（持久）。</li>



<li>消息已经到达持久化交换器。</li>



<li>消息已经到达持久化队列。</li>
</ul>



<p class="wp-block-paragraph">以上四个条件都满足才能保证消息持久化成功。</p>



<h2 class="wp-block-heading" id="cjf1a"><strong>RabbitMQ 持久化有什么缺点？</strong></h2>



<p class="wp-block-paragraph">持久化的缺地就是降低了服务器的吞吐量，因为使用的是磁盘而非内存存储，从而降低了吞吐量。可尽量使用 ssd 硬盘来缓解吞吐量的问题。</p>



<h2 class="wp-block-heading" id="5fvcd"><strong>RabbitMQ 有几种广播类型？</strong></h2>



<ul class="wp-block-list">
<li>direct（默认方式）：最基础最简单的模式，发送方把消息发送给订阅方，如果有多个订阅者，默认采取轮询的方式进行消息发送。</li>



<li>headers：与 direct 类似，只是性能很差，此类型几乎用不到。</li>



<li>fanout：分发模式，把消费分发给所有订阅者。</li>



<li>topic：匹配订阅模式，使用正则匹配到消息队列，能匹配到的都能接收到。</li>
</ul>



<h2 class="wp-block-heading" id="fumpg"><strong>RabbitMQ 怎么实现延迟消息队列？</strong></h2>



<p class="wp-block-paragraph">延迟队列的实现有两种方式：</p>



<ul class="wp-block-list">
<li>通过消息过期后进入死信交换器，再由交换器转发到延迟消费队列，实现延迟功能；</li>



<li>使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。</li>
</ul>



<h2 class="wp-block-heading" id="aaq26"><strong>RabbitMQ 集群有什么用？</strong></h2>



<p class="wp-block-paragraph">集群主要有以下两个用途：</p>



<ul class="wp-block-list">
<li>高可用：某个服务器出现问题，整个 RabbitMQ 还可以继续使用；</li>



<li>高容量：集群可以承载更多的消息量。</li>
</ul>



<h2 class="wp-block-heading" id="ffcpn"><strong>RabbitMQ 节点的类型有哪些？</strong></h2>



<ul class="wp-block-list">
<li>磁盘节点：消息会存储到磁盘。</li>



<li>内存节点：消息都存储在内存中，重启服务器消息丢失，性能高于磁盘类型。</li>
</ul>



<h2 class="wp-block-heading" id="d8b52"><strong>RabbitMQ 集群搭建需要注意哪些问题？</strong></h2>



<ul class="wp-block-list">
<li>各节点之间使用“–link”连接，此属性不能忽略。</li>



<li>各节点使用的 erlang cookie 值必须相同，此值相当于“秘钥”的功能，用于各节点的认证。</li>



<li>整个集群中必须包含一个磁盘节点。</li>
</ul>



<h2 class="wp-block-heading" id="831it"><strong>RabbitMQ 每个节点是其他节点的完整拷贝吗？为什么？</strong></h2>



<p class="wp-block-paragraph">不是，原因有以下两个：</p>



<ul class="wp-block-list">
<li>存储空间的考虑：如果每个节点都拥有所有队列的完全拷贝，这样新增节点不但没有新增存储空间，反而增加了更多的冗余数据；</li>



<li>性能的考虑：如果每条消息都需要完整拷贝到每一个集群节点，那新增节点并没有提升处理消息的能力，最多是保持和单节点相同的性能甚至是更糟。</li>
</ul>



<h2 class="wp-block-heading" id="86l6b"><strong>RabbitMQ 集群中唯一一个磁盘节点崩溃了会发生什么情况？</strong></h2>



<p class="wp-block-paragraph">如果唯一磁盘的磁盘节点崩溃了，不能进行以下操作：</p>



<ul class="wp-block-list">
<li>不能创建队列</li>



<li>不能创建交换器</li>



<li>不能创建绑定</li>



<li>不能添加用户</li>



<li>不能更改权限</li>



<li>不能添加和删除集群节点</li>
</ul>



<p class="wp-block-paragraph">唯一磁盘节点崩溃了，集群是可以保持运行的，但你不能更改任何东西。</p>



<h2 class="wp-block-heading" id="2e0nq">RabbitMQ 对集群节点停止顺序有要求吗？</h2>



<p class="wp-block-paragraph">RabbitMQ 对集群的停止的顺序是有要求的，应该先关闭内存节点，最后再关闭磁盘节点。如果顺序恰好相反的话，可能会造成消息的丢失。</p>



<h2 class="wp-block-heading">如何保证消息不丢失（可靠性）？</h2>



<p class="wp-block-paragraph">保证消息的可靠传输需要从三个环节入手<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>：</p>



<ul class="wp-block-list">
<li><strong>生产者 -> RabbitMQ</strong>：开启<strong>发布确认（Publisher Confirm）机制</strong>。生产者发送消息后，RabbitMQ 会异步返回一个 <code>ack</code>（成功）或 <code>nack</code>（失败），生产者可根据结果重试<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://developer.aliyun.com/article/1324637" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>RabbitMQ 自身</strong>：开启<strong>持久化</strong>。
<ol start="1" class="wp-block-list">
<li><strong>队列持久化</strong>：创建队列时设置 <code>durable = true</code><a href="https://www.finclip.com/news/f/4164.html" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>消息持久化</strong>：发送消息时设置 <code>deliveryMode = 2</code><a href="https://www.finclip.com/news/f/4164.html" target="_blank" rel="noreferrer noopener"></a>。</li>
</ol>
</li>



<li><strong>RabbitMQ -> 消费者</strong>：关闭自动 <code>ack</code>，改为<strong>手动确认（Manual Ack）</strong>。消费者处理完业务逻辑后再手动确认，防止消费者宕机导致消息丢失<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>。</li>
</ul>



<h2 class="wp-block-heading">如何解决消息的重复消费问题（保证幂等性）？</h2>



<p class="wp-block-paragraph">消息重复通常是因为网络波动，消费者已消费但确认 (<code>ack</code>) 未送达 Broker，导致消息被重新投递<a href="https://cloud.tencent.cn/developer/article/2373992?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>。</p>



<ul class="wp-block-list">
<li><strong>核心思路</strong>：保证<strong>幂等性</strong>，即同一条消息执行多次与执行一次的效果相同<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.cn/developer/article/2373992?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>解决方案</strong>：消息体中带一个全局唯一的业务 ID（如订单号、支付 ID）<a href="https://www.bjpowernode.com/middlewaremst/rabbitmq.html" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.cn/developer/article/2373992?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>。
<ul class="wp-block-list">
<li><strong>方案一（数据库）</strong>：利用数据库主键唯一约束去重。消费前先根据业务 ID 查询，若记录已存在则直接返回。</li>



<li><strong>方案二（Redis）</strong>：用业务 ID 作为 Redis 的 key，通过 <code>setNx</code> 操作来判断是否处理过。</li>
</ul>
</li>
</ul>



<h2 class="wp-block-heading">什么是死信队列（DLX）？它有什么用？</h2>



<p class="wp-block-paragraph">死信队列是一种消息补偿机制。当消息成为<strong>死信</strong>时，会被重新发送到指定的交换机（称为死信交换机 DLX），进而进入绑定的死信队列<a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a><a href="https://www.finclip.com/news/f/4164.html" target="_blank" rel="noreferrer noopener"></a>。</p>



<ul class="wp-block-list">
<li><strong>消息成为死信的条件</strong><a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>：
<ol start="1" class="wp-block-list">
<li>消息被消费者拒绝且未重新入队（<code>basic.reject</code> 或 <code>basic.nack</code> 且 <code>requeue = false</code>）。</li>



<li>消息<strong>TTL（存活时间）</strong> 过期。</li>



<li>队列达到最大长度，无法再添加消息。</li>
</ol>
</li>



<li><strong>作用</strong>：可以监听死信队列，对失败的消息进行<strong>延迟处理、异常监控或人工干预</strong>。</li>
</ul>



<h2 class="wp-block-heading">如何实现延迟队列？</h2>



<p class="wp-block-paragraph">RabbitMQ 本身没有直接提供延迟队列，通常通过<strong>死信队列 + TTL</strong> 机制模拟<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>：</p>



<ol start="1" class="wp-block-list">
<li>创建一个队列，不设置消费者，并为其消息设置 <strong>TTL</strong>。</li>



<li>为该队列指定<strong>死信交换机（DLX）</strong>。</li>



<li>消息超时无人消费后，自动转发到死信交换机，再路由到绑定的死信队列。</li>



<li>消费者监听死信队列即可获取到“延迟”的消息。</li>
</ol>



<h2 class="wp-block-heading">如何处理消息堆积？</h2>



<ul class="wp-block-list">
<li><strong>临时扩容</strong>：<strong>增加消费者</strong>数量（前提是队列能支撑并行消费），或为消费者开启<strong>多线程</strong>处理。</li>



<li><strong>队列优化</strong>：对于大量消息积压，可以使用 RabbitMQ 的<strong>惰性队列</strong>，它会将消息直接存入磁盘，减少内存压力，但吞吐量会下降<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>紧急修复</strong>：如果消费者程序有 Bug，先暂停消费者，修复 Bug 后，再启动消费能力更强的程序来追赶进度。</li>
</ul>



<h1 class="wp-block-heading" id="83idb"><strong>Kafka</strong></h1>



<h2 class="wp-block-heading" id="blsj2">kafka 可以脱离 zookeeper 单独使用吗？为什么？</h2>



<p class="wp-block-paragraph">kafka 不能脱离 zookeeper 单独使用，因为 kafka 使用 zookeeper 管理和协调 kafka 的节点服务器。</p>



<h2 class="wp-block-heading" id="6hmbm">kafka 有几种数据保留的策略？</h2>



<p class="wp-block-paragraph">kafka 有两种数据保存策略：按照过期时间保留和按照存储的消息大小保留。</p>



<h2 class="wp-block-heading" id="cade3">kafka 同时设置了 7 天和 10G 清除数据，到第五天的时候消息达到了 10G，这个时候 kafka 将如何处理？</h2>



<p class="wp-block-paragraph">这个时候 kafka 会执行数据清除工作，时间和大小不论那个满足条件，都会清空数据。</p>



<h2 class="wp-block-heading" id="37avg">什么情况会导致 kafka 运行变慢？</h2>



<ul class="wp-block-list">
<li>cpu 性能瓶颈</li>



<li>磁盘读写瓶颈</li>



<li>网络瓶颈</li>
</ul>



<h2 class="wp-block-heading" id="7r3oa">使用 kafka 集群需要注意什么？</h2>



<ul class="wp-block-list">
<li>集群的数量不是越多越好，最好不要超过 7 个，因为节点越多，消息复制需要的时间就越长，整个群组的吞吐量就越低。</li>



<li>集群数量最好是单数，因为超过一半故障集群就不能用了，设置为单数容错率更高。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/17/508/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">508</post-id>	</item>
		<item>
		<title>【Java面试】数据库</title>
		<link>https://blog.lhyshome.com/2026/03/17/498/</link>
					<comments>https://blog.lhyshome.com/2026/03/17/498/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 09:21:30 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=498</guid>

					<description><![CDATA[数据库的三范式是什么？ 一张自增表里面总共有 7 条数据，删除了最后 2 条数据，重启 MySQL 数据库，又… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/17/498/">Read More: 【Java面试】数据… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="aet2b">数据库的三范式是什么？</h2>



<ul class="wp-block-list">
<li>第一范式：强调的是列的原子性，即数据库表的每一列都是不可分割的原子数据项。</li>



<li>第二范式：要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。</li>



<li>第三范式：任何非主属性不依赖于其它非主属性。</li>
</ul>



<h2 class="wp-block-heading" id="52jf2">一张自增表里面总共有 7 条数据，删除了最后 2 条数据，重启 MySQL 数据库，又插入了一条数据，此时 id 是几？</h2>



<ul class="wp-block-list">
<li>表类型如果是 MyISAM ，那 id 就是 8。</li>



<li>表类型如果是 InnoDB，那 id 就是 6。</li>
</ul>



<p class="wp-block-paragraph">InnoDB 表只会把自增主键的最大 id 记录在内存中，所以重启之后会导致最大 id 丢失。</p>



<h2 class="wp-block-heading" id="qouf">如何获取当前数据库版本？</h2>



<p class="wp-block-paragraph">使用 select version() 获取当前 MySQL 数据库版本。</p>



<h2 class="wp-block-heading" id="332r5">说一下 ACID 是什么？</h2>



<ul class="wp-block-list">
<li>Atomicity（原子性）：一个事务（transaction）中的所有操作，或者全部完成，或者全部不完成，不会结束在中间某个环节。事务在执行过程中发生错误，会被恢复（Rollback）到事务开始前的状态，就像这个事务从来没有执行过一样。即，事务不可分割、不可约简。</li>



<li>Consistency（一致性）：在事务开始之前和事务结束以后，数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。</li>



<li>Isolation（隔离性）：数据库允许多个并发事务同时对其数据进行读写和修改的能力，隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别，包括读未提交（Read uncommitted）、读提交（read committed）、可重复读（repeatable read）和串行化（Serializable）。</li>



<li>Durability（持久性）：事务处理结束后，对数据的修改就是永久的，即便系统故障也不会丢失。</li>
</ul>



<h2 class="wp-block-heading" id="3v4hf">char 和 varchar 的区别是什么？</h2>



<ul class="wp-block-list">
<li><strong>「char(n)」</strong>&nbsp;：固定长度类型，比如订阅 char(10)，当你输入”abc”三个字符的时候，它们占的空间还是 10 个字节，其他 7 个是空字节。
<ul class="wp-block-list">
<li>chat 优点：效率高；缺点：占用空间；适用场景：存储密码的 md5 值，固定长度的，使用 char 非常合适。</li>
</ul>
</li>



<li><strong>「varchar(n)」</strong>&nbsp;：可变长度，存储的值是每个值占用的字节再加上一个用来记录其长度的字节的长度。</li>
</ul>



<p class="wp-block-paragraph">所以，从空间上考虑 varcahr 比较合适；从效率上考虑 char 比较合适，二者使用需要权衡。</p>



<h2 class="wp-block-heading" id="3tk6r">float 和 double 的区别是什么？</h2>



<ul class="wp-block-list">
<li>float 最多可以存储 8 位的十进制数，并在内存中占 4 字节。</li>



<li>double 最可可以存储 16 位的十进制数，并在内存中占 8 字节。</li>
</ul>



<h2 class="wp-block-heading" id="5sl88">MySQL 的内连接、左连接、右连接有什么区别？</h2>



<p class="wp-block-paragraph">内连接关键字：inner join；左连接：left join；右连接：right join。</p>



<p class="wp-block-paragraph">内连接是把匹配的关联数据显示出来；左连接是左边的表全部显示出来，右边的表显示出符合条件的数据；右连接正好相反。</p>



<h2 class="wp-block-heading" id="eb0kj">MySQL 索引是怎么实现的？</h2>



<p class="wp-block-paragraph">索引是满足某种特定查找算法的数据结构，而这些数据结构会以某种方式指向数据，从而实现高效查找数据。</p>



<p class="wp-block-paragraph">具体来说 MySQL 中的索引，不同的数据引擎实现有所不同，但目前主流的数据库引擎的索引都是 B+ 树实现的，B+ 树的搜索效率，可以到达二分法的性能，找到数据区域之后就找到了完整的数据结构了，所有索引的性能也是更好的。</p>



<h2 class="wp-block-heading" id="4s7et">怎么验证 MySQL 的索引是否满足需求？</h2>



<p class="wp-block-paragraph">使用 explain 查看 SQL 是如何执行查询语句的，从而分析你的索引是否满足需求。</p>



<p class="wp-block-paragraph">explain 语法：explain select * from table where type=1。</p>



<h2 class="wp-block-heading" id="6kk4d">说一下数据库的事务隔离？</h2>



<p class="wp-block-paragraph">MySQL 的事务隔离是在 MySQL. ini 配置文件里添加的，在文件的最后添加：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">❝transaction-isolation = REPEATABLE-READ ❞</p>
</blockquote>



<p class="wp-block-paragraph">可用的配置值：READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE。</p>



<ul class="wp-block-list">
<li>READ-UNCOMMITTED：未提交读，最低隔离级别、事务未提交前，就可被其他事务读取（会出现幻读、脏读、不可重复读）。</li>



<li>READ-COMMITTED：提交读，一个事务提交后才能被其他事务读取到（会造成幻读、不可重复读）。</li>



<li>REPEATABLE-READ：可重复读，默认级别，保证多次读取同一个数据时，其值都和事务开始时候的内容是一致，禁止读取到别的事务未提交的数据（会造成幻读）。</li>



<li>SERIALIZABLE：序列化，代价最高最可靠的隔离级别，该隔离级别能防止脏读、不可重复读、幻读。</li>
</ul>



<ul class="wp-block-list">
<li><strong>「脏读」</strong>&nbsp;：表示一个事务能够读取另一个事务中还未提交的数据。比如，某个事务尝试插入记录 A，此时该事务还未提交，然后另一个事务尝试读取到了记录 A。</li>



<li><strong>「不可重复读」</strong>&nbsp;：是指在一个事务内，多次读同一数据。</li>



<li><strong>「幻读」</strong>&nbsp;：指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录，但是第二次同等条件下查询却有 n+1 条记录，这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据，同一个记录的数据内容被修改了，所有数据行的记录就变多或者变少了。</li>
</ul>



<h2 class="wp-block-heading" id="b219">说一下 MySQL 常用的引擎？</h2>



<ul class="wp-block-list">
<li>InnoDB 引擎：mysql 5.1 后默认的数据库引擎，提供了对数据库 acid 事务的支持，并且还提供了行级锁和外键的约束，它的设计的目标就是处理大数据容量的数据库系统。MySQL 运行的时候，InnoDB 会在内存中建立缓冲池，用于缓冲数据和索引。但是该引擎是不支持全文搜索，同时启动也比较的慢，它是不会保存表的行数的，所以当进行 select count(*) from table 指令的时候，需要进行扫描全表。由于锁的粒度小，写操作是不会锁定全表的,所以在并发度较高的场景下使用会提升效率的。</li>



<li>MyIASM 引擎：不提供事务的支持，也不支持行级锁和外键。因此当执行插入和更新语句时，即执行写操作的时候需要锁定这个表，所以会导致效率会降低。不过和 InnoDB 不同的是，MyIASM 引擎是保存了表的行数，于是当进行 select count(*) from table 语句时，可以直接的读取已经保存的值而不需要进行扫描全表。所以，如果表的读操作远远多于写操作时，并且不需要事务的支持的，可以将 MyIASM 作为数据库引擎的首选。</li>
</ul>



<h2 class="wp-block-heading" id="16ugm">说一下 MySQL 的行锁和表锁？</h2>



<p class="wp-block-paragraph">MyISAM 只支持表锁，InnoDB 支持表锁和行锁，默认为行锁。</p>



<ul class="wp-block-list">
<li>表级锁：开销小，加锁快，不会出现死锁。锁定粒度大，发生锁冲突的概率最高，并发量最低。</li>



<li>行级锁：开销大，加锁慢，会出现死锁。锁力度小，发生锁冲突的概率小，并发度最高。</li>
</ul>



<h2 class="wp-block-heading" id="fdfu5">说一下乐观锁和悲观锁？</h2>



<ul class="wp-block-list">
<li>乐观锁：每次去拿数据的时候都认为别人不会修改，所以不会上锁，但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。</li>



<li>悲观锁：每次去拿数据的时候都认为别人会修改，所以每次在拿数据的时候都会上锁，这样别人想拿这个数据就会阻止，直到这个锁被释放。</li>
</ul>



<p class="wp-block-paragraph">数据库的乐观锁需要自己实现，在表里面添加一个 version 字段，每次修改成功值加 1，这样每次修改的时候先对比一下，自己拥有的 version 和数据库现在的 version 是否一致，如果不一致就不修改，这样就实现了乐观锁。</p>



<h2 class="wp-block-heading" id="cevsm">MySQL 问题排查都有哪些手段？</h2>



<ul class="wp-block-list">
<li>使用 show processlist 命令查看当前所有连接信息。</li>



<li>使用 explain 命令查询 SQL 语句执行计划。</li>



<li>开启慢查询日志，查看慢查询的 SQL。</li>
</ul>



<h2 class="wp-block-heading" id="86lh0">如何做 MySQL 的性能优化？</h2>



<ul class="wp-block-list">
<li>为搜索字段创建索引。</li>



<li>避免使用 select *，列出需要查询的字段。</li>



<li>垂直分割分表。</li>



<li>选择正确的存储引擎。</li>
</ul>



<h2 class="wp-block-heading">B+树和B树的区别</h2>



<ul class="wp-block-list">
<li><strong>B树</strong>：所有节点都存数据，查找速度快（可能提前结束），但范围查询麻烦。</li>



<li><strong>B+树</strong>：只有叶子节点存数据，内部节点只存键，且叶子节点有链表连接。牺牲了“可能提前找到”的微小概率，换来了<strong>更稳定的查找性能</strong>和<strong>极致的范围查询能力</strong>，因此成为现代数据库索引的默认选择。</li>
</ul>



<h2 class="wp-block-heading">MySQL默认的事务隔离级别是？</h2>



<p class="wp-block-paragraph">MySQL默认的事务隔离级别是&nbsp;<strong>可重复读（REPEATABLE READ）</strong>。</p>



<h2 class="wp-block-heading">什么是事务？</h2>



<p class="wp-block-paragraph">事务是一组不可分割的数据库操作单元，这些操作要么全部成功执行，要么全部失败回滚，确保数据库从一种一致状态转换到另一种一致状态。</p>



<h2 class="wp-block-heading">事务有哪几个特性？</h2>



<p class="wp-block-paragraph">事务具有ACID特性：</p>



<ul class="wp-block-list">
<li><strong>原子性（Atomicity）</strong>：事务中的操作要么全部完成，要么全部不执行。</li>



<li><strong>一致性（Consistency）</strong>：事务执行前后，数据库完整性约束没有被破坏。</li>



<li><strong>隔离性（Isolation）</strong>：多个事务并发执行时，相互隔离，互不干扰。</li>



<li><strong>持久性（Durability）</strong>：事务一旦提交，对数据库的改变是永久性的。</li>
</ul>



<h1 class="wp-block-heading" id="eth9v"><strong>Hibernate</strong></h1>



<h2 class="wp-block-heading" id="2tffl"><strong>为什么要使用 hibernate？</strong></h2>



<ul class="wp-block-list">
<li>hibernate 是对 jdbc 的封装，大大简化了数据访问层的繁琐的重复性代码。</li>



<li>hibernate 是一个优秀的 ORM 实现，很多程度上简化了 DAO 层的编码功能。</li>



<li>可以很方便的进行数据库的移植工作。</li>



<li>提供了缓存机制，是程序执行更改的高效。</li>
</ul>



<h2 class="wp-block-heading" id="533i1"><strong>什么是 ORM 框架？</strong></h2>



<p class="wp-block-paragraph">ORM（Object Relation Mapping）对象关系映射，是把数据库中的关系数据映射成为程序中的对象。</p>



<p class="wp-block-paragraph">使用 ORM 的优点：提高了开发效率降低了开发成本、开发更简单更对象化、可移植更强。</p>



<h2 class="wp-block-heading" id="al1sh"><strong>hibernate 中如何在控制台查看打印的 SQL 语句？</strong></h2>



<p class="wp-block-paragraph">在 Config 里面把 hibernate. show_SQL 设置为 true 就可以。但不建议开启，开启之后会降低程序的运行效率。</p>



<h2 class="wp-block-heading" id="apqnc"><strong>hibernate 有几种查询方式？</strong></h2>



<p class="wp-block-paragraph">三种：hql、原生 SQL、条件查询 Criteria。</p>



<h2 class="wp-block-heading" id="5n9bc"><strong>hibernate 实体类可以被定义为 final 吗？</strong></h2>



<p class="wp-block-paragraph">实体类可以定义为 final 类，但这样的话就不能使用 hibernate 代理模式下的延迟关联提供性能了，所以不建议定义实体类为 final。</p>



<h2 class="wp-block-heading" id="ep062"><strong>在 hibernate 中使用 Integer 和 int 做映射有什么区别？</strong></h2>



<p class="wp-block-paragraph">Integer 类型为对象，它的值允许为 null，而 int 属于基础数据类型，值不能为 null。</p>



<h2 class="wp-block-heading" id="43gku"><strong>hibernate 是如何工作的？</strong></h2>



<ul class="wp-block-list">
<li>读取并解析配置文件。</li>



<li>读取并解析映射文件，创建 SessionFactory。</li>



<li>打开 Session。</li>



<li>创建事务。</li>



<li>进行持久化操作。</li>



<li>提交事务。</li>



<li>关闭 Session。</li>



<li>关闭 SessionFactory。</li>
</ul>



<h2 class="wp-block-heading" id="d808u"><strong>get()和 load()的区别？</strong></h2>



<ul class="wp-block-list">
<li>数据查询时，没有 OID 指定的对象，get() 返回 null；load() 返回一个代理对象。</li>



<li>load()支持延迟加载；get() 不支持延迟加载。</li>
</ul>



<h2 class="wp-block-heading" id="rlb5"><strong>说一下 hibernate 的缓存机制？</strong></h2>



<p class="wp-block-paragraph">hibernate 常用的缓存有一级缓存和二级缓存：</p>



<p class="wp-block-paragraph">一级缓存：也叫 Session 缓存，只在 Session 作用范围内有效，不需要用户干涉，由 hibernate 自身维护，可以通过：evict(object)清除 object 的缓存；clear()清除一级缓存中的所有缓存；flush()刷出缓存；</p>



<p class="wp-block-paragraph">二级缓存：应用级别的缓存，在所有 Session 中都有效，支持配置第三方的缓存，如：EhCache。</p>



<h2 class="wp-block-heading" id="cb9b1"><strong>hibernate 对象有哪些状态？</strong></h2>



<ul class="wp-block-list">
<li>临时/瞬时状态：直接 new 出来的对象，该对象还没被持久化（没保存在数据库中），不受 Session 管理。</li>



<li>持久化状态：当调用 Session 的 save/saveOrupdate/get/load/list 等方法的时候，对象就是持久化状态。</li>



<li>游离状态：Session 关闭之后对象就是游离状态。</li>
</ul>



<h2 class="wp-block-heading" id="6aqkk"><strong>在 hibernate 中 getCurrentSession 和 openSession 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>getCurrentSession 会绑定当前线程，而 openSession 则不会。</li>



<li>getCurrentSession 事务是 Spring 控制的，并且不需要手动关闭，而 openSession 需要我们自己手动开启和提交事务。</li>
</ul>



<h2 class="wp-block-heading" id="573le"><strong>hibernate 实体类必须要有无参构造函数吗？为什么？</strong></h2>



<p class="wp-block-paragraph">hibernate 中每个实体类必须提供一个无参构造函数，因为 hibernate 框架要使用 reflection api，通过调用 ClassnewInstance() 来创建实体类的实例，如果没有无参的构造函数就会抛出异常。</p>



<h1 class="wp-block-heading" id="3o6sc"><strong>MyBatis</strong></h1>



<h2 class="wp-block-heading" id="2pdi2">MyBatis 中 #{}和 ${}的区别是什么？</h2>



<p class="wp-block-paragraph"><code>\#{}</code>是预编译处理，<code>${}</code>是字符替换。在使用&nbsp;<code>#{}</code>时，MyBatis 会将 SQL 中的&nbsp;<code>#{}</code>替换成“?”，配合 PreparedStatement 的 set 方法赋值，这样可以有效的防止 SQL 注入，保证程序的运行安全。</p>



<h2 class="wp-block-heading" id="3v5l1">MyBatis 有几种分页方式？</h2>



<p class="wp-block-paragraph">分页方式：逻辑分页和物理分页。</p>



<p class="wp-block-paragraph"><strong>「逻辑分页：」</strong>&nbsp;使用 MyBatis 自带的 RowBounds 进行分页，它是一次性查询很多数据，然后在数据中再进行检索。</p>



<p class="wp-block-paragraph"><strong>「物理分页：」</strong>&nbsp;自己手写 SQL 分页或使用分页插件 PageHelper，去数据库查询指定条数的分页数据的形式。</p>



<h2 class="wp-block-heading" id="9bls7">RowBounds 是一次性查询全部结果吗？为什么？</h2>



<p class="wp-block-paragraph">RowBounds 表面是在“所有”数据中检索数据，其实并非是一次性查询出所有数据，因为 MyBatis 是对 jdbc 的封装，在 jdbc 驱动中有一个 Fetch Size 的配置，它规定了每次最多从数据库查询多少条数据，假如你要查询更多数据，它会在你执行 next()的时候，去查询更多的数据。就好比你去自动取款机取 10000 元，但取款机每次最多能取 2500 元，所以你要取 4 次才能把钱取完。只是对于 jdbc 来说，当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。</p>



<h2 class="wp-block-heading" id="4ngd3">MyBatis 逻辑分页和物理分页的区别是什么？</h2>



<ul class="wp-block-list">
<li>逻辑分页是一次性查询很多数据，然后再在结果中检索分页的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。</li>



<li>物理分页是从数据库查询指定条数的数据，弥补了一次性全部查出的所有数据的种种缺点，比如需要大量的内存，对数据库查询压力较大等问题。</li>
</ul>



<h2 class="wp-block-heading" id="ft9v2">MyBatis 是否支持延迟加载？延迟加载的原理是什么？</h2>



<p class="wp-block-paragraph">MyBatis 支持延迟加载，设置 lazyLoadingEnabled=true 即可。</p>



<p class="wp-block-paragraph">延迟加载的原理的是调用的时候触发加载，而不是在初始化的时候就加载信息。比如调用 a. getB(). getName()，这个时候发现 a. getB() 的值为 null，此时会单独触发事先保存好的关联 B 对象的 SQL，先查询出来 B，然后再调用 a. setB(b)，而这时候再调用 a. getB(). getName() 就有值了，这就是延迟加载的基本原理。</p>



<h2 class="wp-block-heading" id="2pcdv">说一下 MyBatis 的一级缓存和二级缓存？</h2>



<ul class="wp-block-list">
<li>一级缓存：基于 PerpetualCache 的 HashMap 本地缓存，它的声明周期是和 SQLSession 一致的，有多个 SQLSession 或者分布式的环境中数据库操作，可能会出现脏数据。当 Session flush 或 close 之后，该 Session 中的所有 Cache 就将清空，默认一级缓存是开启的。</li>



<li>二级缓存：也是基于 PerpetualCache 的 HashMap 本地缓存，不同在于其存储作用域为 Mapper 级别的，如果多个SQLSession之间需要共享缓存，则需要使用到二级缓存，并且二级缓存可自定义存储源，如 Ehcache。默认不打开二级缓存，要开启二级缓存，使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)。</li>
</ul>



<p class="wp-block-paragraph">开启二级缓存数据查询流程：二级缓存 -&gt; 一级缓存 -&gt; 数据库。</p>



<p class="wp-block-paragraph">缓存更新机制：当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了C/U/D 操作后，默认该作用域下所有 select 中的缓存将被 clear。</p>



<h2 class="wp-block-heading" id="eakhh">MyBatis 和 hibernate 的区别有哪些？</h2>



<ul class="wp-block-list">
<li>灵活性：MyBatis 更加灵活，自己可以写 SQL 语句，使用起来比较方便。</li>



<li>可移植性：MyBatis 有很多自己写的 SQL，因为每个数据库的 SQL 可以不相同，所以可移植性比较差。</li>



<li>学习和使用门槛：MyBatis 入门比较简单，使用门槛也更低。</li>



<li>二级缓存：hibernate 拥有更好的二级缓存，它的二级缓存可以自行更换为第三方的二级缓存。</li>
</ul>



<h2 class="wp-block-heading" id="6o86">MyBatis 有哪些执行器（Executor）？</h2>



<p class="wp-block-paragraph">MyBatis 有三种基本的Executor执行器：</p>



<ul class="wp-block-list">
<li>SimpleExecutor：每执行一次 update 或 select 就开启一个 Statement 对象，用完立刻关闭 Statement 对象；</li>



<li>ReuseExecutor：执行 update 或 select，以 SQL 作为 key 查找 Statement 对象，存在就使用，不存在就创建，用完后不关闭 Statement 对象，而是放置于 Map 内供下一次使用。简言之，就是重复使用 Statement 对象；</li>



<li>BatchExecutor：执行 update（没有 select，jdbc 批处理不支持 select），将所有 SQL 都添加到批处理中（addBatch()），等待统一执行（executeBatch()），它缓存了多个 Statement 对象，每个 Statement 对象都是 addBatch()完毕后，等待逐一执行 executeBatch()批处理，与 jdbc 批处理相同。</li>
</ul>



<h2 class="wp-block-heading" id="2738q">MyBatis 分页插件的实现原理是什么？</h2>



<p class="wp-block-paragraph">分页插件的基本原理是使用 MyBatis 提供的插件接口，实现自定义插件，在插件的拦截方法内拦截待执行的 SQL，然后重写 SQL，根据 dialect 方言，添加对应的物理分页语句和物理分页参数。</p>



<h2 class="wp-block-heading" id="5m133">MyBatis 如何编写一个自定义插件？</h2>



<p class="wp-block-paragraph"><strong>「自定义插件实现原理」</strong></p>



<p class="wp-block-paragraph">MyBatis 自定义插件针对 MyBatis 四大对象（Executor、StatementHandler、ParameterHandler、ResultSetHandler）进行拦截：</p>



<ul class="wp-block-list">
<li>Executor：拦截内部执行器，它负责调用 StatementHandler 操作数据库，并把结果集通过 ResultSetHandler 进行自动映射，另外它还处理了二级缓存的操作；</li>



<li>StatementHandler：拦截 SQL 语法构建的处理，它是 MyBatis 直接和数据库执行 SQL 脚本的对象，另外它也实现了 MyBatis 的一级缓存；</li>



<li>ParameterHandler：拦截参数的处理；</li>



<li>ResultSetHandler：拦截结果集的处理。</li>
</ul>



<p class="wp-block-paragraph"><strong>「自定义插件实现关键」</strong></p>



<p class="wp-block-paragraph">MyBatis 插件要实现 Interceptor 接口，接口包含的方法，如下：</p>



<p class="wp-block-paragraph">代码语言：javascript</p>



<p class="wp-block-paragraph">代码运行次数：0</p>



<p class="wp-block-paragraph">运行</p>



<p class="wp-block-paragraph">AI代码解释</p>



<pre class="wp-block-code"><code>public interface Interceptor{   
   Object intercept(Invocation invocation)throws Throwable;       
   Object plugin(Object target);    
   void setProperties(Properties properties);
}</code></pre>



<ul class="wp-block-list">
<li>setProperties 方法是在 MyBatis 进行配置插件的时候可以配置自定义相关属性，即：接口实现对象的参数配置；</li>



<li>plugin 方法是插件用于封装目标对象的，通过该方法我们可以返回目标对象本身，也可以返回一个它的代理，可以决定是否要进行拦截进而决定要返回一个什么样的目标对象，官方提供了示例：return Plugin. wrap(target, this)；</li>



<li>intercept 方法就是要进行拦截的时候要执行的方法。</li>
</ul>



<p class="wp-block-paragraph"><strong>「自定义插件实现示例」</strong></p>



<p class="wp-block-paragraph">官方插件实现：</p>



<pre class="wp-block-code"><code>@Intercepts({@Signature(type = Executor. class, method= "query",
        args = {MappedStatement. class, Object. class, RowBounds. class, ResultHandler. class})})
public class TestInterceptorimplementsInterceptor{
   public Object intercept(Invocation invocation)throws Throwable {
     Object target = invocation. getTarget(); //被代理对象
     Method method = invocation. getMethod(); //代理方法
     Object&#91;] args = invocation. getArgs(); //方法参数
     // do something . . . . . .  方法拦截前执行代码块
     Object result = invocation. proceed();
     // do something . . . . . . . 方法拦截后执行代码块
     return result;
   }
   public Object plugin(Object target){
     return Plugin. wrap(target, this);
   }
}</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/17/498/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">498</post-id>	</item>
		<item>
		<title>【Java面试】Spring</title>
		<link>https://blog.lhyshome.com/2026/03/17/456/</link>
					<comments>https://blog.lhyshome.com/2026/03/17/456/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 03:22:55 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=456</guid>

					<description><![CDATA[Spring/Spring MVC 为什么要使用 spring？ 解释一下AOP?意义是什么?实现机制是什么 … <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/17/456/">Read More: 【Java面试】Sp… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 id="aiqcf" class="wp-block-heading"><strong>Spring/Spring MVC</strong></h1>



<h2 id="4ovq1" class="wp-block-heading">为什么要使用 spring？</h2>



<ul class="wp-block-list">
<li>spring 提供 ioc 技术，容器会帮你管理依赖的对象，从而不需要自己创建和管理依赖对象了，更轻松的实现了程序的解耦。</li>



<li>spring 提供了事务支持，使得事务操作变的更加方便。</li>



<li>spring 提供了面向切片编程，这样可以更方便的处理某一类的问题。</li>



<li>更方便的框架集成，spring 可以很方便的集成其他框架，比如 MyBatis、hibernate 等。</li>
</ul>



<h2 class="wp-block-heading"><strong>解释一下AOP?意义是什么?实现机制是什么</strong></h2>



<ol class="wp-block-list">
<li><strong>什么是AOP&#8211;面向切面编程</strong>
<ul class="wp-block-list">
<li>用于将<strong>与业务无关的</strong>，<strong>但却对多个对象产生影响</strong>的<strong>公共行为和逻辑</strong>，使用<strong>动态代理的方式</strong>封装为一个<strong>统一的可重用的模块</strong>，<strong>这个模块被命名为“切面”（Aspect）</strong>，具有<strong>无侵入性</strong>的特点,可以<strong>减少</strong><strong>重复代码</strong>.如用于鉴权、日志、事务、运行监控等</li>
</ul>
</li>



<li><strong>实现机制</strong>
<ul class="wp-block-list">
<li>Spring AOP<strong>基于动态代理实现</strong>.使用<strong>JDK动态代理</strong>或<strong>CGLIB</strong>.JDK动态代理支持接口的代理,不支持类的代理,当无法使用JDK动态代理时,Spring AOP才会使用CGLIB</li>
</ul>
</li>
</ol>



<h2 class="wp-block-heading"><strong>静态代理和动态代理的区别是什么</strong></h2>



<p class="wp-block-paragraph">代理主要分为<strong>静态代理</strong>和<strong>动态代理</strong>。静态代理的代表为<strong>AspectJ</strong>；动态代理的代表为<strong>Spring AOP</strong></p>



<p class="wp-block-paragraph"><strong>生成代理对象的时机不同</strong></p>



<ol class="wp-block-list">
<li>静态代理：由程序员<strong>主动创建</strong>或由<strong>特定工具自动生成</strong>源代码，再对其编译,在程序<strong>运行前代理类的.class文件就已经存在</strong>了。</li>



<li>动态代理：<strong>在程序运行时用反射机制在内存中临时生成代理对象,</strong>该代理对象<strong>包含了目标对象的全部方法</strong>，并且<strong>在特定的切点做了增强处理</strong>，<strong>并回调原对象的方法</strong></li>
</ol>



<p class="wp-block-paragraph"><strong>AspectJ的静态代理</strong>具有<strong>更好的性能</strong>，但<strong>AspectJ需要特定的编译器</strong>进行处理，而<strong>Spring AOP则无需特定的编译器</strong>处理</p>



<h2 id="5dpo0" class="wp-block-heading">解释一下 ioc？</h2>



<p class="wp-block-paragraph">ioc：Inversionof Control（中文：控制反转）是 spring 的核心，对于 spring 框架来说，就是由 spring 来负责控制对象的生命周期和对象间的关系。</p>



<p class="wp-block-paragraph">简单来说，控制指的是当前对象对内部成员的控制权；控制反转指的是，这种控制权不由当前对象管理了，由其他（类,第三方容器）来管理。</p>



<h2 id="a7dg7" class="wp-block-heading">spring 有哪些主要模块？</h2>



<ul class="wp-block-list">
<li>spring core：框架的最基础部分，提供 ioc 和依赖注入特性。</li>



<li>spring context：构建于 core 封装包基础上的 context 封装包，提供了一种框架式的对象访问方法。</li>



<li>spring dao：Data Access Object 提供了JDBC的抽象层。</li>



<li>spring aop：提供了面向切面的编程实现，让你可以自定义拦截器、切点等。</li>



<li>spring Web：提供了针对 Web 开发的集成特性，例如文件上传，利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。</li>



<li>spring Web mvc：spring 中的 mvc 封装包提供了 Web 应用的 Model-View-Controller（MVC）的实现。</li>
</ul>



<h2 id="8oiaf" class="wp-block-heading">spring 常用的注入方式有哪些？</h2>



<ul class="wp-block-list">
<li>setter 属性注入</li>



<li>构造方法注入</li>



<li>注解方式注入</li>
</ul>



<h2 id="77t2b" class="wp-block-heading">spring 中的 bean 是线程安全的吗？</h2>



<p class="wp-block-paragraph">spring 中的 bean 默认是单例模式，spring 框架并没有对单例 bean 进行多线程的封装处理。</p>



<p class="wp-block-paragraph">实际上大部分时候 spring bean 无状态的（比如 dao 类），所有某种程度上来说 bean 也是安全的，但如果 bean 有状态的话（比如 view model 对象），那就要开发者自己去保证线程安全了，最简单的就是改变 bean 的作用域，把“singleton”变更为“prototype”，这样请求 bean 相当于 new Bean()了，所以就可以保证线程安全了。</p>



<ul class="wp-block-list">
<li>有状态就是有数据存储功能。</li>



<li>无状态就是不会保存数据。</li>
</ul>



<h2 id="1ptt4" class="wp-block-heading">spring 支持几种 bean 的作用域？</h2>



<p class="wp-block-paragraph">spring 支持 5 种作用域，如下：</p>



<ul class="wp-block-list">
<li>singleton：spring ioc 容器中只存在一个 bean 实例，bean 以单例模式存在，是系统默认值；</li>



<li>prototype：每次从容器调用 bean 时都会创建一个新的示例，既每次 getBean()相当于执行 new Bean()操作；</li>



<li>Web 环境下的作用域：</li>



<li>request：每次 http 请求都会创建一个 bean；</li>



<li>session：同一个 http session 共享一个 bean 实例；</li>



<li>global-session：用于 portlet 容器，因为每个 portlet 有单独的 session，globalsession 提供一个全局性的 http session。</li>
</ul>



<p class="wp-block-paragraph"><strong>「注意：」</strong>&nbsp;使用 prototype 作用域需要慎重的思考，因为频繁创建和销毁 bean 会带来很大的性能开销。</p>



<h2 id="82ru0" class="wp-block-heading">spring 自动装配 bean 有哪些方式？</h2>



<ul class="wp-block-list">
<li>no：默认值，表示没有自动装配，应使用显式 bean 引用进行装配。</li>



<li>byName：它根据 bean 的名称注入对象依赖项。</li>



<li>byType：它根据类型注入对象依赖项。</li>



<li>构造函数：通过构造函数来注入依赖项，需要设置大量的参数。</li>



<li>autodetect：容器首先通过构造函数使用 autowire 装配，如果不能，则通过 byType 自动装配。</li>
</ul>



<h2 id="c860l" class="wp-block-heading">spring 事务实现方式有哪些？</h2>



<ul class="wp-block-list">
<li>声明式事务：声明式事务也有两种实现方式，基于 xml 配置文件的方式和注解方式（在类上添加 @Transaction 注解）。</li>



<li>编码方式：提供编码的形式管理和维护事务。</li>
</ul>



<h2 id="5bprv" class="wp-block-heading">spring 的事务隔离？</h2>



<p class="wp-block-paragraph">spring 有五大隔离级别，默认值为 ISOLATION_DEFAULT（使用数据库的设置），其他四个隔离级别和数据库的隔离级别一致：</p>



<ul class="wp-block-list">
<li>ISOLATION_DEFAULT：用底层数据库的设置隔离级别，数据库设置的是什么我就用什么；</li>



<li>ISOLATION<strong>READ</strong>UNCOMMITTED：未提交读，最低隔离级别、事务未提交前，就可被其他事务读取（会出现幻读、脏读、不可重复读）；</li>



<li>ISOLATION<strong>READ</strong>COMMITTED：提交读，一个事务提交后才能被其他事务读取到（会造成幻读、不可重复读），SQL server 的默认级别；</li>



<li>ISOLATION<strong>REPEATABLE</strong>READ：可重复读，保证多次读取同一个数据时，其值都和事务开始时候的内容是一致，禁止读取到别的事务未提交的数据（会造成幻读），MySQL 的默认级别；</li>



<li>ISOLATION_SERIALIZABLE：序列化，代价最高最可靠的隔离级别，该隔离级别能防止脏读、不可重复读、幻读。</li>
</ul>



<ul class="wp-block-list">
<li><strong>「脏读」</strong>&nbsp;：表示一个事务能够读取另一个事务中还未提交的数据。比如，某个事务尝试插入记录 A，此时该事务还未提交，然后另一个事务尝试读取到了记录 A。</li>



<li><strong>「不可重复读」</strong>&nbsp;：是指在一个事务内，多次读同一数据。</li>



<li><strong>「幻读」</strong>&nbsp;：指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录，但是第二次同等条件下查询却有 n+1 条记录，这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据，同一个记录的数据内容被修改了，所有数据行的记录就变多或者变少了。</li>
</ul>



<h2 class="wp-block-heading">spring事务的传播方式</h2>



<p class="wp-block-paragraph"><strong>事务传播行为（Propagation）</strong></p>



<p class="wp-block-paragraph">决定调用另一个带事务的方法时，是加入当前事务还是新建事务。</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">传播行为</th><th class="has-text-align-left" data-align="left">含义</th></tr></thead><tbody><tr><td><strong>REQUIRED</strong></td><td>加入当前事务，若无则新建（<strong>最常用</strong>）</td></tr><tr><td><strong>REQUIRES_NEW</strong></td><td>总是新建事务，若已存在则挂起当前事务</td></tr><tr><td>SUPPORTS</td><td>有事务就加入，没有就以非事务运行</td></tr><tr><td>NOT_SUPPORTED</td><td>以非事务运行，如有事务则挂起</td></tr><tr><td>MANDATORY</td><td>必须存在事务，否则抛异常</td></tr><tr><td>NEVER</td><td>必须非事务运行，否则抛异常</td></tr><tr><td>NESTED</td><td>嵌套事务，底层需支持 savepoint（JDBC 可）</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>常见误区</strong>：<code>REQUIRES_NEW</code>&nbsp;会让内部事务与外部事务完全独立，内部回滚不影响外部，外部回滚也不影响已提交的内部事务。</p>



<h2 id="fmae2" class="wp-block-heading">spring mvc 运行流程？</h2>



<ul class="wp-block-list">
<li>spring mvc 先将请求发送给 DispatcherServlet。</li>



<li>DispatcherServlet 查询一个或多个 HandlerMapping，找到处理请求的 Controller。</li>



<li>DispatcherServlet 再把请求提交到对应的 Controller。</li>



<li>Controller 进行业务逻辑处理后，会返回一个ModelAndView。</li>



<li>Dispathcher 查询一个或多个 ViewResolver 视图解析器，找到 ModelAndView 对象指定的视图对象。</li>



<li>视图对象负责渲染返回给客户端。</li>
</ul>



<h2 id="ks5q" class="wp-block-heading">spring mvc 有哪些组件？</h2>



<ul class="wp-block-list">
<li>前置控制器 DispatcherServlet。</li>



<li>映射控制器 HandlerMapping。</li>



<li>处理器 Controller。</li>



<li>模型和视图 ModelAndView。</li>



<li>视图解析器 ViewResolver。</li>
</ul>



<h2 id="5hev0" class="wp-block-heading">@RequestMapping 的作用是什么？</h2>



<p class="wp-block-paragraph">将 http 请求映射到相应的类/方法上。</p>



<h2 id="52hra" class="wp-block-heading">@Autowired 的作用是什么？</h2>



<p class="wp-block-paragraph">@Autowired 它可以对类成员变量、方法及构造函数进行标注，完成自动装配的工作，通过@Autowired 的使用来消除 set/get 方法。</p>



<h2 class="wp-block-heading"><strong>@Autowired和@Resource注解的区别</strong></h2>



<p class="wp-block-paragraph">@Autowired与@Resource都可以用来装配bean，都可以写在<strong>属性</strong>或<strong>set</strong>方法上,<strong>@Autowired还可以写在构造方法上,@Resource不可以</strong></p>



<p class="wp-block-paragraph"><strong>1、@Autowired</strong></p>



<p class="wp-block-paragraph">由Spring提供，只<strong>按照类型</strong>自动注入</p>



<p class="wp-block-paragraph"><strong>2、@Resource</strong></p>



<p class="wp-block-paragraph">由J2EE提供，默认<strong>按照名称</strong>自动注入</p>



<p class="wp-block-paragraph">@Resource有两个属性：<strong>name和type</strong></p>



<p class="wp-block-paragraph">@Resource装配顺序：</p>



<ol class="wp-block-list">
<li>如果同时指定了<strong>name和type</strong>，则从Spring上下文中找到<strong>唯一匹配</strong>的bean进行装配，<strong>找不到则抛出异常</strong></li>



<li>如果指定了<strong>name</strong>，则从Spring上下文中查找<strong>名称匹配</strong>的bean进行装配，<strong>找不到则抛出异常</strong></li>



<li>如果指定了<strong>type</strong>，则从Spring上下文中找到<strong>类型匹配</strong>的bean进行装配，<strong>找不到或找到多个，都抛出异常</strong></li>
</ol>



<h1 id="aqd0p" class="wp-block-heading"><strong>Spring Boot/Spring Cloud</strong></h1>



<h2 id="at6tk" class="wp-block-heading">什么是 spring boot？</h2>



<p class="wp-block-paragraph">spring boot 是为 spring 服务的，是用来简化新 spring 应用的初始搭建以及开发过程的。</p>



<h2 id="120er" class="wp-block-heading">为什么要用 spring boot？</h2>



<ul class="wp-block-list">
<li>配置简单</li>



<li>独立运行</li>



<li>自动装配</li>



<li>无代码生成和 xml 配置</li>



<li>提供应用监控</li>



<li>易上手</li>



<li>提升开发效率</li>
</ul>



<h2 id="531nh" class="wp-block-heading">spring boot 核心配置文件是什么？</h2>



<p class="wp-block-paragraph">spring boot 核心的两个配置文件：</p>



<ul class="wp-block-list">
<li>bootstrap (. yml 或者 . properties)：boostrap 由父 ApplicationContext 加载的，比 applicaton 优先加载，且 boostrap 里面的属性不能被覆盖；</li>



<li>application (. yml 或者 . properties)：用于 spring boot 项目的自动化配置。</li>
</ul>



<h2 id="aair3" class="wp-block-heading">spring boot 配置文件有哪几种类型？它们有什么区别？</h2>



<p class="wp-block-paragraph">配置文件有 . properties 格式和 . yml 格式，它们主要的区别是书法风格不同。</p>



<p class="wp-block-paragraph">. properties 配置如下：</p>



<pre class="wp-block-code"><code>spring.RabbitMQ.port=5672</code></pre>



<p class="wp-block-paragraph">. yml 配置如下：</p>



<pre class="wp-block-code"><code>spring:
    RabbitMQ:
        port: 5672</code></pre>



<p class="wp-block-paragraph">yml 格式不支持 @PropertySource 注解导入。</p>



<h2 id="59tdl" class="wp-block-heading">spring boot 有哪些方式可以实现热部署？</h2>



<ul class="wp-block-list">
<li>使用 devtools 启动热部署，添加 devtools 库，在配置文件中把 spring. devtools. restart. enabled 设置为 true；</li>



<li>使用 Intellij Idea 编辑器，勾上自动编译或手动重新编译。</li>
</ul>



<h2 id="80mgm" class="wp-block-heading">jpa 和 hibernate 有什么区别？</h2>



<p class="wp-block-paragraph">jpa 全称 Java Persistence API，是 Java 持久化接口规范，hibernate 属于 jpa 的具体实现。</p>



<h2 id="esrjj" class="wp-block-heading">什么是 spring cloud？</h2>



<p class="wp-block-paragraph">spring cloud 是一系列框架的有序集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发，如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等，都可以用 spring boot 的开发风格做到一键启动和部署。</p>



<h2 id="64h4e" class="wp-block-heading">spring cloud 断路器的作用是什么？</h2>



<p class="wp-block-paragraph">在分布式架构中，断路器模式的作用也是类似的，当某个服务单元发生故障（类似用电器发生短路）之后，通过断路器的故障监控（类似熔断保险丝），向调用方返回一个错误响应，而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放，避免了故障在分布式系统中的蔓延。</p>



<h2 id="bnlcl" class="wp-block-heading">spring cloud 的核心组件有哪些？</h2>



<ul class="wp-block-list">
<li>Eureka：服务注册于发现。</li>



<li>Feign：基于动态代理机制，根据注解和选择的机器，拼接请求 url 地址，发起请求。</li>



<li>Ribbon：实现负载均衡，从一个服务的多台机器中选择一台。</li>



<li>Hystrix：提供线程池，不同的服务走不同的线程池，实现了不同服务调用的隔离，避免了服务雪崩的问题。</li>



<li>Zuul：网关管理，由 Zuul 网关转发请求给对应的服务。</li>
</ul>



<h2 class="wp-block-heading">Spring循环依赖怎么解决的？</h2>



<p class="wp-block-paragraph">Spring通过三级缓存解决单例Bean的循环依赖（主要是构造器注入无法解决）：</p>



<ul class="wp-block-list">
<li><strong>一级缓存</strong>：singletonObjects，存放完全初始化好的Bean。</li>



<li><strong>二级缓存</strong>：earlySingletonObjects，存放提前暴露的早期对象（尚未填充属性）。</li>



<li><strong>三级缓存</strong>：singletonFactories，存放Bean工厂，用于生成早期对象的引用。</li>



<li>过程：A依赖B，B依赖A。创建A时，将A的工厂放入三级缓存，然后填充属性时发现依赖B，去创建B；B创建时依赖A，从三级缓存获取A的工厂生成早期A（放入二级缓存），B完成注入后创建完毕，然后A从二级缓存拿到B的引用，完成后续初始化。最终A放入一级缓存。</li>
</ul>



<p class="wp-block-paragraph"><strong>详细说明：</strong></p>



<p class="wp-block-paragraph">Spring 框架为了解决单例作用域下的循环依赖问题，设计了一套基于&nbsp;<strong>三级缓存</strong>&nbsp;的机制。下面将详细解释其原理、步骤以及为什么需要三级缓存。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">1. 什么是循环依赖</h3>



<p class="wp-block-paragraph">循环依赖是指两个或多个 Bean 之间互相持有对方的引用，形成一个闭环。例如：</p>



<ul class="wp-block-list">
<li>A 依赖 B，B 依赖 A（最简单的循环依赖）。</li>



<li>A 依赖 B，B 依赖 C，C 依赖 A（更复杂的循环）。</li>
</ul>



<p class="wp-block-paragraph">在创建 Bean 时，如果容器无法打破这个环，就会陷入无限递归，最终导致&nbsp;<code>BeanCurrentlyInCreationException</code>。</p>



<h3 class="wp-block-heading">2. Spring 能解决哪些循环依赖</h3>



<p class="wp-block-paragraph">Spring&nbsp;<strong>仅能解决</strong>&nbsp;以下条件的循环依赖：</p>



<ul class="wp-block-list">
<li>Bean 的作用域是&nbsp;<strong>单例（singleton）</strong></li>



<li>依赖注入的方式为&nbsp;<strong>setter 注入</strong>&nbsp;或&nbsp;<strong>字段注入（@Autowired）</strong><br>（因为这两种方式可以在对象实例化后，再设置属性）</li>



<li>不能解决&nbsp;<strong>构造函数注入</strong>&nbsp;的循环依赖（因为构造函数必须在实例化时完成所有参数的注入，此时对象尚未创建，无法提前暴露）。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">3. 三级缓存的内部结构</h3>



<p class="wp-block-paragraph">Spring 容器内部维护了三个缓存，用于存放不同阶段的 Bean 实例：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">缓存名称</th><th class="has-text-align-left" data-align="left">作用</th></tr></thead><tbody><tr><td><strong>一级缓存</strong>&nbsp;<code>singletonObjects</code></td><td>存放完全初始化好的单例 Bean（成品）</td></tr><tr><td><strong>二级缓存</strong>&nbsp;<code>earlySingletonObjects</code></td><td>存放提前暴露的 Bean 实例（半成品），即对象已经创建，但尚未完成属性填充和初始化</td></tr><tr><td><strong>三级缓存</strong>&nbsp;<code>singletonFactories</code></td><td>存放生成 Bean 实例的&nbsp;<code>ObjectFactory</code>&nbsp;工厂对象（用于生成代理对象等）</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">这三个缓存对应 Spring 的&nbsp;<code>DefaultSingletonBeanRegistry</code>&nbsp;类中的三个成员变量：</p>



<pre class="wp-block-code"><code><em>// 一级</em>
private final Map&lt;String, Object&gt; <strong>singletonObjects</strong> = new ConcurrentHashMap&lt;&gt;(256);          
<em>// 二级</em>
private final Map&lt;String, Object&gt; <strong>earlySingletonObjects</strong> = new ConcurrentHashMap&lt;&gt;(16);     
<em>// 三级</em>
private final Map&lt;String, ObjectFactory&lt;?&gt;&gt; <strong>singletonFactories</strong> = new HashMap&lt;&gt;(16);        </code></pre>



<h3 class="wp-block-heading">4. 解决循环依赖的流程（以 A 依赖 B，B 依赖 A 为例）</h3>



<p class="wp-block-paragraph">假设两个 Bean 都是通过 setter 注入，且作用域为单例。</p>



<div class="wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-4fc3f8e1 wp-block-group-is-layout-flex">
<h4 class="wp-block-heading">步骤 1：开始创建 A</h4>



<ul class="wp-block-list">
<li><code>getBean("a")</code>&nbsp;发现一级缓存中没有 A。</li>



<li>实例化 A（调用构造函数），得到一个原始对象&nbsp;<code>aInstance</code>。</li>



<li><strong>提前暴露 A</strong>：将&nbsp;<code>aInstance</code>&nbsp;封装为一个&nbsp;<code>ObjectFactory</code>&nbsp;放入<strong>三级缓存</strong><code>singletonFactories</code>&nbsp;中（key = &#8220;a&#8221;）。
<ul class="wp-block-list">
<li>这一步实际上执行的是&nbsp;<code>addSingletonFactory("a", () -&gt; getEarlyBeanReference("a", mbd, aInstance))</code>，其中&nbsp;<code>getEarlyBeanReference</code>&nbsp;可能会返回原始对象或代理对象（如果有 AOP 增强）。</li>
</ul>
</li>
</ul>



<h4 class="wp-block-heading">步骤 2：填充 A 的属性</h4>



<ul class="wp-block-list">
<li>发现 A 需要注入 B，因此调用&nbsp;<code>getBean("b")</code>&nbsp;去创建或获取 B。</li>
</ul>



<h4 class="wp-block-heading">步骤 3：创建 B（同样的流程）</h4>



<ul class="wp-block-list">
<li><code>getBean("b")</code>&nbsp;一级缓存无 B。</li>



<li>实例化 B，得到原始对象&nbsp;<code>bInstance</code>。</li>



<li>将 B 的工厂放入三级缓存（key = &#8220;b&#8221;）。</li>
</ul>



<h4 class="wp-block-heading">步骤 4：填充 B 的属性</h4>



<ul class="wp-block-list">
<li>发现 B 需要注入 A，调用&nbsp;<code>getBean("a")</code>。</li>
</ul>



<h4 class="wp-block-heading">步骤 5：获取 A 的“提前引用”</h4>



<ul class="wp-block-list">
<li>此时 A 正在创建中，一级缓存没有 A。</li>



<li>查看二级缓存&nbsp;<code>earlySingletonObjects</code>，也没有 A。</li>



<li>查看三级缓存&nbsp;<code>singletonFactories</code>，发现有 A 的工厂。</li>



<li><strong>执行工厂</strong>，得到提前暴露的 A 的引用（可能是原始对象，也可能是 AOP 代理对象）。</li>



<li>将得到的对象放入<strong>二级缓存</strong>&nbsp;<code>earlySingletonObjects</code>（key = &#8220;a&#8221;），并从三级缓存中移除该工厂。</li>
</ul>



<h4 class="wp-block-heading">步骤 6：B 获得 A 的引用</h4>



<ul class="wp-block-list">
<li>B 成功拿到了 A 的引用（此时 A 尚未完成属性填充，但对象已存在）。</li>



<li>继续完成 B 的剩余属性填充和初始化。</li>



<li>B 初始化完成后，将自己放入<strong>一级缓存</strong>&nbsp;<code>singletonObjects</code>（此时 B 是成品），并清理二、三级缓存中的 B。</li>
</ul>



<h4 class="wp-block-heading">步骤 7：回到 A</h4>



<ul class="wp-block-list">
<li>A 从&nbsp;<code>getBean("b")</code>&nbsp;调用返回，拿到了初始化好的 B 的完整引用。</li>



<li>将 B 注入到 A 中（此时 A 的属性填充完成）。</li>



<li>执行 A 的初始化方法（如&nbsp;<code>afterPropertiesSet</code>、init-method）。</li>



<li>将 A 放入<strong>一级缓存</strong>&nbsp;<code>singletonObjects</code>，并清理二、三级缓存中的 A。</li>
</ul>
</div>



<p class="wp-block-paragraph">至此，循环依赖被完美解决，两个 Bean 都已完成创建并存于一级缓存。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">5. 为什么需要三级缓存，而不是两级？</h3>



<div class="wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-4fc3f8e1 wp-block-group-is-layout-flex">
<p class="wp-block-paragraph">有些人可能会问：二级缓存（提前暴露的对象）加上一级缓存（成品）不就够了吗？为什么还需要第三级缓存（工厂）？</p>



<p class="wp-block-paragraph">关键在于&nbsp;<strong>AOP 代理对象的创建时机</strong>。</p>



<ul class="wp-block-list">
<li>如果 Bean&nbsp;<strong>不需要</strong>&nbsp;AOP 代理，那么提前暴露的对象就是原始对象，没有区别。</li>



<li>如果 Bean&nbsp;<strong>需要</strong>&nbsp;AOP 代理（例如被&nbsp;<code>@Transactional</code>&nbsp;注解），那么代理对象的创建通常是在 Bean 初始化之后（通过&nbsp;<code>BeanPostProcessor</code>&nbsp;的&nbsp;<code>postProcessAfterInitialization</code>）。<br>但是，在解决循环依赖时，其他 Bean 可能在初始化之前就需要引用这个 Bean。如果等到初始化完成才创建代理，那么其他 Bean 拿到的就是原始对象，而非代理对象，这会导致 AOP 功能失效（例如事务不起作用）。</li>
</ul>



<p class="wp-block-paragraph">因此，Spring 通过三级缓存中的&nbsp;<code>ObjectFactory</code>&nbsp;来&nbsp;<strong>延迟创建代理对象</strong>：</p>



<ul class="wp-block-list">
<li>工厂方法&nbsp;<code>getEarlyBeanReference</code>&nbsp;允许在<strong>提前暴露</strong>阶段就生成代理对象（如果 Bean 需要被代理）。</li>



<li>这样，当 B 通过工厂获取 A 的引用时，拿到的可能是一个尚未完全初始化的 A 的代理对象。之后 A 继续完成初始化，但 B 持有的已经是代理对象，保证了 AOP 的正确性。</li>
</ul>



<p class="wp-block-paragraph">如果只有二级缓存（提前暴露原始对象），就无法在必要的时候提前创建代理，导致 AOP 失效。</p>



<h4 class="wp-block-heading">举例说明：</h4>



<ul class="wp-block-list">
<li>A 和 B 互相依赖，且 A 需要被事务代理。</li>



<li>如果没有三级缓存：
<ul class="wp-block-list">
<li>A 提前暴露原始对象，B 拿到原始 A 并完成创建。</li>



<li>之后 A 初始化完成，生成代理对象。</li>



<li>但 B 中注入的仍是原始 A，事务注解无效。</li>
</ul>
</li>



<li>有了三级缓存：
<ul class="wp-block-list">
<li>B 通过工厂获取 A 时，工厂返回一个<strong>代理对象</strong>（尚未完全初始化，但已经是代理）。</li>



<li>B 持有代理 A。</li>



<li>A 后续初始化完成后，容器中保存的也是同一个代理对象（或者代理对象内部包装了原始对象），因此 B 使用的代理正确。</li>
</ul>
</li>
</ul>
</div>



<h3 class="wp-block-heading">6. 哪些循环依赖 Spring 无法解决？</h3>



<ul class="wp-block-list">
<li><strong>构造函数注入</strong>：因为构造函数必须在实例化时完成，无法提前暴露对象，无法打破循环。</li>



<li><strong>非单例作用域</strong>（prototype）：Spring 不缓存 prototype 的 Bean，每次请求都新建，因此无法提前暴露，也无法解决循环依赖。</li>



<li><strong>多例混合</strong>：比如 singleton 依赖 prototype，但反过来则无法解决。</li>
</ul>



<p class="wp-block-paragraph">对于构造函数注入的循环依赖，可以改用 setter 注入，或者使用&nbsp;<code>@Lazy</code>&nbsp;注解延迟加载其中一个依赖。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">7. 源码简要追溯</h3>



<p class="wp-block-paragraph">主要逻辑集中在&nbsp;<code>AbstractBeanFactory.doGetBean()</code>&nbsp;和&nbsp;<code>DefaultSingletonBeanRegistry.getSingleton()</code>&nbsp;方法中。</p>



<p class="wp-block-paragraph"><code>getSingleton(String beanName, boolean allowEarlyReference)</code>&nbsp;方法体现了三级缓存的查找顺序：</p>



<pre class="wp-block-code"><code>protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null &amp;&amp; isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null &amp;&amp; allowEarlyReference) {
                ObjectFactory&lt;?&gt; singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}</code></pre>



<p class="wp-block-paragraph">当创建一个 Bean 时，在&nbsp;<code>doCreateBean</code>&nbsp;中实例化后，会调用&nbsp;<code>addSingletonFactory</code>&nbsp;将工厂放入三级缓存。</p>



<h3 class="wp-block-heading">总结</h3>



<p class="wp-block-paragraph">Spring 通过&nbsp;<strong>三级缓存</strong>&nbsp;巧妙地解决了单例 setter 注入的循环依赖问题：</p>



<ul class="wp-block-list">
<li>一级缓存存放完整 Bean。</li>



<li>二级缓存存放提前暴露的 Bean（半成品）。</li>



<li>三级缓存存放生产 Bean 的工厂，用于在必要时提前生成代理对象，避免 AOP 失效。</li>
</ul>



<p class="wp-block-paragraph">这种设计既保证了循环依赖的解决，又兼顾了 AOP 等增强功能的正确性。</p>



<h2 class="wp-block-heading">@Lazy延迟加载</h2>



<p class="wp-block-paragraph"><code>@Lazy</code>&nbsp;是 Spring 提供的一个灵活控制 Bean 生命周期的注解，主要用途包括：</p>



<ul class="wp-block-list">
<li><strong>性能优化</strong>：延迟非必要 Bean 的初始化，加快启动速度。</li>



<li><strong>打破循环依赖</strong>：特别是解决构造器注入导致的循环依赖。</li>



<li><strong>按需加载</strong>：让资源消耗较大的 Bean 在真正需要时才创建。</li>
</ul>



<h3 class="wp-block-heading">工作原理</h3>



<p class="wp-block-paragraph">Spring 容器在启动时，对于延迟加载的 Bean，并不会立即调用其构造方法和初始化逻辑，而是：</p>



<ul class="wp-block-list">
<li><strong>对于&nbsp;<code>@Lazy</code>&nbsp;标注的类或&nbsp;<code>@Bean</code>&nbsp;方法</strong>：容器会将这些 Bean 的创建推迟到第一次调用&nbsp;<code>getBean()</code>&nbsp;获取它们时。但在容器启动阶段，它们会被记录下来，但不会实例化。</li>



<li><strong>对于注入点上使用&nbsp;<code>@Lazy</code></strong>：Spring 会生成一个<strong>代理对象</strong>（基于 CGLIB 或 JDK 动态代理）并注入到目标 Bean 中。该代理对象会拦截所有方法调用，在首次调用时触发目标 Bean 的创建和初始化，然后将调用委派给真实的 Bean 实例。</li>
</ul>



<p class="wp-block-paragraph">正是通过这种代理机制，<code>@Lazy</code>&nbsp;才能在不破坏依赖关系的情况下实现延迟加载。</p>



<h2 class="wp-block-heading">CGLIB 与 JDK 动态代理</h2>



<ul class="wp-block-list">
<li><strong>JDK 动态代理</strong>&nbsp;是 Java 原生提供的，基于接口的代理，适合接口设计良好的场景。</li>



<li><strong>CGLIB 代理</strong>&nbsp;通过生成子类实现代理，更加灵活，适合没有接口的类或需要更细粒度控制的场景。</li>



<li>Spring 根据目标类是否有接口自动选择，也允许强制使用 CGLIB。</li>



<li>在实际开发中，如果使用 Spring Boot 2.x，<strong>默认 CGLIB </strong>模式可以避免接口定义，使代码更简洁。</li>
</ul>



<h2 class="wp-block-heading">SpringBoot 的核心注解是哪个？</h2>



<p class="wp-block-paragraph">核心注解是&nbsp;<strong><code>@SpringBootApplication</code></strong>&nbsp;<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a><a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>。通常标注在项目的主启动类上。</p>



<h2 class="wp-block-heading">SpringBootApplication注解包含哪几个注解？</h2>



<p class="wp-block-paragraph">它是一个组合注解，主要包含以下三个核心注解&nbsp;<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a><a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>：</p>



<ol start="1" class="wp-block-list">
<li><strong><code>@SpringBootConfiguration</code></strong>：继承自&nbsp;<code>@Configuration</code>，表示该类是一个配置类。</li>



<li><strong><code>@EnableAutoConfiguration</code></strong>：开启自动配置功能。</li>



<li><strong><code>@ComponentScan</code></strong>：启用组件扫描，默认扫描主启动类所在包及其子包。</li>
</ol>



<h2 class="wp-block-heading">SpringBoot最核心的注解有哪些？</h2>



<p class="wp-block-paragraph">除了上面的&nbsp;<code>@SpringBootApplication</code>&nbsp;组合注解外，还有：</p>



<ul class="wp-block-list">
<li><strong><code>@EnableAutoConfiguration</code></strong>：自动配置的核心。</li>



<li><strong><code>@Conditional</code>&nbsp;系列注解</strong>：如&nbsp;<code>@ConditionalOnClass</code>、<code>@ConditionalOnMissingBean</code>，用于控制自动配置在满足特定条件时才生效&nbsp;<a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/information/%e5%bd%93%e6%9c%89%e4%b8%80%e4%ba%9b%e7%b1%bb%e5%8f%af%e8%83%bd%e4%b8%8d%e5%ad%98%e5%9c%a8%e6%97%b6%ef%bc%8c%e6%88%91%e5%ba%94%e8%af%a5%e5%a6%82%e4%bd%95%e5%9c%a8XxxAutoConfiguration%e4%b8%ad%e7%ae%a1%e7%90%86%e5%ae%83%e4%bb%ac%ef%bc%9f" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong><code>@ConfigurationProperties</code></strong>：用于将配置文件（如&nbsp;<code>application.yml</code>）中的属性绑定到 Java Bean 上&nbsp;<a href="https://www.finclip.com/news/f/23737.html" target="_blank" rel="noreferrer noopener"></a><a href="https://bbs.huaweicloud.com/blogs/402045" target="_blank" rel="noreferrer noopener"></a>。</li>
</ul>



<h2 class="wp-block-heading">SpringBoot自动配置原理是什么？</h2>



<p class="wp-block-paragraph">原理主要基于&nbsp;<code>@EnableAutoConfiguration</code>&nbsp;注解&nbsp;<a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>。它会通过&nbsp;<code>SpringFactoriesLoader</code>&nbsp;机制，从 classpath 下所有 JAR 包中的&nbsp;<code>META-INF/spring.factories</code>&nbsp;文件里，加载&nbsp;<code>EnableAutoConfiguration</code>&nbsp;键对应的自动配置类（如&nbsp;<code>XxxAutoConfiguration</code>）<a href="https://www.finclip.com/news/f/23737.html" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>。这些配置类上通常标有&nbsp;<code>@Conditional</code>&nbsp;条件注解，只有满足特定条件（如存在相关类、配置了相关属性）时，这些自动配置类才会生效，实例化相应的 Bean。</p>



<h2 class="wp-block-heading">SpringBoot开启自动配置的原理是什么？</h2>



<p class="wp-block-paragraph">只需要在配置类上使用&nbsp;<strong><code>@EnableAutoConfiguration</code></strong>&nbsp;注解即可。由于&nbsp;<code>@SpringBootApplication</code>&nbsp;已经组合了该注解，所以只要在启动类上标记&nbsp;<code>@SpringBootApplication</code>，就相当于开启了自动配置&nbsp;<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a><a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a>。</p>



<h2 class="wp-block-heading">SpringBoot自动配置的类型在哪注册？</h2>



<p class="wp-block-paragraph">自动配置类（类型）的注册信息存放在所有 JAR 包中的&nbsp;<strong><code>META-INF/spring.factories</code></strong>&nbsp;文件中&nbsp;<a href="https://www.finclip.com/news/f/23737.html" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>。Spring Boot 启动时会扫描该文件，获取所有注册的自动配置类的全限定名。</p>



<h2 class="wp-block-heading">SpringBoot自动配置报告怎么查看？</h2>



<p class="wp-block-paragraph">开启&nbsp;<strong>debug 模式</strong>。在配置文件（<code>application.properties</code>&nbsp;或&nbsp;<code>application.yml</code>）中设置&nbsp;<code>debug: true</code>。启动应用后，控制台会打印&nbsp;<strong>Positive matches</strong>（生效的自动配置）和&nbsp;<strong>Negative matches</strong>（未生效的自动配置）报告，方便开发者了解哪些自动配置类被应用了。</p>



<h2 class="wp-block-heading">SpringBoot怎么排除某些自动配置？</h2>



<p class="wp-block-paragraph">有多种方式可以排除自动配置类&nbsp;<a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/information/%e5%bd%93%e6%9c%89%e4%b8%80%e4%ba%9b%e7%b1%bb%e5%8f%af%e8%83%bd%e4%b8%8d%e5%ad%98%e5%9c%a8%e6%97%b6%ef%bc%8c%e6%88%91%e5%ba%94%e8%af%a5%e5%a6%82%e4%bd%95%e5%9c%a8XxxAutoConfiguration%e4%b8%ad%e7%ae%a1%e7%90%86%e5%ae%83%e4%bb%ac%ef%bc%9f" target="_blank" rel="noreferrer noopener"></a>：</p>



<ol start="1" class="wp-block-list">
<li>在&nbsp;<code>@SpringBootApplication</code>&nbsp;或&nbsp;<code>@EnableAutoConfiguration</code>&nbsp;注解中使用&nbsp;<code>exclude</code>&nbsp;属性：<code>@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})</code></li>



<li>在配置文件中使用&nbsp;<code>spring.autoconfigure.exclude</code>&nbsp;属性：<code>spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration</code></li>
</ol>



<h2 class="wp-block-heading">SpringBoot怎么开启和关闭自动配置？</h2>



<ul class="wp-block-list">
<li><strong>开启</strong>：使用&nbsp;<code>@EnableAutoConfiguration</code>&nbsp;注解（通常通过&nbsp;<code>@SpringBootApplication</code>&nbsp;间接使用）<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>关闭</strong>：一般不建议完全关闭自动配置。如果想禁用特定的自动配置，可以使用排除（exclude）机制。如果不想使用整个 Spring Boot 的自动配置特性，可以不使用&nbsp;<code>@EnableAutoConfiguration</code>&nbsp;注解，但这会丧失 Spring Boot 的核心优势。</li>
</ul>



<h2 class="wp-block-heading">SpringBoot的目录结构是怎样的？</h2>



<p class="wp-block-paragraph">一个典型的 Spring Boot 项目目录结构遵循一定的约定&nbsp;<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a>：</p>



<pre class="wp-block-preformatted">com.example.project
├── Application.java (主启动类，位于根包下)
├── domain/ (或 model) - 实体类
├── repository/ (或 dao) - 数据访问层
├── service/ - 业务逻辑层
│   └── impl/ - 业务实现类
└── web/ (或 controller) - 控制器层</pre>



<p class="wp-block-paragraph">此外，资源文件位于&nbsp;<code>src/main/resources</code>&nbsp;目录下，包含：</p>



<ul class="wp-block-list">
<li><code>application.properties</code>&nbsp;或&nbsp;<code>application.yml</code>：配置文件。</li>



<li><code>static/</code>：存放静态资源（如 CSS, JS, 图片）。</li>



<li><code>templates/</code>：存放模板文件（如 Thymeleaf）。</li>
</ul>



<h2 class="wp-block-heading">SpringBoot中的Starters是什么？</h2>



<p class="wp-block-paragraph">Starters 是一组方便的<strong>依赖描述符</strong>&nbsp;<a href="https://cloud.baidu.com/article/3716859" target="_blank" rel="noreferrer noopener"></a><a href="https://bbs.huaweicloud.com/blogs/402045" target="_blank" rel="noreferrer noopener"></a>。它相当于一个“一站式”的依赖集合。例如，引入&nbsp;<code>spring-boot-starter-web</code>&nbsp;依赖，它就会自动传递引入构建 Web 应用所需的所有依赖（如 Spring MVC、内嵌 Tomcat、Jackson 等），避免了开发者手动寻找和配置一个个兼容版本的依赖，极大地简化了项目的依赖管理&nbsp;<a href="https://www.finclip.com/news/f/23737.html" target="_blank" rel="noreferrer noopener"></a><a href="https://bbs.huaweicloud.com/blogs/402045" target="_blank" rel="noreferrer noopener"></a>。</p>



<h1 class="wp-block-heading">IOC容器与Bean生命周期</h1>



<h2 class="wp-block-heading"><strong>Spring的三级缓存是如何解决循环依赖的？为什么二级缓存不够？</strong></h2>



<ul class="wp-block-list">
<li><strong>三级缓存定义</strong>：
<ul class="wp-block-list">
<li>一级缓存 <code>singletonObjects</code>：存放<strong>完全初始化</strong>的单例Bean（成品池）<a href="https://developer.aliyun.com/article/1739811" target="_blank" rel="noreferrer noopener"></a></li>



<li>二级缓存 <code>earlySingletonObjects</code>：存放<strong>提前暴露</strong>的Bean实例（半成品，属性未完全填充）</li>



<li>三级缓存 <code>singletonFactories</code>：存放 <strong>ObjectFactory</strong> 工厂，用于生成Bean的早期引用<a href="https://www.bjpowernode.com/springmst/springsenior.html" target="_blank" rel="noreferrer noopener"></a></li>
</ul>
</li>



<li><strong>解决流程（以A依赖B、B依赖A为例）</strong> ：
<ol start="1" class="wp-block-list">
<li>实例化A，将A的ObjectFactory放入三级缓存</li>



<li>填充A属性时发现需要B，去获取B</li>



<li>实例化B，填充B属性时发现需要A，从三级缓存取出A的ObjectFactory生成A的引用放入二级缓存</li>



<li>B完成初始化放入一级缓存</li>



<li>A获取B引用完成初始化</li>
</ol>
</li>



<li><strong>为什么必须三级缓存</strong>：
<ul class="wp-block-list">
<li><strong>二级缓存不行</strong>，因为Spring需要在“提前暴露Bean”时兼容<strong>AOP代理的创建时机</strong>。若只有二级缓存，代理对象无法在实例化后、依赖注入前被正确创建。三级缓存的ObjectFactory允许在获取早期引用时动态决定返回原始对象还是代理对象。</li>
</ul>
</li>



<li><strong>无法解决的循环依赖</strong>：
<ul class="wp-block-list">
<li><strong>构造器注入</strong>的循环依赖（实例化阶段即死锁）<a href="https://www.bjpowernode.com/springmst/springsenior.html" target="_blank" rel="noreferrer noopener"></a></li>



<li><strong>prototype作用域</strong>的循环依赖（每次getBean都创建新实例，无限递归导致OOM）<a href="https://www.bjpowernode.com/springmst/springsenior.html" target="_blank" rel="noreferrer noopener"></a></li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>请完整描述Spring Bean的生命周期，并列举核心扩展点。</strong></h2>



<ul class="wp-block-list">
<li><strong>四大核心阶段</strong>（非死记硬背的10步）<a href="https://blog.csdn.net/weixin_54413132/article/details/161087245" target="_blank" rel="noreferrer noopener"></a>：</li>
</ul>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">阶段</th><th class="has-text-align-left" data-align="left">核心动作</th><th class="has-text-align-left" data-align="left">关键扩展点</th></tr></thead><tbody><tr><td><strong>实例化</strong></td><td>通过反射调用构造方法创建Bean实例（仅分配内存）<a href="https://blog.csdn.net/weixin_54413132/article/details/161087245" target="_blank" rel="noreferrer noopener"></a></td><td><code>InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation</code><a href="https://developer.aliyun.com/article/1739811" target="_blank" rel="noreferrer noopener"></a></td></tr><tr><td><strong>属性填充</strong></td><td>注入@Autowired依赖、@Value配置等<a href="https://blog.csdn.net/weixin_54413132/article/details/161087245" target="_blank" rel="noreferrer noopener"></a></td><td><code>InstantiationAwareBeanPostProcessor.postProcessProperties</code><a href="https://developer.aliyun.com/article/1739811" target="_blank" rel="noreferrer noopener"></a></td></tr><tr><td><strong>初始化</strong></td><td>执行自定义初始化逻辑<a href="https://blog.csdn.net/weixin_54413132/article/details/161087245" target="_blank" rel="noreferrer noopener"></a></td><td><code>BeanPostProcessor.postProcessBeforeInitialization</code>&nbsp;→&nbsp;<code>@PostConstruct</code>&nbsp;/&nbsp;<code>InitializingBean</code>&nbsp;→&nbsp;<code>postProcessAfterInitialization</code><a href="https://developer.aliyun.com/article/1739811" target="_blank" rel="noreferrer noopener"></a></td></tr><tr><td><strong>销毁</strong></td><td>容器关闭时释放资源<a href="https://blog.csdn.net/weixin_54413132/article/details/161087245" target="_blank" rel="noreferrer noopener"></a></td><td><code>@PreDestroy</code>&nbsp;/&nbsp;<code>DisposableBean</code></td></tr></tbody></table></figure>



<ul class="wp-block-list">
<li><strong>面试加分扩展点</strong>：
<ul class="wp-block-list">
<li><strong>BeanFactoryPostProcessor</strong>：在Bean定义加载后、实例化前执行，可修改BeanDefinition（如修改作用域、属性值）</li>



<li><strong>BeanPostProcessor</strong>：在Bean初始化前后执行，Spring AOP就是通过它创建代理对象的</li>



<li><strong>Aware接口</strong>：<code>BeanNameAware</code>、<code>BeanFactoryAware</code>、<code>ApplicationContextAware</code>等，让Bean感知容器</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>BeanFactory和FactoryBean的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li><strong>BeanFactory</strong>：Spring IOC容器的<strong>根接口</strong>，负责管理和提供Bean实例，采用懒加载策略<a href="https://blog.csdn.net/wenxuankeji/article/details/162046269" target="_blank" rel="noreferrer noopener"></a></li>



<li><strong>FactoryBean</strong>：是一个<strong>特殊的Bean</strong>，用于自定义复杂对象的创建逻辑</li>



<li><strong>关键区别</strong>：
<ul class="wp-block-list">
<li>通过 <code>getBean("beanName")</code> 获取的是 <strong>FactoryBean生产的对象</strong>，而非FactoryBean本身</li>



<li>若要获取FactoryBean实例本身，需使用 <code>&amp;beanName</code> 前缀</li>



<li>典型应用：MyBatis的 <code>SqlSessionFactoryBean</code>、Spring的 <code>ProxyFactoryBean</code></li>
</ul>
</li>
</ul>



<h1 class="wp-block-heading">AOP面向切面编程</h1>



<h2 class="wp-block-heading"><strong>Spring AOP的底层实现原理是什么？JDK动态代理和CGLIB如何选择？</strong></h2>



<p class="wp-block-paragraph">Spring AOP基于<strong>动态代理</strong>在运行时生成代理对象，将切面逻辑织入<a href="https://blog.csdn.net/weixin_52056853/article/details/161185994" target="_blank" rel="noreferrer noopener"></a>。代理创建需要三个核心信息：拦截器数组、目标对象接口数组、目标对象。</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">对比项</th><th class="has-text-align-left" data-align="left">JDK动态代理</th><th class="has-text-align-left" data-align="left">CGLIB动态代理</th></tr></thead><tbody><tr><td>实现基础</td><td>基于接口 + Java反射（<code>InvocationHandler</code>）<a href="https://blog.csdn.net/u011614717/article/details/160353672" target="_blank" rel="noreferrer noopener"></a></td><td>基于继承 + ASM字节码生成（<code>MethodInterceptor</code>）<a href="https://blog.csdn.net/u011614717/article/details/160353672" target="_blank" rel="noreferrer noopener"></a></td></tr><tr><td>目标要求</td><td><strong>必须实现至少一个接口</strong><a href="https://blog.csdn.net/u011614717/article/details/160353672" target="_blank" rel="noreferrer noopener"></a></td><td>无接口要求，但不能代理<code>final</code>类/方法<a href="https://blog.csdn.net/u011614717/article/details/160353672" target="_blank" rel="noreferrer noopener"></a></td></tr><tr><td>性能</td><td>反射调用，略逊</td><td>直接生成字节码，接近原生方法<a href="https://blog.csdn.net/u011614717/article/details/160353672" target="_blank" rel="noreferrer noopener"></a></td></tr><tr><td>Spring默认策略</td><td>目标实现接口时默认使用<a href="https://blog.csdn.net/u011614717/article/details/160353672" target="_blank" rel="noreferrer noopener"></a></td><td>无接口时自动使用；可配置<code>proxyTargetClass=true</code>强制使用<a href="https://blog.csdn.net/u011614717/article/details/160353672" target="_blank" rel="noreferrer noopener"></a></td></tr></tbody></table></figure>



<ul class="wp-block-list">
<li><strong>Spring选择逻辑</strong>：<code>DefaultAopProxyFactory</code> 根据目标类是否实现接口决定<a href="https://blog.csdn.net/java_cj/article/details/161264622" target="_blank" rel="noreferrer noopener"></a></li>



<li><strong>AOP与AspectJ的区别</strong>：Spring AOP是<strong>运行时增强</strong>（仅方法级），AspectJ是<strong>编译时/类加载时增强</strong>（支持字段、构造器等更细粒度）<a href="https://blog.csdn.net/weixin_52056853/article/details/161185994" target="_blank" rel="noreferrer noopener"></a></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>多个切面是如何形成责任链的？</strong></h2>



<p class="wp-block-paragraph">Spring AOP通过&nbsp;<strong>Advisor（通知器）</strong>&nbsp;将Advice和Pointcut封装<a href="https://blog.csdn.net/java_cj/article/details/161264622" target="_blank" rel="noreferrer noopener"></a>，多个切面按&nbsp;<code>@Order</code>&nbsp;排序后，通过<strong>责任链模式</strong>（<code>ReflectiveMethodInvocation</code>）依次执行<a href="https://blog.csdn.net/java_cj/article/details/161264622" target="_blank" rel="noreferrer noopener"></a>。环绕通知（<code>@Around</code>）通过&nbsp;<code>ProceedingJoinPoint.proceed()</code>&nbsp;控制是否继续执行下一个拦截器或目标方法<a href="https://blog.csdn.net/java_cj/article/details/161264622" target="_blank" rel="noreferrer noopener"></a>。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>为什么类内部方法调用时AOP不生效？</strong></h2>



<p class="wp-block-paragraph"><strong>根本原因</strong>：内部调用绕过了代理对象<a href="https://developer.baidu.com/article/detail.html?id=6126563" target="_blank" rel="noreferrer noopener"></a>。Spring AOP基于代理，只有通过代理对象调用方法时切面才生效。</p>



<p class="wp-block-paragraph"><strong>解决方案</strong><a href="https://developer.baidu.com/article/detail.html?id=6126563" target="_blank" rel="noreferrer noopener"></a>：</p>



<ol start="1" class="wp-block-list">
<li>将方法拆分到独立的Bean中</li>



<li>通过 <code>ApplicationContext.getBean()</code> 获取代理对象再调用</li>



<li>使用 <code>AopContext.currentProxy()</code> 获取当前代理</li>



<li>使用AspectJ的编译时织入（<code>&lt;mode>aspectj&lt;/mode></code>）</li>
</ol>



<h1 class="wp-block-heading">事务管理</h1>



<h2 class="wp-block-heading"><strong>@Transactional在哪些情况下会失效？请列举全部场景。</strong></h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">失效场景</th><th class="has-text-align-left" data-align="left">原理</th><th class="has-text-align-left" data-align="left">源码依据</th></tr></thead><tbody><tr><td><strong>非public方法</strong></td><td>代理仅拦截public方法<a href="https://developer.baidu.com/article/detail.html?id=6126563" target="_blank" rel="noreferrer noopener"></a></td><td><code>computeTransactionAttribute</code>&nbsp;方法校验&nbsp;<code>Modifier.isPublic</code><a href="https://developer.baidu.com/article/detail.html?id=6126563" target="_blank" rel="noreferrer noopener"></a></td></tr><tr><td><strong>同类自调用</strong></td><td>绕过代理对象<a href="https://developer.baidu.com/article/detail.html?id=6126563" target="_blank" rel="noreferrer noopener"></a></td><td>代理机制天然限制</td></tr><tr><td><strong>异常被catch吞掉</strong></td><td>事务管理器感知不到异常<a href="https://developer.baidu.com/article/detail.html?id=6126563" target="_blank" rel="noreferrer noopener"></a></td><td>只有异常抛出才能触发回滚</td></tr><tr><td><strong>抛出检查型异常</strong></td><td>默认只对RuntimeException回滚<a href="https://developer.baidu.com/article/detail.html?id=6126563" target="_blank" rel="noreferrer noopener"></a></td><td>需配置&nbsp;<code>rollbackFor=Exception.class</code><a href="https://developer.baidu.com/article/detail.html?id=6126563" target="_blank" rel="noreferrer noopener"></a></td></tr><tr><td><strong>数据库引擎不支持事务</strong></td><td>如MySQL MyISAM引擎</td><td>需改用InnoDB</td></tr><tr><td><strong>传播机制不支持事务</strong></td><td>如&nbsp;<code>Propagation.NOT_SUPPORTED</code></td><td>事务被挂起</td></tr><tr><td><strong>方法被final修饰</strong></td><td>CGLIB无法继承</td><td>无法生成代理子类</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Spring事务的传播机制有哪些？REQUIRED和REQUIRES_NEW的区别？</strong></h2>



<p class="wp-block-paragraph">Spring定义了<strong>7种传播行为</strong><a href="https://www.bjpowernode.com/springmst/springsenior.html" target="_blank" rel="noreferrer noopener"></a>：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">传播行为</th><th class="has-text-align-left" data-align="left">含义</th></tr></thead><tbody><tr><td><code>REQUIRED</code>（默认）</td><td>当前有事务则加入，无则新建</td></tr><tr><td><code>REQUIRES_NEW</code></td><td>始终新建事务，挂起当前事务</td></tr><tr><td><code>SUPPORTS</code></td><td>当前有事务则加入，无则以非事务方式执行</td></tr><tr><td><code>NOT_SUPPORTED</code></td><td>以非事务方式执行，挂起当前事务</td></tr><tr><td><code>MANDATORY</code></td><td>必须已有事务，否则抛异常</td></tr><tr><td><code>NEVER</code></td><td>必须没有事务，否则抛异常</td></tr><tr><td><code>NESTED</code></td><td>嵌套事务（Savepoint机制）</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>核心区别</strong>：</p>



<ul class="wp-block-list">
<li><code>REQUIRED</code>：内外方法<strong>共享同一个事务</strong>，内方法异常会导致<strong>全部回滚</strong></li>



<li><code>REQUIRES_NEW</code>：内外方法<strong>各自独立事务</strong>，内方法异常不影响外方法（但需注意：内方法异常必须被外方法catch，否则异常传播出去仍会导致外方法回滚）</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Spring事务的底层原理是什么？</strong></h2>



<p class="wp-block-paragraph">声明式事务的本质是<strong>AOP</strong><a href="https://blog.csdn.net/zzz_2368/article/details/161643188" target="_blank" rel="noreferrer noopener"></a><a href="https://www.bjpowernode.com/springmst/springsenior.html" target="_blank" rel="noreferrer noopener"></a>。核心执行链路<a href="https://blog.csdn.net/zzz_2368/article/details/161643188" target="_blank" rel="noreferrer noopener"></a>：</p>



<ol start="1" class="wp-block-list">
<li><strong><code>TransactionInterceptor</code></strong> 拦截事务方法（AOP环绕通知）</li>



<li>通过 <strong><code>PlatformTransactionManager</code></strong> 获取事务<a href="https://blog.csdn.net/zzz_2368/article/details/161643188" target="_blank" rel="noreferrer noopener"></a>：
<ul class="wp-block-list">
<li><code>getTransaction()</code>：根据传播行为决定新建/加入事务<a href="https://blog.csdn.net/zzz_2368/article/details/161643188" target="_blank" rel="noreferrer noopener"></a></li>



<li>常见实现：<code>DataSourceTransactionManager</code>（JDBC/MyBatis）、<code>JpaTransactionManager</code>（JPA/Hibernate）<a href="https://blog.csdn.net/zzz_2368/article/details/161643188" target="_blank" rel="noreferrer noopener"></a></li>
</ul>
</li>



<li>执行目标方法</li>



<li>异常时 <code>rollback()</code>，正常时 <code>commit()</code><a href="https://blog.csdn.net/zzz_2368/article/details/161643188" target="_blank" rel="noreferrer noopener"></a></li>
</ol>



<p class="wp-block-paragraph">Spring事务本质是<strong>数据库事务的封装</strong>，依赖数据库的&nbsp;<code>redo log</code>&nbsp;和&nbsp;<code>undo log</code>&nbsp;实现提交与回滚<a href="https://www.bjpowernode.com/springmst/springsenior.html" target="_blank" rel="noreferrer noopener"></a>。Spring只提供统一管理接口，具体由数据库实现<a href="https://www.bjpowernode.com/springmst/springsenior.html" target="_blank" rel="noreferrer noopener"></a>。</p>



<h1 class="wp-block-heading">Spring Boot与自动装配</h1>



<h2 class="wp-block-heading"><strong>Spring Boot的自动装配原理是什么？</strong></h2>



<p class="wp-block-paragraph">核心入口是&nbsp;<code>@SpringBootApplication</code>，它是一个<strong>三合一注解</strong><a href="https://blog.csdn.net/qq_37515544/article/details/157181222" target="_blank" rel="noreferrer noopener"></a>：</p>



<ul class="wp-block-list">
<li><code>@SpringBootConfiguration</code>：标识配置类</li>



<li><code>@ComponentScan</code>：扫描启动类所在包及其子包</li>



<li><code>@EnableAutoConfiguration</code>：<strong>核心</strong>，负责加载自动配置类<a href="https://blog.csdn.net/qq_37515544/article/details/157181222" target="_blank" rel="noreferrer noopener"></a></li>
</ul>



<p class="wp-block-paragraph"><strong>执行流程</strong><a href="https://blog.csdn.net/qq_37515544/article/details/157181222" target="_blank" rel="noreferrer noopener"></a>：</p>



<ol start="1" class="wp-block-list">
<li><code>@EnableAutoConfiguration</code> 通过 <code>@Import</code> 导入 <code>AutoConfigurationImportSelector</code><a href="https://blog.csdn.net/qq_37515544/article/details/157181222" target="_blank" rel="noreferrer noopener"></a></li>



<li><code>AutoConfigurationImportSelector.selectImports()</code> 加载候选配置类<a href="https://blog.csdn.net/qq_37515544/article/details/157181222" target="_blank" rel="noreferrer noopener"></a></li>
</ol>



<p class="wp-block-paragraph"><strong>配置文件加载（版本差异）</strong>&nbsp;<a href="https://blog.csdn.net/qq_37515544/article/details/157181222" target="_blank" rel="noreferrer noopener"></a>：</p>



<ul class="wp-block-list">
<li><strong>Spring Boot 2.x</strong>：读取 <code>META-INF/spring.factories</code></li>



<li><strong>Spring Boot 3.x</strong>：读取 <code>META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</code>（纯文本行，启动更快）</li>
</ul>



<p class="wp-block-paragraph"><strong>条件过滤</strong>：加载的配置类通过&nbsp;<strong>@Conditional系列注解</strong>&nbsp;按需生效<a href="https://blog.csdn.net/qq_37515544/article/details/157181222" target="_blank" rel="noreferrer noopener"></a>。例如&nbsp;<code>RedisAutoConfiguration</code>&nbsp;上有&nbsp;<code>@ConditionalOnClass(RedisOperations.class)</code>，只有引入Redis依赖时才会生效。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>@Conditional系列注解有哪些？各自的作用是什么？</strong></h2>



<p class="wp-block-paragraph">@Conditional是<strong>条件装配</strong>的元注解，满足条件才注入Bean。</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">注解</th><th class="has-text-align-left" data-align="left">作用</th><th class="has-text-align-left" data-align="left">典型场景</th></tr></thead><tbody><tr><td><code>@ConditionalOnClass</code></td><td>类路径存在指定类时才生效</td><td>Redis自动配置需RedisOperations存在</td></tr><tr><td><code>@ConditionalOnMissingBean</code></td><td>容器中无指定Bean时才生效</td><td>保证用户自定义Bean优先</td></tr><tr><td><code>@ConditionalOnProperty</code></td><td>配置文件中存在指定属性才生效</td><td>数据库配置根据&nbsp;<code>spring.datasource.url</code>&nbsp;决定</td></tr><tr><td><code>@ConditionalOnBean</code></td><td>容器中存在指定Bean时才生效</td><td>依赖其他组件存在</td></tr><tr><td><code>@ConditionalOnWebApplication</code></td><td>当前是Web应用时才生效</td><td>Web相关自动配置</td></tr></tbody></table></figure>



<h1 class="wp-block-heading">Spring扩展机制与进阶</h1>



<h2 class="wp-block-heading"><strong>Spring的事件监听机制是如何实现的？</strong></h2>



<p class="wp-block-paragraph">基于<strong>观察者模式</strong>，三个核心角色：</p>



<ul class="wp-block-list">
<li><strong>事件</strong>：继承 <code>ApplicationEvent</code></li>



<li><strong>事件发布者</strong>：<code>ApplicationEventPublisher.publishEvent()</code></li>



<li><strong>事件监听器</strong>：实现 <code>ApplicationListener</code> 接口 或使用 <code>@EventListener</code> 注解</li>
</ul>



<p class="wp-block-paragraph"><strong>注意</strong>：默认是<strong>同步单线程</strong>执行。若需异步，需配置&nbsp;<code>@Async</code>&nbsp;+ 线程池。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Spring中Bean的作用域有哪些？</strong></h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">作用域</th><th class="has-text-align-left" data-align="left">说明</th></tr></thead><tbody><tr><td><code>singleton</code>（默认）</td><td>每个容器仅有一个实例</td></tr><tr><td><code>prototype</code></td><td>每次获取都创建新实例</td></tr><tr><td><code>request</code></td><td>每个HTTP请求一个实例（仅Web）</td></tr><tr><td><code>session</code></td><td>每个HTTP会话一个实例（仅Web）</td></tr><tr><td><code>application</code></td><td>每个ServletContext一个实例（仅Web）</td></tr><tr><td><code>websocket</code></td><td>每个WebSocket会话一个实例（仅Web）</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Spring MVC的请求处理流程是怎样的？</strong></h2>



<ol start="1" class="wp-block-list">
<li>客户端发起HTTP请求</li>



<li><strong>DispatcherServlet（前端控制器）</strong> 接收请求</li>



<li><strong>HandlerMapping</strong> 根据请求URL找到对应的Controller和方法</li>



<li><strong>HandlerAdapter</strong> 执行Controller方法</li>



<li>Controller返回ModelAndView（或JSON数据）</li>



<li><strong>ViewResolver</strong> 解析视图并渲染响应</li>
</ol>



<p class="wp-block-paragraph"><strong>核心九大组件</strong>包括：<code>HandlerMapping</code>、<code>HandlerAdapter</code>、<code>ViewResolver</code>、<code>HandlerExceptionResolver</code>&nbsp;等。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/17/456/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">456</post-id>	</item>
		<item>
		<title>Ubuntu创建root用户</title>
		<link>https://blog.lhyshome.com/2026/03/11/377/</link>
					<comments>https://blog.lhyshome.com/2026/03/11/377/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Wed, 11 Mar 2026 12:24:34 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[小技巧]]></category>
		<category><![CDATA[脚本]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=377</guid>

					<description><![CDATA[手动执行： 在 Ubuntu 24.04 桌面版中，出于系统安全考虑，root 用户是被默认禁止直接登录图形界… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/11/377/">Read More: Ubuntu创建ro… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">手动执行：</h1>



<p class="wp-block-paragraph">在 Ubuntu 24.04 桌面版中，出于系统安全考虑，root 用户是被默认禁止直接登录图形界面的。如果你确实需要以 root 身份操作桌面，可以按照以下步骤进行配置。</p>



<p class="wp-block-paragraph"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 重要提示</strong>：启用 root 账户登录会带来极大的安全风险。root 拥有系统的最高权限，任何误操作（如误删系统文件）都可能导致系统崩溃或数据丢失。建议仅在必要时（如系统维护、特定测试）开启，用完即关。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">操作步骤</h3>



<h4 class="wp-block-heading">第一步：为 root 用户设置密码</h4>



<p class="wp-block-paragraph">首先，你需要为 root 账户激活一个密码。打开终端，执行以下命令：</p>



<pre class="wp-block-code"><code>sudo passwd root</code></pre>



<p class="wp-block-paragraph">系统会提示你输入并确认新的 root 密码。输入密码时屏幕不会有任何显示，这是正常的。</p>



<h4 class="wp-block-heading">第二步：修改 GDM 配置文件</h4>



<p class="wp-block-paragraph">GDM (GNOME Display Manager) 是 Ubuntu 的登录管理器，需要修改其配置以允许 root 登录。</p>



<ol class="wp-block-list">
<li>使用 <code>nano</code> 编辑器打开配置文件（你也可以用 <code>vim</code> 或 <code>gedit</code> 替代 <code>nano</code>）： <br><code>sudo nano /etc/gdm3/custom.conf</code></li>



<li>在文件中找到 <code>[security]</code> 这一行。如果找不到，可以手动在文件末尾添加。在 <code>[security]</code> 下方添加一行，内容如下： <br><code>[security] </code><br><code>AllowRoot=true</code></li>



<li>按 <code>Ctrl+O</code> 保存文件，然后按 <code>Ctrl+X</code> 退出编辑器。</li>
</ol>



<h4 class="wp-block-heading">第三步：修改 PAM 认证配置</h4>



<p class="wp-block-paragraph">PAM (Pluggable Authentication Modules) 是系统的认证模块，它里面有一条规则明确禁止 root 登录图形界面，我们需要把它注释掉。</p>



<ol class="wp-block-list">
<li>编辑第一个 PAM 文件： <br><code>sudo nano /etc/pam.d/gdm-password</code> <br>找到下面这一行，并在行首添加一个 <code>#</code> 号将其注释掉： <br><code># auth required pam_succeed_if.so user != root quiet_success</code> <br>保存并退出 (<code>Ctrl+O</code>, <code>Ctrl+X</code>)。</li>



<li>用同样的方法编辑第二个 PAM 文件：<br><code>sudo nano /etc/pam.d/gdm-autologin</code><br>同样找到并注释掉这一行：<br><code># auth required pam_succeed_if.so user != root quiet_success</code><br>保存并退出。</li>
</ol>



<h4 class="wp-block-heading">第四步：（可选）修复 root 用户的环境配置文件</h4>



<p class="wp-block-paragraph">首次以 root 登录时，可能会遇到一个关于 <code>/.profile</code> 文件的错误提示。为了避免这个问题，可以提前修复它。</p>



<ol class="wp-block-list">
<li>编辑 root 目录下的 <code>.profile</code> 文件： <br><code>sudo nano /root/.profile</code></li>



<li>找到文件的最后一行，通常是： <br><code>mesg n 2&gt; /dev/null || true</code></li>



<li>将其修改为：<br><code>tty -s &amp;&amp; mesg n || true</code><br>保存并退出。</li>
</ol>



<h4 class="wp-block-heading">第五步：重启系统</h4>



<p class="wp-block-paragraph">所有配置完成后，需要重启系统以使更改生效：</p>



<pre class="wp-block-code"><code>sudo reboot</code></pre>



<h4 class="wp-block-heading">第六步：以 root 身份登录</h4>



<p class="wp-block-paragraph">系统重启后，在登录界面，你会发现列表中没有 root 用户。此时，需要点击 <strong>“未列出？”</strong> 或者 <strong>“Other”</strong> 选项。</p>



<p class="wp-block-paragraph">在弹出的输入框中，用户名输入 <code>root</code>，密码输入你在第一步设置的密码，即可登录到 GNOME 桌面。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 完成后的小建议</strong><br>完成你的工作后，为了系统安全，建议你按照操作步骤的反向操作，将配置改回去（即注释掉 <code>AllowRoot=true</code>，并恢复 PAM 文件中被注释的行），然后重启系统，恢复使用普通用户。</p>



<h1 class="wp-block-heading">脚本执行</h1>



<div class="wp-block-file"><a id="wp-block-file--media-12f8c3e0-ad76-4157-983a-8d3b4f9fecc6" href="https://blog.lhyshome.com/wp-content/uploads/2026/03/ubuntu-root-login.zip">ubuntu-root-login.zip</a><a href="https://blog.lhyshome.com/wp-content/uploads/2026/03/ubuntu-root-login.zip" class="wp-block-file__button wp-element-button" download aria-describedby="wp-block-file--media-12f8c3e0-ad76-4157-983a-8d3b4f9fecc6">下载</a></div>



<p class="wp-block-paragraph">上述文件包含</p>



<figure class="wp-block-image size-full"><img decoding="async" width="221" height="89" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-17.png" alt="" class="wp-image-382"/></figure>



<p class="wp-block-paragraph">enable-root-login.sh 是开启root权限的一键脚本<br>disable-root-login.sh 是关闭root登陆权限的一键脚本</p>



<p class="wp-block-paragraph"><strong>1. 赋予执行权限：</strong></p>



<p class="wp-block-paragraph">chmod +x enable-root-login.sh<br>或者<br><code>chmod +x disable-root-login.sh</code></p>



<p class="wp-block-paragraph"><strong>2.用 sudo 执行：</strong></p>



<p class="wp-block-paragraph">sudo ./enable-root-login.sh<br>或者<br><code>sudo ./disable-root-login.sh</code></p>



<h1 class="wp-block-heading">脚本执行成功后</h1>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1019" height="735" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-20.png" alt="" class="wp-image-387" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-20.png 1019w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-20-300x216.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-20-768x554.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-20-660x476.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-20-500x361.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-20-800x577.png 800w" sizes="(max-width: 1019px) 100vw, 1019px" /></figure>



<p class="wp-block-paragraph">点击&#8221;未列出?&#8221;，就可以使用root用户，用<strong>执行命令行所填的密码</strong>来登录ubuntu桌面版系统了</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1019" height="736" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-19.png" alt="" class="wp-image-386" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-19.png 1019w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-19-300x217.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-19-768x555.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-19-660x477.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-19-500x361.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-19-800x578.png 800w" sizes="(max-width: 1019px) 100vw, 1019px" /></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/11/377/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">377</post-id>	</item>
		<item>
		<title>OpenClaw配置飞书</title>
		<link>https://blog.lhyshome.com/2026/03/11/416/</link>
					<comments>https://blog.lhyshome.com/2026/03/11/416/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Wed, 11 Mar 2026 11:30:06 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[小技巧]]></category>
		<category><![CDATA[生活]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[openclaw]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[飞书]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=416</guid>

					<description><![CDATA[飞书开发者平台： https://accounts.feishu.cn/accounts/page/login… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/11/416/">Read More: OpenClaw配置… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">飞书开发者平台：</h2>



<p class="wp-block-paragraph">https://accounts.feishu.cn/accounts/page/login?app_id=7&#038;no_trap=1&#038;redirect_uri=https%3A%2F%2Fopen.feishu.cn%2Fapp</p>



<h2 class="wp-block-heading">扫描二维码登录自己的飞书账号</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="451" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43-1024x451.png" alt="" class="wp-image-417" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43-1024x451.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43-300x132.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43-768x338.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43-1536x677.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43-660x291.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43-500x220.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43-800x353.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43-1280x564.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-43.png 1920w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">进入后点击“创建企业自建应用”</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="217" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44-1024x217.png" alt="" class="wp-image-418" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44-1024x217.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44-300x63.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44-768x162.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44-1536x325.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44-660x140.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44-500x106.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44-800x169.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44-1280x271.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-44.png 1920w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">点击机器人下面的“+添加”</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="349" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46-1024x349.png" alt="" class="wp-image-420" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46-1024x349.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46-300x102.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46-768x262.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46-1536x524.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46-660x225.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46-500x171.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46-800x273.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46-1280x437.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-46.png 1920w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">配置权限管理</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="890" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-47-1024x890.png" alt="" class="wp-image-421" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-47-1024x890.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-47-300x261.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-47-768x668.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-47-660x574.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-47-500x435.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-47-800x696.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-47.png 1181w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">批量导入权限，将下面json粘贴进框里</h2>



<pre class="wp-block-code"><code>{
  "scopes": {
    "tenant": &#91;
      "im:message",
      "im:message.group_at_msg:readonly",
      "im:message.p2p_msg:readonly",
      "im:message:send_as_bot",
      "im:resource",
      "contact:user.employee_id:readonly"
    ],
    "user": &#91;]
  }
}</code></pre>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="623" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48-1024x623.png" alt="" class="wp-image-422" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48-1024x623.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48-300x182.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48-768x467.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48-1536x934.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48-660x401.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48-500x304.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48-800x487.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48-1280x779.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-48.png 1774w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">申请开通权限</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="905" height="377" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-49.png" alt="" class="wp-image-423" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-49.png 905w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-49-300x125.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-49-768x320.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-49-660x275.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-49-500x208.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-49-800x333.png 800w" sizes="auto, (max-width: 905px) 100vw, 905px" /></figure>



<h2 class="wp-block-heading">配置完成后，将app id，和app secret复制下来</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="456" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50-1024x456.png" alt="" class="wp-image-424" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50-1024x456.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50-300x133.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50-768x342.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50-1536x683.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50-660x294.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50-500x222.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50-800x356.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50-1280x570.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-50.png 1690w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">在OpenClaw的配置步骤中将上图的对应配置贴入</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="469" height="452" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-51.png" alt="" class="wp-image-425" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-51.png 469w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-51-300x289.png 300w" sizes="auto, (max-width: 469px) 100vw, 469px" /></figure>



<h2 class="wp-block-heading">配置完OpenClaw后，执行openclaw gateway启动OpenClaw</h2>



<h2 class="wp-block-heading">返回飞书页面，进入下图菜单，点击红框按钮，配置事件与回调</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="834" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-52-1024x834.png" alt="" class="wp-image-426" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-52-1024x834.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-52-300x244.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-52-768x626.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-52-660x538.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-52-500x407.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-52-800x652.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-52.png 1205w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">选择“消息与群组”，勾选“接受消息”</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="435" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53-1024x435.png" alt="" class="wp-image-427" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53-1024x435.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53-300x128.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53-768x326.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53-1536x653.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53-660x281.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53-500x213.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53-800x340.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53-1280x544.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-53.png 1920w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">完成后点击红框中的“创建版本”</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="672" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54-1024x672.png" alt="" class="wp-image-428" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54-1024x672.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54-300x197.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54-768x504.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54-1536x1009.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54-660x433.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54-500x328.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54-800x525.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54-1280x840.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-54.png 1794w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">填写版本号和更新说明</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="775" height="1024" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-55-775x1024.png" alt="" class="wp-image-429" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-55-775x1024.png 775w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-55-227x300.png 227w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-55-768x1015.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-55-660x872.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-55-500x661.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-55-800x1057.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-55.png 874w" sizes="auto, (max-width: 775px) 100vw, 775px" /></figure>



<h2 class="wp-block-heading">发布版本后，飞书消息列表“开发者小助手”提示应用审批通过，点击“打开应用”</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="590" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-56-1024x590.png" alt="" class="wp-image-430" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-56-1024x590.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-56-300x173.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-56-768x442.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-56-660x380.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-56-500x288.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-56-800x461.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-56.png 1118w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">第一次对话，“弹出以下”聊天信息</h2>



<h2 class="wp-block-heading">将消息最后一行复制，贴入命令行窗口，执行</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="384" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-57-1024x384.png" alt="" class="wp-image-431" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-57-1024x384.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-57-300x113.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-57-768x288.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-57-660x248.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-57-500x188.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-57-800x300.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-57.png 1130w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="131" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58-1024x131.png" alt="" class="wp-image-432" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58-1024x131.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58-300x38.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58-768x98.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58-1536x197.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58-660x85.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58-500x64.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58-800x103.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58-1280x164.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-58.png 1615w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">命令执行成功后，执行命令 openclaw gateway restart 重启OpenClaw</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="585" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-59-1024x585.png" alt="" class="wp-image-433" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-59-1024x585.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-59-300x171.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-59-768x439.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-59-660x377.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-59-500x286.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-59-800x457.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-59.png 1120w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">重启成功后机器人就可以正常对话聊天</h2>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/11/416/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">416</post-id>	</item>
		<item>
		<title>Ubuntu桌面版系统下安装OpenClaw（养龙虾）</title>
		<link>https://blog.lhyshome.com/2026/03/11/389/</link>
					<comments>https://blog.lhyshome.com/2026/03/11/389/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Wed, 11 Mar 2026 10:52:12 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[小技巧]]></category>
		<category><![CDATA[生活]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[openclaw]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=389</guid>

					<description><![CDATA[前言 本文为Linux环境安装，Windows环境博主未作尝试，见谅！ 在Windows中使用虚拟机安装，建议… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/11/389/">Read More: Ubuntu桌面版系… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">前言</h2>



<p class="wp-block-paragraph">本文为<strong>Linux环境</strong>安装，Windows环境博主未作尝试，见谅！</p>



<p class="wp-block-paragraph">在Windows中使用虚拟机安装，建议<strong>内存&gt;32G</strong>，且系统在Windows10以上，需要<strong>开启Windows系统自带的Hyper-v虚拟机</strong>程序，本文对如何使用Hyper-v不再多做赘述。</p>



<p class="wp-block-paragraph">出于<strong>安全</strong>考虑和第一次尝鲜，博主的OpenClaw是安装在Windows系统下<strong>Hyper-v虚拟机</strong>中，部署的是<strong>ubuntu-24.04.1-desktop-amd64</strong>操作系统。<br>Ubuntu虚拟机要求：建议 2核CPU、4GB内存以上，40GB可用磁盘空间（可视情况扩展）<br>博主使用的是：10核CPU、8GB动态内存，120GB可用磁盘空间</p>



<p class="wp-block-paragraph">关键字：<br><strong>OpenClaw 2026.3.8、Windows、Hyper-v虚拟机、ubuntu-24.04.1-desktop、</strong><br><strong>Linux、minimax 2.5、飞书（feishu）</strong></p>



<p class="wp-block-paragraph">以下是博主踩坑后总结的安装流程</p>



<h2 class="wp-block-heading">安装前准备工作：</h2>



<p class="wp-block-paragraph"><strong>申请大模型api：</strong>根据自己的喜好选择。（博主使用的是minimax2.5 模型试用）</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="467" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-23-1024x467.png" alt="" class="wp-image-392" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-23-1024x467.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-23-300x137.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-23-768x351.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-23-660x301.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-23-500x228.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-23-800x365.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-23.png 1058w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>飞书的聊天机器人配置</strong></p>



<p class="wp-block-paragraph"><strong>获取Ubuntu的最高root权限：</strong>（因为龙虾可能需要很高的操作权限，所以安置在root下）<br>命令行登录后 使用<br>sudo -i<br>输入登录用户密码后，进入root用户权限</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="330" height="64" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-21.png" alt="" class="wp-image-390" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-21.png 330w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-21-300x58.png 300w" sizes="auto, (max-width: 330px) 100vw, 330px" /></figure>



<p class="wp-block-paragraph"><strong>创建root用户：</strong><a href="https://blog.lhyshome.com/2026/03/11/377/">https://blog.lhyshome.com/2026/03/11/377/</a></p>



<p class="wp-block-paragraph"><strong>更新系统与安装基础工具</strong>：<br>打开终端，逐行执行以下命令，确保系统包是最新的，并安装 <code>curl</code> 和 <code>wget</code> 等必要工具。 <br><code>apt update &amp;&amp; sudo apt upgrade -y </code><br><code>apt install -y curl wget git python3 build-essential</code></p>



<p class="wp-block-paragraph"><strong>安装 Node.js</strong>：<br>OpenClaw 的核心依赖是 Node.js，<strong>必须安装 22.x 或更高版本</strong>。通过官方源安装是最稳定的方法。 <br><code># 添加 NodeSource 22.x 仓库 </code><br><code>curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - </code><br><code># 安装 Node.js 和 npm </code><br><code>apt install -y nodejs </code><br><code># 验证安装版本 </code><br><code>node --version # 应输出 v22.x.x</code></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="338" height="39" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-22.png" alt="" class="wp-image-391" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-22.png 338w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-22-300x35.png 300w" sizes="auto, (max-width: 338px) 100vw, 338px" /></figure>



<p class="wp-block-paragraph"><strong>（可选）国内环境优化</strong>：<br>如果你在境内，执行以下命令将 npm 镜像源切换到淘宝源，可以大幅提升下载速度和成功率。<br><code>npm config set registry https://registry.npmmirror.com</code></p>



<h2 class="wp-block-heading">安装OpenClaw：</h2>



<p class="wp-block-paragraph"><strong>官方一键安装脚本（最推荐）</strong>这个脚本会自动完成Node.js检测、CLI安装，并立即启动配置向导。 </p>



<pre class="wp-block-code"><code><code>curl -fsSL https://openclaw.ai/install.sh | bash</code></code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="573" height="378" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/8fc54c5b-7b2d-4498-a728-cb6076bd7ce2-1.png" alt="" class="wp-image-404" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/8fc54c5b-7b2d-4498-a728-cb6076bd7ce2-1.png 573w, https://blog.lhyshome.com/wp-content/uploads/2026/03/8fc54c5b-7b2d-4498-a728-cb6076bd7ce2-1-300x198.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/8fc54c5b-7b2d-4498-a728-cb6076bd7ce2-1-500x330.png 500w" sizes="auto, (max-width: 573px) 100vw, 573px" /></figure>



<h2 class="wp-block-heading">配置OpenClaw：（以下是博主的配置，可以参考）</h2>



<p class="wp-block-paragraph">安装完成后命令行自动进入配置安装，后续也可以使用 openclaw onboard 命令进行配置</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="834" height="911" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-24.png" alt="" class="wp-image-393" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-24.png 834w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-24-275x300.png 275w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-24-768x839.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-24-660x721.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-24-500x546.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-24-800x874.png 800w" sizes="auto, (max-width: 834px) 100vw, 834px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="545" height="73" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-25.png" alt="" class="wp-image-394" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-25.png 545w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-25-300x40.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-25-500x67.png 500w" sizes="auto, (max-width: 545px) 100vw, 545px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="306" height="555" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-26.png" alt="" class="wp-image-395" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-26.png 306w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-26-165x300.png 165w" sizes="auto, (max-width: 306px) 100vw, 306px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="421" height="128" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-27.png" alt="" class="wp-image-396" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-27.png 421w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-27-300x91.png 300w" sizes="auto, (max-width: 421px) 100vw, 421px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="312" height="79" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-28.png" alt="" class="wp-image-397" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-28.png 312w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-28-300x76.png 300w" sizes="auto, (max-width: 312px) 100vw, 312px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="763" height="205" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-30.png" alt="" class="wp-image-399" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-30.png 763w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-30-300x81.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-30-660x177.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-30-500x134.png 500w" sizes="auto, (max-width: 763px) 100vw, 763px" /></figure>



<p class="wp-block-paragraph">点击链接，进入页面后选择授权</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="608" height="532" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-31.png" alt="" class="wp-image-400" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-31.png 608w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-31-300x263.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-31-500x438.png 500w" sizes="auto, (max-width: 608px) 100vw, 608px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="763" height="613" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-32.png" alt="" class="wp-image-401" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-32.png 763w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-32-300x241.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-32-660x530.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-32-500x402.png 500w" sizes="auto, (max-width: 763px) 100vw, 763px" /></figure>



<p class="wp-block-paragraph">此处注意和飞书中的先后次序（可以参考：<a href="https://blog.lhyshome.com/2026/03/11/416/">https://blog.lhyshome.com/2026/03/11/416/</a>），要是配置错误页没关系，可以重复执行openclaw onboard 命令进行配置</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="475" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33-1024x475.png" alt="" class="wp-image-405" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33-1024x475.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33-300x139.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33-768x356.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33-1536x713.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33-660x306.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33-500x232.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33-800x371.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33-1280x594.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-33.png 1631w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="436" height="172" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-34.png" alt="" class="wp-image-406" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-34.png 436w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-34-300x118.png 300w" sizes="auto, (max-width: 436px) 100vw, 436px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="205" height="54" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-35.png" alt="" class="wp-image-407" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-35.png 205w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-35-200x54.png 200w" sizes="auto, (max-width: 205px) 100vw, 205px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="358" height="61" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-36.png" alt="" class="wp-image-408" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-36.png 358w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-36-300x51.png 300w" sizes="auto, (max-width: 358px) 100vw, 358px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="803" height="130" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-37.png" alt="" class="wp-image-409" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-37.png 803w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-37-300x49.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-37-768x124.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-37-660x107.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-37-500x81.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-37-800x130.png 800w" sizes="auto, (max-width: 803px) 100vw, 803px" /></figure>



<p class="wp-block-paragraph">在安装了OpenClaw的服务器上打开红框中的地址即可访问OpenClaw的webUI页面</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="839" height="661" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-38.png" alt="" class="wp-image-410" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-38.png 839w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-38-300x236.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-38-768x605.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-38-660x520.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-38-500x394.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-38-800x630.png 800w" sizes="auto, (max-width: 839px) 100vw, 839px" /></figure>



<pre class="wp-block-code"><code>执行命令：openclaw gateway</code></pre>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="461" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39-1024x461.png" alt="" class="wp-image-411" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39-1024x461.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39-300x135.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39-768x346.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39-1536x692.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39-660x297.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39-500x225.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39-800x361.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39-1280x577.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-39.png 1693w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="560" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40-1024x560.png" alt="" class="wp-image-412" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40-1024x560.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40-300x164.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40-768x420.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40-1536x839.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40-660x361.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40-500x273.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40-800x437.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40-1280x700.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-40.png 1914w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">频道中显示飞书</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="826" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-41-1024x826.png" alt="" class="wp-image-413" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-41-1024x826.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-41-300x242.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-41-768x620.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-41-660x533.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-41-500x404.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-41-800x646.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-41.png 1057w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">代理中显示minimax，则执行成功</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="538" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-1024x538.png" alt="" class="wp-image-414" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-1024x538.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-300x158.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-768x403.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-1536x807.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-660x347.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-380x200.png 380w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-500x263.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-800x420.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42-1280x672.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-42.png 1910w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">至此安装配置完成</p>



<h2 class="wp-block-heading">安装技能（skills）</h2>



<p class="wp-block-paragraph"><a href="https://clawhub.ai/skills?sort=downloads">https://clawhub.ai/skills?sort=downloads</a></p>



<p class="wp-block-paragraph">进入这个链接，里面为技能库，可以在这里挑选自己所需要的技能</p>



<p class="wp-block-paragraph">例如：</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="737" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-61-1024x737.png" alt="" class="wp-image-441" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-61-1024x737.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-61-300x216.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-61-768x553.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-61-660x475.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-61-500x360.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-61-800x576.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-61.png 1095w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="640" height="365" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-63.png" alt="" class="wp-image-443" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-63.png 640w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-63-300x171.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-63-500x285.png 500w" sizes="auto, (max-width: 640px) 100vw, 640px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="430" height="319" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-64.png" alt="" class="wp-image-444" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-64.png 430w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-64-300x223.png 300w" sizes="auto, (max-width: 430px) 100vw, 430px" /></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/11/389/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389</post-id>	</item>
		<item>
		<title>iStoreOS的openclash，TUN 接口启动失败</title>
		<link>https://blog.lhyshome.com/2026/03/08/366/</link>
					<comments>https://blog.lhyshome.com/2026/03/08/366/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Sat, 07 Mar 2026 17:46:55 +0000</pubDate>
				<category><![CDATA[小技巧]]></category>
		<category><![CDATA[iStoreOs]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[openClash]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=366</guid>

					<description><![CDATA[问题 iStoreOS的openclash，运行模式选择“TUN”或者“混合”时，提示：警告：TUN 接口启动… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/08/366/">Read More: iStoreOS的o… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">问题</h2>



<p class="wp-block-paragraph">iStoreOS的openclash，运行模式选择“TUN”或者“混合”时，提示：警告：TUN 接口启动失败，尝试重启内核…，导致openclash一直重启，内网以旁路由为网关的虚拟机和服务，访问互联网和外网都出现了无法打开的情况。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="514" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9-1024x514.png" alt="" class="wp-image-367" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9-1024x514.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9-300x151.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9-768x385.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9-1536x771.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9-660x331.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9-500x251.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9-800x402.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9-1280x642.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-9.png 1556w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">访问各大大模型无果后，百度搜索关键字，发现github上已有相关问题处理</p>



<p class="wp-block-paragraph">地址为：<a href="https://github.com/vernesong/OpenClash/issues/4492">https://github.com/vernesong/OpenClash/issues/4492</a></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1009" height="315" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-10.png" alt="" class="wp-image-368" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-10.png 1009w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-10-300x94.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-10-768x240.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-10-660x206.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-10-500x156.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-10-800x250.png 800w" sizes="auto, (max-width: 1009px) 100vw, 1009px" /></figure>



<p class="wp-block-paragraph">使用如上图方法处理成功。</p>



<h2 class="wp-block-heading">处理步骤</h2>



<p class="wp-block-paragraph">执行 “ip link show”命令，如下图发现error</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="787" height="524" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-12.png" alt="" class="wp-image-370" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-12.png 787w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-12-300x200.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-12-768x511.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-12-660x439.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-12-500x333.png 500w" sizes="auto, (max-width: 787px) 100vw, 787px" /></figure>



<p class="wp-block-paragraph">在系统->杂项设置 中，将镜像服务器选择如下图所示。（如果不选，则影响opkg update执行）</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="743" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-11-1024x743.png" alt="" class="wp-image-369" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-11-1024x743.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-11-300x218.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-11-768x557.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-11-660x479.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-11-500x363.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-11-800x580.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-11.png 1154w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">选择镜像源后依次执行</p>



<p class="wp-block-paragraph">opkg update</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="521" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-13-1024x521.png" alt="" class="wp-image-371" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-13-1024x521.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-13-300x153.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-13-768x391.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-13-660x336.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-13-500x254.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-13-800x407.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-13.png 1178w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">opkg install &#8211;force-reinstall ip-full</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="971" height="101" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-14.png" alt="" class="wp-image-372" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-14.png 971w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-14-300x31.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-14-768x80.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-14-660x69.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-14-500x52.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-14-800x83.png 800w" sizes="auto, (max-width: 971px) 100vw, 971px" /></figure>



<p class="wp-block-paragraph">如上两图，控制台中未见明确error相关字眼为成功</p>



<p class="wp-block-paragraph">重新执行“ip link show”命令，如下图为成功</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-15-1024x576.png" alt="" class="wp-image-373" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-15-1024x576.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-15-300x169.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-15-768x432.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-15-660x371.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-15-500x281.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-15-800x450.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-15.png 1101w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">重启openclash后日志显示成功，测试成功</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="292" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-16-1024x292.png" alt="" class="wp-image-374" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-16-1024x292.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-16-300x86.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-16-768x219.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-16-660x188.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-16-500x143.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-16-800x228.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-16-1280x365.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-16.png 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/08/366/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">366</post-id>	</item>
		<item>
		<title>【Java面试】redis</title>
		<link>https://blog.lhyshome.com/2026/03/04/353/</link>
					<comments>https://blog.lhyshome.com/2026/03/04/353/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Wed, 04 Mar 2026 10:07:52 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=353</guid>

					<description><![CDATA[Redis 是什么？都有哪些使用场景？ Redis 是一个使用 C 语言开发的高速缓存数据库。 Redis 使… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/04/353/">Read More: 【Java面试】re… &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="9ld6q">Redis 是什么？都有哪些使用场景？</h2>



<p class="wp-block-paragraph">Redis 是一个使用 C 语言开发的高速缓存数据库。</p>



<p class="wp-block-paragraph">Redis 使用场景：</p>



<ul class="wp-block-list">
<li>记录帖子点赞数、点击数、评论数；</li>



<li>缓存近期热帖；</li>



<li>缓存文章详情信息；</li>



<li>记录用户会话信息。</li>
</ul>



<h2 class="wp-block-heading" id="cmmhk">Redis 有哪些功能？</h2>



<ul class="wp-block-list">
<li>数据缓存功能</li>



<li>分布式锁的功能</li>



<li>支持数据持久化</li>



<li>支持事务</li>



<li>支持消息队列</li>
</ul>



<h2 class="wp-block-heading" id="f5r6k">Redis 和 memcache 有什么区别？</h2>



<ul class="wp-block-list">
<li>存储方式不同：memcache 把数据全部存在内存之中，断电后会挂掉，数据不能超过内存大小；Redis 有部份存在硬盘上，这样能保证数据的持久性。</li>



<li>数据支持类型：memcache 对数据类型支持相对简单；Redis 有复杂的数据类型。</li>



<li>使用底层模型不同：它们之间底层实现方式，以及与客户端之间通信的应用协议不一样，Redis 自己构建了 vm 机制，因为一般的系统调用系统函数的话，会浪费一定的时间去移动和请求。</li>



<li>value 值大小不同：Redis 最大可以达到 512mb；memcache 只有 1mb。</li>
</ul>



<h2 class="wp-block-heading" id="3o5b2">Redis 为什么是单线程的？</h2>



<p class="wp-block-paragraph">因为 cpu 不是 Redis 的瓶颈，Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现，而且 cpu 又不会成为瓶颈，那就顺理成章地采用单线程的方案了。</p>



<p class="wp-block-paragraph">关于 Redis 的性能，官方网站也有，普通笔记本轻松处理每秒几十万的请求。</p>



<p class="wp-block-paragraph">而且单线程并不代表就慢 nginx 和 nodejs 也都是高性能单线程的代表。</p>



<h2 class="wp-block-heading" id="f516k">什么是缓存穿透？怎么解决？</h2>



<p class="wp-block-paragraph">缓存穿透：指查询一个一定不存在的数据，由于缓存是不命中时需要从数据库查询，查不到数据则不写入缓存，这将导致这个不存在的数据每次请求都要到数据库去查询，造成缓存穿透。</p>



<p class="wp-block-paragraph">解决方案：最简单粗暴的方法如果一个查询返回的数据为空（不管是数据不存在，还是系统故障），我们就把这个空结果进行缓存，但它的过期时间会很短，最长不超过五分钟。</p>



<h2 class="wp-block-heading" id="4g04h">Redis 支持的数据类型有哪些？</h2>



<p class="wp-block-paragraph">Redis 支持的数据类型：string（字符串）、list（列表）、hash（字典）、set（集合）、zset（有序集合）。</p>



<h2 class="wp-block-heading" id="9tb8a">Redis 支持的 Java 客户端都有哪些？</h2>



<p class="wp-block-paragraph">支持的 Java 客户端有 Redisson、jedis、lettuce 等。</p>



<h2 class="wp-block-heading" id="vdag">jedis 和 Redisson 有哪些区别？</h2>



<ul class="wp-block-list">
<li>jedis：提供了比较全面的 Redis 命令的支持。</li>



<li>Redisson：实现了分布式和可扩展的 Java 数据结构，与 jedis 相比 Redisson 的功能相对简单，不支持排序、事务、管道、分区等 Redis 特性。</li>
</ul>



<h2 class="wp-block-heading" id="5d4f7">怎么保证缓存和数据库数据的一致性？</h2>



<ul class="wp-block-list">
<li>合理设置缓存的过期时间。</li>



<li>新增、更改、删除数据库操作时同步更新 Redis，可以使用事物机制来保证数据的一致性。</li>
</ul>



<h2 class="wp-block-heading" id="2g5n8">Redis 持久化有几种方式？</h2>



<p class="wp-block-paragraph">Redis 的持久化有两种方式，或者说有两种策略：</p>



<ul class="wp-block-list">
<li>RDB（Redis Database）：指定的时间间隔能对你的数据进行快照存储。</li>



<li>AOF（Append Only File）：每一个收到的写命令都通过write函数追加到文件中。</li>
</ul>



<h2 class="wp-block-heading" id="476uu">Redis 怎么实现分布式锁？</h2>



<p class="wp-block-paragraph">Redis 分布式锁其实就是在系统里面占一个“坑”，其他程序也要占“坑”的时候，占用成功了就可以继续执行，失败了就只能放弃或稍后重试。</p>



<p class="wp-block-paragraph">占坑一般使用 setnx(set if not exists)指令，只允许被一个程序占有，使用完调用 del 释放锁。</p>



<h2 class="wp-block-heading" id="9uhri">Redis 分布式锁有什么缺陷？</h2>



<p class="wp-block-paragraph">Redis 分布式锁不能解决超时的问题，分布式锁有一个超时时间，程序的执行如果超出了锁的超时时间就会出现问题。</p>



<h2 class="wp-block-heading" id="cadob">Redis 如何做内存优化？</h2>



<p class="wp-block-paragraph">尽量使用 Redis 的散列表，把相关的信息放到散列表里面存储，而不是把每个字段单独存储，这样可以有效的减少内存使用。比如将 Web 系统的用户对象，应该放到散列表里面再整体存储到 Redis，而不是把用户的姓名、年龄、密码、邮箱等字段分别设置 key 进行存储。</p>



<h2 class="wp-block-heading" id="efamv">Redis 淘汰策略有哪些？</h2>



<ul class="wp-block-list">
<li>volatile-lru：从已设置过期时间的数据集（server. db[i]. expires）中挑选最近最少使用的数据淘汰。</li>



<li>volatile-ttl：从已设置过期时间的数据集（server. db[i]. expires）中挑选将要过期的数据淘汰。</li>



<li>volatile-random：从已设置过期时间的数据集（server. db[i]. expires）中任意选择数据淘汰。</li>



<li>allkeys-lru：从数据集（server. db[i]. dict）中挑选最近最少使用的数据淘汰。</li>



<li>allkeys-random：从数据集（server. db[i]. dict）中任意选择数据淘汰。</li>



<li>no-enviction（驱逐）：禁止驱逐数据。</li>
</ul>



<h2 class="wp-block-heading" id="1hd4h">Redis 常见的性能问题有哪些？该如何解决？</h2>



<ul class="wp-block-list">
<li>主服务器写内存快照，会阻塞主线程的工作，当快照比较大时对性能影响是非常大的，会间断性暂停服务，所以主服务器最好不要写内存快照。</li>



<li>Redis 主从复制的性能问题，为了主从复制的速度和连接的稳定性，主从库最好在同一个局域网内。</li>
</ul>



<h2 class="wp-block-heading">什么是Redis</h2>



<ul class="wp-block-list">
<li>Redis(Remote Dictionary Server) 是一个使用 C 语言编写的，开源的（BSD许 可）高性能非关系型（NoSQL）的键值对数据库。</li>



<li>Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串，值 支持五种数据类型：字符串、列表、集合、散列表、有序集合。</li>



<li>与传统数据库不同的是 Redis 的数据是存在内存中的，所以读写速度非常快， 因此 redis 被广泛应用于缓存方向，每秒可以处理超过 10万次读写操作，是已知性能快的Key-Value DB。</li>



<li>另外，Redis 也经常用来做分布式 锁。除此之 外，Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。</li>
</ul>



<h2 class="wp-block-heading">Redis有哪些优缺点</h2>



<ul class="wp-block-list">
<li>优点
<ul class="wp-block-list">
<li>读写性能优异， Redis能读的速度是110000次/s，写的速度是81000次/s。</li>



<li>支持数据持久化，支持AOF和RDB两种持久化方式。</li>



<li>AOF：写日志的方式，记录每次对服务器写的操作, 当服务器重启的时候会重新执行这些命令来恢复原始的数据。例如 Mysql binlog，Hbase HLog。</li>



<li>RDB：这是一种快照的方式，它将 Redis 某时间点的数据都进行快照存储。比如 Mysql Dump 也是这种方式。</li>



<li>支持事务，Redis的所有操作都是原子性的，同时Redis还支持对几个操作合并 后的原子性执行。</li>



<li>数据结构丰富，除了支持string类型的value外还支持hash、set、zset、list等 数据结构。</li>



<li>支持主从复制，主机会自动将数据同步到从机，可以进行读写分离。</li>
</ul>
</li>



<li>缺点
<ul class="wp-block-list">
<li>数据库容量受到物理内存的限制，不能用作海量数据的高性能读写，因此Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。</li>



<li>Redis 不具备自动容错和恢复功能，主机从机的宕机都会导致前端部分读写请求 失败，需要等待机器重启或者手动切换前端的IP才能恢复。</li>



<li>主机宕机，宕机前有部分数据未能及时同步到从机，切换IP后还会引入数据不一 致的问题，降低了系统的可用性。</li>



<li>Redis 较难支持在线扩容，在集群容量达到上限时在线扩容会变得很复杂。为避 免这一问题，运维人员在系统上线时必须确保有足够的空间，这对资源造成了很大的浪费。</li>
</ul>
</li>
</ul>



<h2 class="wp-block-heading">为什么要用 Redis/缓存</h2>



<p class="wp-block-paragraph">主要从“高性能”和“高并发”这两点来看待这个问题。<br><br><strong>高性能：</strong><br>假如用户第一次访问数据库中的某些数据。这个过程会比较慢，因为是从硬盘上 读取的。将该用户访问的数据存在数缓存中，这样下一次再访问这些数据的时候 就可以直接从缓存中获取了。操作缓存就是直接操作内存，所以速度相当快。如 果数据库中的对应数据改变的之后，同步改变缓存中相应的数据即可！</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="553" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1024x553.png" alt="" class="wp-image-355" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1024x553.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-300x162.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-768x415.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-660x356.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-500x270.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-800x432.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image.png 1213w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>高并发：</strong><br>直接操作缓存能够承受的请求是远远大于直接访问数据库的，所以我们可以考虑 把数据库中的部分数据转移到缓存中去，这样用户的一部分请求会直接到缓存这 里而不用经过数据库。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="762" height="651" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1.png" alt="" class="wp-image-357" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1.png 762w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1-300x256.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1-660x564.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1-500x427.png 500w" sizes="auto, (max-width: 762px) 100vw, 762px" /></figure>



<h2 class="wp-block-heading">为什么要用Redis 而不用 map/guava 做缓存?</h2>



<p class="wp-block-paragraph">缓存分为本地缓存和分布式缓存。以 Java 为例，使用自带的 map 或者 guava 实现的是本地缓存，主要的特点是轻量以及快速，生命周期随着 jvm 的销毁 而结束，并且在多实例的情况下，每个实例都需要各自保存一份缓存，缓存不具有一致性。<br>使用 redis 或 memcached 之类的称为分布式缓存，在多实例的情况下，各实 例共用一份缓存数据，缓存具有一致性。缺点是需要保持 redis 或 memcached 服务的高可用，整个程序架构上较为复杂。</p>



<h2 class="wp-block-heading">Redis为什么这么快</h2>



<p class="wp-block-paragraph">完全基于内存，绝大部分请求是纯粹的内存操作，非常快速。数据存在内存 中，类似于 HashMap，HashMap 的优势就是查找和操作的时间复杂度都是 O(1)；<br>数据结构简单，对数据操作也简单，Redis 中的数据结构是专门进行设计 的；<br>采用单线程，避免了不必要的上下文切换和竞争条件，也不存在多进程或者多线程导致的切换而消耗CPU，不用去考虑各种锁的问题，不存在加锁释放锁操作，没有因为可能出现死锁而导致的性能消耗；<br>使用多路 I/O 复用模型，非阻塞 IO；<br>使用底层模型不同，它们之间底层实现方式以及与客户端之间通信的应用协议不一样，Redis直接自己构建了 VM 机制 ，因为一般的系统调用系统函数的话，会浪费一定的时间去移动和请求；</p>



<h2 class="wp-block-heading">Redis有哪些数据类型</h2>



<p class="wp-block-paragraph">Redis主要有5种数据类型，包括String，List，Set，Zset，Hash，满足大部分 的使用要求 数据类型 可以存储 的值 操作 应用场景</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td><strong>数据类型</strong></td><td><strong>可以存储的值</strong></td><td><strong>操作</strong></td><td><strong>应用场景</strong></td></tr><tr><td>STRING</td><td>字符串、 整数或者 浮点数</td><td>对整个字 符串或者 字符串的 其中一部 分执行操 作 对整数和 浮点数执 行自增或者自减操作</td><td>做简单的 键值对缓存</td></tr><tr><td>LIST</td><td>列表</td><td>从两端压 入或者弹 出元素 对单个或 者多个元 素进行修 剪， 只保留一 个范围内 的元素</td><td>存储一些 列表型的 数据结 构，类似 粉丝列 表、文章 的评论列 表之类的 数据</td></tr><tr><td>SET</td><td>无序集合</td><td>添加、获 取、移除 单个元素 检查一个 元素是否 存在于集 合中</td><td>交集、并 集、差集 的操作， 比如交 集，可以 把两个人 的粉丝列</td></tr><tr><td>HASH</td><td>包含键值 对的无序 散列表</td><td>添加、获 取、移除 单个键值 对 获取所有 键值对 检查某个 键是否存 在</td><td>结构化的 数据，比 如一个对 象</td></tr><tr><td>ZSET</td><td>有序集合</td><td>添加、获 取、删除 元素 根据分值 范围或者 成员来获 取元素 计算一个 键的排名</td><td>去重但可 以排序， 如获取排 名前几名 的用户</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Redis的应用场景</h2>



<p class="wp-block-paragraph"><strong>7.1 总结一</strong><br><strong>计数器</strong><br>可以对 String 进行自增自减运算，从而实现计数器功能。Redis 这种内存型数 据库的读写性能非常高，很适合存储频繁读写的计数量。<br><strong>缓存</strong><br>将热点数据放到内存中，设置内存的大使用量以及淘汰策略来保证缓存的命中率。<br><strong>会话缓存</strong><br>可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存 储用户的会话信息，也就不再具有状态，一个用户可以请求任意一个应用服务 器，从而更容易实现高可用性以及可伸缩性。<br><strong>全页缓存（FPC）</strong><br>除基本的会话token之外，Redis还提供很简便的FPC平台。以Magento为例， Magento提供一个插件来使用Redis作为全页缓存后端。此外，对WordPress的用户来说，Pantheon有一个非常好的插件 wp-redis，这个插件能帮助你以快速度加载你曾浏览过的页面。<br><strong>查找表</strong><br>例如 DNS 记录就很适合使用 Redis 进行存储。查找表和缓存类似，也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效，而缓存的内容可以失效， 因为缓存不作为可靠的数据来源。<br><strong>消息队列(发布/订阅功能)</strong><br>List 是一个双向链表，可以通过 lpush 和 rpop 写入和读取消息。不过好使用Kafka、RabbitMQ等消息中间件。<br><strong>分布式锁实现</strong><br>在分布式场景下，无法使用单机环境下的锁来对多个节点上的进程进行同步。可 以使用 Redis 自带的 SETNX 命令实现分布式锁，除此之外，还可以使用官方提 供的 RedLock 分布式锁实现。<br><strong>其它</strong><br>Set 可以实现交集、并集等操作，从而实现共同好友等功能。ZSet 可以实现有 序性操作，从而实现排行榜等功能。</p>



<p class="wp-block-paragraph"><strong>7.2 总结二</strong><br>Redis相比其他缓存，有一个非常大的优势，就是支持多种数据类型。<br>数据类型说明string字符串，简单的k-v存储hashhash格式，value为field和 value，适合ID-Detail这样的场景。list简单的list，顺序列表，支持首位或者末 尾插入数据set无序list，查找速度快，适合交集、并集、差集处理sorted set有 序的set<br>其实，通过上面的数据类型的特性，基本就能想到合适的应用场景了。</p>



<ul class="wp-block-list">
<li><strong>string：</strong>适合简单的k-v存储，类似于memcached的存储结构，短信验证 码，配置信息等，就用这种类型来存储。</li>



<li><strong>hash：</strong>一般key为ID或者唯一标示，value对应的就是详情了。如商品详情， 个人信息详情，新闻详情等。</li>



<li><strong>list：</strong>因为list是有序的，比较适合存储一些有序且数据相对固定的数据。如省 市区表、字典表等。因为list是有序的，适合根据写入的时间来排序，如：新的***，消息队列等。</li>



<li><strong>set：</strong>可以简单的理解为ID-List的模式，如微博中一个人有哪些好友，set 牛的地方在于，可以对两个set提供交集、并集、差集操作。例如：查找两个人 共同的好友等。</li>



<li><strong>Sorted Set：</strong>是set的增强版本，增加了一个score参数，自动会根据score的 值进行排序。比较适合类似于top 10等不根据插入的时间来排序的数据。 如上所述，虽然Redis不像关系数据库那么复杂的数据结构，但是，也能适合很 多场景，比一般的缓存数据结构要多。了解每种数据结构适合的业务场景，不仅 有利于提升开发效率，也能有效利用Redis的性能。</li>
</ul>



<h2 class="wp-block-heading">Redis 的持久化机制是什么？各自的优缺点</h2>



<p class="wp-block-paragraph">持久化就是把内存的数据写到磁盘中去，防止服务宕机了内存数据丢失。<br>Redis 提供两种持久化机制 RDB（默认） 和 AOF 机制:<br><strong>RDB：是Redis DataBase缩写快照</strong><br>RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中，对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="196" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-1024x196.png" alt="" class="wp-image-358" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-1024x196.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-300x57.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-768x147.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-660x126.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-500x96.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-800x153.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-1280x245.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2.png 1402w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>优点：</strong></p>



<ol class="wp-block-list">
<li>只有一个文件dump.rdb，方便持久化。</li>



<li>容灾性好，一个文件可以保存到安全的磁盘。</li>



<li>性能大化，fork子进程来完成写操作，让主进程继续处理命令，所以是 IO 大化。使用单独子进程来进行持久化，主进程不会进行任何 IO 操作，保证了 redis 的高性能</li>



<li>相对于数据集大时，比 AOF 的启动效率更高。</li>
</ol>



<p class="wp-block-paragraph"><strong>缺点：</strong></p>



<ol class="wp-block-list">
<li>数据安全性低。RDB是间隔一段时间进行持久化，如果持久化之间redis发生故障，会发生数据丢失。所以这种方式更适合数据要求不那么严谨的时候。</li>



<li>AOF（Append-only file)持久化方式： 是指所有的命令行记录以 redis 命 令请 求协议的格式完全持久化存储)保存为 aof 文件。</li>
</ol>



<p class="wp-block-paragraph"><strong>AOF：持久化</strong></p>



<p class="wp-block-paragraph">AOF持久化(即Append Only File持久化)，则是将Redis执行的每次写命令记录到单独的日志文件中，当重启Redis会重新将持久化的日志中文件恢复数据。 当两种方式同时开启时，数据恢复Redis会优先选择AOF恢复。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="179" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-1024x179.png" alt="" class="wp-image-359" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-1024x179.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-300x53.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-768x134.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-660x116.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-500x88.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-800x140.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-1280x224.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3.png 1377w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>优点：</strong></p>



<ol class="wp-block-list">
<li>数据安全，aof 持久化可以配置 appendfsync 属性，有 always，每进行一 次命令操作就记录到 aof 文件中一次。</li>



<li>通过 append 模式写文件，即使中途服务器宕机，可以通过 redis-checkaof 工具解决数据一致性问题。</li>



<li>AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前（文件过大时会对命 令 进行合并重写），可以删除其中的某些命令（比如误操作的 flushall）。</li>
</ol>



<p class="wp-block-paragraph"><strong>缺点：</strong></p>



<ol class="wp-block-list">
<li>AOF 文件比 RDB 文件大，且恢复速度慢。</li>



<li>数据集大的时候，比 rdb 启动效率低。</li>
</ol>



<p class="wp-block-paragraph"><strong>优缺点是什么？</strong></p>



<ol class="wp-block-list">
<li>AOF文件比RDB更新频率高，优先使用AOF还原数据。</li>



<li>AOF比RDB更安全也更大</li>



<li>RDB性能比AOF好</li>



<li>如果两个都配了优先加载AOF</li>
</ol>



<h2 class="wp-block-heading">如何选择合适的持久化方式</h2>



<ul class="wp-block-list">
<li>一般来说， 如果想达到足以媲美PostgreSQL的数据安全性，你应该 同时使用两种持久化功能。在这种情况下，当 Redis 重启的时候会优先载 入AOF文件来恢复原始的数据，因为在通常情况下AOF文件保存的数据集 要比RDB文件保存的数据集要完整。</li>



<li>如果你非常关心你的数据， 但仍然可以承受数分钟以内的数据丢失， 那么你可以只使用RDB持久化。</li>



<li>有很多用户都只使用AOF持久化，但并不推荐这种方式，因为定时生 成RDB快照（snapshot）非常便于进行数据库备份， 并且 RDB 恢复数据 集的速度也要比AOF恢复的速度要快，除此之外，使用RDB还可以避免 AOF程序的bug。</li>



<li>如果你只希望你的数据在服务器运行的时候存在，你也可以不使用任何持久化方式。</li>
</ul>



<h2 class="wp-block-heading">Redis的过期键的删除策略（持久化）</h2>



<p class="wp-block-paragraph">我们都知道，Redis是key-value数据库，我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了，Redis如何处理。<br>过期策略通常有以下三种：</p>



<ul class="wp-block-list">
<li>定时过期：每个设置过期时间的key都需要创建一个定时器，到过期时间就会立即清除。该策略可以立即清除过期的数据，对内存很友好；但是会占用大量的CPU资源去处理过期的数据，从而影响缓存的响应时间和吞吐量。</li>



<li>惰性过期：只有当访问一个key时，才会判断该key是否已过期，过期则清除。该策略可以最大化地节省CPU资源，却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问，从而不会被清除，占用大量内存。</li>



<li>定期过期：每隔一定的时间，会扫描一定数量的数据库的expires字典中一定数量的key，并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时，可以在不同情况下使得CPU和内存资源达到优的平衡效果。<br>(expires字典会保存所有设置了过期时间的key的过期时间数据，其中，key是指向键空间中的某个键的指针，value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)</li>
</ul>



<p class="wp-block-paragraph">Redis中同时使用了惰性过期和定期过期两种过期策略。</p>



<h2 class="wp-block-heading">我们知道通过expire来设置key 的过期时间，那么对过期的数据怎么处理呢?（针对持久化的数据）</h2>



<p class="wp-block-paragraph">除了缓存服务器自带的缓存失效策略之外（Redis默认的有6中策略可供选择），我们还可以根据具体的业务需求进行自定义的缓存淘汰，常见的策略有两种：</p>



<ol class="wp-block-list">
<li>定时去清理过期的缓存；</li>



<li>当有用户请求过来时，再判断这个请求所用到的缓存是否过期，过期的话就去底层系统得到新数据并更新缓存。</li>
</ol>



<p class="wp-block-paragraph">两者各有优劣，第一种的缺点是维护大量缓存的key是比较麻烦的，第二种的缺点就是每次用户请求过来都要判断缓存失效，逻辑相对比较复杂！具体用哪种方案，大家可以根据自己的应用场景来权衡。</p>



<h2 class="wp-block-heading">redis内存淘汰策略是什么</h2>



<p class="wp-block-paragraph">redis之所以快，因为他是依赖于内存的，但内存大小非常有限，如果超过就会报错。<br>所以当redis内存数据集大小上升到一定大小的时候，就会施行数据淘汰策略。</p>



<h2 class="wp-block-heading">Redis的内存淘汰策略有哪些</h2>



<p class="wp-block-paragraph">Redis的内存淘汰策略是指在Redis用于缓存的内存不足时，怎么处理需要新写入且需要申请额外空间的数据。<br>对全局的键空间选择性移除<br>当内存不足以容纳新写入数据时，有一下几种方式操作：</p>



<ul class="wp-block-list">
<li>noeviction：，新写入操作会报错。</li>



<li>allkeys-lru：在键空间中，移除最近最少使用的key。（这个是最常用的）</li>



<li>allkeys-random：在键空间中，随机移除某个key。</li>
</ul>



<p class="wp-block-paragraph">对设置过期时间的键空间选择性移除</p>



<ul class="wp-block-list">
<li>volatile-lru：在设置了过期时间的键空间中，移除最近最少使用的key。</li>



<li>volatile-random：在设置了过期时间的键空间中，随机移除某个key。</li>



<li>volatile-ttl：在设置了过期时间的键空间中，有更早过期时间的key优先移除。</li>
</ul>



<p class="wp-block-paragraph"><strong>总结</strong></p>



<p class="wp-block-paragraph">Redis的内存淘汰策略的选取并不会影响过期的key的处理。<br>内存淘汰策略用于处理内存不足时的需要申请额外空间的数据（针对于内存）<br>过期策略用于处理过期的缓存数据（针对于数据库）</p>



<h2 class="wp-block-heading">Redis的内存用完了会发生什么？</h2>



<p class="wp-block-paragraph">如果达到设置的上限，Redis的写命令会返回错误信息（但是读命令还可以正常返回）或者你可以配置内存淘汰机制，当Redis达到内存上限时会冲刷掉旧的内容。</p>



<h2 class="wp-block-heading">Redis如何做内存优化？</h2>



<p class="wp-block-paragraph">可以好好利用Hash,list,sorted set,set等集合类型数据，因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表（hashes），散列表（是说散列表里面存储的数少）使用的内存非常小，所以你应该尽可能的将你的数据模型抽象到一个散列表里面（一个对象里面）。</p>



<p class="wp-block-paragraph">例如：你的web系统中有一个用户对象，不要为这个用户的名称，姓氏，邮箱，密码设置单独的key，而是应该把这个用户的所有信息存储到一张散列表里面线程模型。</p>



<h2 class="wp-block-heading">什么是事务？</h2>



<p class="wp-block-paragraph">事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中，不会被其他客户端发送来的命令请求所打断。</p>



<p class="wp-block-paragraph">事务是一个原子操作：事务中的命令要么全部被执行，要么全部都不执行。</p>



<h2 class="wp-block-heading">Redis事务的概念</h2>



<p class="wp-block-paragraph">Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令，一个事务中所有命令都会被序列化。在事务执行过程，会按照顺序串行化执行队列中的命令，其他客户端 提交的命令请求不会插入到事务执行命令序列中。</p>



<p class="wp-block-paragraph">总结说：redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。</p>



<h2 class="wp-block-heading">事务的基本操作</h2>



<p class="wp-block-paragraph">开启事务</p>



<p class="wp-block-paragraph">multi<br>AI生成项目<br>shell<br>1<br>作用：设定事务的开启位置，此指令执行后，后续的所有指令均加入到事务中</p>



<p class="wp-block-paragraph">执行事务</p>



<p class="wp-block-paragraph">exec<br>AI生成项目<br>shell<br>1<br>作用：设定事务的结束位置，同时执行事务。与multi成对出现，成对使用</p>



<p class="wp-block-paragraph">注意：加入事务的命令暂时进入到任务队列中，并没有立即执行，只有执行exec命令才开始执行</p>



<p class="wp-block-paragraph">但是你在命令入队列的时候，发现你有些命令指令输入错误，你不想执行了，这个时候，你不想提交事务了</p>



<p class="wp-block-paragraph">取消事务</p>



<p class="wp-block-paragraph">discard<br>AI生成项目<br>shell<br>1<br>作用：终止当前事务的定义，发生在multi之后，exec之前</p>



<p class="wp-block-paragraph">总结：遇到multi指令，创建队列，遇到discard指令销毁队列</p>



<p class="wp-block-paragraph">事务执行过程中，如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求，将会把请求放入队列中排队(需要研究)</p>



<h2 class="wp-block-heading">Redis事务相关命令</h2>



<p class="wp-block-paragraph">Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的</p>



<p class="wp-block-paragraph">Redis会将一个事务中的所有命令序列化，然后按顺序执行。</p>



<p class="wp-block-paragraph">redis 不支持回滚，“Redis 在事务失败时不进行回滚，而是继续执行余下的命令”， 所以 Redis 的内部可以保持简单且快速。</p>



<p class="wp-block-paragraph">如果在一个事务中的命令出现错误，那么所有的命令都不会执行；</p>



<p class="wp-block-paragraph">如果在一个事务中出现运行错误，那么正确的命令会被执行。</p>



<p class="wp-block-paragraph">WATCH命令是一个乐观锁，可以为 Redis 事务提供 check-and-set （CAS）行为。 可以监控一个或多个键，一旦其中有一个键被修改（或删除），之后的事务就不会执行，监控一直持续到EXEC命令。</p>



<p class="wp-block-paragraph">MULTI命令用于开启一个事务，它总是返回OK。 MULTI执行之后，客户端可以继续向服务器发送任意多条命令，这些命令不会立即被执行，而是被放到一个队列中，当EXEC命令被调用时，所有队列中的命令才会被执行。</p>



<p class="wp-block-paragraph">EXEC：执行所有事务块内的命令。返回事务块内所有命令的返回值，按命令执行的先后顺序排列。 当操作被打断时，返回空值 nil 。</p>



<p class="wp-block-paragraph">通过调用DISCARD，客户端可以清空事务队列，并放弃执行事务， 并且客户端会从事务状态中退出。</p>



<p class="wp-block-paragraph">UNWATCH命令可以取消watch对所有key的监控。</p>



<h2 class="wp-block-heading">Redis事务支持隔离性吗</h2>



<p class="wp-block-paragraph">Redis 是单进程程序，并且它保证在执行事务时，不会对事务进行中断，事务可以运行直到执行完所有事务队列中的命令为止。因此，Redis 的事务是总是带有隔离性的。</p>



<h2 class="wp-block-heading">Redis事务保证原子性吗，支持回滚吗</h2>



<p class="wp-block-paragraph">Redis中，单条命令是原子性执行的，但事务不保证原子性，且没有回滚。事务中任意命令执行失败，其余的命令仍会被执行。</p>



<h2 class="wp-block-heading">Redis事务其他实现</h2>



<p class="wp-block-paragraph">基于Lua脚本，Redis可以保证脚本内的命令一次性、按顺序地执行，其同时也不提供事务运行错误的回滚，执行过程中如果部分命令运行错误，剩下的命令还是会继续运行完</p>



<p class="wp-block-paragraph">基于中间标记变量，通过另外的标记变量来标识事务是否执行完成，读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现，比较繁琐集群方案哨兵模式</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="698" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-1024x698.png" alt="" class="wp-image-360" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-1024x698.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-300x204.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-768x524.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-660x450.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-500x341.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-800x545.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4.png 1178w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">哨兵的介绍</p>



<p class="wp-block-paragraph">sentinel，中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件，主要有以下功能：</p>



<ul class="wp-block-list">
<li><strong>集群监控：</strong>负责监控 redis master 和 slave 进程是否正常工作。</li>



<li><strong>消息通知：</strong>如果某个 redis 实例有故障，那么哨兵负责发送消息作为报警通知给管理员。</li>



<li><strong>故障转移：</strong>如果 master node 挂掉了，会自动转移到 slave node 上。</li>



<li><strong>配置中心：</strong>如果故障转移发生了，通知 client 客户端新的 master 地址。</li>
</ul>



<p class="wp-block-paragraph">哨兵用于实现 redis 集群的高可用，本身也是分布式的，作为一个哨兵集群去运行，互相协同工作。<br>故障转移时，判断一个 master node 是否宕机了，需要大部分的哨兵都同意才行，涉及到了分布式选举的问题。<br>即使部分哨兵节点挂掉了，哨兵集群还是能正常工作的，因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的，那就很坑爹了。</p>



<p class="wp-block-paragraph">哨兵的核心知识</p>



<ul class="wp-block-list">
<li>哨兵至少需要 3 个实例，来保证自己的健壮性。</li>



<li>哨兵 + redis 主从的部署架构，是不保证数据零丢失的，只能保证 redis 集群的高可用性。</li>



<li>对于哨兵 + redis 主从这种复杂的部署架构，尽量在测试环境和生产环境，都进行充足的测试和演练。</li>
</ul>



<p class="wp-block-paragraph"><strong>官方Redis Cluster 方案(服务端路由查询)</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="426" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-1024x426.png" alt="" class="wp-image-361" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-1024x426.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-300x125.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-768x319.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-1536x638.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-660x274.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-500x208.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-800x333.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-1280x532.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5.png 1612w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">redis 集群模式的工作原理能说一下么？在集群模式下，redis 的 key 是如何寻址的？分布式寻址都有哪些算法？了解一致性 hash 算法吗？简介</p>



<p class="wp-block-paragraph">Redis Cluster是一种服务端Sharding技术，3.0版本开始正式提供。Redis Cluster并没有使用一致性hash，而是采用slot(槽)的概念，一共分成16384个槽。将请求发送到任意节点，接收到请求的节点会将查询请求发送到正确的节点上执行。16384个槽数默认值是固定的。</p>



<p class="wp-block-paragraph"><strong>方案说明</strong></p>



<ol class="wp-block-list">
<li>通过哈希的方式，将数据分片，每个节点均分存储一定哈希槽(哈希值) 区间的数据，默认分配了16384 个槽位</li>



<li>每份数据分片会存储在多个互为主从的多节点上</li>



<li>数据写入先写主节点，再同步到从节点(支持配置为阻塞同步)</li>



<li>同一分片多个节点间的数据不保持一致性</li>



<li>读取数据时，当客户端操作的key没有分配在该节点上时，redis会返回转向指令，指向正确的节点</li>



<li>扩容时需要把旧节点的数据迁移一部分到新节点</li>
</ol>



<p class="wp-block-paragraph">在 redis cluster 架构下，每个 redis 要放开两个端口号，一个用来对外供客户端连接，另一个用来节点间互相通信。</p>



<p class="wp-block-paragraph">比如一个是 6379，另外一个就是 加1w 的端口号，比如 16379。</p>



<p class="wp-block-paragraph">16379 端口号是用来进行节点间通信的，也就是 cluster bus 的东西，cluster bus 的通信，用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议，gossip 协议，用于节点间进行高效的数据交换，占用更少的网络带宽和处理时间。</p>



<p class="wp-block-paragraph">节点间的内部通信机制</p>



<p class="wp-block-paragraph">基本通信原理</p>



<p class="wp-block-paragraph">集群元数据的维护有两种方式：集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。</p>



<h2 class="wp-block-heading">Redis主从架构</h2>



<p class="wp-block-paragraph">单机的 redis，能够承载的 QPS 大概就在上万到几万不等。对于缓存来说，一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构，一主多从，主负责写，并且将数据复制到其它的 slave 节点，从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="594" height="422" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-6.png" alt="" class="wp-image-362" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-6.png 594w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-6-300x213.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-6-500x355.png 500w" sizes="auto, (max-width: 594px) 100vw, 594px" /></figure>



<p class="wp-block-paragraph">redis 采用异步方式复制数据到 slave 节点，不过 redis2.8 开始，slave node 会周期性地确认自己每次复制的数据量；</p>



<p class="wp-block-paragraph">一个 master node 是可以配置多个 slave node 的； slave node 也可以连接其他的 slave node；</p>



<p class="wp-block-paragraph">slave node 做复制的时候，不会（阻碍）block master node 的正常工作；</p>



<p class="wp-block-paragraph">slave node 在做复制的时候，也不会 block 对自己的查询操作，它会用旧的数据集来提供服务；但是复制完成的时候，需要删除旧数据集，加载新数据集，这个时候就会暂停对外服务了；</p>



<p class="wp-block-paragraph">slave node 主要用来进行横向扩容，做读写分离，扩容的 slave node 可以提高读的吞吐量。</p>



<p class="wp-block-paragraph">注意：如果采用了主从架构，那么建议必须开启 master node 的持久化，不建议用 slave node 作为 master node 的数据热备，因为那样的话，如果你关掉 master 的持久化，可能在 master 宕机重启的时候数据是空的，然后可能一经过复制， slave node 的数据也丢了。</p>



<p class="wp-block-paragraph">另外，master 的各种备份方案，也需要做。万一本地的所有文件丢失了，从备份中挑选一份 rdb 去恢复 master，这样才能确保启动的时候，是有数据的，即使采用了后续讲解的高可用机制，slave node 可以自动接管 master node，但也可能 sentinel 还没检测到 master failure，master node 就自动重启了，还是可能导致上面所有的 slave node 数据被清空。</p>



<p class="wp-block-paragraph">redis 主从复制的核心原理当启动一个 slave node 的时候，它会发送一个 PSYNC 命令给 master node。</p>



<p class="wp-block-paragraph">如果这是 slave node 初次连接到 master node，那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程，开始生成一份 RDB 快照文件，同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后， master 会将这个 RDB 发送给 slave，slave 会先写入本地磁盘，然后再从本地磁盘加载到内存中，接着 master 会将内存中缓存的写命令发送到 slave，slave 也会同步这些数据。</p>



<p class="wp-block-paragraph">slave node 如果跟 master node 有网络故障，断开了连接，会自动重连，连接之后 master node 仅会复制给 slave 部分缺少的数据。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="916" height="327" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7.png" alt="" class="wp-image-363" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7.png 916w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-300x107.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-768x274.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-660x236.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-500x178.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-800x286.png 800w" sizes="auto, (max-width: 916px) 100vw, 916px" /></figure>



<ol class="wp-block-list">
<li>当从库和主库建立MS关系后，会向主数据库发送SYNC命令</li>



<li>主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程)，并将期间接收到的写命令存起来（都是主节点在做）</li>



<li>当快照完成后，主Redis会将快照文件和所有缓存的写命令发送给从Redis</li>



<li>从Redis接收到后，会载入快照文件并且执行收到的缓存的命令</li>



<li>之后，主Redis每当接收到写命令时就会将命令发送从Redis，从而保证数据的一致</li>
</ol>



<p class="wp-block-paragraph">缺点</p>



<p class="wp-block-paragraph">所有的slave节点数据的复制和同步都由master节点来处理，会照成master节点压力太大，使用主从从结构来解决</p>



<h2 class="wp-block-heading">Redis集群的主从复制模型是怎样的？</h2>



<p class="wp-block-paragraph">为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用，所以集群使用了主从复制模型，每个节点都会有N-1个复制品生产环境中的 redis 是怎么部署的？</p>



<p class="wp-block-paragraph">redis cluster，10 台机器，5 台机器部署了 redis 主实例，另外 5 台机器部署了 redis 的从实例，每个主实例挂了一个从实例，5 个节点对外提供读写服务，每个节点的读写高峰qps可能可以达到每秒 5 万，5 台机器最多是 25 万读写请求/s。</p>



<p class="wp-block-paragraph">机器是什么配置？32G 内存+ 8 核 CPU + 1T 磁盘，但是分配给 redis 进程的是10g内存，一般线上生产环境，redis 的内存尽量不要超过 10g，超过 10g 可能会有问题。</p>



<p class="wp-block-paragraph">5 台机器对外提供读写，一共有 50g 内存。</p>



<p class="wp-block-paragraph">因为每个主实例都挂了一个从实例，所以是高可用的，任何一个主实例宕机，都会自动故障迁移，redis 从实例会自动变成主实例继续提供读写服务。</p>



<p class="wp-block-paragraph">你往内存里写的是什么数据？每条数据的大小是多少？商品数据，每条数据是 10kb。100 条数据是 1mb，10 万条数据是 1g。常驻内存的是 200 万条商品数据，占用内存是 20g，仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。</p>



<p class="wp-block-paragraph">其实大型的公司，会有基础架构的 team 负责缓存集群的运维。</p>



<h2 class="wp-block-heading">说说Redis哈希槽的概念？</h2>



<p class="wp-block-paragraph">Redis集群没有使用一致性hash,而是引入了哈希槽的概念，Redis集群有16384个哈希槽，每个key通过CRC16校验后对16384取模来决定放置哪个槽，集群的每个节点负责一部分hash槽。</p>



<p class="wp-block-paragraph">Redis 的哈希槽（Hash Slot）是 Redis 集群中的一个概念，用于将数据分布到不同的节点上。Redis 集群中使用哈希槽来实现数据的分片和负载均衡，将大量的数据分散到多个节点上，以提高系统的可扩展性和性能。</p>



<p class="wp-block-paragraph">具体来说，Redis 会将所有的哈希槽均分到不同的节点上，每个节点负责处理一部分哈希槽的数据。</p>



<p class="wp-block-paragraph">例如：一个 Redis 集群有 3 个节点，每个节点负责处理 16384 个哈希槽，那么每个节点负责处理的哈希槽范围为：<br>第一个节点：0-5460<br>第二个节点：5461-10922<br>第三个节点：10923-16383<br>当客户端需要访问某个键值对时，Redis 集群会根据键名计算出哈希值，并将该哈希值映射到一个哈希槽上。然后，Redis 集群会根据哈希槽的分布情况，将该键值对传递到对应的节点上进行处理。</p>



<p class="wp-block-paragraph">在 Redis 集群中，哈希槽的默认数量是固定的，且不可更改。默认情况下，Redis 集群中有 16384 个哈希槽，可以通过 cluster slots 命令来设置哈希槽数量。当某个节点失效或者新增节点时，Redis 集群会重新计算哈希槽的分布情况，将负载均衡到其他节点上。<br>在 Redis 集群中，哈希槽的分布情况是通过哈希函数计算得出的。Redis 默认使用的哈希函数是 MurmurHash2，该函数可以将任意长度的数据映射成一个 32 位的二进制数，并保证均匀分布。除了 MurmurHash2，Redis 还支持其他的哈希函数，例如 CRC16 和 CRC32 等。<br>总的来说，Redis 的哈希槽是 Redis 集群中的一个重要概念，用于将数据分布到不同的节点上。Redis 集群通过哈希槽的分配和负载均衡，实现了高可用性、可扩展性和高性能的分布式存储服务。</p>



<h2 class="wp-block-heading">Redis集群会有写操作丢失吗？为什么？</h2>



<p class="wp-block-paragraph">Redis并不能保证数据的强一致性，这意味这在实际中集群在特定的条件下可能会丢失写操作。</p>



<p class="wp-block-paragraph">Redis集群之间是如何复制的？</p>



<p class="wp-block-paragraph">异步复制</p>



<p class="wp-block-paragraph">Redis集群最大节点个数是多少？</p>



<p class="wp-block-paragraph">16384个，因为哈希槽最多16384个</p>



<p class="wp-block-paragraph">Redis集群如何选择数据库？</p>



<p class="wp-block-paragraph">Redis集群目前无法做数据库选择，默认在0数据库。</p>



<h2 class="wp-block-heading">Redis是单线程的，如何提高多核CPU的利用率？</h2>



<p class="wp-block-paragraph">可以在同一个服务器部署多个Redis的实例，并把他们当作不同的服务器来使用，在某些时候，无论如何一个服务器是不够的， 所以，如果你想使用多个CPU，你可以考虑一下分片（shard）。</p>



<h2 class="wp-block-heading">为什么要做Redis分区？</h2>



<p class="wp-block-paragraph">分区可以让Redis管理更大的内存，Redis将可以使用所有机器的内存。如果没有分区，你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升，Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。</p>



<h2 class="wp-block-heading">你知道有哪些Redis分区实现方案？</h2>



<ul class="wp-block-list">
<li>客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个 redis节点读取。大多数客户端已经实现了客户端分区。</li>



<li>代理分区意味着客户端将请求发送给代理，然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例，然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy</li>



<li>查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例，然后由Redis将请求转发给正确的Redis节点。Redis Cluster（redis集群）实现了一种混合形式的查询路由，但并不是直接将请求从一个redis节点转发到另一个redis节点，而是在客户端的帮助下直接redirected到正确的redis节点。</li>
</ul>



<h2 class="wp-block-heading">Redis分区有什么缺点？</h2>



<ul class="wp-block-list">
<li>涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集，因为他们可能被存储到不同的Redis实例（实际上这种情况也有办法，但是不能直接使用交集指令）。</li>



<li>同时操作多个key,则不能使用Redis事务。</li>



<li>分区使用的粒度是key，不能使用一个非常长的排序key存储一个数据集（The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set）</li>



<li>当使用分区的时候，数据处理会非常复杂，例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。</li>



<li>分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点，能做到最大程度对用户透明地数据再平衡，但其他一些客户端分区或者代理分区方法则不支持这种特性。然而，有一种预分片的技术也可以较好的解决这个问题。</li>
</ul>



<h2 class="wp-block-heading">Redis实现分布式锁</h2>



<p class="wp-block-paragraph">具体实现可参考：基于redis实现分布式锁</p>



<p class="wp-block-paragraph">Redis为单进程单线程模式，采用队列模式将并发访问变成串行访问，且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。</p>



<p class="wp-block-paragraph">当且仅当 key 不存在，将 key 的值设为 value。 若给定的 key 已经存在，则SETNX 不做任何动作</p>



<p class="wp-block-paragraph">SETNX 是『SET if Not eXists』(如果不存在，则 SET)的简写。</p>



<p class="wp-block-paragraph">返回值：设置成功，返回 1 。设置失败，返回 0 。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="790" height="254" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8.png" alt="" class="wp-image-364" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8.png 790w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8-300x96.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8-768x247.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8-660x212.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8-500x161.png 500w" sizes="auto, (max-width: 790px) 100vw, 790px" /></figure>



<p class="wp-block-paragraph">使用SETNX完成同步锁的流程及事项如下(img)：使用SETNX命令获取锁，若返回0（key已存在，锁已存在）则获取失败，反之获取成功为了防止获取锁后程序出现异常，导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态，需要为该key设置一个“合理”的过期时间释放锁，使用DEL命令将锁数据删除</p>



<h2 class="wp-block-heading">分布式Redis是前期做还是后期规模上来了再做好？为什么？</h2>



<p class="wp-block-paragraph">既然Redis是如此的轻量（单实例只使用1M内存），为防止以后的扩容， 好的办法就是一开始就启动较多实例。即便你只有一台服务器，你也可以一开始就让Redis以分布式的方式运行，使用分区，在同一台服务器上启动多个实例。</p>



<p class="wp-block-paragraph">一开始就多设置几个Redis实例，例如32或者64个实例，对大多数用户来说这操作起来可能比较麻烦，但是从长久来看做这点牺牲是值得的。</p>



<p class="wp-block-paragraph">这样的话，当你的数据不断增长，需要更多的Redis服务器时，你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已（而不用考虑重新分区的问题）。一旦你添加了另一台服务器，你需要将你一半的Redis实例从第一台机器迁移到第二台机器。</p>



<h2 class="wp-block-heading">缓存雪崩</h2>



<p class="wp-block-paragraph">缓存雪崩是指缓存同一时间大面积的失效，所以，后面的请求都会落到数据库上，造成数据库短时间内承受大量请求而崩掉。</p>



<p class="wp-block-paragraph">解决方案</p>



<ol class="wp-block-list">
<li>缓存数据的过期时间设置随机，防止同一时间大量数据过期现象发生。</li>



<li>一般并发量不是特别多的时候，使用最多的解决方案是加锁排队。</li>



<li>给每一个缓存数据增加相应的缓存标记，记录缓存的是否失效，如果缓存标记失效，则更新数据缓存。</li>
</ol>



<h2 class="wp-block-heading">缓存穿透</h2>



<p class="wp-block-paragraph">缓存穿透是指缓存和数据库中都没有的数据，导致所有的请求都落到数据库上，造成数据库短时间内承受大量请求而崩掉。</p>



<p class="wp-block-paragraph">解决方案</p>



<ol class="wp-block-list">
<li>接口层增加校验，如用户鉴权校验，id做基础校验，id&lt;=0的直接拦截；</li>



<li>从缓存取不到的数据，在数据库中也没有取到，这时也可以将key-value对写为key-null，缓存有效时间可以设置短点，如30秒（设置太长会导致正常情况也没法使用）。这样可以防止攻击用户反复用同一个id暴力攻击</li>



<li>采用布隆过滤器，将所有可能存在的数据哈希到一个足够大的 bitmap 中，一个一定不存在的数据会被这个 bitmap 拦截掉，从而避免了对底层存储系统的查询压力附加</li>
</ol>



<p class="wp-block-paragraph">Bitmap： 典型的就是哈希表缺点是，Bitmap对于每个元素只能记录1bit信息，如果还想完成额外的功能，恐怕只能靠牺牲更多的空间、时间来完成了。</p>



<p class="wp-block-paragraph">布隆过滤器（推荐）</p>



<p class="wp-block-paragraph">就是引入了k(k&gt;1)k(k&gt;1)个相互独立的哈希函数，保证在给定的空间、误判率下，完成元素判重的过程。</p>



<p class="wp-block-paragraph">它的优点是空间效率和查询时间都远远超过一般的算法，缺点是有一定的误识别率和删除困难。</p>



<p class="wp-block-paragraph">Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。</p>



<p class="wp-block-paragraph">Hash存在一个冲突（碰撞）的问题，用同一个Hash得到的两个URL的值有可能相同。为了减少冲突，我们可以多引入几个Hash，如果通过其中的一个Hash值我们得出某元素不在集合中，那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时，才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。</p>



<p class="wp-block-paragraph">Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。</p>



<h2 class="wp-block-heading">缓存击穿</h2>



<p class="wp-block-paragraph">缓存击穿是指缓存中没有但数据库中有的数据（一般是缓存时间到期），这时由于并发用户特别多<strong>，同时读缓存没读到数据，又同时去数据库去取数据</strong>，引起数据库压力瞬间增大，造成过大压力。</p>



<p class="wp-block-paragraph">和缓存雪崩不同的是，缓存击穿指并发查同一条数据，缓存雪崩是不同数据都过期了，很多数据都查不到从而查数据库。</p>



<p class="wp-block-paragraph">解决方案</p>



<ol class="wp-block-list">
<li>设置热点数据永远不过期。</li>



<li>加互斥锁，互斥锁缓存预热</li>
</ol>



<p class="wp-block-paragraph">缓存预热就是系统上线后，将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候，先查询数据库，然后再将数据缓存的问题！用户直接查询事先被预热的缓存数据！解决方案</p>



<ol class="wp-block-list">
<li>直接写个缓存刷新页面，上线时手工操作一下；</li>



<li>数据量不大，可以在项目启动的时候自动进行加载；</li>



<li>定时刷新缓存；</li>
</ol>



<h2 class="wp-block-heading">缓存降级</h2>



<p class="wp-block-paragraph">当访问量剧增、服务出现问题（如响应时间慢或不响应）或非核心服务影响到核心流程的性能时，仍然需要保证服务还是可用的，即使是有损服务。系统可以根据一些关键数据进行自动降级，也可以配置开关实现人工降级。</p>



<p class="wp-block-paragraph">缓存降级的最终目的是保证核心服务可用，即使是有损的。而且有些服务是无法降级的（如加入购物车、结算）。</p>



<p class="wp-block-paragraph">在进行降级之前要对系统进行梳理，看看系统是不是可以丢卒保帅；从而梳理出哪些必须誓死保护，哪些可降级；比如可以参考日志级别设置预案：</p>



<ol class="wp-block-list">
<li>一般：比如有些服务偶尔因为网络抖动或者服务正在上线而超时，可以自动降级；</li>



<li>警告：有些服务在一段时间内成功率有波动（如在95~100%之间），可以自动降级或人工降级，并发送告警；</li>



<li>错误：比如可用率低于90%，或者数据库连接池被打爆了，或者访问量突然猛增到系统能承受的最大阀值，此时可以根据情况自动降级或者人工降级；</li>



<li>严重错误：比如因为特殊原因数据错误了，此时需要紧急人工降级。</li>
</ol>



<p class="wp-block-paragraph">服务降级的目的，是为了防止Redis服务故障，导致数据库跟着一起发生雪崩问题。因此，对于不重要的缓存数据，可以采取服务降级策略，例如一个比较常见的做法就是，Redis出现问题，不去数据库查询，而是直接返回默认值给用户。</p>



<h2 class="wp-block-heading">降级的方案有哪些？（*）</h2>



<h2 class="wp-block-heading">热点数据和冷数据</h2>



<p class="wp-block-paragraph">热点数据，缓存才有价值，对于冷数据而言，大部分数据可能还没有再次访问到就已经被挤出内存，不仅占用内存，而且价值不大。频繁修改的数据，看情况考虑使用缓存对于热点数据，比如我们的某IM产品，生日祝福模块，当天的寿星列表，缓存以后可能读取数十万次。再举个例子，某导航产品，我们将导航信息，缓存以后可能读取数百万次。<br>数据更新前至少读取两次，缓存才有意义。这个是最基本的策略，如果缓存还没有起作用就失效了，那就没有太大价值了。<br>那存不存在，修改频率很高，但是又不得不考虑缓存的场景呢？<br>有！比如，这个读取接口对数据库的压力很大，但是又是热点数据，这个时候就需要考虑通过缓存手段，减少数据库的压力，比如我们的某助手产品的，点赞数，收藏数，分享数等是非常典型的热点数据，但是又不断变化，此时就需要将数据同步保存到Redis缓存，减少数据库压力。</p>



<h2 class="wp-block-heading">缓存热点key</h2>



<p class="wp-block-paragraph">缓存中的一个Key(比如一个促销商品)，在某个时间点过期的时候，恰好在这个时间点对这个Key有大量的并发请求过来，这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存，这个时候大并发的请求可能会瞬间把后端DB压垮。</p>



<p class="wp-block-paragraph">解决方案对缓存查询加锁，如果KEY不存在，就加锁，然后查DB入缓存，然后解锁；其他进程如果发现有锁就等待，然后等解锁后返回数据或者进入DB查询常用工具</p>



<p class="wp-block-paragraph">Redis支持的Java客户端都有哪些？官方推荐用哪个？</p>



<p class="wp-block-paragraph">Redisson、Jedis、lettuce等等，官方推荐使用Redisson。</p>



<h2 class="wp-block-heading">Redis和Redisson有什么关系？</h2>



<p class="wp-block-paragraph">Redisson是一个高级的分布式协调Redis客服端，能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap,ScoredSortedSet, SortedSet, Map, ConcurrentMap, List,ListMultimap,Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock,ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe,HyperLogLog)。</p>



<h2 class="wp-block-heading">Jedis与Redisson对比有什么优缺点？</h2>



<p class="wp-block-paragraph">Jedis是Redis的Java实现的客户端，其API提供了比较全面的Redis命令的支持；Redisson实现了分布式和可扩展的Java数据结构，和Jedis相比，功能较为简单，不支持字符串操作，不支持排序、事务、管道、分区等Redis特性。</p>



<p class="wp-block-paragraph">Redisson的宗旨是促进使用者对Redis的关注分离，从而让使用者能够将精力更集中地放在处理业务逻辑上。</p>



<h2 class="wp-block-heading">Redis与Memcached的区别</h2>



<p class="wp-block-paragraph">两者都是非关系型内存键值数据库，现在公司一般都是用 Redis 来实现缓存，而且 Redis 自身也越来越强大了！Redis 与 Memcached 主要有以下不同：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td><strong>对比参数</strong></td><td><strong>Redis</strong></td><td><strong>Memcac hed</strong></td></tr><tr><td>类型</td><td>1. 支持内存 2. 非关系型数据库</td><td>1. 支持内存 2. 键值对形式 3. 缓存形式</td></tr><tr><td>数据存储类型</td><td>1. String 2. List 3. Set 4. Hash 5. Sort Set 【俗称 ZSet】</td><td>1. 文本型 2. 二进制类型</td></tr><tr><td>查询【操作】类型</td><td>1. 批量操作 2. 事务支持 3. 每个类型不同的 CRUD</td><td>1.常用的 CRUD 2. 少量的其他命令</td></tr><tr><td>附加功能</td><td>1. 发布/订阅模式 2. 主从分区 3. 序列化 支持 4. 脚本支持 【Lua脚本】</td><td>1. 多线程服务支持</td></tr><tr><td>网络IO模型</td><td>1. 单线程的多路 IO 复用模型</td><td>1. 多线程，非阻塞IO模式</td></tr><tr><td>事件库</td><td>自封转简易事件库 AeEvent</td><td>贵族血统的 LibEvent 事件库</td></tr><tr><td><strong>持久化支持</strong></td><td><strong>1. RDB 2. AOF</strong></td><td><strong>不支持</strong></td></tr><tr><td><strong>集群模式</strong></td><td><strong>原生支持 cluster 模式，可以实现主从复制，读写分离</strong></td><td>没有原生的集群模式，需要依靠客户端来实现往集群中分片写入数据</td></tr><tr><td>内存管理机制</td><td>在 Redis 中，并不是所有数据都一直存储在内存中，可以将一些很久没用 的 value 交换到磁盘</td><td>Memcach ed 的数据则会一直在内存中， Memcach ed 将内存分割成特定长度的块来存储数据，以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高，例如块的大小为 128 bytes，只存储 100 bytes 的数据，那么剩下的 28 bytes 就浪费掉了。</td></tr><tr><td>适用场景</td><td>复杂数据结构，有持久化，高可用需求，value 存储内容较大</td><td>纯key- value，数据量非常大，并发量非常大的业务</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><br>(1) memcached所有的值均是简单的字符串，redis作为其替代者，支持更为丰富的数据类型</p>



<p class="wp-block-paragraph">(2) redis的速度比memcached快很多</p>



<p class="wp-block-paragraph">(3) redis可以持久化其数据</p>



<p class="wp-block-paragraph">如何保证缓存与数据库双写时的数据一致性？<br>你只要用缓存，就可能会涉及到缓存与数据库双存储双写，你只要是双写，就一定会有数据一致性的问题，那么你如何解决一致性问题？一般来说，就是如果你的系统不是严格要求缓存+数据库必须一致性的话，缓存可以稍微的跟数据库偶尔有不一致的情况，最好不要做这个方案，读请求和写请求串行化，串到一个内存队列里去，这样就可以保证一定不会出现不一致的情况串行化之后，就会导致系统的吞吐量会大幅度的降低，用比正常情况下多几倍的机器去支撑线上的一个请求。</p>



<p class="wp-block-paragraph">还有一种方式就是可能会暂时产生不一致的情况，但是发生的几率特别小，就是先更新数据库，然后再删除缓存。</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td><strong>问题场景</strong></td><td><strong>描述</strong></td><td><strong>解决</strong></td></tr><tr><td>先写缓存，再写数据库，缓存写成功，数据库写失败</td><td>缓存写成功，但写数据库失败或者响应延迟，则下次读取（并发读）缓存时，就出现脏读</td><td>这个写缓存的方式，本身就是错误的，需要改为先写数据库，把旧缓存置为失效；读取数据的时候，如果缓存不存在，则读取数据库再写缓存</td></tr><tr><td>先写数据库，再写缓存，数据库写成功，缓存写失败</td><td>写数据库成功，但写缓存失败，则下次读取（并发读）缓存时，则读不到数据</td><td>缓存使用时，假如读缓存失败，先读数据库，再回写缓存的方式实现</td></tr><tr><td>需要缓存异步刷新</td><td>指数据库操作和写缓存不在一个操作步骤中，比如在分布式场景下，无法做到同时写缓存或需要异步刷新（补救措施）时候</td><td>确定哪些数据适合此类场景，根据经验值确定合理的数据不一致时间，用户数据刷新的时间间隔</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Redis常见性能问题和解决方案？</h2>



<ul class="wp-block-list">
<li>Master最好不要做任何持久化工作，包括内存快照和AOF日志文件，特别是不要启用内存快照做持久化。</li>



<li>如果数据比较关键，某个Slave开启AOF备份数据，策略为每秒同步一次。</li>



<li>为了主从复制的速度和连接的稳定性，Slave和Master 好在同一个局域网内。</li>



<li>尽量避免在压力较大的主库上增加从库</li>



<li>Master调用BGREWRITEAOF重写AOF文件，AOF在重写的时候会占大量的CPU和内存资源，导致服务load过高，出现短暂服务暂停现象。</li>



<li>为了Master的稳定性，主从复制不要用图状结构，用单向链表结构更稳定，即主从关系为：Master&lt;–Slave1&lt;–Slave2&lt;–Slave3…，这样的结构也方便解决单点故障问题，实现Slave对Master的替换，也即，如果Master挂了，可以立马启用Slave1做Master，其他不变。</li>
</ul>



<h2 class="wp-block-heading">Redis官方为什么不提供Windows版本？</h2>



<p class="wp-block-paragraph">因为目前Linux版本已经相当稳定，而且用户量很大，无需开发windows版本，反而会带来兼容性等问题。</p>



<h2 class="wp-block-heading">一个字符串类型的值能存储最大容量是多少？</h2>



<p class="wp-block-paragraph">512M</p>



<p class="wp-block-paragraph">Redis如何做大量数据插入？ Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。</p>



<h2 class="wp-block-heading">假如Redis里面有1亿个key，其中有10w个key是以某个固定的已知的前缀开头的，如果将它们全部找出来？</h2>



<p class="wp-block-paragraph">使用keys指令可以扫出指定模式的key列表。</p>



<p class="wp-block-paragraph">对方接着追问：如果这个redis正在给线上的业务提供服务，那使用keys指令会有什么问题？这个时候你要回答redis关键的一个特性：redis的单线程的。keys指令会导致线程阻塞一段时间，线上服务会停顿，直到指令执行完毕，服务才能恢复。</p>



<p class="wp-block-paragraph">这个时候可以使用scan指令，scan指令可以无阻塞的提取出指定模式的key列表，但是会有一定的重复概率，在客户端做一次去重就可以了，但是整体所花费的时间会比直接用keys指令长。</p>



<h2 class="wp-block-heading">Redis如何实现延时队列</h2>



<p class="wp-block-paragraph">使用sortedset，使用时间戳做score, 消息内容作为key,调用zadd来生产消息，消费者使用zrangbyscore获取n秒之前的数据做轮询处理。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/04/353/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">353</post-id>	</item>
	</channel>
</rss>
