<?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>Tue, 24 Mar 2026 08:47:49 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
<site xmlns="com-wordpress:feed-additions:1">219834889</site>	<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 &#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>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>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>zookeeper 的核心是原子广播，这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 zab 协议。zab 协议有两种模式，分别是恢复模式（选主）和广播模式（同步）。当服务启动或者在领导者崩溃后，zab 就进入了恢复模式，当领导者被选举出来，且大多数 server 完成了和 leader 的状态同步以后，恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态。</p>



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



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



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



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



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



<p>客户端端会对某个 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 &#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>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>vhost：每个 RabbitMQ 都能创建很多 vhost，我们称之为虚拟主机，每个虚拟主机其实都是 mini 版的RabbitMQ，它拥有自己的队列，交换器和绑定，拥有自己的权限机制。</p>



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



<p>首先客户端必须连接到 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>以上四个条件都满足才能保证消息持久化成功。</p>



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



<p>持久化的缺地就是降低了服务器的吞吐量，因为使用的是磁盘而非内存存储，从而降低了吞吐量。可尽量使用 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>延迟队列的实现有两种方式：</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>集群主要有以下两个用途：</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>不是，原因有以下两个：</p>



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



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



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



<p>如果唯一磁盘的磁盘节点崩溃了，不能进行以下操作：</p>



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



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



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



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



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



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



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



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



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



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



<p>保证消息的可靠传输需要从三个环节入手<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>消息重复通常是因为网络波动，消费者已消费但确认 (<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>死信队列是一种消息补偿机制。当消息成为<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>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>kafka 不能脱离 zookeeper 单独使用，因为 kafka 使用 zookeeper 管理和协调 kafka 的节点服务器。</p>



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



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



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



<p>这个时候 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 &#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>InnoDB 表只会把自增主键的最大 id 记录在内存中，所以重启之后会导致最大 id 丢失。</p>



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



<p>使用 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>所以，从空间上考虑 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>内连接关键字：inner join；左连接：left join；右连接：right join。</p>



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



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



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



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



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



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



<p>explain 语法：explain select * from table where type=1。</p>



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



<p>MySQL 的事务隔离是在 MySQL. ini 配置文件里添加的，在文件的最后添加：</p>



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



<p>可用的配置值：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>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>数据库的乐观锁需要自己实现，在表里面添加一个 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>MySQL默认的事务隔离级别是&nbsp;<strong>可重复读（REPEATABLE READ）</strong>。</p>



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



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



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



<p>事务具有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>ORM（Object Relation Mapping）对象关系映射，是把数据库中的关系数据映射成为程序中的对象。</p>



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



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



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



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



<p>三种：hql、原生 SQL、条件查询 Criteria。</p>



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



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



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



<p>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>hibernate 常用的缓存有一级缓存和二级缓存：</p>



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



<p>二级缓存：应用级别的缓存，在所有 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>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><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>分页方式：逻辑分页和物理分页。</p>



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



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



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



<p>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>MyBatis 支持延迟加载，设置 lazyLoadingEnabled=true 即可。</p>



<p>延迟加载的原理的是调用的时候触发加载，而不是在初始化的时候就加载信息。比如调用 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>开启二级缓存数据查询流程：二级缓存 -&gt; 一级缓存 -&gt; 数据库。</p>



<p>缓存更新机制：当某一个作用域(一级缓存 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>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>分页插件的基本原理是使用 MyBatis 提供的插件接口，实现自定义插件，在插件的拦截方法内拦截待执行的 SQL，然后重写 SQL，根据 dialect 方言，添加对应的物理分页语句和物理分页参数。</p>



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



<p><strong>「自定义插件实现原理」</strong></p>



<p>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><strong>「自定义插件实现关键」</strong></p>



<p>MyBatis 插件要实现 Interceptor 接口，接口包含的方法，如下：</p>



<p>代码语言：javascript</p>



<p>代码运行次数：0</p>



<p>运行</p>



<p>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><strong>「自定义插件实现示例」</strong></p>



<p>官方插件实现：</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 &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading" id="aiqcf"><strong>Spring/Spring MVC</strong></h1>



<h2 class="wp-block-heading" id="4ovq1">为什么要使用 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>代理主要分为<strong>静态代理</strong>和<strong>动态代理</strong>。静态代理的代表为<strong>AspectJ</strong>；动态代理的代表为<strong>Spring AOP</strong></p>



<p><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><strong>AspectJ的静态代理</strong>具有<strong>更好的性能</strong>，但<strong>AspectJ需要特定的编译器</strong>进行处理，而<strong>Spring AOP则无需特定的编译器</strong>处理</p>



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



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



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



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



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



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



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



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



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



<p>实际上大部分时候 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 class="wp-block-heading" id="1ptt4">spring 支持几种 bean 的作用域？</h2>



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



<h2 class="wp-block-heading" id="82ru0">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 class="wp-block-heading" id="c860l">spring 事务实现方式有哪些？</h2>



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



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



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



<p>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" id="fmae2">说一下 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 class="wp-block-heading" id="ks5q">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 class="wp-block-heading" id="5hev0">@RequestMapping 的作用是什么？</h2>



<p>将 http 请求映射到相应的类/方法上。</p>



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



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



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



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



<p><strong>1、@Autowired</strong></p>



<p>由Spring提供，只<strong>按照类型</strong>自动注入</p>



<p><strong>2、@Resource</strong></p>



<p>由J2EE提供，默认<strong>按照名称</strong>自动注入</p>



<p>@Resource有两个属性：<strong>name和type</strong></p>



<p>@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 class="wp-block-heading" id="aqd0p"><strong>Spring Boot/Spring Cloud</strong></h1>



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



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



<h2 class="wp-block-heading" id="120er">为什么要用 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 class="wp-block-heading" id="531nh">spring boot 核心配置文件是什么？</h2>



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



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



<p>. properties 配置如下：</p>



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



<p>. yml 配置如下：</p>



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



<p>yml 格式不支持 @PropertySource 注解导入。</p>



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



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



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



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



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



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



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



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



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



<h2 class="wp-block-heading" id="bnlcl">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>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><strong>详细说明：</strong></p>



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



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



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



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



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



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



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



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



<p>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>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>这三个缓存对应 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>假设两个 Bean 都是通过 setter 注入，且作用域为单例。</p>



<div class="wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-8cf370e7 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>至此，循环依赖被完美解决，两个 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-8cf370e7 wp-block-group-is-layout-flex">
<p>有些人可能会问：二级缓存（提前暴露的对象）加上一级缓存（成品）不就够了吗？为什么还需要第三级缓存（工厂）？</p>



<p>关键在于&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>因此，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>如果只有二级缓存（提前暴露原始对象），就无法在必要的时候提前创建代理，导致 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>对于构造函数注入的循环依赖，可以改用 setter 注入，或者使用&nbsp;<code>@Lazy</code>&nbsp;注解延迟加载其中一个依赖。</p>



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



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



<p>主要逻辑集中在&nbsp;<code>AbstractBeanFactory.doGetBean()</code>&nbsp;和&nbsp;<code>DefaultSingletonBeanRegistry.getSingleton()</code>&nbsp;方法中。</p>



<p><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>当创建一个 Bean 时，在&nbsp;<code>doCreateBean</code>&nbsp;中实例化后，会调用&nbsp;<code>addSingletonFactory</code>&nbsp;将工厂放入三级缓存。</p>



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



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



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



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



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



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



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



<p><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>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>正是通过这种代理机制，<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>核心注解是&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>它是一个组合注解，主要包含以下三个核心注解&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>：继承自 <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>除了上面的&nbsp;<code>@SpringBootApplication</code>&nbsp;组合注解外，还有：</p>



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



<li><strong><code>@Conditional</code> 系列注解</strong>：如 <code>@ConditionalOnClass</code>、<code>@ConditionalOnMissingBean</code>，用于控制自动配置在满足特定条件时才生效 <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>：用于将配置文件（如 <code>application.yml</code>）中的属性绑定到 Java Bean 上 <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>原理主要基于 <code>@EnableAutoConfiguration</code> 注解 <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>。它会通过 <code>SpringFactoriesLoader</code> 机制，从 classpath 下所有 JAR 包中的 <code>META-INF/spring.factories</code> 文件里，加载 <code>EnableAutoConfiguration</code> 键对应的自动配置类（如 <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>。这些配置类上通常标有 <code>@Conditional</code> 条件注解，只有满足特定条件（如存在相关类、配置了相关属性）时，这些自动配置类才会生效，实例化相应的 Bean。</p>



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



<p>只需要在配置类上使用&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>自动配置类（类型）的注册信息存放在所有 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>开启&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>有多种方式可以排除自动配置类&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>在 <code>@SpringBootApplication</code> 或 <code>@EnableAutoConfiguration</code> 注解中使用 <code>exclude</code> 属性：<code>@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})</code></li>



<li>在配置文件中使用 <code>spring.autoconfigure.exclude</code> 属性：<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>：使用 <code>@EnableAutoConfiguration</code> 注解（通常通过 <code>@SpringBootApplication</code> 间接使用）<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a>。</li>



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



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



<p>一个典型的 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>此外，资源文件位于&nbsp;<code>src/main/resources</code>&nbsp;目录下，包含：</p>



<ul class="wp-block-list">
<li><code>application.properties</code> 或 <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>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>
]]></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 &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">手动执行：</h1>



<p>在 Ubuntu 24.04 桌面版中，出于系统安全考虑，root 用户是被默认禁止直接登录图形界面的。如果你确实需要以 root 身份操作桌面，可以按照以下步骤进行配置。</p>



<p><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>首先，你需要为 root 账户激活一个密码。打开终端，执行以下命令：</p>



<pre class="wp-block-code"><code>sudo passwd root</code></pre>



<p>系统会提示你输入并确认新的 root 密码。输入密码时屏幕不会有任何显示，这是正常的。</p>



<h4 class="wp-block-heading">第二步：修改 GDM 配置文件</h4>



<p>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>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>首次以 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>所有配置完成后，需要重启系统以使更改生效：</p>



<pre class="wp-block-code"><code>sudo reboot</code></pre>



<h4 class="wp-block-heading">第六步：以 root 身份登录</h4>



<p>系统重启后，在登录界面，你会发现列表中没有 root 用户。此时，需要点击 <strong>“未列出？”</strong> 或者 <strong>“Other”</strong> 选项。</p>



<p>在弹出的输入框中，用户名输入 <code>root</code>，密码输入你在第一步设置的密码，即可登录到 GNOME 桌面。</p>



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



<p><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>上述文件包含</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>enable-root-login.sh 是开启root权限的一键脚本<br>disable-root-login.sh 是关闭root登陆权限的一键脚本</p>



<p><strong>1. 赋予执行权限：</strong></p>



<p>chmod +x enable-root-login.sh<br>或者<br><code>chmod +x disable-root-login.sh</code></p>



<p><strong>2.用 sudo 执行：</strong></p>



<p>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>点击&#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 &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">飞书开发者平台：</h2>



<p>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></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 &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">前言</h2>



<p>本文为<strong>Linux环境</strong>安装，Windows环境博主未作尝试，见谅！</p>



<p>在Windows中使用虚拟机安装，建议<strong>内存&gt;32G</strong>，且系统在Windows10以上，需要<strong>开启Windows系统自带的Hyper-v虚拟机</strong>程序，本文对如何使用Hyper-v不再多做赘述。</p>



<p>出于<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>关键字：<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>以下是博主踩坑后总结的安装流程</p>



<h2 class="wp-block-heading">安装前准备工作：</h2>



<p><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><strong>飞书的聊天机器人配置</strong></p>



<p><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><strong>创建root用户：</strong><a href="https://blog.lhyshome.com/2026/03/11/377/">https://blog.lhyshome.com/2026/03/11/377/</a></p>



<p><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><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><strong>（可选）国内环境优化</strong>：<br>如果你在境内，执行以下命令将 npm 镜像源切换到淘宝源，可以大幅提升下载速度和成功率。<br><code>npm config set registry https://registry.npmmirror.com</code></p>



<h2 class="wp-block-heading">安装OpenClaw：</h2>



<p><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>安装完成后命令行自动进入配置安装，后续也可以使用 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>点击链接，进入页面后选择授权</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>此处注意和飞书中的先后次序（可以参考：<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>在安装了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>频道中显示飞书</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>代理中显示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>至此安装配置完成</p>



<h2 class="wp-block-heading">安装技能（skills）</h2>



<p><a href="https://clawhub.ai/skills?sort=downloads">https://clawhub.ai/skills?sort=downloads</a></p>



<p>进入这个链接，里面为技能库，可以在这里挑选自己所需要的技能</p>



<p>例如：</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 &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">问题</h2>



<p>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>访问各大大模型无果后，百度搜索关键字，发现github上已有相关问题处理</p>



<p>地址为：<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>使用如上图方法处理成功。</p>



<h2 class="wp-block-heading">处理步骤</h2>



<p>执行 “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>在系统->杂项设置 中，将镜像服务器选择如下图所示。（如果不选，则影响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>选择镜像源后依次执行</p>



<p>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>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>如上两图，控制台中未见明确error相关字眼为成功</p>



<p>重新执行“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>重启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 &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="9ld6q">Redis 是什么？都有哪些使用场景？</h2>



<p>Redis 是一个使用 C 语言开发的高速缓存数据库。</p>



<p>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>因为 cpu 不是 Redis 的瓶颈，Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现，而且 cpu 又不会成为瓶颈，那就顺理成章地采用单线程的方案了。</p>



<p>关于 Redis 的性能，官方网站也有，普通笔记本轻松处理每秒几十万的请求。</p>



<p>而且单线程并不代表就慢 nginx 和 nodejs 也都是高性能单线程的代表。</p>



<h2 class="wp-block-heading" id="f516k">什么是缓存穿透？怎么解决？</h2>



<p>缓存穿透：指查询一个一定不存在的数据，由于缓存是不命中时需要从数据库查询，查不到数据则不写入缓存，这将导致这个不存在的数据每次请求都要到数据库去查询，造成缓存穿透。</p>



<p>解决方案：最简单粗暴的方法如果一个查询返回的数据为空（不管是数据不存在，还是系统故障），我们就把这个空结果进行缓存，但它的过期时间会很短，最长不超过五分钟。</p>



<h2 class="wp-block-heading" id="4g04h">Redis 支持的数据类型有哪些？</h2>



<p>Redis 支持的数据类型：string（字符串）、list（列表）、hash（字典）、set（集合）、zset（有序集合）。</p>



<h2 class="wp-block-heading" id="9tb8a">Redis 支持的 Java 客户端都有哪些？</h2>



<p>支持的 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>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>Redis 分布式锁其实就是在系统里面占一个“坑”，其他程序也要占“坑”的时候，占用成功了就可以继续执行，失败了就只能放弃或稍后重试。</p>



<p>占坑一般使用 setnx(set if not exists)指令，只允许被一个程序占有，使用完调用 del 释放锁。</p>



<h2 class="wp-block-heading" id="9uhri">Redis 分布式锁有什么缺陷？</h2>



<p>Redis 分布式锁不能解决超时的问题，分布式锁有一个超时时间，程序的执行如果超出了锁的超时时间就会出现问题。</p>



<h2 class="wp-block-heading" id="cadob">Redis 如何做内存优化？</h2>



<p>尽量使用 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>主要从“高性能”和“高并发”这两点来看待这个问题。<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><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>缓存分为本地缓存和分布式缓存。以 Java 为例，使用自带的 map 或者 guava 实现的是本地缓存，主要的特点是轻量以及快速，生命周期随着 jvm 的销毁 而结束，并且在多实例的情况下，每个实例都需要各自保存一份缓存，缓存不具有一致性。<br>使用 redis 或 memcached 之类的称为分布式缓存，在多实例的情况下，各实 例共用一份缓存数据，缓存具有一致性。缺点是需要保持 redis 或 memcached 服务的高可用，整个程序架构上较为复杂。</p>



<h2 class="wp-block-heading">Redis为什么这么快</h2>



<p>完全基于内存，绝大部分请求是纯粹的内存操作，非常快速。数据存在内存 中，类似于 HashMap，HashMap 的优势就是查找和操作的时间复杂度都是 O(1)；<br>数据结构简单，对数据操作也简单，Redis 中的数据结构是专门进行设计 的；<br>采用单线程，避免了不必要的上下文切换和竞争条件，也不存在多进程或者多线程导致的切换而消耗CPU，不用去考虑各种锁的问题，不存在加锁释放锁操作，没有因为可能出现死锁而导致的性能消耗；<br>使用多路 I/O 复用模型，非阻塞 IO；<br>使用底层模型不同，它们之间底层实现方式以及与客户端之间通信的应用协议不一样，Redis直接自己构建了 VM 机制 ，因为一般的系统调用系统函数的话，会浪费一定的时间去移动和请求；</p>



<h2 class="wp-block-heading">Redis有哪些数据类型</h2>



<p>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><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><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>持久化就是把内存的数据写到磁盘中去，防止服务宕机了内存数据丢失。<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><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><strong>缺点：</strong></p>



<ol class="wp-block-list">
<li>数据安全性低。RDB是间隔一段时间进行持久化，如果持久化之间redis发生故障，会发生数据丢失。所以这种方式更适合数据要求不那么严谨的时候。</li>



<li>AOF（Append-only file)持久化方式： 是指所有的命令行记录以 redis 命 令请 求协议的格式完全持久化存储)保存为 aof 文件。</li>
</ol>



<p><strong>AOF：持久化</strong></p>



<p>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><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><strong>缺点：</strong></p>



<ol class="wp-block-list">
<li>AOF 文件比 RDB 文件大，且恢复速度慢。</li>



<li>数据集大的时候，比 rdb 启动效率低。</li>
</ol>



<p><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>我们都知道，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>Redis中同时使用了惰性过期和定期过期两种过期策略。</p>



<h2 class="wp-block-heading">我们知道通过expire来设置key 的过期时间，那么对过期的数据怎么处理呢?（针对持久化的数据）</h2>



<p>除了缓存服务器自带的缓存失效策略之外（Redis默认的有6中策略可供选择），我们还可以根据具体的业务需求进行自定义的缓存淘汰，常见的策略有两种：</p>



<ol class="wp-block-list">
<li>定时去清理过期的缓存；</li>



<li>当有用户请求过来时，再判断这个请求所用到的缓存是否过期，过期的话就去底层系统得到新数据并更新缓存。</li>
</ol>



<p>两者各有优劣，第一种的缺点是维护大量缓存的key是比较麻烦的，第二种的缺点就是每次用户请求过来都要判断缓存失效，逻辑相对比较复杂！具体用哪种方案，大家可以根据自己的应用场景来权衡。</p>



<h2 class="wp-block-heading">redis内存淘汰策略是什么</h2>



<p>redis之所以快，因为他是依赖于内存的，但内存大小非常有限，如果超过就会报错。<br>所以当redis内存数据集大小上升到一定大小的时候，就会施行数据淘汰策略。</p>



<h2 class="wp-block-heading">Redis的内存淘汰策略有哪些</h2>



<p>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>对设置过期时间的键空间选择性移除</p>



<ul class="wp-block-list">
<li>volatile-lru：在设置了过期时间的键空间中，移除最近最少使用的key。</li>



<li>volatile-random：在设置了过期时间的键空间中，随机移除某个key。</li>



<li>volatile-ttl：在设置了过期时间的键空间中，有更早过期时间的key优先移除。</li>
</ul>



<p><strong>总结</strong></p>



<p>Redis的内存淘汰策略的选取并不会影响过期的key的处理。<br>内存淘汰策略用于处理内存不足时的需要申请额外空间的数据（针对于内存）<br>过期策略用于处理过期的缓存数据（针对于数据库）</p>



<h2 class="wp-block-heading">Redis的内存用完了会发生什么？</h2>



<p>如果达到设置的上限，Redis的写命令会返回错误信息（但是读命令还可以正常返回）或者你可以配置内存淘汰机制，当Redis达到内存上限时会冲刷掉旧的内容。</p>



<h2 class="wp-block-heading">Redis如何做内存优化？</h2>



<p>可以好好利用Hash,list,sorted set,set等集合类型数据，因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表（hashes），散列表（是说散列表里面存储的数少）使用的内存非常小，所以你应该尽可能的将你的数据模型抽象到一个散列表里面（一个对象里面）。</p>



<p>例如：你的web系统中有一个用户对象，不要为这个用户的名称，姓氏，邮箱，密码设置单独的key，而是应该把这个用户的所有信息存储到一张散列表里面线程模型。</p>



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



<p>事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中，不会被其他客户端发送来的命令请求所打断。</p>



<p>事务是一个原子操作：事务中的命令要么全部被执行，要么全部都不执行。</p>



<h2 class="wp-block-heading">Redis事务的概念</h2>



<p>Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令，一个事务中所有命令都会被序列化。在事务执行过程，会按照顺序串行化执行队列中的命令，其他客户端 提交的命令请求不会插入到事务执行命令序列中。</p>



<p>总结说：redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。</p>



<h2 class="wp-block-heading">事务的基本操作</h2>



<p>开启事务</p>



<p>multi<br>AI生成项目<br>shell<br>1<br>作用：设定事务的开启位置，此指令执行后，后续的所有指令均加入到事务中</p>



<p>执行事务</p>



<p>exec<br>AI生成项目<br>shell<br>1<br>作用：设定事务的结束位置，同时执行事务。与multi成对出现，成对使用</p>



<p>注意：加入事务的命令暂时进入到任务队列中，并没有立即执行，只有执行exec命令才开始执行</p>



<p>但是你在命令入队列的时候，发现你有些命令指令输入错误，你不想执行了，这个时候，你不想提交事务了</p>



<p>取消事务</p>



<p>discard<br>AI生成项目<br>shell<br>1<br>作用：终止当前事务的定义，发生在multi之后，exec之前</p>



<p>总结：遇到multi指令，创建队列，遇到discard指令销毁队列</p>



<p>事务执行过程中，如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求，将会把请求放入队列中排队(需要研究)</p>



<h2 class="wp-block-heading">Redis事务相关命令</h2>



<p>Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的</p>



<p>Redis会将一个事务中的所有命令序列化，然后按顺序执行。</p>



<p>redis 不支持回滚，“Redis 在事务失败时不进行回滚，而是继续执行余下的命令”， 所以 Redis 的内部可以保持简单且快速。</p>



<p>如果在一个事务中的命令出现错误，那么所有的命令都不会执行；</p>



<p>如果在一个事务中出现运行错误，那么正确的命令会被执行。</p>



<p>WATCH命令是一个乐观锁，可以为 Redis 事务提供 check-and-set （CAS）行为。 可以监控一个或多个键，一旦其中有一个键被修改（或删除），之后的事务就不会执行，监控一直持续到EXEC命令。</p>



<p>MULTI命令用于开启一个事务，它总是返回OK。 MULTI执行之后，客户端可以继续向服务器发送任意多条命令，这些命令不会立即被执行，而是被放到一个队列中，当EXEC命令被调用时，所有队列中的命令才会被执行。</p>



<p>EXEC：执行所有事务块内的命令。返回事务块内所有命令的返回值，按命令执行的先后顺序排列。 当操作被打断时，返回空值 nil 。</p>



<p>通过调用DISCARD，客户端可以清空事务队列，并放弃执行事务， 并且客户端会从事务状态中退出。</p>



<p>UNWATCH命令可以取消watch对所有key的监控。</p>



<h2 class="wp-block-heading">Redis事务支持隔离性吗</h2>



<p>Redis 是单进程程序，并且它保证在执行事务时，不会对事务进行中断，事务可以运行直到执行完所有事务队列中的命令为止。因此，Redis 的事务是总是带有隔离性的。</p>



<h2 class="wp-block-heading">Redis事务保证原子性吗，支持回滚吗</h2>



<p>Redis中，单条命令是原子性执行的，但事务不保证原子性，且没有回滚。事务中任意命令执行失败，其余的命令仍会被执行。</p>



<h2 class="wp-block-heading">Redis事务其他实现</h2>



<p>基于Lua脚本，Redis可以保证脚本内的命令一次性、按顺序地执行，其同时也不提供事务运行错误的回滚，执行过程中如果部分命令运行错误，剩下的命令还是会继续运行完</p>



<p>基于中间标记变量，通过另外的标记变量来标识事务是否执行完成，读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现，比较繁琐集群方案哨兵模式</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>哨兵的介绍</p>



<p>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>哨兵用于实现 redis 集群的高可用，本身也是分布式的，作为一个哨兵集群去运行，互相协同工作。<br>故障转移时，判断一个 master node 是否宕机了，需要大部分的哨兵都同意才行，涉及到了分布式选举的问题。<br>即使部分哨兵节点挂掉了，哨兵集群还是能正常工作的，因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的，那就很坑爹了。</p>



<p>哨兵的核心知识</p>



<ul class="wp-block-list">
<li>哨兵至少需要 3 个实例，来保证自己的健壮性。</li>



<li>哨兵 + redis 主从的部署架构，是不保证数据零丢失的，只能保证 redis 集群的高可用性。</li>



<li>对于哨兵 + redis 主从这种复杂的部署架构，尽量在测试环境和生产环境，都进行充足的测试和演练。</li>
</ul>



<p><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>redis 集群模式的工作原理能说一下么？在集群模式下，redis 的 key 是如何寻址的？分布式寻址都有哪些算法？了解一致性 hash 算法吗？简介</p>



<p>Redis Cluster是一种服务端Sharding技术，3.0版本开始正式提供。Redis Cluster并没有使用一致性hash，而是采用slot(槽)的概念，一共分成16384个槽。将请求发送到任意节点，接收到请求的节点会将查询请求发送到正确的节点上执行。16384个槽数默认值是固定的。</p>



<p><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>在 redis cluster 架构下，每个 redis 要放开两个端口号，一个用来对外供客户端连接，另一个用来节点间互相通信。</p>



<p>比如一个是 6379，另外一个就是 加1w 的端口号，比如 16379。</p>



<p>16379 端口号是用来进行节点间通信的，也就是 cluster bus 的东西，cluster bus 的通信，用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议，gossip 协议，用于节点间进行高效的数据交换，占用更少的网络带宽和处理时间。</p>



<p>节点间的内部通信机制</p>



<p>基本通信原理</p>



<p>集群元数据的维护有两种方式：集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。</p>



<h2 class="wp-block-heading">Redis主从架构</h2>



<p>单机的 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>redis 采用异步方式复制数据到 slave 节点，不过 redis2.8 开始，slave node 会周期性地确认自己每次复制的数据量；</p>



<p>一个 master node 是可以配置多个 slave node 的； slave node 也可以连接其他的 slave node；</p>



<p>slave node 做复制的时候，不会（阻碍）block master node 的正常工作；</p>



<p>slave node 在做复制的时候，也不会 block 对自己的查询操作，它会用旧的数据集来提供服务；但是复制完成的时候，需要删除旧数据集，加载新数据集，这个时候就会暂停对外服务了；</p>



<p>slave node 主要用来进行横向扩容，做读写分离，扩容的 slave node 可以提高读的吞吐量。</p>



<p>注意：如果采用了主从架构，那么建议必须开启 master node 的持久化，不建议用 slave node 作为 master node 的数据热备，因为那样的话，如果你关掉 master 的持久化，可能在 master 宕机重启的时候数据是空的，然后可能一经过复制， slave node 的数据也丢了。</p>



<p>另外，master 的各种备份方案，也需要做。万一本地的所有文件丢失了，从备份中挑选一份 rdb 去恢复 master，这样才能确保启动的时候，是有数据的，即使采用了后续讲解的高可用机制，slave node 可以自动接管 master node，但也可能 sentinel 还没检测到 master failure，master node 就自动重启了，还是可能导致上面所有的 slave node 数据被清空。</p>



<p>redis 主从复制的核心原理当启动一个 slave node 的时候，它会发送一个 PSYNC 命令给 master node。</p>



<p>如果这是 slave node 初次连接到 master node，那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程，开始生成一份 RDB 快照文件，同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后， master 会将这个 RDB 发送给 slave，slave 会先写入本地磁盘，然后再从本地磁盘加载到内存中，接着 master 会将内存中缓存的写命令发送到 slave，slave 也会同步这些数据。</p>



<p>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>缺点</p>



<p>所有的slave节点数据的复制和同步都由master节点来处理，会照成master节点压力太大，使用主从从结构来解决</p>



<h2 class="wp-block-heading">Redis集群的主从复制模型是怎样的？</h2>



<p>为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用，所以集群使用了主从复制模型，每个节点都会有N-1个复制品生产环境中的 redis 是怎么部署的？</p>



<p>redis cluster，10 台机器，5 台机器部署了 redis 主实例，另外 5 台机器部署了 redis 的从实例，每个主实例挂了一个从实例，5 个节点对外提供读写服务，每个节点的读写高峰qps可能可以达到每秒 5 万，5 台机器最多是 25 万读写请求/s。</p>



<p>机器是什么配置？32G 内存+ 8 核 CPU + 1T 磁盘，但是分配给 redis 进程的是10g内存，一般线上生产环境，redis 的内存尽量不要超过 10g，超过 10g 可能会有问题。</p>



<p>5 台机器对外提供读写，一共有 50g 内存。</p>



<p>因为每个主实例都挂了一个从实例，所以是高可用的，任何一个主实例宕机，都会自动故障迁移，redis 从实例会自动变成主实例继续提供读写服务。</p>



<p>你往内存里写的是什么数据？每条数据的大小是多少？商品数据，每条数据是 10kb。100 条数据是 1mb，10 万条数据是 1g。常驻内存的是 200 万条商品数据，占用内存是 20g，仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。</p>



<p>其实大型的公司，会有基础架构的 team 负责缓存集群的运维。</p>



<h2 class="wp-block-heading">说说Redis哈希槽的概念？</h2>



<p>Redis集群没有使用一致性hash,而是引入了哈希槽的概念，Redis集群有16384个哈希槽，每个key通过CRC16校验后对16384取模来决定放置哪个槽，集群的每个节点负责一部分hash槽。</p>



<p>Redis 的哈希槽（Hash Slot）是 Redis 集群中的一个概念，用于将数据分布到不同的节点上。Redis 集群中使用哈希槽来实现数据的分片和负载均衡，将大量的数据分散到多个节点上，以提高系统的可扩展性和性能。</p>



<p>具体来说，Redis 会将所有的哈希槽均分到不同的节点上，每个节点负责处理一部分哈希槽的数据。</p>



<p>例如：一个 Redis 集群有 3 个节点，每个节点负责处理 16384 个哈希槽，那么每个节点负责处理的哈希槽范围为：<br>第一个节点：0-5460<br>第二个节点：5461-10922<br>第三个节点：10923-16383<br>当客户端需要访问某个键值对时，Redis 集群会根据键名计算出哈希值，并将该哈希值映射到一个哈希槽上。然后，Redis 集群会根据哈希槽的分布情况，将该键值对传递到对应的节点上进行处理。</p>



<p>在 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>Redis并不能保证数据的强一致性，这意味这在实际中集群在特定的条件下可能会丢失写操作。</p>



<p>Redis集群之间是如何复制的？</p>



<p>异步复制</p>



<p>Redis集群最大节点个数是多少？</p>



<p>16384个，因为哈希槽最多16384个</p>



<p>Redis集群如何选择数据库？</p>



<p>Redis集群目前无法做数据库选择，默认在0数据库。</p>



<h2 class="wp-block-heading">Redis是单线程的，如何提高多核CPU的利用率？</h2>



<p>可以在同一个服务器部署多个Redis的实例，并把他们当作不同的服务器来使用，在某些时候，无论如何一个服务器是不够的， 所以，如果你想使用多个CPU，你可以考虑一下分片（shard）。</p>



<h2 class="wp-block-heading">为什么要做Redis分区？</h2>



<p>分区可以让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>具体实现可参考：基于redis实现分布式锁</p>



<p>Redis为单进程单线程模式，采用队列模式将并发访问变成串行访问，且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。</p>



<p>当且仅当 key 不存在，将 key 的值设为 value。 若给定的 key 已经存在，则SETNX 不做任何动作</p>



<p>SETNX 是『SET if Not eXists』(如果不存在，则 SET)的简写。</p>



<p>返回值：设置成功，返回 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>使用SETNX完成同步锁的流程及事项如下(img)：使用SETNX命令获取锁，若返回0（key已存在，锁已存在）则获取失败，反之获取成功为了防止获取锁后程序出现异常，导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态，需要为该key设置一个“合理”的过期时间释放锁，使用DEL命令将锁数据删除</p>



<h2 class="wp-block-heading">分布式Redis是前期做还是后期规模上来了再做好？为什么？</h2>



<p>既然Redis是如此的轻量（单实例只使用1M内存），为防止以后的扩容， 好的办法就是一开始就启动较多实例。即便你只有一台服务器，你也可以一开始就让Redis以分布式的方式运行，使用分区，在同一台服务器上启动多个实例。</p>



<p>一开始就多设置几个Redis实例，例如32或者64个实例，对大多数用户来说这操作起来可能比较麻烦，但是从长久来看做这点牺牲是值得的。</p>



<p>这样的话，当你的数据不断增长，需要更多的Redis服务器时，你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已（而不用考虑重新分区的问题）。一旦你添加了另一台服务器，你需要将你一半的Redis实例从第一台机器迁移到第二台机器。</p>



<h2 class="wp-block-heading">缓存雪崩</h2>



<p>缓存雪崩是指缓存同一时间大面积的失效，所以，后面的请求都会落到数据库上，造成数据库短时间内承受大量请求而崩掉。</p>



<p>解决方案</p>



<ol class="wp-block-list">
<li>缓存数据的过期时间设置随机，防止同一时间大量数据过期现象发生。</li>



<li>一般并发量不是特别多的时候，使用最多的解决方案是加锁排队。</li>



<li>给每一个缓存数据增加相应的缓存标记，记录缓存的是否失效，如果缓存标记失效，则更新数据缓存。</li>
</ol>



<h2 class="wp-block-heading">缓存穿透</h2>



<p>缓存穿透是指缓存和数据库中都没有的数据，导致所有的请求都落到数据库上，造成数据库短时间内承受大量请求而崩掉。</p>



<p>解决方案</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>Bitmap： 典型的就是哈希表缺点是，Bitmap对于每个元素只能记录1bit信息，如果还想完成额外的功能，恐怕只能靠牺牲更多的空间、时间来完成了。</p>



<p>布隆过滤器（推荐）</p>



<p>就是引入了k(k&gt;1)k(k&gt;1)个相互独立的哈希函数，保证在给定的空间、误判率下，完成元素判重的过程。</p>



<p>它的优点是空间效率和查询时间都远远超过一般的算法，缺点是有一定的误识别率和删除困难。</p>



<p>Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。</p>



<p>Hash存在一个冲突（碰撞）的问题，用同一个Hash得到的两个URL的值有可能相同。为了减少冲突，我们可以多引入几个Hash，如果通过其中的一个Hash值我们得出某元素不在集合中，那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时，才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。</p>



<p>Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。</p>



<h2 class="wp-block-heading">缓存击穿</h2>



<p>缓存击穿是指缓存中没有但数据库中有的数据（一般是缓存时间到期），这时由于并发用户特别多<strong>，同时读缓存没读到数据，又同时去数据库去取数据</strong>，引起数据库压力瞬间增大，造成过大压力。</p>



<p>和缓存雪崩不同的是，缓存击穿指并发查同一条数据，缓存雪崩是不同数据都过期了，很多数据都查不到从而查数据库。</p>



<p>解决方案</p>



<ol class="wp-block-list">
<li>设置热点数据永远不过期。</li>



<li>加互斥锁，互斥锁缓存预热</li>
</ol>



<p>缓存预热就是系统上线后，将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候，先查询数据库，然后再将数据缓存的问题！用户直接查询事先被预热的缓存数据！解决方案</p>



<ol class="wp-block-list">
<li>直接写个缓存刷新页面，上线时手工操作一下；</li>



<li>数据量不大，可以在项目启动的时候自动进行加载；</li>



<li>定时刷新缓存；</li>
</ol>



<h2 class="wp-block-heading">缓存降级</h2>



<p>当访问量剧增、服务出现问题（如响应时间慢或不响应）或非核心服务影响到核心流程的性能时，仍然需要保证服务还是可用的，即使是有损服务。系统可以根据一些关键数据进行自动降级，也可以配置开关实现人工降级。</p>



<p>缓存降级的最终目的是保证核心服务可用，即使是有损的。而且有些服务是无法降级的（如加入购物车、结算）。</p>



<p>在进行降级之前要对系统进行梳理，看看系统是不是可以丢卒保帅；从而梳理出哪些必须誓死保护，哪些可降级；比如可以参考日志级别设置预案：</p>



<ol class="wp-block-list">
<li>一般：比如有些服务偶尔因为网络抖动或者服务正在上线而超时，可以自动降级；</li>



<li>警告：有些服务在一段时间内成功率有波动（如在95~100%之间），可以自动降级或人工降级，并发送告警；</li>



<li>错误：比如可用率低于90%，或者数据库连接池被打爆了，或者访问量突然猛增到系统能承受的最大阀值，此时可以根据情况自动降级或者人工降级；</li>



<li>严重错误：比如因为特殊原因数据错误了，此时需要紧急人工降级。</li>
</ol>



<p>服务降级的目的，是为了防止Redis服务故障，导致数据库跟着一起发生雪崩问题。因此，对于不重要的缓存数据，可以采取服务降级策略，例如一个比较常见的做法就是，Redis出现问题，不去数据库查询，而是直接返回默认值给用户。</p>



<h2 class="wp-block-heading">降级的方案有哪些？（*）</h2>



<h2 class="wp-block-heading">热点数据和冷数据</h2>



<p>热点数据，缓存才有价值，对于冷数据而言，大部分数据可能还没有再次访问到就已经被挤出内存，不仅占用内存，而且价值不大。频繁修改的数据，看情况考虑使用缓存对于热点数据，比如我们的某IM产品，生日祝福模块，当天的寿星列表，缓存以后可能读取数十万次。再举个例子，某导航产品，我们将导航信息，缓存以后可能读取数百万次。<br>数据更新前至少读取两次，缓存才有意义。这个是最基本的策略，如果缓存还没有起作用就失效了，那就没有太大价值了。<br>那存不存在，修改频率很高，但是又不得不考虑缓存的场景呢？<br>有！比如，这个读取接口对数据库的压力很大，但是又是热点数据，这个时候就需要考虑通过缓存手段，减少数据库的压力，比如我们的某助手产品的，点赞数，收藏数，分享数等是非常典型的热点数据，但是又不断变化，此时就需要将数据同步保存到Redis缓存，减少数据库压力。</p>



<h2 class="wp-block-heading">缓存热点key</h2>



<p>缓存中的一个Key(比如一个促销商品)，在某个时间点过期的时候，恰好在这个时间点对这个Key有大量的并发请求过来，这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存，这个时候大并发的请求可能会瞬间把后端DB压垮。</p>



<p>解决方案对缓存查询加锁，如果KEY不存在，就加锁，然后查DB入缓存，然后解锁；其他进程如果发现有锁就等待，然后等解锁后返回数据或者进入DB查询常用工具</p>



<p>Redis支持的Java客户端都有哪些？官方推荐用哪个？</p>



<p>Redisson、Jedis、lettuce等等，官方推荐使用Redisson。</p>



<h2 class="wp-block-heading">Redis和Redisson有什么关系？</h2>



<p>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>Jedis是Redis的Java实现的客户端，其API提供了比较全面的Redis命令的支持；Redisson实现了分布式和可扩展的Java数据结构，和Jedis相比，功能较为简单，不支持字符串操作，不支持排序、事务、管道、分区等Redis特性。</p>



<p>Redisson的宗旨是促进使用者对Redis的关注分离，从而让使用者能够将精力更集中地放在处理业务逻辑上。</p>



<h2 class="wp-block-heading">Redis与Memcached的区别</h2>



<p>两者都是非关系型内存键值数据库，现在公司一般都是用 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><br>(1) memcached所有的值均是简单的字符串，redis作为其替代者，支持更为丰富的数据类型</p>



<p>(2) redis的速度比memcached快很多</p>



<p>(3) redis可以持久化其数据</p>



<p>如何保证缓存与数据库双写时的数据一致性？<br>你只要用缓存，就可能会涉及到缓存与数据库双存储双写，你只要是双写，就一定会有数据一致性的问题，那么你如何解决一致性问题？一般来说，就是如果你的系统不是严格要求缓存+数据库必须一致性的话，缓存可以稍微的跟数据库偶尔有不一致的情况，最好不要做这个方案，读请求和写请求串行化，串到一个内存队列里去，这样就可以保证一定不会出现不一致的情况串行化之后，就会导致系统的吞吐量会大幅度的降低，用比正常情况下多几倍的机器去支撑线上的一个请求。</p>



<p>还有一种方式就是可能会暂时产生不一致的情况，但是发生的几率特别小，就是先更新数据库，然后再删除缓存。</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>因为目前Linux版本已经相当稳定，而且用户量很大，无需开发windows版本，反而会带来兼容性等问题。</p>



<h2 class="wp-block-heading">一个字符串类型的值能存储最大容量是多少？</h2>



<p>512M</p>



<p>Redis如何做大量数据插入？ Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。</p>



<h2 class="wp-block-heading">假如Redis里面有1亿个key，其中有10w个key是以某个固定的已知的前缀开头的，如果将它们全部找出来？</h2>



<p>使用keys指令可以扫出指定模式的key列表。</p>



<p>对方接着追问：如果这个redis正在给线上的业务提供服务，那使用keys指令会有什么问题？这个时候你要回答redis关键的一个特性：redis的单线程的。keys指令会导致线程阻塞一段时间，线上服务会停顿，直到指令执行完毕，服务才能恢复。</p>



<p>这个时候可以使用scan指令，scan指令可以无阻塞的提取出指定模式的key列表，但是会有一定的重复概率，在客户端做一次去重就可以了，但是整体所花费的时间会比直接用keys指令长。</p>



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



<p>使用sortedset，使用时间戳做score, 消息内容作为key,调用zadd来生产消息，消费者使用zrangbyscore获取n秒之前的数据做轮询处理。</p>



<p></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>
		<item>
		<title>【Java面试】基础</title>
		<link>https://blog.lhyshome.com/2025/07/07/316/</link>
					<comments>https://blog.lhyshome.com/2025/07/07/316/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Mon, 07 Jul 2025 08:57:54 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=316</guid>

					<description><![CDATA[Java 基础 1. JDK 和 JRE 有什么区别？ 具体来说 JDK 其实包含了 JRE，同时还包含了编译… <span class="read-more"><a href="https://blog.lhyshome.com/2025/07/07/316/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading" id="ec6e2"><strong>Java 基础</strong></h1>



<h2 class="wp-block-heading" id="aes7e"><strong>1. JDK 和 JRE 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>JDK：Java Development Kit 的简称，Java 开发工具包，提供了 Java 的开发环境和运行环境。</li>



<li>JRE：Java Runtime Environment 的简称，Java 运行环境，为 Java 的运行提供了所需环境。</li>
</ul>



<p>具体来说 JDK 其实包含了 JRE，同时还包含了编译 Java 源码的编译器 Javac，还包含了很多 Java 程序调试和分析的工具。简单来说：如果你需要运行 Java 程序，只需安装 JRE 就可以了，如果你需要编写 Java 程序，需要安装 JDK。</p>



<h2 class="wp-block-heading" id="chqqj"><strong>2. == 和 equals 的区别是什么？</strong></h2>



<p><strong>「== 解读」</strong></p>



<p>对于基本类型和引用类型 == 的作用效果是不同的，如下所示：</p>



<ul class="wp-block-list">
<li>基本类型：比较的是值是否相同；</li>



<li>引用类型：比较的是引用是否相同；</li>
</ul>



<p>代码示例：</p>



<pre class="wp-block-code"><code>String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true</code></pre>



<p>代码解读：因为 x 和 y 指向的是同一个引用，所以 == 也是 true，而 new String()方法则重写开辟了内存空间，所以 == 结果为 false，而 equals 比较的一直是值，所以结果都为 true。</p>



<p><strong>「equals 解读」</strong></p>



<p>equals 本质上就是 ==，只不过 String 和 Integer 等重写了 equals 方法，把它变成了值比较。看下面的代码就明白了。</p>



<p>首先来看默认情况下 equals 比较一个有相同值的对象，代码如下：</p>



<pre class="wp-block-code"><code>classCat{
    publicCat(String name){
        this.name = name;
    }

    private String name;

    public String getName(){
        return name;
    }

    publicvoidsetName(String name){
        this.name = name;
    }
}

Cat c1 = new Cat("王磊");
Cat c2 = new Cat("王磊");
System.out.println(c1.equals(c2)); // false</code></pre>



<p>输出结果出乎我们的意料，竟然是 false？这是怎么回事，看了 equals 源码就知道了，源码如下：</p>



<pre class="wp-block-code"><code>publicbooleanequals(Object obj){
        return (this == obj);
}</code></pre>



<p>原来 equals 本质上就是 ==。</p>



<p>那问题来了，两个相同值的 String 对象，为什么返回的是 true？代码如下：</p>



<pre class="wp-block-code"><code>String s1 = new String("老王");
String s2 = new String("老王");
System.out.println(s1.equals(s2)); // true</code></pre>



<p>同样的，当我们进入 String 的 equals 方法，找到了答案，代码如下：</p>



<pre class="wp-block-code"><code>publicbooleanequals(Object anObject){
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1&#91;] = value;
            char v2&#91;] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1&#91;i] != v2&#91;i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}</code></pre>



<p>原来是 String 重写了 Object 的 equals 方法，把引用比较改成了值比较。</p>



<p><strong>「总结」</strong> ：== 对于基本类型来说是值比较，对于引用类型来说是比较的是引用；而 equals 默认情况下是引用比较，只是很多类重新了 equals 方法，比如 String、Integer 等把它变成了值比较，所以一般情况下 equals 比较的是值是否相等。</p>



<h2 class="wp-block-heading" id="ff96r"><strong>3. 两个对象的 hashCode() 相同，则 equals() 也一定为 true，对吗？</strong></h2>



<p>不对，两个对象的 hashCode() 相同，equals() 不一定 true。</p>



<p>代码示例：</p>



<pre class="wp-block-code"><code>String str1 = "通话";
String str2 = "重地";
System. out. println(String. format("str1：%d | str2：%d",  str1. hashCode(),str2. hashCode()));
System. out. println(str1. equals(str2));</code></pre>



<p>执行的结果：</p>



<pre class="wp-block-code"><code>str1：1179395 | str2：1179395

false</code></pre>



<p>代码解读：很显然“通话”和“重地”的 hashCode() 相同，然而 equals() 则为 false，因为在散列表中，hashCode() 相等即两个键值对的哈希值相等，然而哈希值相等，并不一定能得出键值对相等。</p>



<h2 class="wp-block-heading" id="3l7rh"><strong>4. final 在 Java 中有什么作用？</strong></h2>



<ul class="wp-block-list">
<li>final 修饰的类叫最终类，该类不能被继承。</li>



<li>final 修饰的方法不能被重写。</li>



<li>final 修饰的变量叫常量，常量必须初始化，初始化之后值就不能被修改。</li>
</ul>



<h2 class="wp-block-heading" id="ds89n"><strong>5. Java 中的 Math. round(-1. 5) 等于多少？</strong></h2>



<p>等于 -1，因为在数轴上取值时，中间值（0.5）向右取整，所以正 0.5 是往上取整，负 0.5 是直接舍弃。</p>



<h2 class="wp-block-heading" id="da68n"><strong>6. String 属于基础的数据类型吗？</strong></h2>



<p>String 不属于基础类型，基础类型有 8 种：byte、boolean、char、short、int、float、long、double，而 String 属于对象。</p>



<h2 class="wp-block-heading" id="70io6">7. Java 中操作字符串都有哪些类？它们之间有什么区别？</h2>



<p>操作字符串的类有：String、StringBuffer、StringBuilder。</p>



<p>String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象，每次操作都会生成新的 String 对象，然后将指针指向新的 String 对象，而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作，所以在经常改变字符串内容的情况下最好不要使用 String。</p>



<p>StringBuffer 和 StringBuilder 最大的区别在于，StringBuffer 是线程安全的，而 StringBuilder 是非线程安全的，但 StringBuilder 的性能却高于 StringBuffer，所以在单线程环境下推荐使用 StringBuilder，多线程环境下推荐使用 StringBuffer。</p>



<h2 class="wp-block-heading" id="25eoj"><strong>8. String str=”i”与 String str=new String(“i”)一样吗？</strong></h2>



<p>不一样，因为内存的分配方式不一样。String str=”i”的方式，Java 虚拟机会将其分配到常量池中；而 String str=new String(“i”) 则会被分到堆内存中。</p>



<h2 class="wp-block-heading" id="7k2lg"><strong>9. 如何将字符串反转？</strong></h2>



<p>使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。</p>



<p>示例代码：</p>



<pre class="wp-block-code"><code>// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba</code></pre>



<h2 class="wp-block-heading" id="bi58l"><strong>10. String 类的常用方法都有那些？</strong></h2>



<ul class="wp-block-list">
<li>indexOf()：返回指定字符的索引。</li>



<li>charAt()：返回指定索引处的字符。</li>



<li>replace()：字符串替换。</li>



<li>trim()：去除字符串两端空白。</li>



<li>split()：分割字符串，返回一个分割后的字符串数组。</li>



<li>getBytes()：返回字符串的 byte 类型数组。</li>



<li>length()：返回字符串长度。</li>



<li>toLowerCase()：将字符串转成小写字母。</li>



<li>toUpperCase()：将字符串转成大写字符。</li>



<li>substring()：截取字符串。</li>



<li>equals()：字符串比较。</li>
</ul>



<h2 class="wp-block-heading" id="8io15"><strong>11. 抽象类必须要有抽象方法吗？</strong></h2>



<p>不需要，抽象类不一定非要有抽象方法。</p>



<p>示例代码：</p>



<pre class="wp-block-code"><code>abstract class Cat{
    public static void sayHi(){
        System.out.println("hi~");
    }
}</code></pre>



<p>上面代码，抽象类并没有抽象方法但完全可以正常运行。</p>



<h2 class="wp-block-heading" id="cadrv"><strong>12. 普通类和抽象类有哪些区别？</strong></h2>



<ul class="wp-block-list">
<li>普通类不能包含抽象方法，抽象类可以包含抽象方法。</li>



<li>抽象类不能直接实例化，普通类可以直接实例化。</li>
</ul>



<h2 class="wp-block-heading" id="cmlug"><strong>13. 抽象类能使用 final 修饰吗？</strong></h2>



<p>不能，定义抽象类就是让其他类继承的，如果定义为 final 该类就不能被继承，这样彼此就会产生矛盾，所以 final 不能修饰抽象类，如下图所示，编辑器也会提示错误信息：</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="350" height="125" src="https://blog.lhyshome.com/wp-content/uploads/2025/07/image.png" alt="" class="wp-image-317" srcset="https://blog.lhyshome.com/wp-content/uploads/2025/07/image.png 350w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-300x107.png 300w" sizes="auto, (max-width: 350px) 100vw, 350px" /></figure>



<h2 class="wp-block-heading" id="2n09q"><strong>14. 接口和抽象类有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>实现：抽象类的子类使用 extends 来继承；接口必须使用 implements 来实现接口。</li>



<li>构造函数：抽象类可以有构造函数；接口不能有。</li>



<li>实现数量：类可以实现很多个接口；但是只能继承一个抽象类。</li>



<li>访问修饰符：接口中的方法默认使用 public 修饰；抽象类中的方法可以是任意访问修饰符。</li>
</ul>



<h2 class="wp-block-heading" id="8dlcf"><strong>15. Java 中 IO 流分为几种？</strong></h2>



<p>按功能来分：输入流（input）、输出流（output）。</p>



<p>按类型来分：字节流和字符流。</p>



<p>字节流和字符流的区别是：字节流按 8 位传输以字节为单位输入输出数据，字符流按 16 位传输以字符为单位输入输出数据。</p>



<h2 class="wp-block-heading" id="80rmf"><strong>16. BIO、NIO、AIO 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>BIO：Block IO 同步阻塞式 IO，就是我们平常使用的传统 IO，它的特点是模式简单使用方便，并发处理能力低。</li>



<li>NIO：Non IO 同步非阻塞 IO，是传统 IO 的升级，客户端和服务器端通过 Channel（通道）通讯，实现了多路复用。</li>



<li>AIO：Asynchronous IO 是 NIO 的升级，也叫 NIO2，实现了异步非堵塞 IO ，异步 IO 的操作基于事件和回调机制。</li>
</ul>



<h2 class="wp-block-heading" id="9q5um"><strong>17. Files的常用方法都有哪些？</strong></h2>



<ul class="wp-block-list">
<li>Files. exists()：检测文件路径是否存在。</li>



<li>Files. createFile()：创建文件。</li>



<li>Files. createDirectory()：创建文件夹。</li>



<li>Files. delete()：删除一个文件或目录。</li>



<li>Files. copy()：复制文件。</li>



<li>Files. move()：移动文件。</li>



<li>Files. size()：查看文件个数。</li>



<li>Files. read()：读取文件。</li>



<li>Files. write()：写入文件。</li>
</ul>



<h2 class="wp-block-heading" id="351s2"><strong>18. Java 容器都有哪些？</strong></h2>



<p>Java 容器分为 Collection 和 Map 两大类，其下又有很多子类，如下所示：</p>



<ul class="wp-block-list">
<li>Collection</li>



<li>List
<ul class="wp-block-list">
<li>ArrayList</li>



<li>LinkedList</li>



<li>Vector</li>



<li>Stack</li>
</ul>
</li>



<li>Set
<ul class="wp-block-list">
<li>HashSet</li>



<li>LinkedHashSet</li>



<li>TreeSet</li>
</ul>
</li>



<li>Map</li>



<li>HashMap
<ul class="wp-block-list">
<li>LinkedHashMap</li>
</ul>
</li>



<li>TreeMap</li>



<li>ConcurrentHashMap</li>



<li>Hashtable</li>
</ul>



<h2 class="wp-block-heading" id="1vklq"><strong>19. Collection 和 Collections 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>Collection 是一个集合接口，它提供了对集合对象进行基本操作的通用接口方法，所有集合都是它的子类，比如 List、Set 等。</li>



<li>Collections 是一个包装类，包含了很多静态方法，不能被实例化，就像一个工具类，比如提供的排序方法：Collections. sort(list)。</li>
</ul>



<h2 class="wp-block-heading" id="2o4dd"><strong>20. List、Set、Map 之间的区别是什么？</strong></h2>



<p>List、Set、Map 的区别主要体现在两个方面：元素是否有序、是否允许元素重复。</p>



<p>三者之间的区别，如下表：</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="326" src="https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-1024x326.png" alt="" class="wp-image-319" srcset="https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-1024x326.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-300x96.png 300w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-768x245.png 768w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-660x210.png 660w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-500x159.png 500w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-800x255.png 800w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2.png 1165w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading" id="uqlc"><strong>21. HashMap 和 Hashtable 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>存储：HashMap 运行 key 和 value 为 null，而 Hashtable 不允许。</li>



<li>线程安全：Hashtable 是线程安全的，而 HashMap 是非线程安全的。</li>



<li>推荐使用：在 Hashtable 的类注释可以看到，Hashtable 是保留类不建议使用，推荐在单线程环境下使用 HashMap 替代，如果需要多线程使用则用 ConcurrentHashMap 替代。</li>
</ul>



<h2 class="wp-block-heading" id="8fisa"><strong>22. 如何决定使用 HashMap 还是 TreeMap？</strong></h2>



<p>对于在 Map 中插入、删除、定位一个元素这类操作，HashMap 是最好的选择，因为相对而言 HashMap 的插入会更快，但如果你要对一个 key 集合进行有序的遍历，那 TreeMap 是更好的选择。</p>



<h2 class="wp-block-heading" id="dfivu"><strong>23. 说一下 HashMap 的实现原理？</strong></h2>



<p>HashMap 基于 Hash 算法实现的，我们通过 put(key,value)存储，get(key)来获取。当传入 key 时，HashMap 会根据 key. hashCode() 计算出 hash 值，根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时，我们称之为 hash 冲突，HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时，使用链表否则使用红黑树。</p>



<h2 class="wp-block-heading" id="k934"><strong>24. 说一下 HashSet 的实现原理？</strong></h2>



<p>HashSet 是基于 HashMap 实现的，HashSet 底层使用 HashMap 来保存所有元素，因此 HashSet 的实现比较简单，相关 HashSet 的操作，基本上都是直接调用底层 HashMap 的相关方法来完成，HashSet 不允许重复的值。</p>



<h2 class="wp-block-heading" id="5nfao"><strong>25. ArrayList 和 LinkedList 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>数据结构实现：ArrayList 是动态数组的数据结构实现，而 LinkedList 是双向链表的数据结构实现。</li>



<li>随机访问效率：ArrayList 比 LinkedList 在随机访问的时候效率要高，因为 LinkedList 是线性的数据存储方式，所以需要移动指针从前往后依次查找。</li>



<li>增加和删除效率：在非首尾的增加和删除操作，LinkedList 要比 ArrayList 效率要高，因为 ArrayList 增删操作要影响数组内的其他数据的下标。</li>
</ul>



<p>综合来说，在需要频繁读取集合中的元素时，更推荐使用 ArrayList，而在插入和删除操作较多时，更推荐使用 LinkedList。</p>



<h2 class="wp-block-heading" id="ab8cu"><strong>26. 如何实现数组和 List 之间的转换？</strong></h2>



<ul class="wp-block-list">
<li>数组转 List：使用 Arrays. asList(array) 进行转换。</li>



<li>List 转数组：使用 List 自带的 toArray() 方法。</li>
</ul>



<p>代码示例：</p>



<pre class="wp-block-code"><code>// list to array
List&lt;String&gt; list = new ArrayList&lt;String&gt;();
list. add("王磊");
list. add("的博客");
list. toArray();
// array to list
String&#91;] array = new String&#91;]{"王磊","的博客"};
Arrays. asList(array);</code></pre>



<h2 class="wp-block-heading" id="gtki"><strong>27. ArrayList 和 Vector 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>线程安全：Vector 使用了 Synchronized 来实现线程同步，是线程安全的，而 ArrayList 是非线程安全的。</li>



<li>性能：ArrayList 在性能方面要优于 Vector。</li>



<li>扩容：ArrayList 和 Vector 都会根据实际的需要动态的调整容量，只不过在 Vector 扩容每次会增加 1 倍，而 ArrayList 只会增加 50%。</li>
</ul>



<h2 class="wp-block-heading" id="2e32l"><strong>28. Array 和 ArrayList 有何区别？</strong></h2>



<ul class="wp-block-list">
<li>Array 可以存储基本数据类型和对象，ArrayList 只能存储对象。</li>



<li>Array 是指定固定大小的，而 ArrayList 大小是自动扩展的。</li>



<li>Array 内置方法没有 ArrayList 多，比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。</li>
</ul>



<h2 class="wp-block-heading" id="43im8"><strong>29. 在 Queue 中 poll()和 remove()有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>相同点：都是返回第一个元素，并在队列中删除返回的对象。</li>



<li>不同点：如果没有元素 poll()会返回 null，而 remove()会直接抛出 NoSuchElementException 异常。</li>
</ul>



<p>代码示例：</p>



<pre class="wp-block-code"><code>Queue&lt;String&gt; queue = new LinkedList&lt;String&gt;();
queue. offer("string"); // add
System. out. println(queue. poll());
System. out. println(queue. remove());
System. out. println(queue. size());</code></pre>



<h2 class="wp-block-heading" id="9c3t8"><strong>30. 哪些集合类是线程安全的？</strong></h2>



<p>Vector、Hashtable、Stack 都是线程安全的，而像 HashMap 则是非线程安全的，不过在 JDK 1.5 之后随着 Java. util. concurrent 并发包的出现，它们也有了自己对应的线程安全类，比如 HashMap 对应的线程安全类就是 ConcurrentHashMap。</p>



<h2 class="wp-block-heading" id="c0k5a"><strong>31. 迭代器 Iterator 是什么？</strong></h2>



<p>Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration，迭代器允许调用者在迭代过程中移除元素。</p>



<h2 class="wp-block-heading" id="efniq"><strong>32. Iterator 怎么使用？有什么特点？</strong></h2>



<p>Iterator 使用代码如下：</p>



<pre class="wp-block-code"><code>List&lt;String&gt; list = new ArrayList&lt;&gt;();
Iterator&lt;String&gt; it = list. iterator();
while(it. hasNext()){
  String obj = it. next();
  System. out. println(obj);
}</code></pre>



<p>Iterator 的特点是更加安全，因为它可以确保，在当前遍历的集合元素被更改的时候，就会抛出 ConcurrentModificationException 异常。</p>



<h2 class="wp-block-heading" id="91gaf"><strong>33. Iterator 和 ListIterator 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>Iterator 可以遍历 Set 和 List 集合，而 ListIterator 只能遍历 List。</li>



<li>Iterator 只能单向遍历，而 ListIterator 可以双向遍历（向前/后遍历）。</li>



<li>ListIterator 从 Iterator 接口继承，然后添加了一些额外的功能，比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。</li>
</ul>



<h2 class="wp-block-heading" id="83j4p"><strong>34. 怎么确保一个集合不能被修改？</strong></h2>



<p>可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合，这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。</p>



<p>示例代码如下：</p>



<pre class="wp-block-code"><code>List&lt;String&gt; list = new ArrayList&lt;&gt;();
list. add("x");
Collection&lt;String&gt; clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());</code></pre>



<h1 class="wp-block-heading" id="eobkt"><strong>多线程</strong></h1>



<h2 class="wp-block-heading" id="edb48"><strong>35. 并行和并发有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>并行：多个处理器或多核处理器同时处理多个任务。</li>



<li>并发：多个任务在同一个 CPU 核上，按细分的时间片轮流(交替)执行，从逻辑上来看那些任务是同时执行。</li>
</ul>



<p>如下图：</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="877" height="662" src="https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3.png" alt="" class="wp-image-320" srcset="https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3.png 877w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-300x226.png 300w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-768x580.png 768w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-660x498.png 660w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-500x377.png 500w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-800x604.png 800w" sizes="auto, (max-width: 877px) 100vw, 877px" /></figure>



<p>并发 = 两个队列和一台咖啡机。</p>



<p>并行 = 两个队列和两台咖啡机。</p>



<h2 class="wp-block-heading" id="9g1bh"><strong>36. 线程和进程的区别？</strong></h2>



<p>一个程序下至少有一个进程，一个进程下至少有一个线程，一个进程下也可以有多个线程来增加程序的执行速度。</p>



<h2 class="wp-block-heading" id="ejjmq"><strong>37. 守护线程是什么？</strong></h2>



<p>守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。</p>



<h2 class="wp-block-heading" id="29b3k"><strong>38. 创建线程有哪几种方式？</strong></h2>



<p>创建线程有三种方式：</p>



<ul class="wp-block-list">
<li>继承 Thread 重写 run 方法；</li>



<li>实现 Runnable 接口；</li>



<li>实现 Callable 接口。</li>
</ul>



<h2 class="wp-block-heading" id="6di6b"><strong>39. 说一下 runnable 和 callable 有什么区别？</strong></h2>



<p>runnable 没有返回值，callable 可以拿到有返回值，callable 可以看作是 runnable 的补充。</p>



<h2 class="wp-block-heading" id="4l8mo"><strong>40. 线程有哪些状态？</strong></h2>



<p>线程的状态：</p>



<ul class="wp-block-list">
<li>NEW 尚未启动</li>



<li>RUNNABLE 正在执行中</li>



<li>BLOCKED 阻塞的（被同步锁或者IO锁阻塞）</li>



<li>WAITING 永久等待状态</li>



<li>TIMED_WAITING 等待指定的时间重新被唤醒的状态</li>



<li>TERMINATED 执行完成</li>
</ul>



<h2 class="wp-block-heading" id="2h6cb"><strong>41. sleep() 和 wait() 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>类的不同：sleep() 来自 Thread，wait() 来自 Object。</li>



<li>释放锁：sleep() 不释放锁；wait() 释放锁。</li>



<li>用法不同：sleep() 时间到会自动恢复；wait() 可以使用 notify()/notifyAll()直接唤醒。</li>
</ul>



<h2 class="wp-block-heading" id="1gbva"><strong>42. notify()和 notifyAll()有什么区别？</strong></h2>



<p>notifyAll()会唤醒所有的线程，notify()之后唤醒一个线程。notifyAll() 调用后，会将全部线程由等待池移到锁池，然后参与锁的竞争，竞争成功则继续执行，如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程，具体唤醒哪一个线程由虚拟机控制。</p>



<h2 class="wp-block-heading" id="2tfuc"><strong>43. 线程的 run() 和 start() 有什么区别？</strong></h2>



<p>start() 方法用于启动线程，run() 方法用于执行线程的运行时代码。run() 可以重复调用，而 start() 只能调用一次。</p>



<h2 class="wp-block-heading" id="3kgbu"><strong>44. 创建线程池有哪几种方式？</strong></h2>



<p>线程池创建有七种方式，最核心的是最后一种：</p>



<ul class="wp-block-list">
<li>newSingleThreadExecutor()：它的特点在于工作线程数目被限制为 1，操作一个无界的工作队列，所以它保证了所有任务的都是被顺序执行，最多会有一个任务处于活动状态，并且不允许使用者改动线程池实例，因此可以避免其改变线程数目；</li>



<li>newCachedThreadPool()：它是一种用来处理大量短时间工作任务的线程池，具有几个鲜明特点：它会试图缓存线程并重用，当无缓存线程可用时，就会创建新的工作线程；如果线程闲置的时间超过 60 秒，则被终止并移出缓存；长时间闲置时，这种线程池，不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列；</li>



<li>newFixedThreadPool(int nThreads)：重用指定数目（nThreads）的线程，其背后使用的是无界的工作队列，任何时候最多有 nThreads 个工作线程是活动的。这意味着，如果任务数量超过了活动队列数目，将在工作队列中等待空闲线程出现；如果有工作线程退出，将会有新的工作线程被创建，以补足指定的数目 nThreads；</li>



<li>newSingleThreadScheduledExecutor()：创建单线程池，返回 ScheduledExecutorService，可以进行定时或周期性的工作调度；</li>



<li>newScheduledThreadPool(int corePoolSize)：和newSingleThreadScheduledExecutor()类似，创建的是个 ScheduledExecutorService，可以进行定时或周期性的工作调度，区别在于单一工作线程还是多个工作线程；</li>



<li>newWorkStealingPool(int parallelism)：这是一个经常被人忽略的线程池，Java 8 才加入这个创建方法，其内部会构建ForkJoinPool，利用Work-Stealing算法，并行地处理任务，不保证处理顺序；</li>



<li>ThreadPoolExecutor()：是最原始的线程池创建，上面1-3创建方式都是对ThreadPoolExecutor的封装。</li>
</ul>



<h2 class="wp-block-heading" id="1fdgd"><strong>45. 线程池都有哪些状态？</strong></h2>



<ul class="wp-block-list">
<li>RUNNING：这是最正常的状态，接受新的任务，处理等待队列中的任务。</li>



<li>SHUTDOWN：不接受新的任务提交，但是会继续处理等待队列中的任务。</li>



<li>STOP：不接受新的任务提交，不再处理等待队列中的任务，中断正在执行任务的线程。</li>



<li>TIDYING：所有的任务都销毁了，workCount 为 0，线程池的状态在转换为 TIDYING 状态时，会执行钩子方法 terminated()。</li>



<li>TERMINATED：terminated()方法结束后，线程池的状态就会变成这个。</li>
</ul>



<h2 class="wp-block-heading" id="c53r5"><strong>46. 线程池中 submit() 和 execute() 方法有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>execute()：只能执行 Runnable 类型的任务。</li>



<li>submit()：可以执行 Runnable 和 Callable 类型的任务。</li>
</ul>



<p>Callable 类型的任务可以获取执行的返回值，而 Runnable 执行无返回值。</p>



<h2 class="wp-block-heading" id="6apde"><strong>47. 在 Java 程序中怎么保证多线程的运行安全？</strong></h2>



<ul class="wp-block-list">
<li>方法一：使用安全类，比如 Java. util. concurrent 下的类。</li>



<li>方法二：使用自动锁 synchronized。</li>



<li>方法三：使用手动锁 Lock。</li>
</ul>



<p>手动锁 Java 示例代码如下：</p>



<pre class="wp-block-code"><code>Lock lock = new ReentrantLock();
lock. lock();
try {
    System. out. println("获得锁");
} catch (Exception e) {
    // TODO: handle exception
} finally {
    System. out. println("释放锁");
    lock. unlock();
}</code></pre>



<h2 class="wp-block-heading" id="avaeq"><strong>48. 多线程中 synchronized 锁升级的原理是什么？</strong></h2>



<p>synchronized 锁升级原理：在锁对象的对象头里面有一个 threadid 字段，在第一次访问的时候 threadid 为空，jvm 让其持有偏向锁，并将 threadid 设置为其线程 id，再次进入的时候会先判断 threadid 是否与其线程 id 一致，如果一致则可以直接使用此对象，如果不一致，则升级偏向锁为轻量级锁，通过自旋循环一定次数来获取锁，执行一定次数之后，如果还没有正常获取到要使用的对象，此时就会把锁从轻量级升级为重量级锁，此过程就构成了 synchronized 锁的升级。</p>



<p>锁的升级的目的：锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式，使用了偏向锁升级为轻量级锁再升级到重量级锁的方式，从而减低了锁带来的性能消耗。</p>



<h2 class="wp-block-heading" id="d60mn"><strong>49. 什么是死锁？</strong></h2>



<p>当线程 A 持有独占锁a，并尝试去获取独占锁 b 的同时，线程 B 持有独占锁 b，并尝试获取独占锁 a 的情况下，就会发生 AB 两个线程由于互相持有对方需要的锁，而发生的阻塞现象，我们称为死锁。</p>



<h2 class="wp-block-heading" id="bfapg"><strong>50. 怎么防止死锁？</strong></h2>



<ul class="wp-block-list">
<li>尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock)，设置超时时间，超时可以退出防止死锁。</li>



<li>尽量使用 Java. util. concurrent 并发类代替自己手写锁。</li>



<li>尽量降低锁的使用粒度，尽量不要几个功能用同一把锁。</li>



<li>尽量减少同步的代码块。</li>
</ul>



<h2 class="wp-block-heading" id="8ms48"><strong>51. ThreadLocal 是什么？有哪些使用场景？</strong></h2>



<p>ThreadLocal 为每个使用该变量的线程提供独立的变量副本，所以每一个线程都可以独立地改变自己的副本，而不会影响其它线程所对应的副本。</p>



<p>ThreadLocal 的经典使用场景是数据库连接和 session 管理等。</p>



<h2 class="wp-block-heading" id="2pog2"><strong>52. 说一下 synchronized 底层实现原理？</strong></h2>



<p>synchronized 是由一对 monitorenter/monitorexit 指令实现的，monitor 对象是同步的基本实现单元。在 Java 6 之前，monitor 的实现完全是依靠操作系统内部的互斥锁，因为需要进行用户态到内核态的切换，所以同步操作是一个无差别的重量级操作，性能也很低。但在 Java 6 的时候，Java 虚拟机 对此进行了大刀阔斧地改进，提供了三种不同的 monitor 实现，也就是常说的三种不同的锁：偏向锁（Biased Locking）、轻量级锁和重量级锁，大大改进了其性能。</p>



<h2 class="wp-block-heading" id="7vgs8"><strong>53. synchronized 和 volatile 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>volatile 是变量修饰符；synchronized 是修饰类、方法、代码段。</li>



<li>volatile 仅能实现变量的修改可见性，不能保证原子性；而 synchronized 则可以保证变量的修改可见性和原子性。</li>



<li>volatile 不会造成线程的阻塞；synchronized 可能会造成线程的阻塞。</li>
</ul>



<h2 class="wp-block-heading" id="a7324"><strong>54. synchronized 和 Lock 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>synchronized 可以给类、方法、代码块加锁；而 lock 只能给代码块加锁。</li>



<li>synchronized 不需要手动获取锁和释放锁，使用简单，发生异常会自动释放锁，不会造成死锁；而 lock 需要自己加锁和释放锁，如果使用不当没有 unLock()去释放锁就会造成死锁。</li>



<li>通过 Lock 可以知道有没有成功获取锁，而 synchronized 却无法办到。</li>
</ul>



<h2 class="wp-block-heading" id="b7j3u"><strong>55. synchronized 和 ReentrantLock 区别是什么？</strong></h2>



<p>synchronized 早期的实现比较低效，对比 ReentrantLock，大多数场景性能都相差较大，但是在 Java 6 中对 synchronized 进行了非常多的改进。</p>



<p>主要区别如下：</p>



<ul class="wp-block-list">
<li>ReentrantLock 使用起来比较灵活，但是必须有释放锁的配合动作；</li>



<li>ReentrantLock 必须手动获取与释放锁，而 synchronized 不需要手动释放和开启锁；</li>



<li>ReentrantLock 只适用于代码块锁，而 synchronized 可用于修饰方法、代码块等。</li>
</ul>



<h2 class="wp-block-heading" id="4shpd"><strong>56. 说一下 atomic 的原理？</strong></h2>



<p>atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法来保证原子操作，从而避免 synchronized 的高开销，执行效率大为提升。</p>



<h1 class="wp-block-heading" id="fsmil"><strong>反射</strong></h1>



<h2 class="wp-block-heading" id="4cvsb"><strong>57. 什么是反射？</strong></h2>



<p>反射是在运行状态中，对于任意一个类，都能够知道这个类的所有属性和方法；对于任意一个对象，都能够调用它的任意一个方法和属性；这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。</p>



<h2 class="wp-block-heading" id="8obi5"><strong>58. 什么是 Java 序列化？什么情况下需要序列化？</strong></h2>



<p>Java 序列化是为了保存各种对象在内存中的状态，并且可以把保存的对象状态再读出来。</p>



<p>以下情况需要使用 Java 序列化：</p>



<ul class="wp-block-list">
<li>想把的内存中的对象状态保存到一个文件中或者数据库中时候；</li>



<li>想用套接字在网络上传送对象的时候；</li>



<li>想通过RMI（远程方法调用）传输对象的时候。</li>
</ul>



<h2 class="wp-block-heading" id="f64rm"><strong>59. 动态代理是什么？有哪些应用？</strong></h2>



<p>动态代理是运行时动态生成代理类。</p>



<p>动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpc，Java注解对象获取等。</p>



<h2 class="wp-block-heading" id="5ch7r"><strong>60. 怎么实现动态代理？</strong></h2>



<p>JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的，而 cglib 是基于继承当前类的子类实现的。</p>



<h1 class="wp-block-heading" id="7fg0d"><strong>对象拷贝</strong></h1>



<h2 class="wp-block-heading" id="7j8u5"><strong>61. 为什么要使用克隆？</strong></h2>



<p>克隆的对象可能包含一些已经修改过的属性，而 new 出来的对象的属性都还是初始化时候的值，所以当需要一个新的对象来保存当前对象的“状态”就靠克隆方法了。</p>



<h2 class="wp-block-heading" id="8r4ca"><strong>62. 如何实现对象克隆？</strong></h2>



<ul class="wp-block-list">
<li>实现 Cloneable 接口并重写 Object 类中的 clone() 方法。</li>



<li>实现 Serializable 接口，通过对象的序列化和反序列化实现克隆，可以实现真正的深度克隆。</li>
</ul>



<h2 class="wp-block-heading" id="ito0"><strong>63. 深拷贝和浅拷贝区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>浅克隆：当对象被复制时只复制它本身和其中包含的值类型的成员变量，而引用类型的成员对象并没有复制。</li>



<li>深克隆：除了对象本身被复制外，对象所包含的所有成员变量也将复制。</li>
</ul>



<h2 class="wp-block-heading" id="8vl6v"><strong>Java Web</strong></h2>



<h2 class="wp-block-heading" id="brsa7"><strong>64. JSP 和 servlet 有什么区别？</strong></h2>



<p>JSP 是 servlet 技术的扩展，本质上就是 servlet 的简易方式。servlet 和 JSP 最主要的不同点在于，servlet 的应用逻辑是在 Java 文件中，并且完全从表示层中的 html 里分离开来，而 JSP 的情况是 Java 和 html 可以组合成一个扩展名为 JSP 的文件。JSP 侧重于视图，servlet 主要用于控制逻辑。</p>



<h2 class="wp-block-heading" id="5u3id"><strong>65. JSP 有哪些内置对象？作用分别是什么？</strong></h2>



<p>JSP 有 9 大内置对象：</p>



<ul class="wp-block-list">
<li>request：封装客户端的请求，其中包含来自 get 或 post 请求的参数；</li>



<li>response：封装服务器对客户端的响应；</li>



<li>pageContext：通过该对象可以获取其他对象；</li>



<li>session：封装用户会话的对象；</li>



<li>application：封装服务器运行环境的对象；</li>



<li>out：输出服务器响应的输出流对象；</li>



<li>config：Web 应用的配置对象；</li>



<li>page：JSP 页面本身（相当于 Java 程序中的 this）；</li>



<li>exception：封装页面抛出异常的对象。</li>
</ul>



<h2 class="wp-block-heading" id="7r5ae"><strong>66. 说一下 JSP 的 4 种作用域？</strong></h2>



<ul class="wp-block-list">
<li>page：代表与一个页面相关的对象和属性。</li>



<li>request：代表与客户端发出的一个请求相关的对象和属性。一个请求可能跨越多个页面，涉及多个 Web 组件；需要在页面显示的临时数据可以置于此作用域。</li>



<li>session：代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的 session 中。</li>



<li>application：代表与整个 Web 应用程序相关的对象和属性，它实质上是跨越整个 Web 应用程序，包括多个页面、请求和会话的一个全局作用域。</li>
</ul>



<h2 class="wp-block-heading" id="1j8re"><strong>67. session 和 cookie 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>存储位置不同：session 存储在服务器端；cookie 存储在浏览器端。</li>



<li>安全性不同：cookie 安全性一般，在浏览器存储，可以被伪造和修改。</li>



<li>容量和个数限制：cookie 有容量限制，每个站点下的 cookie 也有个数限制。</li>



<li>存储的多样性：session 可以存储在 Redis 中、数据库中、应用程序中；而 cookie 只能存储在浏览器中。</li>
</ul>



<h2 class="wp-block-heading" id="e1dkt"><strong>68. 说一下 session 的工作原理？</strong></h2>



<p>session 的工作原理是客户端登录完成之后，服务器会创建对应的 session，session 创建完之后，会把 session 的 id 发送给客户端，客户端再存储到浏览器中。这样客户端每次访问服务器时，都会带着 sessionid，服务器拿到 sessionid 之后，在内存找到与之对应的 session 这样就可以正常工作了。</p>



<h2 class="wp-block-heading" id="ckf7j"><strong>69. 如果客户端禁止 cookie 能实现 session 还能用吗？</strong></h2>



<p>可以用，session 只是依赖 cookie 存储 sessionid，如果 cookie 被禁用了，可以使用 url 中添加 sessionid 的方式保证 session 能正常使用。</p>



<h2 class="wp-block-heading" id="7vace"><strong>70. spring mvc 和 struts 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>拦截级别：struts2 是类级别的拦截；spring mvc 是方法级别的拦截。</li>



<li>数据独立性：spring mvc 的方法之间基本上独立的，独享 request 和 response 数据，请求数据通过参数获取，处理结果通过 ModelMap 交回给框架，方法之间不共享变量；而 struts2 虽然方法之间也是独立的，但其所有 action 变量是共享的，这不会影响程序运行，却给我们编码和读程序时带来了一定的麻烦。</li>



<li>拦截机制：struts2 有以自己的 interceptor 机制，spring mvc 用的是独立的 aop 方式，这样导致struts2 的配置文件量比 spring mvc 大。</li>



<li>对 ajax 的支持：spring mvc 集成了ajax，所有 ajax 使用很方便，只需要一个注解 @ResponseBody 就可以实现了；而 struts2 一般需要安装插件或者自己写代码才行。</li>
</ul>



<h2 class="wp-block-heading" id="1hnfi"><strong>71. 如何避免 SQL 注入？</strong></h2>



<ul class="wp-block-list">
<li>使用预处理 PreparedStatement。</li>



<li>使用正则表达式过滤掉字符中的特殊字符。</li>
</ul>



<h2 class="wp-block-heading" id="8ldgu"><strong>72. 什么是 XSS 攻击，如何避免？</strong></h2>



<p>XSS 攻击：即跨站脚本攻击，它是 Web 程序中常见的漏洞。原理是攻击者往 Web 页面里插入恶意的脚本代码（css 代码、Javascript 代码等），当用户浏览该页面时，嵌入其中的脚本代码会被执行，从而达到恶意攻击用户的目的，如盗取用户 cookie、破坏页面结构、重定向到其他网站等。</p>



<p>预防 XSS 的核心是必须对输入的数据做过滤处理。</p>



<h2 class="wp-block-heading" id="espi7"><strong>73. 什么是 CSRF 攻击，如何避免？</strong></h2>



<p>CSRF：Cross-Site Request Forgery（中文：跨站请求伪造），可以理解为攻击者盗用了你的身份，以你的名义发送恶意请求，比如：以你名义发送邮件、发消息、购买商品，虚拟货币转账等。</p>



<p>防御手段：</p>



<ul class="wp-block-list">
<li>验证请求来源地址；</li>



<li>关键操作添加验证码；</li>



<li>在请求地址添加 token 并验证。</li>
</ul>



<h2 class="wp-block-heading" id="ammj6"><strong>异常</strong></h2>



<h2 class="wp-block-heading" id="6do6r"><strong>74. throw 和 throws 的区别？</strong></h2>



<ul class="wp-block-list">
<li>throw：是真实抛出一个异常。</li>



<li>throws：是声明可能会抛出一个异常。</li>
</ul>



<h2 class="wp-block-heading" id="1c39e"><strong>75. final、finally、finalize 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>final：是修饰符，如果修饰类，此类不能被继承；如果修饰方法和变量，则表示此方法和此变量不能在被改变，只能使用。</li>



<li>finally：是 try{} catch{} finally{} 最后一部分，表示不论发生任何情况都会执行，finally 部分可以省略，但如果 finally 部分存在，则一定会执行 finally 里面的代码。</li>



<li>finalize：是 Object 类的一个方法，在垃圾收集器执行的时候会调用被回收对象的此方法。</li>
</ul>



<h2 class="wp-block-heading" id="rq9f"><strong>76. try-catch-finally 中哪个部分可以省略？</strong></h2>



<p>try-catch-finally 其中 catch 和 finally 都可以被省略，但是不能同时省略，也就是说有 try 的时候，必须后面跟一个 catch 或者 finally。</p>



<h2 class="wp-block-heading" id="c2a5k"><strong>77. try-catch-finally 中，如果 catch 中 return 了，finally 还会执行吗？</strong></h2>



<p>finally 一定会执行，即使是 catch 中 return 了，catch 中的 return 会等 finally 中的代码执行完之后，才会执行。</p>



<h2 class="wp-block-heading" id="eup5i"><strong>78. 常见的异常类有哪些？</strong></h2>



<ul class="wp-block-list">
<li>NullPointerException 空指针异常</li>



<li>ClassNotFoundException 指定类不存在</li>



<li>NumberFormatException 字符串转换为数字异常</li>



<li>IndexOutOfBoundsException 数组下标越界异常</li>



<li>ClassCastException 数据类型转换异常</li>



<li>FileNotFoundException 文件未找到异常</li>



<li>NoSuchMethodException 方法不存在异常</li>



<li>IOException IO 异常</li>



<li>SocketException Socket 异常</li>
</ul>



<h2 class="wp-block-heading" id="bm75k"><strong>网络</strong></h2>



<h2 class="wp-block-heading" id="b15ln"><strong>79. http 响应码 301 和 302 代表的是什么？有什么区别？</strong></h2>



<p>301：永久重定向。</p>



<p>302：暂时重定向。</p>



<p>它们的区别是，301 对搜索引擎优化（SEO）更加有利；302 有被提示为网络拦截的风险。</p>



<h2 class="wp-block-heading" id="jjrp"><strong>80. forward 和 redirect 的区别？</strong></h2>



<p>forward 是转发 和 redirect 是重定向：</p>



<ul class="wp-block-list">
<li>地址栏 url 显示：foward url 不会发生改变，redirect url 会发生改变；</li>



<li>数据共享：forward 可以共享 request 里的数据，redirect 不能共享；</li>



<li>效率：forward 比 redirect 效率高。</li>
</ul>



<h2 class="wp-block-heading" id="c6muv"><strong>81. 简述 tcp 和 udp的区别？</strong></h2>



<p>tcp 和 udp 是 OSI 模型中的运输层中的协议。tcp 提供可靠的通信传输，而 udp 则常被用于让广播和细节控制交给应用的通信传输。</p>



<p>两者的区别大致如下：</p>



<ul class="wp-block-list">
<li>tcp 面向连接，udp 面向非连接即发送数据前不需要建立链接；</li>



<li>tcp 提供可靠的服务（数据传输），udp 无法保证；</li>



<li>tcp 面向字节流，udp 面向报文；</li>



<li>tcp 数据传输慢，udp 数据传输快；</li>
</ul>



<h2 class="wp-block-heading" id="5a9jv"><strong>82. tcp 为什么要三次握手，两次不行吗？为什么？</strong></h2>



<p>如果采用两次握手，那么只要服务器发出确认数据包就会建立连接，但由于客户端此时并未响应服务器端的请求，那此时服务器端就会一直在等待客户端，这样服务器端就白白浪费了一定的资源。若采用三次握手，服务器端没有收到来自客户端的再此确认，则就会知道客户端并没有要求建立请求，就不会浪费服务器的资源。</p>



<h2 class="wp-block-heading" id="ea13g"><strong>83. 说一下 tcp 粘包是怎么产生的？</strong></h2>



<p>tcp 粘包可能发生在发送端或者接收端，分别来看两端各种产生粘包的原因：</p>



<ul class="wp-block-list">
<li>发送端粘包：发送端需要等缓冲区满才发送出去，造成粘包；</li>



<li>接收方粘包：接收方不及时接收缓冲区的包，造成多个包接收。</li>
</ul>



<h2 class="wp-block-heading" id="91q2n"><strong>84. OSI 的七层模型都有哪些？</strong></h2>



<ul class="wp-block-list">
<li>物理层：利用传输介质为数据链路层提供物理连接，实现比特流的透明传输。</li>



<li>数据链路层：负责建立和管理节点间的链路。</li>



<li>网络层：通过路由选择算法，为报文或分组通过通信子网选择最适当的路径。</li>



<li>传输层：向用户提供可靠的端到端的差错和流量控制，保证报文的正确传输。</li>



<li>会话层：向两个实体的表示层提供建立和使用连接的方法。</li>



<li>表示层：处理用户信息的表示问题，如编码、数据格式转换和加密解密等。</li>



<li>应用层：直接向用户提供服务，完成用户希望在网络上完成的各种工作。</li>
</ul>



<h2 class="wp-block-heading" id="a9s5m"><strong>85. get 和 post 请求有哪些区别？</strong></h2>



<ul class="wp-block-list">
<li>get 请求会被浏览器主动缓存，而 post 不会。</li>



<li>get 传递参数有大小限制，而 post 没有。</li>



<li>post 参数传输更安全，get 的参数会明文限制在 url 上，post 不会。</li>
</ul>



<h2 class="wp-block-heading" id="fmft6"><strong>86. 如何实现跨域？</strong></h2>



<p>实现跨域有以下几种方案：</p>



<ul class="wp-block-list">
<li>服务器端运行跨域 设置 CORS 等于 *；</li>



<li>在单个接口使用注解 @CrossOrigin 运行跨域；</li>



<li>使用 jsonp 跨域；</li>
</ul>



<h2 class="wp-block-heading" id="9mpdt"><strong>87. 说一下 JSONP 实现原理？</strong></h2>



<p>jsonp：JSON with Padding，它是利用script标签的 src 连接可以访问不同源的特性，加载远程返回的“JS 函数”来执行的。</p>



<h1 class="wp-block-heading" id="b43jm"><strong>设计模式</strong></h1>



<h2 class="wp-block-heading" id="8tpj"><strong>88. 说一下你熟悉的设计模式？</strong></h2>



<ul class="wp-block-list">
<li>单例模式：保证被创建一次，节省系统开销。</li>



<li>工厂模式（简单工厂、抽象工厂）：解耦代码。</li>



<li>观察者模式：定义了对象之间的一对多的依赖，这样一来，当一个对象改变时，它的所有的依赖者都会收到通知并自动更新。</li>



<li>外观模式：提供一个统一的接口，用来访问子系统中的一群接口，外观定义了一个高层的接口，让子系统更容易使用。</li>



<li>模版方法模式：定义了一个算法的骨架，而将一些步骤延迟到子类中，模版方法使得子类可以在不改变算法结构的情况下，重新定义算法的步骤。</li>



<li>状态模式：允许对象在内部状态改变时改变它的行为，对象看起来好像修改了它的类。</li>
</ul>



<h2 class="wp-block-heading" id="1d30b"><strong>89. 简单工厂和抽象工厂有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>简单工厂：用来生产同一等级结构中的任意产品，对于增加新的产品，无能为力。</li>



<li>工厂方法：用来生产同一等级结构中的固定产品，支持增加任意产品。</li>



<li>抽象工厂：用来生产不同产品族的全部产品，对于增加新的产品，无能为力；支持增加产品族。</li>
</ul>



<h2 class="wp-block-heading" id="ad4mj">内存泄漏和内存溢出</h2>



<p><strong>内存溢出：</strong>当 JVM 因为无法申请到足够的内存（可能已用完，也可能没有连续空间），并且垃圾收集器也无法回收更多内存时，就会抛出 <code>OutOfMemoryError</code></p>



<h3 class="wp-block-heading">常见的内存溢出场景</h3>



<ol start="1" class="wp-block-list">
<li><strong>Java 堆溢出</strong>
<ul class="wp-block-list">
<li>原因：对象过多（如无限创建对象）、对象过大、内存泄漏导致对象无法回收。</li>



<li>错误信息：<code>java.lang.OutOfMemoryError: Java heap space</code></li>



<li>排查：通过 <code>jmap</code>、<code>MAT</code> 等工具分析堆转储文件，找出占用内存最多的对象及其引用链。</li>
</ul>
</li>



<li><strong>栈溢出</strong>
<ul class="wp-block-list">
<li>原因：线程请求的栈深度超过 JVM 允许的最大深度（如递归调用过深）；或栈帧过大。</li>



<li>错误信息：<code>java.lang.StackOverflowError</code>（通常不叫 OutOfMemoryError，但本质也是内存问题）。</li>



<li>排查：检查递归逻辑，减少局部变量大小，或通过 <code>-Xss</code> 增加栈大小。</li>
</ul>
</li>



<li><strong>元空间（Metaspace）溢出</strong>
<ul class="wp-block-list">
<li>原因：加载的类太多（如动态生成大量代理类）、字符串常量池过大（Java 8 后常量池在堆中，但类元数据在元空间）。</li>



<li>错误信息：<code>java.lang.OutOfMemoryError: Metaspace</code></li>



<li>排查：调整 <code>-XX:MaxMetaspaceSize</code>，检查是否过度使用动态代理、CGLib 等。</li>
</ul>
</li>



<li><strong>直接内存溢出</strong>
<ul class="wp-block-list">
<li>原因：使用 NIO 的 <code>ByteBuffer.allocateDirect()</code> 分配了过多直接内存，且未释放。</li>



<li>错误信息：<code>java.lang.OutOfMemoryError: Direct buffer memory</code></li>



<li>排查：注意直接内存的回收依赖 <code>Cleaner</code> 和 GC，避免忘记释放。</li>
</ul>
</li>



<li><strong>GC 开销超过限制</strong>
<ul class="wp-block-list">
<li>原因：GC 花费了大量时间（如超过 98%）却只回收了极少内存，JVM 会抛出此异常。</li>



<li>错误信息：<code>java.lang.OutOfMemoryError: GC overhead limit exceeded</code></li>



<li>排查：这往往是内存泄漏或堆太小的前兆，需分析堆转储。</li>
</ul>
</li>
</ol>



<p><strong>内存泄漏：</strong>程序中已不再使用的对象，因为仍然被其他对象引用，导致垃圾收集器无法回收它们，这些对象无用地占据内存。随着时间推移，内存泄漏积累会耗尽可用内存，最终导致内存溢出。</p>



<h3 class="wp-block-heading">Java 中典型的内存泄漏场景</h3>



<ol start="1" class="wp-block-list">
<li><strong>静态集合类持有对象引用</strong>
<ul class="wp-block-list">
<li>例如：一个 <code>static List</code> 不断添加对象，但从未清理，这些对象生命周期与应用一致，无法回收。</li>



<li>解决：明确对象不再使用时从集合中移除，或使用 <code>WeakHashMap</code>。</li>
</ul>
</li>



<li><strong>未关闭的资源</strong>
<ul class="wp-block-list">
<li>数据库连接、文件流、网络连接等未正确关闭（<code>close()</code>），导致资源对象被 <code>finalizer</code> 或直接引用占用内存。</li>



<li>解决：使用 try-with-resources 自动关闭，或在 finally 中手动关闭。</li>
</ul>
</li>



<li><strong>内部类/匿名内部类持有外部类引用</strong>
<ul class="wp-block-list">
<li>非静态内部类会隐式持有外部类的引用，如果内部类对象生命周期较长，外部类对象就无法回收。</li>



<li>解决：将内部类改为静态内部类，或使用弱引用。</li>
</ul>
</li>



<li><strong>缓存设计不当</strong>
<ul class="wp-block-list">
<li>使用 <code>HashMap</code> 作为缓存，但未设置过期策略，缓存数据不断积累。</li>



<li>解决：使用 <code>WeakHashMap</code>、<code>LinkedHashMap</code> 的 <code>removeEldestEntry</code> 或第三方缓存（如 Caffeine、Guava Cache）。</li>
</ul>
</li>



<li><strong>监听器/回调注册后未注销</strong>
<ul class="wp-block-list">
<li>注册了事件监听器或回调，但在对象销毁时没有注销，导致监听器长期持有对象引用。</li>



<li>解决：在 <code>dispose()</code> 或类似方法中显式注销。</li>
</ul>
</li>



<li><strong>ThreadLocal 使用不当</strong>
<ul class="wp-block-list">
<li><code>ThreadLocal</code> 中的对象被当前线程持有，如果线程长期存活（如线程池中的线程），且未调用 <code>remove()</code>，则对象无法释放。</li>



<li>解决：每次用完 <code>ThreadLocal</code> 后调用 <code>remove()</code>，或设计为存放小对象。</li>
</ul>
</li>
</ol>



<h3 class="wp-block-heading">三、内存泄漏与内存溢出的关系</h3>



<ul class="wp-block-list">
<li><strong>内存泄漏是内存溢出的一个重要原因</strong>。泄漏导致可用内存逐渐减少，最终达到临界点，任何分配请求都无法满足时，就会发生内存溢出。</li>



<li>但内存溢出也可能由其他原因引起，例如一次性加载过多数据（即使没有泄漏，但内存瞬间被占满）、JVM 堆设置过小等。</li>



<li><strong>内存泄漏是“慢性”的</strong>，通常需要长时间运行才能显现；而内存溢出可能是“急性”的，例如创建超大数组立即触发。</li>
</ul>



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



<h3 class="wp-block-heading">四、如何检测与解决</h3>



<h4 class="wp-block-heading">检测内存泄漏</h4>



<ul class="wp-block-list">
<li><strong>观察</strong>：程序长时间运行后响应变慢，甚至抛出 OOM。</li>



<li><strong>监控工具</strong>：使用 <code>jstat</code>、<code>jvisualvm</code>、<code>JConsole</code> 观察 GC 行为和堆内存趋势。</li>



<li><strong>堆转储分析</strong>：当怀疑泄漏时，通过 <code>jmap -dump:format=b,file=heap.hprof &lt;pid></code> 生成堆转储文件，然后用 <strong>Eclipse MAT</strong> 或 <strong>VisualVM</strong> 分析，查找“可疑对象”或“支配树”中占内存最大的对象，并查看其 GC Roots 引用链。</li>



<li><strong>GC 日志</strong>：开启 <code>-XX:+PrintGCDetails</code>，观察 GC 后内存是否持续增长。</li>
</ul>



<h4 class="wp-block-heading">解决策略</h4>



<ul class="wp-block-list">
<li><strong>代码审查</strong>：重点检查静态集合、未关闭资源、内部类、缓存等常见泄漏点。</li>



<li><strong>使用弱引用</strong>：对于缓存、观察者等场景，考虑使用 <code>WeakReference</code> 或 <code>WeakHashMap</code>。</li>



<li><strong>资源管理</strong>：确保所有 <code>Closeable</code> 资源在使用后及时关闭。</li>



<li><strong>避免不必要的对象持有</strong>：例如，不要将大对象长期保存在 <code>ThreadLocal</code> 中。</li>



<li><strong>合理设置 JVM 参数</strong>：根据应用特点调整堆大小、新生代比例等，但根本还是要避免泄漏。</li>
</ul>



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



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



<ul class="wp-block-list">
<li><strong>内存溢出</strong>是结果——内存不够用了。</li>



<li><strong>内存泄漏</strong>是原因之一——内存被无用对象占据，无法释放。</li>



<li>在开发中，应养成良好的编码习惯，定期使用工具检测内存占用趋势，及时修复泄漏隐患。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2025/07/07/316/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">316</post-id>	</item>
	</channel>
</rss>
