<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[ZealSingerの博客啦~]]></title> 
<atom:link href="http://8.154.40.120:24180/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[]]></description>
<link>http://8.154.40.120:24180/</link>
<language>zh-cn</language>
<generator>www.emlog.net</generator>
<item>
    <title>Python深度学习入门-day03-DNN</title>
    <link>http://8.154.40.120:24180/?post=60</link>
    <description><![CDATA[<p><!-- obsidian --></p>
<h1 data-heading="前言">前言</h1>
<p>在学习PyTorch的时候，我们首先需要将其与NumPy相联系，还是之前NumPy中提到过的，PyTorch中很多概念直接由NumPy中迁移对比理解就行</p>
<ul>
<li><strong>np=&gt;torch</strong></li>
<li><strong>数组array=&gt;张量tensor</strong></li>
<li><strong>n维数组/数组的维度=&gt;n阶张量/张量的阶数</strong></li>
</ul>
<p>然后我们需要<code>了解GPU存储张量/数据</code>，如下，我们按照之前的方式创建一个阶张量并且打印</p>
<pre><code class="language-python">import torch  
ts1 = torch.randn(3,4)  
print(ts1)

输出
tensor([[ 1.3641, -0.3565, -1.1902,  0.4284],
        [ 0.6414,  0.0928,  0.7420,  0.0064],
        [ 1.3765, -0.8911, -0.5673, -1.7459]])

</code></pre>
<p>一切运行都没问题，但是默认上所有的输出都是在CPU上进行的，我们的深度学习要利用GPU，所以我们必须让GPU存储张量，这个时候我们需要用到<code>data.to("cuda:0")</code>，使用这个就可以让data存储到对应的0号GPU上(GPU有几号取决于你的设备，通过<code>nvidia-smi查看有几个，有多个的可以cuda:1</code>)</p>
<pre><code class="language-python">import torch  
ts1 = torch.randn(3,4).to("cuda")  
print(ts1)
</code></pre>
<p>同样的，我们在后续的学习中也需要把神经网络的模型也要搬到GPU上才可以正常运行，所以需要使用如下代码</p>
<pre><code class="language-python">class DNN(torch.nn.Module):
    ...略...

model = DNN().to("cuda:0")
</code></pre>
<p>所以这个<code>to("cuda:0")</code>很常用，大家需要记住和学会使用</p>
<h1 data-heading="DNN原理篇">DNN原理篇</h1>
<p>DNN就是深度神经网络，其核心就是：<code>通过学习大量样本的输入和输出特征之间的关系，以拟合出输入和输出之间的方程，学习完毕后，能达到你给输出特征，他给出输出特征</code>。神经网络可以分为如下几个步骤：<strong>划分数据集，训练网络，测试网络，使用网络</strong></p>
<h2 data-heading="划分数据集">划分数据集</h2>
<p>首先，数据集是一个包含多个样本的集合，每个样本都<code>必须要有输入和输出</code>，将数据集按照一定比例划<code>分为训练集和测试集，分别用于训练网络和测试网络</code>（简单理解：数据集=训练集+测试集）<br>如下数据集，1000个样本，三个输入，三个输出，按照8:2的比例划分<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130155834753.png" alt="image.png"></p>
<p><code>神经节点是根据数据集来的</code>，因为输入和输出都是三个，那么肯定输入和输出都会需要三个神经元，<strong>即输入神经元必须和每个样本的输入特征数量一致，输出层必须和每个样本的输出特征数量一致</strong>。至于<code>中间的隐藏层可以自行设计</code><br>如下图，是可以适用于上面数据集的神经网络结构，因为在编程语言中索引都是从0开始的，所以我们神经网络结构图也从输入层开始称之为第0层，依次往后就是第1层，第2层...<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130162107095.png" alt="image.png"></p>
<h2 data-heading="训练网络">训练网络</h2>
<p>神经网络的训练过程就是<strong>经过很多次前向传播和反向传播的轮回</strong>，最终不断调整其内部参数（<code>权重w和偏置b</code>）以拟合任意复杂函数的过程，<code>内部参数一开始是随机</code>的（例如Xavier初始值，He初始值），但是最终会被不断优化到最佳，<code>神经网络的最终目的就是为了优化内部参数，仅此一个目标而已</code></p>
<p>当然，还有一些训练网络在开始之前需要设置好一些外部参数，<code>例如网络的层数，每个隐藏层的节点数，每个节点的激活函数类型，学习率，轮回次数，每次轮回的样本数等等</code>，业界习惯把内部参数称之为参数，外部参数称之为超参数</p>
<h4 data-heading="前向传播">前向传播</h4>
<p>所谓前向传播就是：<strong>将输入特征送入到神经网络输出层后，神经网络逐层计算最终到输出层，简单理解就是从输入到输出的一个过程就称之为前向传播</strong></p>
<p><code>计算过程中所使用的参数就是内部参数，所有的隐藏层与输出层的神经元都有内部参数,输入层是没有参数的</code><br>假设现在我们要将输入进入到第一层的第一个神经元上进行计算，该神经元节点的计算过程为<code>y=w1*x1+w2*x2+w3*x3+b</code>。你可以理解为，<strong>每一根线就是一个权重 &omega;，每一个神经元节点也都有它自己的偏置 b。</strong><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130163242561.png" alt="image.png"></p>
<p>因为按照上面的过程得到每个神经元节点的函数都是线性的方程，<strong>所以必须在计算完成后套一个非线性的函数，也称之为激活函数</strong><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130163709716.png" alt="image.png"></p>
<p>为什么需要一个非线性的激活函数呢？这个很好理解，训练网络就是来回训练，你全部都是线性计算那么其实根本不需要来回训练，训练一次其实就拟合出来了。如下，一个比较简单的四层神经网络，反向来看，y=w5 <em> z5+w6 </em> z6 +d，然后依次把z5和z6逐层拆分，那么在最后肯定是得到一个关于X的线性方程即ax+b这样的形式，如果有多个输入，也无非就是多加了几个元，即y=a1x1+a2x2+b，所以<code>激活函数是必备的</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130164019817.png" alt="image.png"></p>
<h4 data-heading="反向传播">反向传播</h4>
<p>经过前向传播，网络会根据当前的内部参数计算出一个输出特征的预测值，但是这个预测值和真实值之间肯定会存在很大的差距。首先我们会需要一个<code>损失函数来计算这个差距</code>（例如求预测值和真实值之间的差的绝对值，这个就是一个典型的损失函数）</p>
<p>损失函数计算完之后，<code>逐层退回求梯度(这个过程涉及到了比较复杂的数学理论,目前先可以不需要掌握。这个过程的逻辑大概就是看看每个内部参数变大还是变小，能减少损失函数值，也就是调整内部参数以减少损失函数值)</code>，从而达到优化内部参数的目的</p>
<p>在这个过程中，<code>有一个外部参数称之为学习率，学习率越大，内部参数优化越快，但是不能过大，过大的学习率会导致损失函数越过最低点在谷底反复横跳，因此在训练开始前选择一个合适的学习率很重要</code>（可以这么理解，想象一个先下降后上升的函数，在这个过程中我们的损失函数是越来越低的，但是会存在一个最低点，学习率越大，下降的越快，但是可能下降的过多导致超过了这个最低点，最低点后又会变大了，变大后就得逆向调整，但是因为每次变化都比较大，就是达到不到那个最低点，从而导致反复横跳）</p>
<h4 data-heading="batch_size">batch_size</h4>
<p>前向传播和反向传播一次时，有如下三种情况</p>
<ul>
<li><code>批量梯度下降（BGD）</code>，把<code>所有的样本一次性输入进网络，这种方式计算开销很大，速度很慢</code></li>
<li><code>随机梯度下降（SGD）</code>，<code>每次只把一个样本输入进网络</code>，每计算一个样本就更新一次参数，这种方式<code>速度很快但是收敛性很差，可能会在最优点附近震荡，两次参数的更新也有可能抵消</code></li>
<li><code>小批量梯度下降(MBGD)</code>，为了中和上面两种方式而诞生的，<code>把样本规划为若干批，按照批次来更新参数</code><br>所谓的<strong>batch_size也就是一批次中的样本数，也就是一次性喂进网络的样本数</strong>，此外由于 Batch Normalization 层（Batch正规化层，用于将每次产生的小批量样本进行标准化）的存在，<strong>batch_size 一般设置为 2 的幂次方，并且不能为 1。</strong>
<blockquote>
<p>注意，PyTorch实现时只支持批量和小批量梯度下降，即第一个和第三个情况，不支持单个样本的输入方式。PyTorch中的torch.optim.SGD只表示梯度下降</p>
</blockquote>
</li>
</ul>
<h4 data-heading="epochs">epochs</h4>
<p><strong>一个epochs就是指全部数据完成了一次前向传播和后向传播</strong><br>为了帮助理解，可以看一下如下的说明<br>假设样本一共有10240个，batch_size=1024，epochs=5，这个情况说明</p>
<ol>
<li>1个epochs需要10次（10240/1024）前向传播和后向传播</li>
<li>全部样本都将进行5次前向传播和后向传播</li>
<li>整个过程经过了（10* 5）次前向传播和后向传播</li>
</ol>
<h2 data-heading="测试网络">测试网络</h2>
<p>测试网络的<code>目的主要是为了防止训练过拟合</code>，所谓的过拟合就是，训练过头，网络优化后的内部参数只针对于训练样本有效，对于别的样本无效。（可以理解为原本要训练成一个普遍适用的函数，结果训练过头成为了训练样本的特例函数）<strong>所以网络训练好后，需要拿测试集作为输入，进行一次前向传播后，将结果与测试集的真实输出进行对比查看准确率</strong><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130171518632.png" alt="image.png"></p>
<h2 data-heading="使用网络">使用网络</h2>
<p>真正使用网络进行预测时，样本只知输入，不知输出。直接将样本的输入进行 1 次前向传播，即可得到预测的输出。</p>
<h1 data-heading="DNN的实现">DNN的实现</h1>
<p>在Python的第三方库中，<code>torch.nn</code>提供了搭建网络所需要的所有组件，所以我们可以映入库且为了方便别名nn</p>
<pre><code class="language-python">import torch.nn as nn
</code></pre>
<p>按照之前原理篇内容，我们知道DNN的四个步骤，其中第一个步骤就是划分数据集</p>
<p>那么我们首先来<code>准备数据集</code>，在这里我们生成10000个样本，每个样本设定三个输入特征(X1,X2,X3)和三个输出特征(Y1,Y2,Y3)，我们规定</p>
<ul>
<li>每个输入特征相互独立，均服从均匀分布</li>
<li>当（X1+X2+X3）&lt;1的时候，Y1为1，否则Y1=0</li>
<li>当1&lt;（X1+X2+X3）&lt;2的时候，Y2为1，否则Y2=0</li>
<li>当（X1+X2+X3）&gt;2的时候，Y3为1，否则Y3=0<br>那么对应的Python代码如下</li>
</ul>
<pre><code class="language-python">import torch.nn as nn  
import torch  

# 生成三个输入特征，rand生成1000行1列的范围在[0,1]的张量  
X1 = torch.rand(1000,1)  
X2 = torch.rand(1000,1)  
X3 = torch.rand(1000,1)  

# 生成输出特征, .float()将布尔张量转化为浮点型张量  
Y1 = ( (X1+X2+X3)&lt;1 ).float() # 输出特征 1
Y2 = ( (1&lt;(X1+X2+X3)) &amp; ((X1+X2+X3)&lt;2) ).float() # 输出特征 2
Y3 = ( (X1+X2+X3)&gt;2 ).float() # 输出特征 3  
data = torch.cat([X1,X2,X3,Y1,Y2,Y3],axis=1) # 整合数据集  
data = data.to('cuda:0')  # 迁入到GPU
print(data.shape) # torch.Size([1000, 6])
</code></pre>
<hr>
<p>数据集准备完毕后，我们需要<code>划分数据集为测试集和样本集</code></p>
<pre><code class="language-python"># 划分数据集大小 我们采用7:3  
train_size = int(len(data)*0.7)  
test_size = len(data)-train_size  
# 打乱数据  data.size(0)获取第0维度即第一列的长度 
# arr.randperm(n)打乱arr上0~n-1索引内的范围
data = data[torch.randperm(data.size(0)),:]  
train_data = data[0:train_size,:] # 训练集  
test_data = data[train_size:len(data)] # 测试集  
print(train_data.shape, test_data.shape,sep="##")  # 打印查看是否正确划分
</code></pre>
<p>这部分基本属于通用代码，以后如果需要都可以直接拿着用</p>
<hr>
<p>数据都准备好了，接下就是需要构建网络,一般在搭建自己的神经网络的时候，可以<code>以nn.Module</code>为父类，该对象中包含了网络各个层的定义</p>
<p>在定义的网络子类中，通常包含<code>__init__构造方法</code>和<code>forward方法</code>，其中<code>init</code>方法用于构造自己的神经网络结构，<code>forward方法</code>用于将输入数据进行前向传播。由于张量可以自动计算梯度，所以不需要出现反向传播方法。</p>
<pre><code class="language-python">class MyDNN(nn.Module):  
    def __init__(self):  
        ''' 搭建神经网络各个层 '''        
        super(MyDNN,self).__init__() 
        #Sequential() 可以理解为顺序队列，在里面按顺序定义各个隐藏层，不要把输入层定义在里面 
        self.net = nn.Sequential( 
        # 隐藏层也称之为线形层所以我们这方法也就是.Linear(preNodeNumber,selfNodeNumber)  
        # 两个参数是 上一层节点数和本层节点数  
        # 因为这是第一层隐藏层，所以上一层就是输入层，我们是三个输入特征，所以第一个参数为3  
            nn.Linear(3,5),  
            # 激活函数 激活函数一般有ReLU tanh sigmoid,一般选ReLU  
            nn.ReLU(),  
            # 再来一个隐藏层 只不过这个隐藏层的输入是上一层的输出，所以第一个参数为5  
            nn.Linear(5,5),  
            nn.ReLU(),  
            nn.Linear(5,5),  
            nn.ReLU(),  
            nn.Linear(5,3)  
        )  

    def forward(self,x):  
        '''  前向传播 '''        # net就是上面定义的顺序队列，上面叫啥这里就调用啥  
        y = self.net(x)  
        return y # y即输出数据

# 创建MyDNN实例对象且放入GPU，打印查看一下
dnn = MyDNN().to('cuda:0')
print(dnn)

输出
MyDNN(
  (net): Sequential(
    (0): Linear(in_features=3, out_features=5, bias=True)
    (1): ReLU()
    (2): Linear(in_features=5, out_features=5, bias=True)
    (3): ReLU()
    (4): Linear(in_features=5, out_features=5, bias=True)
    (5): ReLU()
    (6): Linear(in_features=5, out_features=3, bias=True)
  )
)
</code></pre>
<hr>
<p>我们构建了网络，但是有没有发现好像并没有设计到内部参数即权重和偏置，其内部参数其实在神经网络训练前会被赋予随机数，且随着训练会逐渐迭代至最佳值<br>我们可以通过<code>DNN对象.named_parameters()+for循环</code>进行查看</p>
<pre><code class="language-python">for name,param in dnn.named_paramaeters():
    print(f"参数:{name}\n 形状:{param.shape}\n 数值:{param}\n")
</code></pre>
<p>可以看到遍历了所有的神经元节点，展示了每个节点的权重w和偏置b，可以看到w是[5,3]那么也就是一共15根线，每一根线是不同的权重，这个和我们上面的那个图是一致的；b是[5]，而我们的第一层隐藏层也是五个节点，也就是每个节点都有自己的偏置b，初始值都是随机的<br>而且可以看到，有权重和偏置的是0，2，4，6这几个节点，这是因为1，3，5都是激活函数；所有的节点后面都是<code>device="cuda:0"</code>说明都在GPU上 ； 所有的<code>requires_grad=True</code>是说明是否需要计算梯度，这里都是True说明需要进行反向传播的内部参数都打开了张量自带的梯度计算功能<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130193237389.png" alt="image.png"></p>
<hr>
<p>我们再来看一下外部参数，外部参数是调参师/炼丹师！关注的重点，包括但不限于<code>网络层数;各隐藏层节点数;各节点激活函数;内部参数的初始值</code>，<strong>和训练网络有关的超参数有损失函数,学习率,优化算法,batch_size,epochs</strong></p>
<p>对于激活函数，我们可以访问访问对应版本的文档<a class="external-link" href="https://docs.pytorch.org/docs/2.1/nn.html#non-linear-activations-weighted-sum-nonlinearity" target="_blank" rel="noopener nofollow" aria-label="https://docs.pytorch.org/docs/2.1/nn.html#non-linear-activations-weighted-sum-nonlinearity" data-tooltip-position="top">torch.nn &mdash; PyTorch 2.1 documentation</a>查看PyTorch内置的激活函数，有很多而且点对应的函数可以看到其数学公式和函数图像，我们这里使用的就是<code>ReLu()</code></p>
<p>对于<code>损失函数</code>，可以访问对应版本的<a class="external-link" href="https://docs.pytorch.org/docs/2.1/nn.html#loss-functions" target="_blank" rel="noopener nofollow" aria-label="https://docs.pytorch.org/docs/2.1/nn.html#loss-functions" data-tooltip-position="top">torch.nn &mdash; PyTorch 2.1 documentation</a>，也有很多</p>
<pre><code class="language-python">loss_fn = nn.MSELoss()
</code></pre>
<p>对于<code>学习率和优化算法</code>可以访问<a class="external-link" href="https://docs.pytorch.org/docs/2.10/optim.html" target="_blank" rel="noopener nofollow" aria-label="https://docs.pytorch.org/docs/2.10/optim.html" data-tooltip-position="top">torch.optim &mdash; PyTorch 2.10 documentation</a>，这里面比较出名的就是SGD,Adam，可以按照他的实例进行使用,lr参数就是学习率，需要注意的是，PyTorch实现时只支持BGD和MBGD，不支持单个样本的输入方式，我们下面案例代码中<code>torch.optim.SGD只代表梯度下降</code></p>
<pre><code class="language-python">learning_rate = 0.01 #学习率
optimizer = torch.optim.SGD(dnn.parameters(),lr=learing_rate)# 优化算法
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130195917065.png" alt="image.png"></p>
<hr>
<p>都准备好了，接下来就可以训练网络了。<br>训练网络就是一个来回进行前向传播和反向传播的过程，所以最外层我们就是一个循环<br>然后使用<code>dnn(输入特征)</code>可以得到预期值<code>Pred</code><br>然后有了预期值就需要和真实值计算误差函数<code>loss = loss_fn(预期值Pred,真实输出特征Y)</code> 这里的<code>loss_fn是前面定义的损失函数名</code><br>每一轮梯度都会不一样，为了不受前面的影响，需要记得清除上一轮滞留的梯度<code>optimizer.zero_grad()</code>，其中<code>optimizer是优化算法对象</code><br>之后进行反向传播<code>loss.backward()</code>，<code>loss是上面误差函数调用后的返回值</code><br>最后利用<code>优化算法对象</code>进行一次内部参数优化<code>optimizer.step()</code></p>
<pre><code class="language-python"># 训练网络  
epochs = 1000 # 所有样本都会轮回1000次  
losses = [] # 用于记录损失函数变化的列表  

# 给训练集划分输入和输出(在我们当前案例中前三列为输入，后三列为输出)  
X = train_data[:,:3]  
Y = train_data[:,-3:]  
# 开始循环进行训练  
for epoch in range(epochs):  
    Pred = dnn(X) # 一次前向传播BGD  
    loss = loss_fn(Pred, Y) # 计算损失函数  
    # 保存记录损失值  
    # loss.item()方法可以将损失函数降级为一个元素  
    # 因为loss是一个张量，张量不能直接存储在list中，所以这里要先降级  
    losses.append(loss.item())  
    optimizer.zero_grad() # 每一轮梯度都不一样 消除上一轮滞留的梯度  
    loss.backward() # 一次反向传播  
    optimizer.step() # 优化内部参数  

Fig = plt.figure()  
plt.plot(range(epochs),losses)  
plt.xlabel('epochs')  
plt.ylabel('loss')  
plt.show()
</code></pre>
<p>可以看到训练后损失函数越来越少<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130202157340.png" alt="image.png"></p>
<hr>
<p>最后是测试网络<br>测试时只需要让测试集进行一次向前传播即可，不需要计算梯度，所以可以局部关闭梯度，该操作使用<code>with torch.no_grad()</code>命令实现<br>考虑到输出特征时独热编码，而预测的数据一般都接近0/1的小数，为了让预测数据和真实数据之间进行比较，需要对预测数据进行规整。除此之外还要计算预测值正确总数从而计算占比</p>
<pre><code class="language-python"># 测试网络  
# 将测试集的输入特征和输出特征分开  
X = train_data[:,:3]  
Y = train_data[:,-3:]  
# 进行一次前向传播  
# 局部关闭梯度计算功能 这个部分的代码块不会进行梯度计算  
with torch.no_grad():  
    Pred = dnn(X) # 前向传播一次  
    # 输出特征是独热编码，预测数据一般都是0~1内的小数，需要进行格式规整  
    # 预测结果Pred中，每行样本都是三个输出，最大的那个也就是最有可能的，将其设置为1，其余的设置为0  
    # 这样子Pred就和真实结果集格式一致  
    Pred[:,torch.argmax(Pred,axis=1)]=1  
    Pred[Pred!=1]=0  
    # 计算测试集的准确度  
    # Pred==Y 让预测结果和真实结果比较 得到3000行3列布尔类型的张量  
    # .all()方法会扫描只有全部为True的时候才会返回True (1)代表按行扫描 得到一个3000行1列的张量  
    # torch.sum就是将3000行相加，True为1False为0，所以最终结果就是预测值==真实值的正确样本总数  
    correct = torch.sum((Pred==Y).all(1)) # 预测结果正确的样本  

    # 正常数组shape(行,列,...) 类比到张量就是 第0维就是对应行，第1维对应列  
    # 可以理解为shape(0维,1维,2维)  
    # size(维度)是张量特有的，numpy中没有  
    total = Y.size(0) # 第一个维度上的值也就是3000  
    print(f'测试集精准度:{100*correct/total}%')
</code></pre>
<hr>
<p>归纳：到目前为止，我们已经初步完成了一个DNN的入门代码案例，整个代码内容和运行结果如下</p>
<pre><code class="language-python">import torch.nn as nn  
import torch  
from matplotlib import pyplot as plt  

class MyDNN(nn.Module):  
    def __init__(self):  
        ''' 搭建神经网络各个层 '''  
        #Sequential() 可以理解为顺序队列，在里面按顺序定义各个隐藏层，不要把输入层定义在里面 
        super(MyDNN,self).__init__()  
        self.net = nn.Sequential( 
        # 隐藏层也称之为线形层 所以我们这方法也就是.Linear(preNodeNumber,selfNodeNumber) 
        # 两个参数是 上一层节点数和本层节点数  
        # 因为这是第一层隐藏层，所以上一层就是输入层，我们是三个输入特征，所以第一个参数为3  
            nn.Linear(3,5),  
        # 激活函数 激活函数一般有ReLU tanh sigmoid,一般选ReLU  
            nn.ReLU(),  
        # 再来一个隐藏层 只不过这个隐藏层的输入是上一层的输出，所以第一个参数为5  
            nn.Linear(5,5),  
            nn.ReLU(),  
            nn.Linear(5,5),  
            nn.ReLU(),  
            nn.Linear(5,3)  
        )  

    def forward(self,x):  
        '''  前向传播 '''        
        # net就是上面定义的顺序队列，上面叫啥这里就调用啥  
        y = self.net(x)  
        return y # y即输出数据  

dnn = MyDNN().to('cuda:0') # 放入GPU  
loss_fn = nn.MSELoss() # 损失函数  
learing_rate = 0.01 # 学习率  
optimizer = torch.optim.SGD(dnn.parameters(),lr=learing_rate) # 优化算法  

# 生成三个输入特征，rand生成1000行1列的范围在[0,1]的张量  
X1 = torch.rand(1000,1)  
X2 = torch.rand(1000,1)  
X3 = torch.rand(1000,1)  

# 生成输出特征, .float()将布尔张量转化为浮点型张量  
Y1 = ( (X1+X2+X3)&lt;1 ).float() # 输出特征 1
Y2 = ( (1&lt;(X1+X2+X3)) &amp; ((X1+X2+X3)&lt;2) ).float() # 输出特征 2
Y3 = ( (X1+X2+X3)&gt;2 ).float() # 输出特征 3  
data = torch.cat([X1,X2,X3,Y1,Y2,Y3],axis=1) # 整合数据集  
data = data.to('cuda:0')  
print(data.shape) # torch.Size([1000, 6])  

# 划分数据集大小 我们采用7:3  
train_size = int(len(data)*0.7)  
test_size = len(data)-train_size  
# 打乱数据  
data = data[torch.randperm(data.size(0)),:]  
train_data = data[0:train_size,:] # 训练集  
test_data = data[train_size:len(data)] # 测试集  
print(train_data.shape, test_data.shape,sep="##")  # 打印查看是否正确划分  

# 训练网络  
epochs = 1000 # 所有样本都会轮回1000次  
losses = [] # 用于记录损失函数变化的列表  

# 给训练集划分输入和输出(在我们当前案例中前三列为输入，后三列为输出)  
X = train_data[:,:3]  
Y = train_data[:,-3:]  
# 开始循环进行训练  
for epoch in range(epochs):  
    Pred = dnn(X) # 一次前向传播BGD  
    loss = loss_fn(Pred, Y) # 计算损失函数  
    # 保存记录损失值  
    # loss.item()方法可以将损失函数降级为一个元素  
    # 因为loss是一个张量，张量不能直接存储在list中，所以这里要先降级  
    losses.append(loss.item())  
    optimizer.zero_grad() # 每一轮梯度都不一样 消除上一轮滞留的梯度  
    loss.backward() # 一次反向传播  
    optimizer.step() # 优化内部参数  

Fig = plt.figure()  
plt.plot(range(epochs),losses)  
plt.xlabel('epochs')  
plt.ylabel('loss')  
plt.show()  

# 测试网络  
# 将测试集的输入特征和输出特征分开  
X = train_data[:,:3]  
Y = train_data[:,-3:]  
# 进行一次前向传播  
# 局部关闭梯度计算功能 这个部分的代码块不会进行梯度计算  
with torch.no_grad():  
    Pred = dnn(X) # 前向传播一次  
# 输出特征是独热编码，预测数据一般都是0~1内的小数，需要进行格式规整  
# 预测结果Pred中，每行样本都是三个输出，最大的那个也就是最有可能的，将其设置为1，其余的设置为0  
# 这样子Pred就和真实结果集格式一致  
    Pred[:,torch.argmax(Pred,axis=1)]=1  
    Pred[Pred!=1]=0  
# 计算测试集的准确度  
# Pred==Y 让预测结果和真实结果比较 得到3000行3列布尔类型的张量  
# .all()方法会扫描只有全部为True的时候才会返回True (1)代表按行扫描 得到一个3000行1列的张量  
# torch.sum就是将3000行相加，True为1False为0，所以最终结果就是预测值==真实值的正确样本总数  
    correct = torch.sum((Pred==Y).all(1)) # 预测结果正确的样本  

# 正常数组shape(行,列,...) 类比到张量就是 第0维就是对应行，第1维对应列  
# 可以理解为shape(0维,1维,2维)  
# size(维度)是张量特有的，numpy中没有  
    total = Y.size(0) # 第一个维度上的值也就是3000  
    print(f'测试集精准度:{100*correct/total}%')
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130211441697.png" alt="image.png"></p>
<h2 data-heading="网络保存与导入">网络保存与导入</h2>
<p>有时候训练一个大网络需要几天，那么必须要把整个网络连同里面的优化好的内部参数给保存下来下次使用或者别的设备使用，这里就需要用到网络的保存与导入<br>网络的保存使用<code>torch.save(dnn,文件名.pth)</code>；网络的导入使用<code>torch.load(文件名)</code>（load方法有个<code>weights_only</code> 参数决定安全策略，低版本中默认为False直接使用没问题，但是高版本中默认值从 <code>False</code> 改为 <code>True</code>，所以高版本中要使用需要指定一下<code>torch.load(文件名,weights_only=False)</code>）<br>我们继续拿上面的案例尝试一下保存和导入，然后进行测试，测试准确率一致</p>
<pre><code class="language-python">torch.save(dnn,'MyFirstDNN.pth')
newDnn = torch.load('MyFirstDNN.pth',weights_only=False)  
# 用导入的newDnn进行测试  
with torch.no_grad():  
    Pred = newDnn(X)  
    Pred[:,torch.argmax(Pred,axis=1)]=1  
    Pred[Pred!=1]=0  
    correct = torch.sum((Pred==Y).all(1))  
    total = Y.size(0)  
    print(f'newDnn测试集精准度:{100*correct/total}%')
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130212340362.png" alt="image.png"></p>
<h2 data-heading="批量梯度下降">批量梯度下降</h2>
<p>上面的DNN的相关实现的案例其实就是一种批量梯度下降，我们暂且先不用管批量梯度下降的具体含义和定义。接下来，我们使用一个已有的数据进行实战<br>我们这里有一份Data.xlsx的关于糖尿病相关的数据文件<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260205105205269.png" alt="image.png"><br>对此我们进行依次批量梯度下降的深度神经网络学习的代码编写</p>
<pre><code class="language-python">import numpy as np  
import pandas as pd  
import torch.nn as nn  
import torch  
from matplotlib import pyplot as plt  

class MyDnn(nn.Module):  
    def __init__(self):  
        ''' 搭建自己的网络 '''        super(MyDnn, self).__init__()  
        self.net = nn.Sequential(  
            nn.Linear(8,32),  
            nn.Sigmoid(),  
            nn.Linear(32,8),  
            nn.Sigmoid(),  
            nn.Linear(8,4),  
            nn.Sigmoid(),  
            nn.Linear(4,1),  
            nn.Sigmoid()  
        )  

    # 核心修正：forword &rarr; forward  
    def forward(self,x):  
        ''' 前向传播 '''        y = self.net(x)  
        return y  

sourceData = pd.read_csv(r"D:\ZhuoMian\Data.csv", index_col=0)  
# 验证读取结果（打印前5行 + 数据基本信息）  
print(sourceData.head())  # 打印前5行  
# 转化为NumPy数据类型并且转化为float类型  
arr = sourceData.values.astype(np.float32)  
# 转化为张量且放入GPU上  
ts = torch.tensor(arr)  
ts = ts.to('cuda:0')  
print(ts)  
print("======================")  

# 依旧按照3：7划分训练集和测试集  
train_size = int(len(ts)*0.7)  
test_size = len(ts)-train_size  
# 打乱数据  
random_ts = ts[torch.randperm(ts.size(0)),:]  
train_data = random_ts[:train_size]  
test_data = random_ts[train_size:]  
print(train_data)  
print(test_data)  

model = MyDnn().to('cuda:0')  
print(model)  

# 创建损失函数 和 优化算法  
loss_fun = nn.BCELoss(reduction='mean')  
learing_rate = 0.005  # 注：learning 拼写错误，建议改为 learning_rateoptimizer = torch.optim.Adam(model.parameters(), lr=learing_rate)  

# 开始训练  
epochs = 5000  
losses = []  

X = train_data[:,:-1]  
Y = train_data[:,-1].reshape(-1,1) # reshape将一阶张量变为二阶  
for epoch in range(epochs):  
    pred = model(X)  # 对应变量名修正  
    loss = loss_fun(pred,Y)  
    losses.append(loss.item())  
    optimizer.zero_grad()  
    loss.backward()  
    optimizer.step()  

# 画图展示  
fig = plt.figure()  
plt.plot(range(epochs),losses)  
plt.xlabel("epoch")  
plt.ylabel("loss")  
plt.show()  

# 测试  
X = test_data[:,:-1]  
Y = test_data[:,-1].reshape(-1,1)  
with torch.no_grad():  
    pred = model(X)   
    pred[pred &gt;=0.5] = 1  
    pred[pred &lt;0.5] = 0  
    currentSize = torch.sum((pred==Y).all(1))  
    # 精度打印修正：转浮点型，避免张量显示问题  
    accuracy = 100 * currentSize / Y.size(0)  
    print(f"测试准确度 {accuracy.item():.2f}%")
</code></pre>
<h2 data-heading="小批量梯度下降">小批量梯度下降</h2>
<p>我们依旧使用上面的数据和神经网络结构，但是本次采用的方式为小批量梯度下降，使用小批量梯度下降的时候需要使用Pytorch的三个内置的实用工具</p>
<ul>
<li><code>DataSize</code>用于封装数据集</li>
<li><code>DataLoader</code>用于加载数据不同的批次</li>
<li><code>random_split</code>用于划分训练集与测试集</li>
</ul>
<pre><code class="language-python">from torch.utils.data import Dataset, DataLoader,random_split
</code></pre>
<p>整体步骤是差不多的，首先依旧是<code>制作数据集</code><br>在小批量梯度下降制作数据集的时候，我们需要用制作一个类继承上面说的<code>DataSet且重写其__init__方法;__getitem__;__len__三个方法</code>，三个方法的作用分别是<code>加载数据集，获取数据索引，获取数据总量</code><br>小批量梯度下降和批量梯度下降在代码上，主要是<code>数据集的封装划分</code>;<code>训练网络</code>；<code>测试网络</code>上有一些不同，其余的定义神经网络模型，损失函数定义和使用，优化算法的定义和使用都是一样的</p>
<p>这里不详细逐步介绍了，自行看代码查看不同和理解小批量梯度下降的处理逻辑</p>
<pre><code class="language-python">import numpy as np  
import pandas as pd  
import torch.nn as nn  
import torch  
from torch.utils.data import Dataset, DataLoader,random_split  
from matplotlib import pyplot as plt  

# 小批量梯度下降  
# 封装数据集  
class MyData(Dataset):  
    def __init__(self, filePath):  
        ''' 加载数据集 '''        # 我们的数据集还是之前那些数据，所以把之前通过Pandas获取数据的逻辑写到这里来即可  
        sourceData = pd.read_csv(filePath, index_col=0)  
        # 验证读取结果（打印前5行 + 数据基本信息）  
        print(sourceData.head())  # 打印前5行  
        # 转化为NumPy数据类型并且转化为float类型  
        arr = sourceData.values.astype(np.float32)  
        # 转化为张量且放入GPU上  
        ts = torch.tensor(arr)  
        ts = ts.to('cuda:0')  
        self.X = ts[:, :-1]  # 前八列为输入特征  
        self.Y = ts[:, -1].reshape(-1, 1)  # 最后一列为输出特征  
        self.len = ts.size(0)  

    # 获取数据索引  
    def __getitem__(self, index):  
        return self.X[index], self.Y[index]  

    # 获取数据集的长度  
    def __len__(self):  
        return self.len  

Data = MyData("D:/ZhuoMian/Data.csv")  
train_size = int(len(Data) * 0.7)  # 训练集大小  
test_size = len(Data) - train_size  # 测试集大小  
train_data, test_data = random_split(Data, [train_size, test_size])  # 直接利用工具函数 让其帮助我们进行测试集和训练集的划分  
# 批次加载器 第一个为数据集 第二个数据为批次数据 第三个参数为是否每一个epoch之内都要进行小批次划分 true为是的 false为否  
# 我们可以从批次加载器中获取训练数据  
# 一般我的训练集需要shuffler为True 保证每次不同从而训练效果好 测试集因为只要一次 所以没必要每次都划分  
train_loader = DataLoader(train_data, batch_size=128, shuffle=True)  
test_loader = DataLoader(test_data, batch_size=64, shuffle=False)  

# 搭建神经网络  
class DNN(nn.Module):  
    def __init__(self):  
        super(DNN, self).__init__()  
        self.net = nn.Sequential(  
            nn.Linear(8, 32), nn.Sigmoid(),  
            nn.Linear(32, 8), nn.Sigmoid(),  
            nn.Linear(8, 4), nn.Sigmoid(),  
            nn.Linear(4, 1), nn.Sigmoid(),  
        )  

    def forward(self, x):  
        y = self.net(x)  
        return y  

# 构建网络对象且放入GPU  
module = DNN().to('cuda:0')  
# 设置学习率，损失函数，优化算法  
loss_fun = nn.BCELoss(reduction='mean')  
learing_rate = 0.005  
optimizer = torch.optim.Adam(module.parameters(), lr=learing_rate)  
# 训练网络  
epochs = 500  
losses = []  
for epoch in range(epochs):  
    for (x, y) in train_loader:  
        Pred = module(x)  
        loss = loss_fun(Pred, y)  
        losses.append(loss.item())  
        optimizer.zero_grad()  
        loss.backward()  
        optimizer.step()  

Fig = plt.figure()  
plt.plot(range(len(losses)), losses)  
plt.xlabel("epoch")  
plt.ylabel("loss")  
plt.show()  

# 测试网络  
correct = 0  
total = 0  
for (x, y) in test_loader:  
    Pred = module(x)  
    Pred[Pred&gt;0.5] = 1  
    Pred[Pred&lt;0.5] = 0  
    correct += torch.sum((Pred==y).all(1))  
    total += y.size(0)  
    print(f"测试集准确率为{100*correct/total:.2f}%") // 这里我们是每批次都相加展示了一次

// 实际上应该在这里统一打印一次最后的
// print(f"测试集准确率为{100*correct/total:.2f}%")
// 不过问题不大，这里的打印结果肯定和上面for里面最后一次循环的打印是一致的
</code></pre>
<p>运行后可以看到，图片展示的loss损失值的变化和批量梯度下降完全不一样，并且测试网络给出的数据也是多个（因为分批次了，每个批次都有一个准确率的计算）<br><strong>可以看到最终结果其实准确率要比批量梯度下降要好一些，虽然从图上来看损失函数不是递减的</strong><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260206103406828.png" alt="image.png"></p>
<h1 data-heading="手写数字识别">手写数字识别</h1>
<p>手写数字数据集(MNIST)是机器学习领域的标准数据集，被称之为机器学习领域的HelloWorld<br>,因为任何的AI算法都可以用词标准数据集进行校验，MNIST内的每个样本都是一副二维的灰度图像<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260206111515785.png" alt="image.png"><br>在MNIST中，模型的输入就是一副图像，模型的输出就是与图像中对应的数字（0-9，非独热编码）</p>
<blockquote>
<p>我们不用手动将输出转化为独热编码，因为PyTorch会在整个过程中自动将数据集的输出转化为独热编码，我们只需要在测试网络的时候，将预测输出和真正结果对比的时候进行转化对比即可</p>
</blockquote>
<p>每一图/样本，其实都是一个28X28的二维数组（二维就是灰度图；三维对应的就是彩图）<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260206112010055.png" alt="image.png"><br><strong>和之前不同的是，在这种多分类问题中，神经网络的输出层需要一个softmax激活函数(之前的只要在隐藏层后面跟上激活函数即我们的.ReLU()，在这里最后的输出层那里也需要添加一个激活函数)</strong>，它可以把输出层的数据归一化到0-1上且相加为1，从而模拟出概率的意思</p>
<blockquote>
<p>如何理解这句话呢，就是我们这个数字识别，其输出层肯定是10个即0-9，每一组输入一张图都会有十个输出，这是个输出的值都会被压缩到0~1内且相加为1，就相当于0~9九个可能，每个可能的数值就是其概率，最大的那个也就是概率最大的</p>
</blockquote>
<p>那我们现在开始制作数据集，这次的数据集需要在<code>torchvision库</code>中分别下载训练集和测试集，因此需要从<code>torchvision库中导入datasets</code>，下载前还需要借助<code>torchvision中的ransforms进行图像转换,将数据集变为张量且调整数据集的统计分布</code><br>首先导入需要的包</p>
<pre><code class="language-python">import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import datasets
import matplotlib.pyplot as plt
</code></pre>
<h2 data-heading="制作数据集">制作数据集</h2>
<p>在下载数据集之前，我们需要设定转换参数<code>transform</code>，利用函数<code>transforms.Compose([..])</code>，该参数解决如下两个问题</p>
<ul>
<li><code>ToTensor</code>将图像数据转化为张量，且调整三个维度的顺序为C <em> W </em> H，C为通道数，二维灰度图的通道数为1，三维RGB彩图的通道数为3</li>
<li><code>Normalize</code>正规化，将神经网络的输入数据转化为标准正态分布，有助于训练效果，根据统计计算，MNIST的训练集所有的像素的均值为0.1307，标准差为0.3081（将一个普通的正态分部转化为一个标准正态分部的方式：所有值减去均值然后除以标准差）</li>
</ul>
<pre><code class="language-python"># 设定下载集参数 这段代码基本固定 可以重复使用  
transform = transforms.Compose([  
    transforms.ToTensor(),  
    transforms.Normalize((0.1307,), (0.3081,))  
])
</code></pre>
<p>设置好下载参数之后，就可以下载训练集和测试集，下载数据集需要利用<code>datasets.MNIST(root=...;train=...;transform=...;download=...)</code><br>下载会比较耗时间，需要等待</p>
<pre><code class="language-python"># 下载数据集  
# 四个参数含义:  
# 下载后保存的位置;是否为训练集(false则代表为测试集);  
# download代表检测第一个参数root下是否有,如果root存在则不需要下载  
train_Data = datasets.MNIST(  
    root='D:/ZhuoMian/bigThree/PythonLearn/BasePy/data/mnist',  
    train=True,  
    transform=transform,  
    download=True  
)  

test_Data = datasets.MNIST(  
    root='D:/ZhuoMian/bigThree/PythonLearn/BasePy/data/mnist',  
    train=False,  
    transform=transform,  
    download=True  
)
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260206164855398.png" alt="image.png"></p>
<h2 data-heading="批次加载器">批次加载器</h2>
<p>在这里我们也会需要用到批次加载器，和上面小批量梯度下降一致的使用</p>
<pre><code class="language-python"># 批次加载器  
train_Loader = DataLoader(train_Data,batch_size=64,shuffle=True)  
test_Loader = DataLoader(test_Data,batch_size=64,shuffle=False)
</code></pre>
<h2 data-heading="构建网络">构建网络</h2>
<p>整体还是一样的，继承<code>nn.Module</code>然后重写<code>__init__和forward函数</code>,不一样的是，因为输入是图片，我们需要将图片铺成一维的，所以在输入层之前需要借助<code>nn.Flatten()</code>实现</p>
<pre><code class="language-python"># 搭建神经网络  
class MnistDNN(nn.Module):  
    def __init__(self):  
        # 每个输入都是28*28的二维数组 所以输入得为28*28  
        # 输出为0-9所以需要10个节点  
        super(MnistDNN,self).__init__()  
        self.net = nn.Sequential(  
            nn.Flatten(),   # 把图片铺成一维  
            nn.Linear(28*28,512),  
            nn.ReLU(),  
            nn.Linear(512,256),  
            nn.ReLU(),  
            nn.Linear(256,128),  
            nn.ReLU(),  
            nn.Linear(128,64),  
            nn.ReLU(),  
            nn.Linear(64,10)  
        )  

    def forward(self, x):  
        return self.net(x)

# 创建模型对象且放入GPU  
model = MnistDNN().to("cuda:0")
</code></pre>
<h2 data-heading="损失函数和优化算法">损失函数和优化算法</h2>
<p>损失函数和优化算法也还是一样的，这次我们使用的是<code>CrossEntropyLoss这个损失函数，优化算法依旧选择SGD</code><br>可以看到，我们上面说了，这种多分类问题需要在输出层后面再加一个softMax激活函数，可以明显地看到我们在构建网络的链路最后并没有加，这是因为<code>CrossEntropyLoss自带softMax激活函数</code>，而损失函数刚好是计算输出层的数值的，所以这里就可以直接作用上</p>
<pre><code class="language-python"># 损失函数和优化算法  
loss_fn = nn.CrossEntropyLoss() # 自带softMax激活函数  
learning_rate = 0.01  
# 这里比平常多了一个参数momentum动量的设置,它使得梯度下降算法有了力和惯性  
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate,momentum=0.5)
</code></pre>
<h2 data-heading="训练网络">训练网络</h2>
<p>因为使用了批次加载器，那么我们依旧是从批次中获取数据<br>需要注意的是，因为我们的数据集是下载在文件下的，但是数据集内部我们无法进入，所以我们不能直接将整个数据集放入到cuda上，所以在遍历获取的时候，需要先将x,y放入到cuda上</p>
<pre><code class="language-python">epochs = 5  
losses = []  
for epoch in range(epochs):  
    for(x,y) in train_Loader:  
        # 因为无法将整个数据集放入到cuda上 所以这里在计算的时候再放入到cuda上  
        x,y = x.to("cuda:0"),y.to("cuda:0")  
        Pred = model(x)  
        loss = loss_fn(Pred, y)  
        losses.append(loss.item())  
        optimizer.zero_grad()  
        loss.backward()  
        optimizer.step()  

Fig = plt.figure()  
plt.plot(range(len(losses)),losses)  
plt.xlabel("Epochs")  
plt.ylabel("Loss")  
plt.show()
</code></pre>
<h2 data-heading="测试网络">测试网络</h2>
<p>同小批次梯度下降的测试网络过程，需要从测试集的批量加载器中获取数据，然后求出预估值之后，<strong>利用<code>a,b = torch,max(Pred.data,dim=1)</code>获取每一行的最大值和最大值的位置，最大位置其实就是我们的预测值</strong>(假设你一行输入，最后得到了10个数值索引为0-9，分别代表了0-9每个数字的占比/权重/概率，最大的那个就是我们的预测结果，所以这里获取最大值的位置其实就是对应的数字了)<br>因为<code>predicted和y都是一阶张量（类比于一维数组）</code>，所以不能使用<code>.all()</code>方法</p>
<pre><code class="language-python">correct = 0  
total = 0  
with torch.no_grad():  
    for (x,y) in test_Loader:  
        x,y = x.to("cuda:0"),y.to("cuda:0")  
        Pred = model(x)  
        _,predicted = torch.max(Pred.data,dim=1)  
        correct += torch.sum((predicted==y))  
        total += y.size(0)  
print(f"测试集精准度:{100*correct/total}%")
</code></pre>
<p>该案例的最终代码如下</p>
<pre><code class="language-python">import torch  
import torch.nn as nn  
from torch.utils.data import DataLoader  
from torchvision import transforms  
from torchvision import datasets  
import matplotlib.pyplot as plt  

# 设定下载集参数 这段代码基本固定 可以重复使用  
transform = transforms.Compose([  
    transforms.ToTensor(),  
    transforms.Normalize((0.1307,), (0.3081,))  
])  

# 下载数据集  
# 四个参数含义:  
# 下载后保存的位置;是否为训练集(false则代表为测试集);  
# download代表检测第一个参数root下是否有,如果root存在则不需要下载  
train_Data = datasets.MNIST(  
    root='D:/ZhuoMian/bigThree/PythonLearn/BasePy/data/mnist',  
    train=True,  
    transform=transform,  
    download=True  
)  

test_Data = datasets.MNIST(  
    root='D:/ZhuoMian/bigThree/PythonLearn/BasePy/data/mnist',  
    train=False,  
    transform=transform,  
    download=True  
)  

# 批次加载器  
train_Loader = DataLoader(train_Data,batch_size=64,shuffle=True)  
test_Loader = DataLoader(test_Data,batch_size=64,shuffle=False)  

# 搭建神经网络  
class MnistDNN(nn.Module):  
    def __init__(self):  
        # 每个输入都是28*28的二维数组 所以输入得为28*28  
        # 输出为0-9所以需要10个节点  
        super(MnistDNN,self).__init__()  
        self.net = nn.Sequential(  
            nn.Flatten(),   # 把图片铺成一维  
            nn.Linear(28*28,512),  
            nn.ReLU(),  
            nn.Linear(512,256),  
            nn.ReLU(),  
            nn.Linear(256,128),  
            nn.ReLU(),  
            nn.Linear(128,64),  
            nn.ReLU(),  
            nn.Linear(64,10)  
        )  

    def forward(self, x):  
        return self.net(x)  

# 创建模型对象且放入GPU  
model = MnistDNN().to("cuda:0")  

# 损失函数和优化算法  
loss_fn = nn.CrossEntropyLoss() # 自带softMax激活函数  
learning_rate = 0.01  
# 这里比平常多了一个参数momentum动量的设置,它使得梯度下降算法有了力和惯性  
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate,momentum=0.5)  

# 训练网络  
epochs = 5  
losses = []  
for epoch in range(epochs):  
    for(x,y) in train_Loader:  
        # 因为无法将整个数据集放入到cuda上 所以这里在计算的时候再放入到cuda上  
        x,y = x.to("cuda:0"),y.to("cuda:0")  
        Pred = model(x)  
        loss = loss_fn(Pred, y)  
        losses.append(loss.item())  
        optimizer.zero_grad()  
        loss.backward()  
        optimizer.step()  

Fig = plt.figure()  
plt.plot(range(len(losses)),losses)  
plt.xlabel("Epochs")  
plt.ylabel("Loss")  
plt.show()  

# 测试网络  
correct = 0  
total = 0  
with torch.no_grad():  
    for (x,y) in test_Loader:  
        x,y = x.to("cuda:0"),y.to("cuda:0")  
        Pred = model(x)  
        _,predicted = torch.max(Pred.data,dim=1)  
        correct += torch.sum((predicted==y))  
        total += y.size(0)  
print(f"测试集精准度:{100*correct/total}%")
</code></pre>
<p>最后的运行结果为<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260207100335634.png" alt="image.png"></p>]]></description>
    <pubDate>Sat, 30 May 2026 21:59:32 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=60</guid>
</item>
<item>
    <title>Python深度学习入门-day02-第三方库篇</title>
    <link>http://8.154.40.120:24180/?post=59</link>
    <description><![CDATA[<p><!-- obsidian --></p>
<p>学习此章之前请有一定的Python基础，如果无，可以查看<a class="external-link" href="/?post=57" target="_blank" rel="noopener" aria-label="obsidian://open?vault=ALLWorkPlace&amp;file=Python%2F%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%85%A5%E9%97%A8-Base%E7%AF%87" data-tooltip-position="top">深度学习入门-Base篇</a><br>这一篇主要是学习Numpy，Pandas和Matplotlib</p>
<h1 data-heading="Numpy">Numpy</h1>
<p>NumPy是为了克服Python原生的列表List的一些缺点而被创建的<br>要能在Python程序中使用NumPy，首先就需要记住要导包</p>
<pre><code class="language-python">import numpy as np
</code></pre>
<h2 data-heading="数据类型">数据类型</h2>
<p>NumPy的每一个列表中只能包含一种数据类型以节约内存<br>NumPy.array一般由<code>np.array(list)</code>创建，即传入Python原生列表对象得到NumPy的array对象<br>用NumPy创建常见的数据类型的数组,同时通过输出语句可以观察到，<code>NumPy.array对象的打印结果之间使用空格分隔而不会出现逗号(,)而Python原生List打印时会使用逗号进行分隔</code></p>
<pre><code class="language-python">import numpy as np  
intNumber_arr = np.array([1,2,3,4,5])  #整数型数组
floatNumber_arr = np.array([1.0,2.2,3.3,4.4,5.5]) #浮点型数组  
str_arr = np.array(["A","AB","哈哈"]) #字符型数组
print(intNumber_arr)  # [1 2 3 4 5]
print(floatNumber_arr)  
print(str_arr) #[A AB 哈哈]
</code></pre>
<p>因为类型是确定的，<code>所以如果往一个已经确定类型的array中插入别的类型的元素，就会自动的转换/强转</code></p>
<pre><code class="language-python">import numpy as np  
intNumber_arr = np.array([1,2,3,4,5])  
floatNumber_arr = np.array([1.9,2.2,3.3,4.4,5.5])  
intNumber_arr[0]=11.99  #往整型中插入浮点型
floatNumber_arr[0]=100  #往浮点型中插入整型
print(intNumber_arr[0]) # 11 小数部分被截断，精度缺失
print(floatNumber_arr[0]) # 100.0
</code></pre>
<p>如果需要整体转化，即将一个本来为整型类型的array转化为浮点数类型的array，可以使用<code>.astype()方法</code></p>
<pre><code class="language-python">import numpy as np  
intNumber_arr = np.array([1,2,3,4,5])  
int2Float_arr = intNumber_arr.astype(float)  
print(int2Float_arr) # [1. 2. 3. 4. 5.]
</code></pre>
<h2 data-heading="数组的创建">数组的创建</h2>
<p>NumPy中常见的array创建方式有如下几种</p>
<ul>
<li>
<p><code>np.array(list)和np.array(list,dtype)</code>，都是根据元组/列表进行创建，后者的dtype可以指定类型，假如你的list都是整型，但是你想得到一个浮点型的array，就可以让<code>dtype=float</code></p>
</li>
<li>
<p><code>np.zero()</code>创建一个空array</p>
</li>
<li>
<p><code>np.ones(shape)</code>创建一个shape形状的整型array，元素全部默认为1,<code>拓展，如果我们需要一个全x的数组，可以 x*np.ones(shape) 实现</code></p>
</li>
<li>
<p><code>np.full(shape,x)</code>创建一个shape形状的，内容全部为x的array</p>
</li>
<li>
<p><code>np.eye(n,k=0)</code>创建一个<code>n * n</code> 的矩阵，对角线为k，k可指定，默认为0</p>
</li>
<li>
<p><code>np.arange(start=0,stop,step=1)和np.arange(start=0,stop,step=1,dtype)</code>，注意区间左开右闭</p>
</li>
<li>
<pre><code class="language-python"># 语法：np.arange(start, stop, step, dtype) 
arr_arange1 = np.arange(10) # 从0开始，步长1，到10终止（不包含10） 
arr_arange2 = np.arange(2, 10, 2) # 从2开始，步长2，到10终止 
arr_arange3 = np.arange(0, 1, 0.2) # 支持浮点数步长 
print("默认序列：", arr_arange1) 
print("步长2序列：", arr_arange2) 
print("浮点步长序列：", arr_arange3)

</code></pre>
</li>
<li>
<p><code>np.linspcae(start,stop,num=50,endpoint=True)</code>在start-stop的范围内生成number个数据，会尽力保证等差，endpoint参数决定是否包含stop，也就是说该函数默认是包含终止值，但是如果不需要，可以通过参数控制</p>
</li>
<li>
<pre><code class="language-python">import numpy as np  
# 语法：np.linspace(start, stop, num=50, endpoint=True)  
arr_lin1 = np.linspace(0, 10, 5)  # 0到10之间，生成5个均匀分布的数  
arr_lin2 = np.linspace(0, np.pi, 10, endpoint=False)  # 不包含终止值&pi;  
print("5个元素的等差序列：", arr_lin1)  
print("10个元素（不含&pi;）的序列：", arr_lin2)
</code></pre>
</li>
<li>
<p><code>np.logspcae(start,stop,num=50)</code>生成从10^start 到 10^stop 范围内的 number个数，可以理解为生成一个等比序列</p>
</li>
<li>
<p><code>np.random.rand(...)</code>创建一个元素内容在[0,1]内的arr，可变入参，每个参数作为一个维度</p>
</li>
<li>
<pre><code class="language-python">import numpy as np  
# 1. 不传参数：生成单个随机数（标量）  
d1 = np.random.rand()  
print("不传参数：", d1, type(d1))  
# 2. 传单个整数：生成一维数组  
d2 = np.random.rand(5)  
print("\n传单个整数（一维数组）：", d2)  
# 3. 传多个整数：生成多维数组（2行3列）  
d3 = np.random.rand(2, 3)  
print("\n传多个整数（二维数组）：\n", d3)  
# 4. 不推荐：传元组（不会报错，但生成的是 1行2列数组，不符合预期）  
d4 = np.random.rand((2, 3))  # 不报错，但结果非预期  
print("\n传元组（非预期结果）：\n", d4, d4.shape)
</code></pre>
</li>
<li>
<p><code>np.random.random(shape)</code>,功能和<code>rand</code>一样，主要区别在于传参，这里需要传入shap，而不能传入多个参数</p>
</li>
<li>
<pre><code class="language-python">import numpy as np  
# 1. 不传参数：生成单个随机数（不是数组，是标量）  
r1 = np.random.random()  
print("不传参数：", r1, type(r1))  
# 2. 传单个整数：生成一维数组  
r2 = np.random.random(5)  
print("\n传单个整数（一维数组）：", r2) 
# 3. 传元组：生成多维数组（2行3列）  
r3 = np.random.random((2, 3))  
print("\n传元组（二维数组）：\n", r3)  
# 4. 错误用法：直接传多个整数（会抛出 TypeError）  
# r4 = np.random.random(2, 3)  # 报错！
</code></pre>
</li>
<li>
<p><code>np.random.randn</code>生成均值为0，方差为1的标准正态分布(高斯分布)随机数，语法同<code>rand()</code></p>
</li>
<li>
<pre><code class="language-python">import numpy as np  
randn1 = np.random.randn(5) #一行五列  
randn2 = np.random.randn(2, 3) # 两行三列  
print("一维标准正态分布随机数：", randn1)  
print("\n二维标准正态分布随机数：\n", randn2)
</code></pre>
</li>
<li>
<p><code>np.random.randint(start,stop,shape)</code>生成一个元素范围在start-stop之间，shape形状的array，其实也可以通过<code>random函数变化得到,例如10-100: 90*np.random.random(shape)+10</code></p>
</li>
<li>
<p><code>np.random.normal(均值,标准差,样本数量/shape)</code>生成符合<strong>指定均值、指定标准差</strong>的正态分布随机数，返回 NumPy 数组（<code>ndarray</code>）或单个标量。<code>randn()方法其实就是normal(0,1)的一种特里</code></p>
</li>
</ul>
<h2 data-heading="多维数组">多维数组</h2>
<p>多维数组本身还是很好理解的,Numpy中稍微有点稀奇的一个是<code>shape形状</code>这个概念，有些函数例如<code>np.ones(shape)</code>需要传入这个来创建数组</p>
<pre><code class="language-python">arr1 = [1,2,3]  # 形状为 3 or (3,) 表示三列，一维数组
arr2 = [[1,2,3]] # 形状为 (1,3) 表示是一个二维数组，目前是一行三列
arr3 = [[[1,2,3]]] # 形状为 (1,1,3) 表示三维数组
</code></pre>
<p>来看一下<code>np.ones(shape)</code>创建多维数组，同时，我们可以利用<code>array.shape</code>来获取一个数组的形状</p>
<pre><code class="language-python">import numpy as np  
arr1 = np.ones(4)  
arr2 = np.ones((2,4)) 
# 三维数组 最外层2个元素 每个元素中1个二维元素 每个二维元素中一个长度为4的一维数组 
arr3 = np.ones((2,1,4))  
print(arr1,arr1.shape)
print("========================")  
print(arr2)  
print("========================")  
print(arr3)

输入如下
[1. 1. 1. 1.] (4,)
========================
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]]
========================
[[[1. 1. 1. 1.]]

 [[1. 1. 1. 1.]]]

</code></pre>
<p>在很多时候，我们会需要进行数字的运算，运算的时候我们可能会需要将一维数组转化为二维或者将二维数组转化为一维数组，这个时候就需要借助<code>reshape(shape)</code>,这个方法在传递参数的时候，可以只传入一个维度，剩下的维度传入<code>-1</code>可以函数底层进行计算</p>
<pre><code class="language-python">import numpy as np  
arr1 = np.ones(4) # 一行四列  
arr2 = np.ones((2,4)) # 两行四列  
arr3 = arr1.reshape((4,1))  
print(arr3)  
print("========================")  
arr4 = arr2.reshape((1,-1)) # 重新塑形 但是我只指塑性为一行,列的参数-1让函数底层自行计算  
print(arr4)

输出
[[1.]
 [1.]
 [1.]
 [1.]]
========================
[[1. 1. 1. 1. 1. 1. 1. 1.]]

</code></pre>
<h2 data-heading="元素的访问和修改">元素的访问和修改</h2>
<p>访问array的元素和访问List元素一样，可以采用下标索引访问，同样支持负数；如果是矩阵/多维，则使用[index1,index2]的方式</p>
<pre><code class="language-python">import numpy as np  
arr = np.array([[1,2,3],[4,5,6]])  
print(arr)  
print(arr[1,1]) # 5  
arr[1,1] = 99  
print(arr[1,1]) # 99 其实也可以和别的语言一样使用 arr[index1][index2]的形式
</code></pre>
<p>还有一种<code>花式索引</code>，其形式为[ [ index1,index2,index3.... ] ]，适合让我们批量挑取元素进行访问</p>
<pre><code class="language-python">import numpy as np  
# 假设这里是一个100长度的一维数组，我们可以利用花式索引批量拿出0,10,15,10位置的元素  
arr1 = np.arange(100)  
temp = arr1[[0,10,15,10]]  
print(temp)  
print("=============================")  
# 这里有个多维数组  
arr2 = np.random.randint(0,10,(5,5))  
print(arr2)  
print("=============================")  
# 批量将(0,0),(1,1),(2,2),(3,3)位置的元素赋值为100  
arr2[ [0,1,2,3],[0,1,2,3] ] = 100  
print(arr2)
</code></pre>
<h2 data-heading="数组的切片">数组的切片</h2>
<p>array中也可以和List一样具有切片的特性</p>
<pre><code class="language-python">import numpy as np  
# 假设这里是一个100长度的一维数组，我们可以利用花式索引批量拿出0,10,15,10位置的元素  
arr1 = np.random.randint(0,100,10)  
# [29 13 15 98 49 54 83 80 56 95]  
print(arr1)  
print("===========================")  
# [13 15 98 49 54 83]  
print(arr1[1:-3]) # 拿出1到倒数第三个元素  
print("===========================")  
# [29 15 49 83 56]  
print(arr1[::2]) # 拿出所有元素，步长为2  
print("===========================")  

arr2 = np.random.randint(0,100,(5,5))  
'''  
[[93 80  8 97 58]  
 [25  0 73 54 23] [56 66 34 70 33] [ 4 80  8  6 77] [62 44 33 12 49]]'''  
print(arr2)  
print("===========================") 
print(arr2[2,:]) #提取第二行 其实也可以arr2[2] 只是这么写没那么规范 
'''  
[[ 0 73 54]  
 [66 34 70]]'''  
print(arr2[1:3,1:-1]) # 切除1到3行;每行为1到倒数第二个元素
</code></pre>
<p>利用切片来提取矩阵的列</p>
<pre><code class="language-python">import numpy as np  
arr = np.arange(1,21).reshape((4,5))  
# 提取整个第二列 但是需要注意,提取出单个列的话返回的是向量而不是矩阵
print(arr[:,2]) #  [ 3  8 13 18]  
print(arr[:,2:4]) # 多个列就是矩阵

# 如果想把 arr[:,2]得到的向量转化为矩阵  
# 方法一  
print("==================")  
print(arr[:,2:3])  
# 方法二  
print("==================")  
temp = arr[:,2].reshape((-1,1))  
print(temp)  
# 方法三  
print("==================")  
temp2 = arr[:,2].reshape((1,-1))  
print(temp2.T) # T可以得到矩阵的倒置,只对矩阵有效
</code></pre>
<p><strong>然后对于切片，之前也提到过，在NumPy中切片是数组的视图，而不是一份新数据，所以修改也会影响原来的数组，这个和List的特点不一样，并且赋值也不会创建新的变量(例如 arr2 = arr1，修改arr2也会导致arr1被修改)。如果真的需要拷贝一份新的，则使用copy()方法</strong></p>
<h2 data-heading="数组的变形">数组的变形</h2>
<p>数组的变形主要是针对于矩阵，向量是不存在什么变形的说法的，如果一定要说，向量应该也就一个reshape()操作了</p>
<ul>
<li>
<p><code>arr.T</code>矩阵的倒置</p>
</li>
<li>
<p><code>np.flipud(arr)和np.fliplr(arr)</code>前者为上下翻转(flip-up-down)，后者为左右翻转(flip-left-right)，向量只能使用<code>flipud(arr)</code>(这里可能会感觉，向量不是一行么，为何只能上下反倒不能左右，其实这个是和数学上一致的，数学上向量默认为列向量)</p>
</li>
<li>
<p><code>np.reshape()</code> 不多介绍了，很熟悉，矩阵的重塑</p>
</li>
<li>
<p><code>np.concatenate(arrays,axis=0)</code>，默认按照行进行拼接(行数会增加)，axis=1则按照列进行拼接(列数会增加),第一个参数<code>arrays</code>是要参与拼接的所有arr组成的arr</p>
</li>
<li>
<pre><code class="language-python">import numpy as np  
arr1 = np.arange(1,7).reshape((2,3))  
arr2 = np.arange(10,16).reshape((2,3))  
print(np.concatenate((arr1,arr2)))  
print(np.concatenate((arr1,arr2),1))
</code></pre>
</li>
<li>
<p><code>np.split(arr,[index1,index2...])</code>在arr的index1，index2...的索引位置前进行切分</p>
</li>
<li>
<pre><code class="language-python">import numpy as np  
arr = np.arange(10,90,10)  
print(arr)  
tempA,tempB = np.split(arr,[5])  
print(tempA)  # [10 20 30 40 50]
print(tempB)  # [60 70 80]

temp1,temp2,temp3 = np.split(arr,[3,6])  
print(temp1)  # [10 20 30]
print(temp2)  # [40 50 60]
print(temp3)  # [70 80]
</code></pre>
<p>如果是对矩阵进行切分</p>
</li>
<li>
<pre><code class="language-python">import numpy as np  
arr = np.arange(10,90,10).reshape(4,2)  
'''  
[[10 20]  
 [30 40] 
 [50 60] 
 [70 80]]'''  
print(arr)  
temp1,temp2 = np.split(arr,[2]) # 默认按行，那就从第二行开始切分  
'''  
[[10 20]  
 [30 40]]'''  
print(temp1)  

'''  
[[50 60]  
 [70 80]]'''  
print(temp2)  

temp1,temp2 = np.split(arr,[1],axis=1) # 按列切分,从第一列开始竖着切分  
'''  
[[10]  
 [30] 
 [50] 
 [70]]'''  
print(temp1)  

'''  
[[20]  
 [40] 
 [60] 
 [80]]'''  
print(temp2)
</code></pre>
</li>
</ul>
<h2 data-heading="数组的运算">数组的运算</h2>
<p>直接看代码案例,首先是单个矩阵的基本运算，记住一个点<code>所有的元素都会被操作和计算</code></p>
<pre><code class="language-python">import numpy as np  
baseArr = np.arange(1,13).reshape(3,4)  
print(baseArr)  
# 加法-作用到每个元素上，每个元素都加  
print(baseArr + 10)  
# 减法-作用到每个元素上，每个元素都减  
print(baseArr - 10)  
# 乘法-作用到每个元素上，每个元素都乘  
print(baseArr * 10)  
# 除法-作用到每个元素上，每个元素都除  
print(baseArr / 10)  
# 取模-作用到每个元素上，每个元素都取模  
print(baseArr % 10)
# 相反数-每个元素都取反
print(-baseArr)
</code></pre>
<p>然后是两个矩阵之间的计算</p>
<pre><code class="language-python">import numpy as np  
arr1 = np.arange(10,20).reshape(2,5)  
arr2 = np.arange(1,11).reshape(2,5)  
# 矩阵间的减法-对应位置的元素相减  
print(arr1-arr2)  
# 矩阵间的加法-对应位置的元素相加  
print(arr1+arr2)  
# 矩阵间的乘法-对应位置的元素相乘,也就是逐元素乘积,线性代数中的那种乘法形式之后会有别的函数负责 
print(arr1*arr2)  
# 矩阵间的除法-对应位置的元素相除  
print(arr1/arr2)  
# 矩阵间的取模-对应位置的元素相取模  
print(arr1%arr2)
</code></pre>
<p>上面的都是必须要满足<code>两个矩阵维度相匹配的前提</code>,对于和不同形状的之间的计算，涉及到了广播。有如下两个规则<br><strong>如果向量和矩阵之间计算，那么向量会被广播成满足矩阵计算要求的行矩阵</strong><br><strong>如果某个矩阵是行矩阵/列矩阵，则其会被广播从而适配另外一个矩阵</strong><br>如下案例代码，展示向量和矩阵之间的计算，当一个(x,y)的矩阵和一个向量进行计算的时候，向量必须满足格式即一行y列，该向量会被升级为(1,y)的行距阵，然后再广播为(x,y)的矩阵以适配另外一个矩阵</p>
<pre><code class="language-python">import numpy as np  
arr2 = np.arange(1,7).reshape(2,3)  
arr1 = np.array([-100,0,100])  
# arr1是向量 需要去主动适配arr2  
# 所以arr1在计算的时候就会变为  
'''  
[  
[-100,0,100],  
[-100,0,100],  
[-100,0,100]  
]  
'''  
# 然后每一个元素和arr2进行运算  
print(arr1*arr2)
</code></pre>
<p>当一个(x,y)的矩阵和一个列矩阵进行计算的时候，列矩阵必须满足(x,1)即X行1列，该列矩阵会被广播为(x,y)的矩阵从而适配</p>
<pre><code class="language-python">import numpy as np  
arr2 = np.arange(1,7).reshape(3,2)  
arr1 = np.array([-100,0,100]).reshape(3,1)  
# arr1是向量 需要去主动适配arr2  
# 所以arr1在计算的时候就会变为  
'''  
[  
[-100  -100]  
 [ 0 0] [ 100 100]]  
'''  
# 然后每一个元素和arr2进行运算  
print(arr1*arr2)

输出
[[-100 -200]
 [   0    0]
 [ 500  600]]
</code></pre>
<p>如果是一个行矩阵(1,y)和一个列矩阵(x,1)，双方都会广播为(x,y)的矩阵后进行操作</p>
<pre><code class="language-python">import numpy as np  
arr2 = np.arange(1,4).reshape(1,3)  
arr1 = np.array([-100,0,100]).reshape(3,1)  

print(arr1)  
print(arr2)  

print(arr1*arr2)

输出
[[-100]
 [   0]
 [ 100]]

[[1 2 3]]

[[-100 -200 -300]
 [   0    0    0]
 [ 100  200  300]]
</code></pre>
<h2 data-heading="数组的函数">数组的函数</h2>
<h3 data-heading="矩阵乘积-np.dot">矩阵乘积-np.dot</h3>
<p>我们在之前讲的数组的一些运算，可以发现都是一些逐元计算。那么线性代数上的矩阵的乘积怎么实现呢？这里就需要借助<code>np.dot(arr1,arr2)</code>函数了<br>向量和向量的乘积，就和线代上的向量乘法一致，利用<code>(x,y) * (y,x)然后相加即可</code></p>
<pre><code class="language-python">import numpy as np  
arr1 = np.arange(1,4)  
arr2 = np.arange(11,14)  
print(arr1)  # [1 2 3 ]
print(arr2)  # [11 12 13]
# 1*11+2*12+3*13=74  
print(np.dot(arr1,arr2))
</code></pre>
<p>同理，如果是向量和矩阵相乘，向量依次和矩阵的每一列每个元素相乘且求和</p>
<pre><code class="language-python">import numpy as np  
arr1 = np.arange(5)  
arr2 = np.arange(15).reshape(5,3)  
print(arr1)  
print(arr2)  
'''  
按照(1,5)*(5,3)=(1,3)  

             [[ 0  1  2]  
             [ 3  4  5]     [[0*0+1*3+2*6+3*9+4*12=90]                            [0 1 2 3 4]* [ 6  7  8]   =  [0*1+1*4+2*7+3*10+4*13=100]]  
             [ 9 10 11]     [0*2+1*5+2*8+3*11+4*14=110]]                                        [12 13 14]]  
'''  
print(np.dot(arr1,arr2))
</code></pre>
<p>需要知道啊，线代中ab和ba是不一定相等的，所以同理，我们矩阵和向量相乘结果不一定和向量和矩阵相乘是一致的</p>
<pre><code class="language-python">import numpy as np  
arr1 = np.arange(5)  
arr2 = np.arange(15).reshape(3,5)  
print(arr1)  
print(arr2)  
print(np.dot(arr2,arr1)) # [30 80 130]
</code></pre>
<p>矩阵和矩阵相乘</p>
<pre><code class="language-python">import numpy as np  
arr1 = np.arange(10).reshape((5,2))  
arr2 = np.arange(16).reshape(2,8)  
print(arr1)  
print(arr2)  
print(np.dot(arr1,arr2))
</code></pre>
<h3 data-heading="数学函数">数学函数</h3>
<p>NumPy中提供了不少数学相关的函数，这里主要来介绍几个</p>
<ul>
<li><code>np.abs()</code>求绝对值</li>
<li><code>np.sin();np.cos();np.tan()</code>三角函数相关</li>
<li><code>np.exp(x)</code>求e^x</li>
<li><code>np.log(x)</code>求ln(x)；利用换底公式可以得到<code>log2(x) = np.log(x)/np.log(2);log10(x) = np.log(x)/np.log(10)</code></li>
</ul>
<h3 data-heading="聚合函数">聚合函数</h3>
<p>矩阵和向量的聚合函数使用方式基本一致，只是向量使用的时候没有<code>axis</code>这个参数，常用的聚合函数有六个</p>
<ul>
<li><code>np.max(arr,axis)</code>求最大值，如果axis=0,则结果和行数相等，那么也是取出每列中最大值然后组成新的arr；同理，如果axis=1则结果和列数相等，也就是取出每一行的最大值然后组合；如果不写axis，则返回整个arr中最大的值</li>
<li><code>np.sum(arr,axis)</code>求和，如果axis=0，则对每列求和；axis=1则对每行求和；不写axis则整体求和</li>
<li><code>np.mean()</code>均值函数，axis的规则是一样的，求得是平均值</li>
<li><code>np.std()</code>求标准差，axis规则一样</li>
<li><code>np.nan..()</code>考虑到矩阵/向量中会存在缺失值，如果使用以上聚合函数遇到缺失会报错，那么我们可以在<code>函数名前面加nan</code>使用其安全的版本，计算的时候会自动忽略缺失值</li>
</ul>
<h2 data-heading="布尔数组">布尔数组</h2>
<p>布尔数组在NumPy中主要是通过整型数组/浮点数数组通过比较得方式之后得到得，和常数进行比较或者两个数组之间比较都会产生布尔数组</p>
<pre><code class="language-python">import numpy as np  
arr1 = np.arange(5)  
bool_arr = arr1 &gt; 3  
print(bool_arr) #[False False False False  True]

</code></pre>
<p>复杂一点的就是多条件比较，<code>需要注意，在Python中是使用and or not,在NumPy中是使用&amp; | ~</code></p>
<pre><code class="language-python">import numpy as np  
arr1 = np.arange(1,11)  
bool_arr = (arr1 &gt; 3)&amp;(arr1 &lt; 7)  
print(bool_arr) # [False False False  True  True  True False False False False]
</code></pre>
<p>对于布尔数组而言，主要的应用在于聚合函数<br><code>np.sum()</code>中传入的是布尔数组的时候，返回True的个数；<code>np.any()</code>只有布尔数组中有一个或者多于一个元素为True则返回True(例如判断两个arr中是否含有相同的元素)；<code>np.all()</code>只有当布尔数组中所有的元素都为True的时候才会返回True</p>
<p>布尔数组也可以作为数字数字的掩码，即利用布尔数组的结果来从原本的数组中进行快速的筛选。这个怎么理解呢，看案例代码</p>
<pre><code class="language-python">import numpy as np  
arr1 = np.arange(1,11).reshape(5,2)  
print(arr1[(arr1&gt;7)|(arr1&lt;2)]) # [ 1  8  9 10]

# 在两个arr比较筛选出arr2&gt;arr3的部分 和 arr3大于arr2的部分 这种需求下也可以使用
arr2 = np.random.randint(1,100, (3,4))  
arr3 = np.random.randint(1,100, (3,4))  
print(arr2[ arr2&gt;arr3 ])  
print(arr3[ arr3&gt;arr2 ])

arr4 = np.random.randint(300,900,(10,10))
print(np.where( arr4&gt;600 )) # 得到所有大于600的索引位置
print(np.where( arr4&gt;600 )[0]) # 得到大于600的第一索引位置 类似findFirst的效果
print(np.where( arr4==np.max(arr4) )) # 找到最大元素所在索引位置
</code></pre>
<h2 data-heading="数组与张量">数组与张量</h2>
<p>这部分内容是对于PyTorch的，因为NumPy很流行，PyTorch很大程度上吸收了NumPy的语法，我们可以将<code>np=&gt;torch;数组array=&gt;张量tensor;n维数组/数组的维度=&gt;n阶张量/张量的阶数</code>这三个概念直接进行替换，从而能帮助我们很方便的上手PyTorch<br>并且张量和数组之间可以直接转换<br><code>ts = torch.tensor(arr) ; array = np.array(ts)</code><br>当然，也没有说完全100%一样，但是不一样的地方是极少数，下图这几个方法是两者的不同之处，其余的方法是一致的<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129165450527.png" alt="image.png"></p>
<h1 data-heading="Pandas">Pandas</h1>
<p>Pands是一个标签库，其主要的作用就是在NumPy数组基础上基于其行列标签。Pandas中数组所有的特性依旧存在<br>一样的，我们在使用Pands得时候需要先导入库</p>
<pre><code class="language-python">import pandas as pd
</code></pre>
<h2 data-heading="对象的创建">对象的创建</h2>
<h4 data-heading="一维对象">一维对象</h4>
<p><strong>方法一：借助字典创建</strong><br>Numpy中我们可以通过np.array(list)将列表转化为数组，在Pandas中我们可以利用<code>pd.Series(dict)将Python的dict字典对象转化为Series对象</code><br>可以看到，Series的输出格式和dict的是不一样，而且在最后会告诉你其value的是什么对象类型，当然只是value的类型和Key无关</p>
<pre><code class="language-python">import pandas as pd  
dict_v = {"A":1, "B":2, "C":3}  
print(dict_v)  
series = pd.Series(dict_v)  
print(series)

输出
{'A': 1, 'B': 2, 'C': 3}
A    1
B    2
C    3
dtype: int64
</code></pre>
<p><strong>方法二：借助数组创建</strong><br>考虑到字典的创建本来就比较麻烦，我们可以利用两个数组创建，即<code>pd.Series(values,keys)</code>,传入两个参数，<strong>第一个为value序列，第二个为Key序列(序列可以是列表，元组，数组，张量都可以)</strong><br><strong>keys可以省略，省略后key序列将会从0开始递增</strong></p>
<pre><code class="language-python">import pandas as pd  
values = (1,2,3)  
keys = ("a","b","c")  
series = pd.Series(values,keys)  
print(series)
</code></pre>
<p>Series对象可以通过<code>.values和.index</code>两个属性分别得到value序列和key序列，需要注意的是，无论构建的时候你传入的是张量还是元组还是列表还是其他的，<code>最后用values得到的都是数组的数据类型</code><br>所以可以知道，Pandas其实是基于NumPy创建的库</p>
<h4 data-heading="二维对象">二维对象</h4>
<p>把一维Series对象当作一列，二维对象就是多个一列合在一起，那么就是多列，所以二维对象一定是基于多个Series对象的，每一个Series就是一列数据<br>创建Series的时候，字典的Key是index，竖直方向延展；当需要创建二维DataFrame对象的时候，肯定还需要水平方向向的键，也就是<code>colums</code></p>
<p>(1)我们可以利用多个Series创建DataFrame对象<code>pd.DataFrame({key1:series1,key2:series2})</code></p>
<pre><code class="language-python">import pandas as pd  
value = [1,2,3,4,5,6]  
key_age = [27,45,33,36,27,43]  
key_name = ["张三","李四","王五","赵六","孙七","周八"]  
#s1理解为病人序号和年龄的字典表  
s1 = pd.Series(key_age,value)  
#s2理解为病人序号和姓名的字典表  
s2 = pd.Series(key_name,value)  
# 如果要合并成二维，那么就是序号-年龄-姓名  
df = pd.DataFrame( {"年龄":s1,"姓名":s2} )  
print(s1)  
print(s2)  
print(df)

输出
1    27
2    45
3    33
4    36
5    27
6    43
dtype: int64
1    张三
2    李四
3    王五
4    赵六
5    孙七
6    周八
dtype: object
   年龄  姓名
1  27  张三
2  45  李四
3  33  王五
4  36  赵六
5  27  孙七
6  43  周八

</code></pre>
<p><strong>从结果看到，行标签就是每个Series的Key序列，那如果两者的Keys序列不一样会怎么样？那么最终的DataFrame会去两个Key序列的并集，那么必然出现数据缺失，确实的地方会使用NaN</strong></p>
<p>(2）直接利用三个数组创建二维DataFrame<br><code>pd.DataFrame(values,indexs,columns)</code>第一个为value序列(序列中的每个元素就是对应结果表中的每一行的内容)，第二个行标签序列，第三个为列标签序列，后两个参数可以不写，会默认从0开始递增作为序列</p>
<pre><code class="language-python">import pandas as pd  
values = [["女",18,"发烧"],["男",19,"咽喉炎"],["未知",29,"腱鞘炎"],["男",35,"急性肠胃炎"],["女",33,"鼻炎"] ]  
index = [1,2,3,4,5]  
colums = ["性别","年龄","病因"]  
df = pd.DataFrame(values, index=index, columns=colums)  
print(df)
</code></pre>
<p>对于二维DataFrame，因为多了一个<code>columns属性</code>，所以可以通过<code>df.columns获取列标签序列</code></p>
<h2 data-heading="对象索引">对象索引</h2>
<p>Pandas中的索引分为了<code>显式索引</code>和<code>隐式索引</code>。显式索引是Pandas对象提供的索引，隐式索引是数组本身自带的从0开始的索引。那么如果说，我们此时显式索引也是整型的话，就会导致sr[0]你不知道是按照显式访问还是隐式访问的<br>所以Pandas作者提供了索引器，可以通过<code>loc访问显式;iloc访问隐式</code></p>
<pre><code class="language-python">import pandas as pd  
sr = pd.Series(["x", "a", "b", "c"],[2,4,3,1])  
print(sr)  
# 分别通过显式索引和隐式索引访问元素b  
print(sr.loc[3]) # 显式索引 b的key为3 所以用3访问  
print(sr.iloc[2])# 隐式索引 b的key2 所以用2访问
</code></pre>
<p>当然，如上是基于隐式和显式会冲突的前提下，如果不会冲突，则可以不要使用loc,再者，因为之前也提到了Pandas基于NumPy的，所以花式索引也能使用来着；同样，也具备切片的特点，且切片也只是视图，共享内存地址</p>
<p>在二维对象DataFrame中，即使显式索引和隐式索引从理论上来讲不会冲突，但是你不使用索引器就会报错，所以在使用Pandas的时候我们得习惯使用索引器访问</p>
<pre><code class="language-python">import pandas as pd  
# 字典创建法  
i=["1号","2号","3号","4号"]  
v1 =[53,64,72,82 ]  
v2=["女",'男','男','女']  
sr1 =pd.Series(v1,index=i)  
sr2 = pd.Series(v2,index=i)  
df=pd.DataFrame({"年龄":sr1,"性别":sr2})  
# 访问指定元素  
print(df.loc["1号","年龄"]) #索引  
print(df.loc[["1号","3号"],["年龄","性别"]])#花式索引  
print(df.loc["1号":"4号","性别"])#切片  
print(df.loc[:,"年龄"])#切片 获取第一列
</code></pre>
<p>提取某一行或者某一列的时候，可以简写<code>df.loc["3号"];df["年龄"]</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129191147546.png" alt="image.png"></p>
<h2 data-heading="对象变形">对象变形</h2>
<p>有时候的数据是畸形的，比如说行是特性，列是个体，那么我们必须要进行转置，此时使用<code>df.T</code></p>
<pre><code class="language-python">import pandas as pd  
# 先构建畸形数据
arr1 = ["1号", "2号", "3号", "4号"]  
arr2 = ["年龄", "性别"]  
arr3 = [[19,20,21,22],["男","女","男","女"]]  
'''  
    1号  2号  3号  4号  
年龄  19  20  21  22
性别   男   女   男   女  
'''  
df = pd.DataFrame(arr3, index=arr2, columns=arr1)  
print(df)  
print("===================")  
'''  
    年龄 性别  
1号  19  男  
2号  20  女  
3号  21  男  
4号  22  女  
'''  
print(df.T)
</code></pre>
<p>除此之外，我们还可能会需要交换两行/两列的顺序，<strong>即上下翻转或者左右翻转，这个需要借助切片中step=-1即倒着采样的特性</strong></p>
<pre><code class="language-python">import pandas as pd  
arr1 = ["1号", "2号", "3号", "4号"]  
arr2 = ["年龄", "性别"]  
arr3 = [[19,20,21,22],["男","女","男","女"]]  
df = pd.DataFrame(arr3, index=arr2, columns=arr1)  
print(df)   
'''  
    年龄 性别  
1号  19  男  
2号  20  女  
3号  21  男  
4号  22  女  
'''  
print(df.T)  
df = df.T  
print("========接着上面的案例===========") 
print(df.iloc[:,::-1])#左右翻转 即性别和年龄列翻过来  
print(df.iloc[::-1,:])#上下翻转 1234变为4321
</code></pre>
<h2 data-heading="对象的重塑">对象的重塑</h2>
<p>因为对象比数组多了行列标签，所以原本的reshape()方法其实已经不适用。Pandas对象的重塑没那么灵活，但是在实际场景中用的也少。<br>我们可以直接通过<code>df[newKey]=newSr</code>的方式，给当前df对象直接新增一列，列的标签为newKey，列下面的values即newSr；通过<code>df.loc["一号"]=[...]</code>的方式新增一行</p>
<p>我们还可以进行Serise对象的相加，使用<code>pd.concat([sr1,sr2])</code><br>结果的key为两个key的并集，并且由于Pandas舍弃了字典和集合的不可重复性，所以concat后的结果是会有重复的<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129194856405.png" alt="image.png"><br>可以看到，这个方法是默认在行的基础上进行合并的(增加行数)，如果需要在列的基础上进行合并(增加列数)，则使用<code>pd.concat([sr1,sr2],axis=1)</code></p>
<p>如果需要不重复性，可以利用<code>.index.is_unique或.colomns.is_unique</code>返回是否为True来判断，如果为True则说明没有重复，如果为False则说明有重复需要去重</p>
<h2 data-heading="对象的计算">对象的计算</h2>
<p>如果我们现在需要对某一列的数据全部加10，那么可以直接<code>df[列名]=df[列名]+10</code>，其余计算操作一样的</p>
<p>如果我们需要进行对象和对象之间的计算，例如df1+df2<br>那么需要保证双方都是数字对象，两者可以维度不同，没有的数据NaN（相加的双方只要有一方没有那行，计算结果就会为NaN），有的数据相互加<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129200218249.png" alt="image.png"></p>
<p>并且，NumPy中的数学函数例如<code>np.cos()等</code>都可以直接作用于Pandas的对象中</p>
<pre><code class="language-python">import numpy as np  
import pandas as pd  
v1 = [ [10, '女'], [20, '男'], [30, '男'], [40, '女'] ]  
v2 = [ 1, 2 ,3, 6 ]  
i1 = [ '1 号', '2 号', '3 号', '4 号' ]; c1 = [ '年龄', '性别' ]  
i2 = [ '1 号', '2 号', '3 号', '6 号' ]; c2 = [ '牌照' ]  
df1 = pd.DataFrame( v1, index=i1, columns=c1 )  
df2 = pd.DataFrame( v2, index=i2, columns=c2 )  
df1['年龄'] = np.log(df1['年龄'])  
print(df1)
</code></pre>
<h2 data-heading="缺失值">缺失值</h2>
<p>Serise和DataFrame对象都能使用<code>.isnull()</code>方法检查是否为缺失值，如果要检测是否不为缺失值，可以使用<code>.notnull() 或者 ~(.isnull())取反操作</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129201004244.png" alt="image.png"><br>对于Serise对象，我们可以直接使用<code>.dropna()</code>方法去除缺失值，对于DataFrame对象，要么去除含有NaN的那一行或者那一列，使用<code>df.dropna()</code>默认是剔除那一行，如果需要去除那一列，需要指定axis参数<code>df.dropna(axis="columns")</code>，一般我们都是去一行，因为一行才是个体，列是特征<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129201151537.png" alt="image.png"></p>
<p>当然，如果因为一个就删除一行可能过于激进，我们可以使用<code>df.dropna(how='all')</code>这种方式，只有当这一行/列全部为NaN的时候才会被去除</p>
<p>如果需要填充缺失值，可以使用<code>sr.fillna(x)和df.fillna(x)</code>，会使用X将NaN进行填充；<br>搭配np的一些聚合函数，例如<code>.fillna(np.mean(sr/df))</code>则可以实现均值填充；<br><code>.fillna(method='ffill')</code>则可以实现用上一值/前值进行填充<br><code>.fillna(method='bfill')</code>则可以实现用下一值/后值进行填充</p>
<h2 data-heading="操作Excel/CSV和数据分析">操作Excel/CSV和数据分析</h2>
<p>我们一般还是使用CSV,需要注意的是，如果你的文档数据里面没有行列标签，<strong>那么你需要手动补上行标签和列标签</strong><br>当我们需要导入CSV的时候，可以使用<code>pd.read_csv(path,index_col=0)</code>,第二个参数啥意思暂且不需要在意，需要注意的是，<strong>Pandas可能会要求CSV文件为utf-8的格式才能解析</strong></p>
<p>当我们读取到的CSV比较大，得到df对象也比较大，我们可以使用<code>df.head()</code>只打印前五行的内容，然后就能用NumPy的方法，因为Pandas将其变为了对象的方法，所以也可以直接用<code>df对象直接调用一些聚合函数,从而不需要导入Numpy包，且这些函数都属于安全版本，即都会忽略NaN</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129203322910.png" alt="image.png"></p>
<p>可以看到，如果每次数据分析我们都需要来回调用<code>df.max;df.min;df.mean</code>这些聚合函数，显得十分繁琐和冗余，所以有一个<code>.describe()方法</code>能直接查看所有相关的聚合信息，可以看到这个函数会忽略字符串列（因为字符串没有计算意义）<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129203646090.png" alt="image.png"></p>
<h2 data-heading="数据透视">数据透视</h2>
<p>数据透视对于数据分析极其的重要<br>如下 我们这里有一个泰坦尼克号的人员信息和生还信息名单，我现在需要考察<code>前面的其他特征 和 考察核心即是否生还 之间的关系</code>（这个可以类比神经网络，是否生还即为输出，前面的为输入）<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129204033931.png" alt="image.png"></p>
<p>对上述进行数据透视，需要借助<code>.pivot_table方法</code>，如下，传入两个参数<code>第一个为考察核心，第二个为参考特征</code>，整个方法就是考察性别如何影响生还率的，看到输出知道，女性生还率70%+<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129204336769.png" alt="image.png"><br>除此之外，还可以指定一个<code>columns参数作为第二个参考特征</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129204642260.png" alt="image.png"><br>在这个函数的入参中，有一个<code>aggfunc参数，默认为mean</code>，除此之外，所有的聚合函数都可以作为其入参，会影响其计算，例如我们可以<code>df.pivot_table('是否生还',index='性别',aggfunc='max')</code>，当然，在我们目前的案例中，mean平均值是最佳选择</p>
<h3 data-heading="多特征的数据透视">多特征的数据透视</h3>
<p>上述的操作我们只引用了性别和船舱两个参考特征，如果我们还需要引入更多的特征，例如表格中的年龄和费用。<br>可以观察到，这两个列的数据都十分的分散，如果需要加入分类，会导致dt对象横向和纵向索引都会很大，那么这个时候就不能再按类别分了，此时需要借助<code>pd.cut()和pd.qcut()</code><br>例如年龄，我们可以不一岁一岁的进行分类，而是按照范围进行分</p>
<pre><code class="language-python">age = pd.cut(df['年龄'],[0,18,120])
</code></pre>
<p>上述代码利用cut方法就将年龄分为了0-18;18-120两个区间，此时我们再将其加入到<code>pivot_table函数中进行计算</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129205702911.png" alt="image.png"><br>我们还可以加入费用，可以看到，<code>pd.cut可以让我们自己决定如何分隔数据，pd.qcut()只需要给出希望分成多少份，函数能自己帮我们分</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129205754530.png" alt="image.png"></p>
<h1 data-heading="Matplotlib">Matplotlib</h1>
<h3 data-heading="简单使用">简单使用</h3>
<p>Matplotlib本身库比较大，我们一般也只需要用到核心模块matplotlib.pyplot,</p>
<pre><code class="language-python">import matplotlib.pyplot as plt
</code></pre>
<p>我们先来尝试一下最简单的画图,这里使用<code>.plot(x,y)</code>函数</p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
fig1 = plt.figure()  #创建新图窗
x = [1,2,3,4,5]  #数据的X值
y = [1,4,9,16,25]  #数据的Y值
plt.plot(x,y)  #.plot(x,y)函数，先描点再画线
plt.show()
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129212344693.png" alt="image.png"><br>可以看到这个图虽然出来了，但是不是很高清，我们可以通过添加另外一个库让图片更加清晰</p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
from matplotlib_inline import backend_inline  
backend_inline.set_matplotlib_formats('svg')  
fig1 = plt.figure()  
x = [1,2,3,4,5]  
y = [1,4,9,16,25]  
plt.plot(x,y)  
plt.show()
</code></pre>
<h3 data-heading="保存图片">保存图片</h3>
<p>保存图片需要使用<code>.savefig()</code>，入参里面填入要保存的绝对路径和图形名以及后缀，需要注意这里要有个r字符串,即<code>r'绝对路径\图形名.后缀 </code>，这个后缀，为了保存清晰的图片，我们基本都推荐保存svg矢量格式的图片<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129213424058.png" alt="image.png"></p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
# 移除 matplotlib_inline 相关代码，因为它们仅适用于 Jupyter 环境  
fig1 = plt.figure()  
x = [1,2,3,4,5]  
y = [1,4,9,16,25]  
plt.plot(x,y)  
plt.show()  
fig1.savefig('fig1.png')
</code></pre>
<h3 data-heading="两种画图方式">两种画图方式</h3>
<p>Matplotlib中有两种画图方式：Matlab方式和面向对象的方式，我们上面展示的是前者<br>两种方式都可以完成需求并且大部分时候两者能互相转换，一般建议自己使用Matlab，但是面向对象的方式也要知道，遇到别人的工程这么写的时候能知道<br>如下对比知道，面向对象的方式先调用了一次<code>axes()方法</code>，fig是整个窗口，axes是窗口内画图的部分，也就是坐标轴<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129213851871.png" alt="image.png"></p>
<h3 data-heading="图窗与坐标轴">图窗与坐标轴</h3>
<p>图形窗口（figure）在 Matlab 中会单独弹出，该窗口中可容纳元素，也可以是空的窗口。画图之前记得手动创建一下Fig对象，虽然例如使用Jupyter中不一定看得到，但是如果有保存图片的需求，这个Fig对象是必不可少的<br>坐标轴（axes）是一个矩形，其下方是 x 轴的数值与刻度，左侧是 y 轴的数值与刻度。</p>
<h3 data-heading="多图形的绘制">多图形的绘制</h3>
<p>如上图，我们只是划了一条线，我们想要在一个窗口内画多个该如何做？很简单，就是利用plt对象多调用几次plot函数即可</p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
fig1 = plt.figure()  
x = [ 1, 2, 3, 4, 5 ] # 数据的 x 值  
y1 = [ 1, 2, 3, 4, 5 ] # 数据的 y1 值  
y2 = [ 0, 0, 0, 0, 0 ] # 数据的 y2 值  
y3 = [ -1, -2, -3, -4, -5 ] # 数据的 y3 值  

# 使用Matlab的方式绘制  
plt.plot(x,y1)  
plt.plot(x,y2)  
plt.plot(x,y3)  
plt.show()  

# 使用面向对象的方式绘制  
ax1 = plt.axes()  
ax1.plot(x,y1)  
ax1.plot(x,y2)  
ax1.plot(x,y3)
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129214543590.png" alt="image.png"></p>
<h3 data-heading="绘制多个子图">绘制多个子图</h3>
<p>在绘制多个子图上，两种方式可能区别会大一点<br>首先，两种方式都是借助<code>plt.subplot()方法</code><br>可以看到，使用Matlab的方式绘制，就是依次交替使用<code>subplot()方法和plot()方法,且需要传入三个参数</code> ；<br>而使用面向对象的方式，需要向<code>plt.subplot(x) </code>传入一个参数x代表需要几个子图，然后会<code>返回Fig对象和ax数组对象</code>，需要注意，这里会返回Fig对象，就不需要再次使用<code>plt.figure()</code>了，然后因为ax反参是一个数组，数组的长度就是子图的个数，代表每个坐标系，所以我们只需要遍历并且都调用<code>plot方法即可</code>,我们之后都可以利用<code>plt.subplot()代替plt.figure()和plt.axes()</code></p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
fig1 = plt.figure()  
x = [ 1, 2, 3, 4, 5 ] # 数据的 x 值  
y1 = [ 1, 2, 3, 4, 5 ] # 数据的 y1 值  
y2 = [ 0, 0, 0, 0, 0 ] # 数据的 y2 值  
y3 = [ -1, -2, -3, -4, -5 ] # 数据的 y3 值  

# 使用Matlab的方式绘制
# 传入三个参数 分别代表 需要 3行1列个子图，当前是子图的第一个
plt.subplot(3,1,1)  
plt.plot(x,y1)  
plt.subplot(3,1,2)  
plt.plot(x,y2)  
plt.subplot(3,1,3)  
plt.plot(x,y3)  

# 使用面向对象的方式绘制  
fig2,ax2 = plt.subplot(3)  
ax2[0].plot(x,y1)  
ax2[1].plot(x,y2)  
ax2[2].plot(x,y3)
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129215637303.png" alt="image.png"></p>
<h3 data-heading="图表类型">图表类型</h3>
<p><code>plt</code>提供了五类基本的图表类型<code>二维图;网络图;统计图;轮廓图;三维图</code><br>我们这里只看深度学习可能会要用到的，如果遇到了不会的，可以直接去官方现场学<br><a class="external-link" href="https://matplotlib.org/stable/plot_types/index" target="_blank" rel="noopener nofollow" aria-label="https://matplotlib.org/stable/plot_types/index" data-tooltip-position="top">Plot types &mdash; Matplotlib 3.10.8 documentation</a><br>作者也还准备了许多提前画好的图，我们可以借鉴和学习<a class="external-link" href="https://matplotlib.org/stable/gallery/index.html" target="_blank" rel="noopener nofollow" aria-label="https://matplotlib.org/stable/gallery/index.html" data-tooltip-position="top">Examples &mdash; Matplotlib 3.10.8 documentation</a></p>
<h4 data-heading="二维图">二维图</h4>
<p>二维图，只需要给定两个向量即可画图，其中线型图其实可以代替其余大部分的二维图，画对应图就使用名字旁对应的函数，我们这里只会介绍线型图Plot<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129222126087.png" alt="image.png"><br>我们这里只学习<code>plot()</code><br>首先对于<code>plot()</code>的基本使用我们已经了解，最基本的需要两个入参<code>plot(x,y)代表横坐标和纵坐标</code><br><strong>如果需要使用颜色</strong>，可以添加入参<code>color,例如plt.plot(x,y,color="#7CB5EC")</code>，需要注意，这个color是<code>十六进制的方式进行获取和输入，可以通过其余网站或者QQ截图的方式获取</code></p>
<pre><code class="language-python">#绘制不同颜色的二维图
import matplotlib.pyplot as plt  
fig1 = plt.figure()  
x = [ 1, 2, 3, 4, 5 ] # 数据的 x 值  
y1 = [ 0, 1, 2, 3, 4 ] # 数据的 y1 值  
y2 = [ 1, 2, 3, 4, 5 ] # 数据的 y2 值  
y3 = [ 2, 3, 4, 5, 6 ] # 数据的 y3 值  
y4 = [ 3, 4, 5, 6, 7 ] # 数据的 y4 值  
y5 = [ 4, 5, 6, 7, 8 ] # 数据的 y5 值  

plt.plot(x, y1, color='#7CB5EC')  
plt.plot(x, y2, color='#F7A35C')  
plt.plot(x, y3, color='#A2A2D0')  
plt.plot(x, y4, color='#F6675D')  
plt.plot(x, y5, color='#47ADC7')  
plt.show()

# 使用面向对象的方式
Fig2, ax2 = plt.subplots()
ax2.plot(x, y1, color='#7CB5EC')
ax2.plot(x, y2, color='#F7A35C')
ax2.plot(x, y3, color='#A2A2D0')
ax2.plot(x, y4, color='#F6675D')
ax2.plot(x, y5, color='#47ADC7')
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129223014835.png" alt="image.png"></p>
<p><strong>如果需要设置线条样式</strong>，可以使用<code>linestyle入参,例如plt.plot(x, y1, linestyle='-')</code>，这个参数默认就是<code>-表示实线</code>，除此之外我们一般还可以设置<code>:表示点线</code>,<code> -.表示虚线</code>,<code>--表示虚线</code>,<code> 表示隐藏该线条</code>等等</p>
<pre><code class="language-python">
import matplotlib.pyplot as plt  
fig1 = plt.figure()  
x = [ 1, 2, 3, 4, 5 ] # 数据的 x 值  
y1 = [ 0, 1, 2, 3, 4 ] # 数据的 y1 值  
y2 = [ 1, 2, 3, 4, 5 ] # 数据的 y2 值  
y3 = [ 2, 3, 4, 5, 6 ] # 数据的 y3 值  
y4 = [ 3, 4, 5, 6, 7 ] # 数据的 y4 值  
y5 = [ 4, 5, 6, 7, 8 ] # 数据的 y5 值  

plt.plot(x, y1, linestyle='-')  
plt.plot(x, y2, linestyle='--')  
plt.plot(x, y3, linestyle='-.')  
plt.plot(x, y4, linestyle=':')  
plt.plot(x, y5, linestyle=' ')  
plt.show()

# 面向对象方式
Fig2, ax2 = plt.subplots()
ax2.plot(x, y1, linestyle='-')
ax2.plot(x, y2, linestyle='--')
ax2.plot(x, y3, linestyle='-.')
ax2.plot(x, y4, linestyle=':')
ax2.plot(x, y5, linestyle=' ')
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129223547189.png" alt="image.png"></p>
<p><strong>如果需要设置粗细</strong>，可以使用<code>linewidth,例如plt.plot(x, y1, linewidth=0.5)</code>，一般我们设置0.5-3的范围就差不多了<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129223836238.png" alt="image.png"></p>
<p><strong>如果需要标记节点</strong>，可以使用<code>marker,例如plt.plot(x, y1, marker='.')</code>，如果还需要修改标记的大小，可以使用<code>markersize</code>,我们一般设置为3-9的大小差不多<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129224016090.png" alt="image.png"></p>
<p>那么综上，我们可以一起用起来，如下图所示<br>此时可以关注一下线条五，可以看到线条五<code>linestyle=''并且marker='o' </code>，这个就是隐藏线条的最大作用，<strong>可以模拟出二维图中的散点图即plt.scatter()并且效率更高，scatter是一个一个点单独渲染，plot是整体渲染且格式整齐</strong><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260129224132353.png" alt="image.png"><br>还可以将上述属性综合起来写在第三个参数上，例如</p>
<pre><code class="language-python"># 如下第三个参数bs-  b表示颜色为blue s表示使用正方形标记节点  -表示线条样式
plt.plot(train_process['epoch'], train_process.val_acc_all, "bs-", label="Val acc")
</code></pre>
<h4 data-heading="网格图">网格图</h4>
<p>网格图我们只介绍<code>imshow()函数</code>，其余两个在深度学习中用处不大<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130100659956.png" alt="image.png"></p>
<p><code>imshow()</code>的功能是<code>绘制图像</code>，可以接受二维数组（灰度图）或者三维数组（彩色图）等将其作为图像数据渲染成可视化图像，在绘制图片、热力图、卷积神经网络的特征图等基于像素 / 数组的可视化中都有应用，<code>执行之后不会有任何窗口弹出，如下案例代码弹窗是show()函数的功能</code></p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
import numpy as np  
x = np.linspace(0,10,1000)  
# 1000 行 &times;1 列的彩色竖条热力图，数据基于 `sin(x)*cos(x)` 的周期性波动
l = np.sin(x)*np.cos(x).reshape(-1,1)  
fig1 = plt.figure()  
plt.imshow(l)  # imshow函数负责将l这个二维数组绘画出来
plt.show() # 图片展示功能是show()的作用 如果没有这一行 不会弹窗的

# 面向对象的方式
plt.subplots()
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130101627566.png" alt="image.png"><br>除此之外，所有的网格图都可以配置加一个颜色条，但是这种功能在面向对象的方式中是缺失的，所以我们只能通过Matlab的方式中进行操作</p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
import numpy as np  
x = np.linspace(0,10,1000)  
l = np.sin(x)*np.cos(x).reshape(-1,1)  
fig1 = plt.figure()  
plt.imshow(l)  
plt.colorbar()  
plt.show()
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130102915994.png" alt="image.png"></p>
<h4 data-heading="统计图">统计图</h4>
<p>在深度学习领域，我们一般只要用到<code>直方图，其余的都是数据分析领域的，我们这里也只学习hist()函数</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130103031739.png" alt="image.png"></p>
<p>需要注意的是，<code>条形图bar和直方图hist不要弄混，条型图bar可以被plot替代，hist则是统计学中的函数，为了看清楚分布的均值与标准差</code><br>先来看看其简单的使用，传入样本数组，即可进行绘制，如下图，利用NumPy生成符合正态分布的100个样本data，然后将其传入到<code>hist(data)</code>中</p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
import numpy as np  
arr = np.random.randn(100)  
print(arr)  
fig1 = plt.figure()  
plt.hist(arr)  
plt.show()
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130103943547.png" alt="image.png"></p>
<p><strong>可以通过bins参数设置区间划分的数量</strong>，默认是10，也就是10条柱，可以修改为20试试<br><code>plt.hist(data,bins=20)或者ax.hist(data,bins=20)</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130104506718.png" alt="image.png"></p>
<p><strong>可以通过alpha参数设置透明度</strong>，alpha参数的默认为1，我们可以修改为0.5试试<br><code>plt.hist(data,alpha=0.5)/ax.hist(data,alpha=0.5)</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130104749060.png" alt="image.png"></p>
<p><strong>可以通过histtype设置图表类型</strong>，默认为<code>bar</code>，可以从<code>["bar", "barstacked", "step", "stepfilled"]</code>中进行选择，例如<code>plt.hist(data,histtype="step")/ax.hist(data,histtype="step")</code>就是设置为无填充的样式<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130105754147.png" alt="image.png"></p>
<p><strong>可以通过color设置直方图颜色</strong>，和plot()的颜色设置一样，采用十六进制颜色设置<code>plt.hist(arr,bins=20,alpha=0.5,histtype="step",color="#14C468")</code>设置为绿色<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130110307747.png" alt="image.png"></p>
<p><strong>通过edgecolor设置直方图边缘颜色，即两两直方柱之间的边缘部分的颜色</strong>。例如<code>plt.hist(data,edgecolor="#191A1C")</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130110629737.png" alt="image.png"></p>
<h3 data-heading="图床属性">图床属性</h3>
<h4 data-heading="坐标轴上下限设置">坐标轴上下限设置</h4>
<p>尽管 Matplotlib 会自动调整图窗为最佳的坐标轴上下限，但叛逆的我们知道，很多时候仍需手动设置，才能适应当时的情况。<br>这里有两种方式<code>lim</code>和<code>axis</code></p>
<h5 data-heading="lim法">lim法</h5>
<p>lim法分为了<code>xlim(start，end)和ylim(start,end),分别用于设置X轴和Y轴的上下限</code>，我们可以看到，如下是没有指定的时候，自动给我们配置的XY轴情况<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130112127579.png" alt="image.png"><br>我们自己指定后展示如下，需要注意的是，plt和ax对于这个方法相对调用稍微不太一样<code>plt.xlim()/ax.set_xlim()</code></p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
fig = plt.figure()  
x = [1,2,3,4,5,6]  
y = [1,16,81,100,125,180]  
plt.plot(x,y)  
plt.xlim(1,7)  
plt.ylim(1,180)  
plt.show()

# 面向对象方式（lim 法）
Fig2, ax2 = plt.subplots()
ax2.plot(x,y)
ax2.set_xlim(1,5)
ax2.set_ylim(1,125)
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130112248355.png" alt="image.png"></p>
<h5 data-heading="axis法">axis法</h5>
<p><code>axis</code>主要是入参上和lim法不一样，传入一个lenth=4的列表，分别为[x.start , x.end , y.start , y.end ]，除此之外，还可以使用<code>axis('equal')让x轴和y轴比例达到1:1</code></p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
fig = plt.figure()  
x = [1,2,3,4,5,6]  
y = [1,16,81,100,125,180]  
plt.plot(x,y)  
plt.axis([1,7,1,180])  
plt.show()
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130112746968.png" alt="image.png"></p>
<h4 data-heading="标题与轴名称">标题与轴名称</h4>
<p>这里也是Matlab方式和面向对象的方式最后的一个区别<br>我们会需要给X轴还有Y轴还有整个图像命名，这个时候就需要借助<code>plt.title设置标题</code>;<code>plt.xlabel设置X轴名字</code>;<code>plt.ylabel设置Y轴名字</code><br>这里顺便汇总一些Matlab和面向对象的一些方法区别汇总<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130120007956.png" alt="image.png"></p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
fig = plt.figure()  
x = [1,2,3,4,5,6]  
y = [1,16,81,100,125,180]  
plt.plot(x,y)  
plt.title('Plot Title')  
plt.xlabel('X Axis')  
plt.ylabel('Y Axis')  
plt.show()
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130120357279.png" alt="image.png"><br>额外提一下，面向对象方式统一了五个函数，所以我们可以写成<br><code>ax.set(xlim=(),ylim(),title="",xlabel="",ylabel="")</code></p>
<h3 data-heading="图例">图例</h3>
<p>图例一般只会出现在二维图和统计图中，网格图一般使用颜色条<br>图例主要就是使用<code>label</code>参数，如果你想要这个线条有图例就加入label参数，反之如果不想要就不加即可,默认展示在左上角<br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130121323115.png" alt="image.png"><br>还有一种就是使用<code>plt.legend()</code>函数，给其传入一个数组，数组一一对应前面你的线条，同时<code>legend</code>函数还有三个常用的关键字参数<br>第一个 <code>loc</code>设置图例位置，可选<code>upper;center;lower</code>和<code>left;center,right</code>两组中分别选一个，那么就可使用<code>legend(data,loc='upper right')</code>，也可以直接使用<code>legend(data,loc='best')</code>程序自行调整为最佳<br>第二个 <code>frameon</code>图例边框，默认True，为显示；如果不想要边框则设置为False<br>第三个 <code>ncol</code>用于设置图例的列数，默认为1即所有图例都一列展示，可以根据需求设置为多列，例如设置为两列<code>ncol=2</code><br><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130121552423.png" alt="image.png"></p>
<h3 data-heading="网格">网格</h3>
<p>可以给图形背景上网格，主要利用<code>plt.grid()</code>函数<br>该函数有两个参数<code>color设置颜色以及linestyle设置线条格式</code></p>
<pre><code class="language-python">import matplotlib.pyplot as plt  
fig = plt.figure()  
x = [1,2,3,4,5,6]  
y = [1,16,81,100,125,180]  
plt.plot(x,y)  
plt.title('Plot Title')  
plt.xlabel('X Axis')  
plt.ylabel('Y Axis')  
plt.grid(linestyle='--')  
plt.show()
</code></pre>
<p><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260130123018253.png" alt="image.png"></p>]]></description>
    <pubDate>Fri, 29 May 2026 22:45:41 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=59</guid>
</item>
<item>
    <title>Python深度学习入门-day01-Python语法基础</title>
    <link>http://8.154.40.120:24180/?post=57</link>
    <description><![CDATA[<div>
<blockquote>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">主要的参考资料来源</span> <span class="md-plain">《深度学习入门：基于Python的理论与实现 (斋藤康毅) 》（鱼书）</span> <span class="md-plain">B站Up主爆肝杰哥的相关视频</span> <span class="md-plain">主要考虑到是入门以及快速能上手工程，没有基础，不能在数学等其余问题上过多的纠结，本身掌握程度不够，所以不会以李沐老师的课成为第一课程，后续才会去看</span> <span class="md-plain">此外，因为是为了深度学习才接触的Python，并非专攻Python方向，所以对于Python的很多其他的特性也不会涉及，一切以深度学习的需要进行学习</span></p>
</blockquote>
<h1 class="md-end-block md-heading"><span class="md-plain">前言</span></h1>
<p class="md-end-block md-p"><span class="md-plain">在深度学习领域，我们主要是利用Python以及几个库进行，分别是</span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>NumPy</code></span><span class="md-plain">-为Python加上了关键的数组变量类型</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>Pandas</code></span><span class="md-plain">-在NumPy的基础上添加了类似Excel的行列标签</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>Matplotlib</code></span><span class="md-plain">-借鉴</span><span class="md-pair-s" spellcheck="false"><code>Matlab</code></span><span class="md-plain">，为Python提供了绘图能力</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>Scikit-learn</code></span><span class="md-plain">-机器学习库，包含了分类，回归，聚类，将为等多种算法</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>Tensorflow</code></span><span class="md-plain">-Goole研发的深度学习框架</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>PyTorch</code></span><span class="md-plain">-FaceBook研发的深度学习框架</span></p>
</li>
</ul>
<h2 class="md-end-block md-heading"><span class="md-plain">深度学习的相关概念</span></h2>
<p class="md-end-block md-p"><span class="md-plain">大家可能听说过人工智能；机器学习；神经网络；深度学习等多个专有名词，实际上这几个都是有联系的</span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">人工智能是最大的一个概念，最顶层的，其中一个重要的分支就是机器学习</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">机器学习的算法多种多样，其中核心就是神经网络</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">神经网络的隐藏层如果够深，就可以被称之为深层神经网络，这也属于我们所说的深度学习</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">深度学习包括了深度神经网络；卷积神经网络；循环神经网络</span></p>
</li>
</ul>
<h1 class="md-end-block md-heading"><span class="md-plain">变量类型与输出语句</span></h1>
<h2 class="md-end-block md-heading"><span class="md-plain">总览</span></h2>
<p class="md-end-block md-p"><span class="md-plain">学习Python首先需要注意如下几点</span></p>
<ol class="ol-list" start="">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Python的注释是采用</span><span class="md-pair-s" spellcheck="false"><code>#</code></span><span class="md-plain">而不是 </span><span class="md-pair-s" spellcheck="false"><code>//</code></span><span class="md-plain">，多行注释要采用三个双引号</span><span class="md-pair-s" spellcheck="false"><code>"""..."""</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>Python是动态语言类型不是静态</code></span><span class="md-plain">，在声明变量的时候不需要指定变量类型，</span><span class="md-pair-s" spellcheck="false"><code>如果需要指定数据类型，可以使用类型注解</code></span><span class="md-plain">（但是即使使用，也只是作为提示作用，不会受到编译器强制校验）</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Python</span><span class="md-pair-s" spellcheck="false"><code>没有末尾分号</code></span><span class="md-plain">，但是有比较烦人的</span><span class="md-pair-s" spellcheck="false"><code>缩进</code></span><span class="md-plain">，所以不要在Python中随便使用空格</span></p>
</li>
</ol>
<p class="md-end-block md-p"><span class="md-plain">首先我们来看看如何定义一个变量</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span class="cm-comment"># 直接声明和初始化即可</span><br><span role="presentation"><span class="cm-variable">a</span> <span class="cm-operator">=</span> <span class="cm-number">2</span> &nbsp;</span><br><span class="cm-comment"># 如果需要指定类型 可以使用类型注解，即name:type的形式</span><br><span role="presentation"><span class="cm-variable">b</span>:<span class="cm-builtin">int</span> <span class="cm-operator">=</span> <span class="cm-number">2</span> &nbsp;</span><br><span class="cm-comment"># 但是类型注解只是起到提示的作用，编译器并不会校验，所以如下代码也不会报错</span><br><span role="presentation"><span class="cm-variable">c</span>:<span class="cm-builtin">int</span> <span class="cm-operator">=</span> <span class="cm-string">"abc"</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">Python原生的数据类型分为两大类：</span><span class="md-pair-s" spellcheck="false"><code>基本变量类型</code></span><span class="md-plain">和</span><span class="md-pair-s" spellcheck="false"><code>高级变量类型</code></span><span class="md-plain">，一共七种数据类型</span> <span class="md-pair-s" spellcheck="false"><code>基本数据类型包括：字符串，数字，布尔类型</code></span><span class="md-plain"> ； </span><span class="md-pair-s" spellcheck="false"><code>高级变量类型包括：集合，元组，列表，字典</code></span> <span class="md-plain">七种类型展示如下，需要注意一下几点</span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Python不能够声明但是不赋值，这是因为在Python中变量只有被赋值的时候才是真正被创建，在其他语言中，例如Java中允许</span><span class="md-pair-s" spellcheck="false"><code>int a;</code></span><span class="md-plain">这是因为在Java中声明即创建了对应的内存空间，a已经存在只是没有数值</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Python中的布尔类型严格遵循大小写，只能是</span><span class="md-pair-s" spellcheck="false"><code>True</code></span><span class="md-plain">和</span><span class="md-pair-s" spellcheck="false"><code>False</code></span><span class="md-plain">，而不能是</span><span class="md-pair-s" spellcheck="false"><code>true</code></span><span class="md-plain">或</span><span class="md-pair-s" spellcheck="false"><code>false</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Python四大高级变量类型，</span><span class="md-pair-s" spellcheck="false"><code>set集合是使用大括号{} ； list列表使用中括号[] ； tuple元组使用的小括号() ； dict字典是大括号但为Key-Value的形式</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">list集合：</span><span class="md-pair-s" spellcheck="false"><code>有序</code></span><span class="md-plain">，</span><span class="md-pair-s" spellcheck="false"><code>支持下标访问</code></span><span class="md-plain">，</span><span class="md-pair-s" spellcheck="false"><code>地址不可变但是内容可变</code></span><span class="md-plain">，</span><span class="md-pair-s" spellcheck="false"><code>支持增删改</code></span><span class="md-plain">，</span><span class="md-pair-s" spellcheck="false"><code>允许重复</code></span><span class="md-plain">，空列表使用</span><span class="md-pair-s" spellcheck="false"><code>[]或者list()</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">tuple元组：</span><span class="md-pair-s" spellcheck="false"><code>有序</code></span><span class="md-plain">，同列表</span><span class="md-pair-s" spellcheck="false"><code>可下标访问</code></span><span class="md-plain">；</span><strong><span class="md-plain">不可变，一旦确认不能修改，不能增删改</span></strong><span class="md-plain">；</span><span class="md-pair-s" spellcheck="false"><code>允许重复</code></span><span class="md-plain">；空元组使用</span><span class="md-pair-s" spellcheck="false"><code>()或者tuple()</code></span><span class="md-plain">；</span><strong><span class="md-plain">如果是只有一个元素的元组创建，必须加一个逗号(1,)</span></strong></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">set集合：</span><span class="md-pair-s" spellcheck="false"><code>无序</code></span><span class="md-plain">,不能下标查找；</span><span class="md-pair-s" spellcheck="false"><code>可变，支持增删，但不能修改已有元素</code></span><span class="md-plain">；</span><strong><span class="md-plain">不可重复，自动去重</span></strong><span class="md-plain">；空集合使用</span><span class="md-pair-s" spellcheck="false"><code>set()</code></span><span class="md-plain">；</span><strong><span class="md-plain">支持集合运算符交集(&amp;),并集( | ),差集(-)</span></strong><span class="md-plain">等</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">dict字典：</span><strong><span class="md-plain">3.7版本及其以上默认按照插入顺序排序；3.7之前无序</span></strong><span class="md-plain"> ； 可变 </span><span class="md-pair-s" spellcheck="false"><code>Key唯一，重复会覆盖原本的</code></span><span class="md-plain"> ； Value可重复 ；</span><span class="md-pair-s" spellcheck="false"><code>Key必须是不可变类型(例如int,str,tuple；列表/集合/字典就不能作为Key)</code></span><span class="md-plain"> ； 空字典使用</span><span class="md-pair-s" spellcheck="false"><code>dict()或者{}</code></span><span class="md-plain">，只能通过键值对访问而不能通过索引访问</span></p>
</li>
</ul>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span class="cm-comment"># 声明但不赋值 这个在python中是不允许的</span><br><span role="presentation"><span class="cm-variable">s</span>:<span class="cm-builtin">int</span></span><br><span role="presentation">​</span><br><span class="cm-comment"># 基本变量类型 &nbsp;</span><br><span role="presentation"><span class="cm-variable">s</span> <span class="cm-operator">=</span> <span class="cm-string">"zeal"</span> <span class="cm-comment"># 字符串 &nbsp;</span></span><br><span class="cm-comment"># 数字类型 &nbsp;</span><br><span role="presentation"><span class="cm-variable">age</span> <span class="cm-operator">=</span> <span class="cm-number">22</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">height</span> <span class="cm-operator">=</span> <span class="cm-number">1.75</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">weight</span> <span class="cm-operator">=</span> <span class="cm-number">120.0</span> &nbsp;</span><br><span class="cm-comment"># 布尔类型 &nbsp;</span><br><span role="presentation"><span class="cm-variable">bool_t</span> <span class="cm-operator">=</span> <span class="cm-keyword">True</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">bool_f</span> <span class="cm-operator">=</span> <span class="cm-keyword">False</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 高级变量类型 &nbsp;</span><br><span class="cm-comment">#集合set 利用大括号包裹{} &nbsp;</span><br><span role="presentation"><span class="cm-variable">set_v</span> <span class="cm-operator">=</span> {<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>} &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">set_v</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment cm-error"># 元组tuple 利用 小括号包裹() &nbsp;</span><br><span role="presentation"><span class="cm-variable">tuple_v</span> <span class="cm-operator">=</span> (<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">tuple_v</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 列表 &nbsp;</span><br><span role="presentation"><span class="cm-variable">list_v</span> <span class="cm-operator">=</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-number">4</span>,<span class="cm-number">5</span>] &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 字典dict &nbsp;</span><br><span role="presentation"><span class="cm-variable">dict_v</span> <span class="cm-operator">=</span> {<span class="cm-string">"name"</span>:<span class="cm-string">"zeal"</span>,<span class="cm-string">"age"</span>:<span class="cm-number">22</span>} &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">dict_v</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260128152551255.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260128152551255.png" alt="image.png"></span></p>
<figure class="md-table-fig table-figure">
<table class="md-table">
<thead>
<tr class="md-end-block">
<th><span class="md-plain">特性</span></th>
<th><span class="md-plain">list（列表）</span></th>
<th><span class="md-plain">tuple（元组）</span></th>
<th><span class="md-plain">set（集合）</span></th>
<th><span class="md-plain">dict（字典）</span></th>
</tr>
</thead>
<tbody>
<tr class="md-end-block">
<td><span class="md-plain">有序性</span></td>
<td><span class="md-plain">是</span></td>
<td><span class="md-plain">是</span></td>
<td><span class="md-plain">否</span></td>
<td><span class="md-plain">3.7+是，3.6及以下否</span></td>
</tr>
<tr class="md-end-block">
<td><span class="md-plain">可变性</span></td>
<td><span class="md-plain">是（支持增/删/改元素）</span></td>
<td><span class="md-plain">否（创建后不可修改）</span></td>
<td><span class="md-plain">是（支持增/删元素，无修改）</span></td>
<td><span class="md-plain">是（支持增/删/改键值对）</span></td>
</tr>
<tr class="md-end-block">
<td><span class="md-plain">元素重复性</span></td>
<td><span class="md-plain">允许重复元素</span></td>
<td><span class="md-plain">允许重复元素</span></td>
<td><span class="md-plain">不允许重复元素（自动去重）</span></td>
<td><span class="md-plain">键唯一（重复覆盖），值允许重复</span></td>
</tr>
<tr class="md-end-block">
<td><span class="md-plain">访问方式</span></td>
<td><span class="md-plain">通过索引（下标）访问</span></td>
<td><span class="md-plain">通过索引（下标）访问</span></td>
<td><span class="td-span"><span class="md-plain">无索引（仅</span><span class="md-pair-s" spellcheck="false"><code>in</code></span><span class="md-plain">判断存在）</span></span></td>
<td><span class="md-plain">通过键（key）访问值</span></td>
</tr>
<tr class="md-end-block">
<td><span class="md-plain">定义符号</span></td>
<td><span class="td-span"><span class="md-pair-s" spellcheck="false"><code>[]</code></span><span class="md-plain">（空列表：</span><span class="md-pair-s" spellcheck="false"><code>[]</code></span><span class="md-plain">/</span><span class="md-pair-s" spellcheck="false"><code>list()</code></span><span class="md-plain">）</span></span></td>
<td><span class="td-span"><span class="md-pair-s" spellcheck="false"><code>()</code></span><span class="md-plain">（空元组：</span><span class="md-pair-s" spellcheck="false"><code>()</code></span><span class="md-plain">/</span><span class="md-pair-s" spellcheck="false"><code>tuple()</code></span><span class="md-plain">）</span></span></td>
<td><span class="td-span"><span class="md-pair-s" spellcheck="false"><code>{}</code></span><span class="md-plain">（空集合：</span><span class="md-pair-s" spellcheck="false"><code>set()</code></span><span class="md-plain">）</span></span></td>
<td><span class="td-span"><span class="md-pair-s" spellcheck="false"><code>{key: value}</code></span><span class="md-plain">（空字典：</span><span class="md-pair-s" spellcheck="false"><code>{}</code></span><span class="md-plain">/</span><span class="md-pair-s" spellcheck="false"><code>dict()</code></span><span class="md-plain">）</span></span></td>
</tr>
<tr class="md-end-block">
<td><span class="md-plain">键/元素要求</span></td>
<td><span class="md-plain">无特殊要求</span></td>
<td><span class="md-plain">无特殊要求</span></td>
<td><span class="md-plain">元素需为不可变类型</span></td>
<td><span class="md-plain">键需为不可变类型，值无限制</span></td>
</tr>
<tr class="md-end-block">
<td><span class="md-plain">核心用途</span></td>
<td><span class="md-plain">动态有序的序列数据</span></td>
<td><span class="md-plain">固定不变的有序数据</span></td>
<td><span class="md-plain">数据去重/集合运算</span></td>
<td><span class="md-plain">键值对映射（如用户信息、配置）</span></td>
</tr>
<tr class="md-end-block">
<td><span class="md-plain">查找效率</span></td>
<td><span class="md-plain">O(n)（按索引O(1)）</span></td>
<td><span class="md-plain">O(n)（按索引O(1)）</span></td>
<td><span class="td-span"><span class="md-plain">O(1)（</span><span class="md-pair-s" spellcheck="false"><code>in</code></span><span class="md-plain">判断）</span></span></td>
<td><span class="md-plain">O(1)（按键查找）</span></td>
</tr>
</tbody>
</table>
</figure>
<h2 class="md-end-block md-heading"><span class="md-plain">基本变量类型</span></h2>
<h3 class="md-end-block md-heading"><span class="md-plain">字符串类型</span></h3>
<p class="md-end-block md-p"><span class="md-plain">Python中字符串类型，也就是我们别的编程语言中说的String类型，</span><span class="md-pair-s" spellcheck="false"><code>可以使用双引号，也可以使用单引号</code></span><span class="md-plain">,支持两种方式的原因是为了保证当字符串本身有单引号或者双引号的时候可以灵活处理，如果字符串本身同时具备单引号和双引号，那么和别的编程语言一样，就需要使用转义了。</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span class="cm-comment"># 当变量中本身具备单引号 &nbsp;</span><br><span role="presentation"><span class="cm-variable">s1</span> <span class="cm-operator">=</span> <span class="cm-string">"I'm a student."</span> &nbsp;<span class="cm-comment"># 使用双引号包裹字符串 防止字符串本身的单引号截断 &nbsp;</span></span><br><span class="cm-comment"># 当变量中本身具备双引号 &nbsp;</span><br><span role="presentation"><span class="cm-variable">s2</span> <span class="cm-operator">=</span> <span class="cm-string">'He said, "I am a student."'</span> &nbsp;<span class="cm-comment"># 使用单引号包裹字符串 防止字符串本身的双引号截断 &nbsp;</span></span><br><span class="cm-comment"># 当变量中同时具备单引号和双引号 &nbsp;</span><br><span role="presentation"><span class="cm-variable">s3</span> <span class="cm-operator">=</span> <span class="cm-string">"He said, \"I'm a student.\""</span> &nbsp;<span class="cm-comment"># 使用转义字符 \ 来表示字符串中的双引号,当然，也可以转义单引号</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">当我们要想组合字符串类型的变量的时候，可以利用 </span><span class="md-pair-s" spellcheck="false"><code>f字符串</code></span><span class="md-plain">，将需要引入的字符串变量在f后的字符串中通过大括号{...}包裹起来从而实现引用，在输出中我们常这么使用</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">name</span> <span class="cm-operator">=</span> <span class="cm-string">"zeal"</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">love</span> <span class="cm-operator">=</span> <span class="cm-string">"play game"</span> &nbsp;</span><br><span class="cm-comment"># 组合字符串为一个新字符串</span><br><span role="presentation"><span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-string">f"My name is </span>{<span class="cm-variable">name</span>}<span class="cm-string"> and I love </span>{<span class="cm-variable">love</span>}<span class="cm-string">"</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">result</span>)</span><br><span class="cm-comment"># 直接在打印语句中组合字符串 &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"My name is </span>{<span class="cm-variable">name</span>}<span class="cm-string"> and I love </span>{<span class="cm-variable">love</span>}<span class="cm-string">"</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-plain">当然，</span><span class="md-pair-s" spellcheck="false"><code>print</code></span><span class="md-plain">还有另外一个用法，直接利用逗号</span><span class="md-pair-s" spellcheck="false"><code>,</code></span><span class="md-plain">进行拼接，但是因为默认</span><span class="md-pair-s" spellcheck="false"><code>,</code></span><span class="md-plain">会用一个空格隔开，我们可以在最后用</span><span class="md-pair-s" spellcheck="false"><code>sep=</code></span><span class="md-plain">设置间隔符从而实现无间隔拼接</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"my name is"</span>,<span class="cm-variable">name</span>,<span class="cm-string">"and age is"</span>,<span class="cm-variable">age</span>,<span class="cm-variable">sep</span><span class="cm-operator">=</span><span class="cm-string">" "</span>)</span></pre>
<h3 class="md-end-block md-heading"><span class="md-plain">数字类型</span></h3>
<p class="md-end-block md-p"><span class="md-plain">数字类型分为整型和浮点型，具体的区别我们在NumPy中再去介绍。</span> <span class="md-plain">Python中常见运算规则如下</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python " spellcheck="false"><span class="cm-comment"># 1. 算术运算符</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">a</span> <span class="cm-operator">=</span> <span class="cm-number">10</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">b</span> <span class="cm-operator">=</span> <span class="cm-number">3</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"a = </span>{<span class="cm-variable">a</span>}<span class="cm-string">, b = </span>{<span class="cm-variable">b</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"加法: a + b = </span>{<span class="cm-variable">a</span>}<span class="cm-string"> + </span>{<span class="cm-variable">b</span>}<span class="cm-string"> = </span>{<span class="cm-variable">a</span> <span class="cm-operator">+</span> <span class="cm-variable">b</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"减法: a - b = </span>{<span class="cm-variable">a</span>}<span class="cm-string"> - </span>{<span class="cm-variable">b</span>}<span class="cm-string"> = </span>{<span class="cm-variable">a</span> <span class="cm-operator">-</span> <span class="cm-variable">b</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"乘法: a * b = </span>{<span class="cm-variable">a</span>}<span class="cm-string"> * </span>{<span class="cm-variable">b</span>}<span class="cm-string"> = </span>{<span class="cm-variable">a</span> <span class="cm-operator">*</span> <span class="cm-variable">b</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span class="cm-comment">#需要注意python中的单个/是完整的除法 也就是会计算完</span><br><span class="cm-comment">#别的语言中 / 是整除，python中 // 才是整除</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"除法: a / b = </span>{<span class="cm-variable">a</span>}<span class="cm-string"> / </span>{<span class="cm-variable">b</span>}<span class="cm-string"> = </span>{<span class="cm-variable">a</span> <span class="cm-operator">/</span> <span class="cm-variable">b</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"整除: a // b = </span>{<span class="cm-variable">a</span>}<span class="cm-string"> // </span>{<span class="cm-variable">b</span>}<span class="cm-string"> = </span>{<span class="cm-variable">a</span> <span class="cm-operator">//</span> <span class="cm-variable">b</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"取余: a % b = </span>{<span class="cm-variable">a</span>}<span class="cm-string"> % </span>{<span class="cm-variable">b</span>}<span class="cm-string"> = </span>{<span class="cm-variable">a</span> <span class="cm-operator">%</span> <span class="cm-variable">b</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"幂运算: a ** b = </span>{<span class="cm-variable">a</span>}<span class="cm-string"> ** </span>{<span class="cm-variable">b</span>}<span class="cm-string"> = </span>{<span class="cm-variable">a</span> <span class="cm-operator">**</span> <span class="cm-variable">b</span>}<span class="cm-string">"</span>) &nbsp;<span class="cm-comment"># a的b次方,即a^b</span></span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 2. 比较运算符</span><br><span class="cm-comment"># 等于为== 不等于为!= 可以直接使用&gt;= 和 &lt;=</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">x</span> <span class="cm-operator">=</span> <span class="cm-number">15</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">y</span> <span class="cm-operator">=</span> <span class="cm-number">20</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"x = </span>{<span class="cm-variable">x</span>}<span class="cm-string">, y = </span>{<span class="cm-variable">y</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"等于: x == y -&gt; </span>{<span class="cm-variable">x</span>}<span class="cm-string"> == </span>{<span class="cm-variable">y</span>}<span class="cm-string"> = </span>{<span class="cm-variable">x</span> <span class="cm-operator">==</span> <span class="cm-variable">y</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"不等于: x != y -&gt; </span>{<span class="cm-variable">x</span>}<span class="cm-string"> != </span>{<span class="cm-variable">y</span>}<span class="cm-string"> = </span>{<span class="cm-variable">x</span> <span class="cm-operator">!=</span> <span class="cm-variable">y</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"大于: x &gt; y -&gt; </span>{<span class="cm-variable">x</span>}<span class="cm-string"> &gt; </span>{<span class="cm-variable">y</span>}<span class="cm-string"> = </span>{<span class="cm-variable">x</span> <span class="cm-operator">&gt;</span> <span class="cm-variable">y</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"小于: x &lt; y -&gt; </span>{<span class="cm-variable">x</span>}<span class="cm-string"> &lt; </span>{<span class="cm-variable">y</span>}<span class="cm-string"> = </span>{<span class="cm-variable">x</span> <span class="cm-operator">&lt;</span> <span class="cm-variable">y</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"大于等于: x &gt;= y -&gt; </span>{<span class="cm-variable">x</span>}<span class="cm-string"> &gt;= </span>{<span class="cm-variable">y</span>}<span class="cm-string"> = </span>{<span class="cm-variable">x</span> <span class="cm-operator">&gt;=</span> <span class="cm-variable">y</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"小于等于: x &lt;= y -&gt; </span>{<span class="cm-variable">x</span>}<span class="cm-string"> &lt;= </span>{<span class="cm-variable">y</span>}<span class="cm-string"> = </span>{<span class="cm-variable">x</span> <span class="cm-operator">&lt;=</span> <span class="cm-variable">y</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 3. 赋值运算符 (Assignment Operators)print("\n【3】赋值运算符 (Assignment Operators)") &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">c</span> <span class="cm-operator">=</span> <span class="cm-number">5</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"初始值: c = </span>{<span class="cm-variable">c</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">c</span> <span class="cm-operator">+=</span> <span class="cm-number">3</span> &nbsp; &nbsp; &nbsp;<span class="cm-comment"># c = c + 3 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"加法赋值: c += 3 -&gt; c = </span>{<span class="cm-variable">c</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">c</span> <span class="cm-operator">-=</span> <span class="cm-number">2</span> &nbsp; &nbsp; &nbsp;<span class="cm-comment"># c = c - 2 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"减法赋值: c -= 2 -&gt; c = </span>{<span class="cm-variable">c</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">c</span> <span class="cm-operator">*=</span> <span class="cm-number">4</span> &nbsp; &nbsp; &nbsp;<span class="cm-comment"># c = c * 4 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"乘法赋值: c *= 4 -&gt; c = </span>{<span class="cm-variable">c</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">c</span> <span class="cm-operator">/=</span> <span class="cm-number">2</span> &nbsp; &nbsp; &nbsp;<span class="cm-comment"># c = c / 2 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"除法赋值: c /= 2 -&gt; c = </span>{<span class="cm-variable">c</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">c</span> <span class="cm-operator">//=</span> <span class="cm-number">3</span> &nbsp; &nbsp; <span class="cm-comment"># c = c // 3 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"整除赋值: c //= 3 -&gt; c = </span>{<span class="cm-variable">c</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">c</span> <span class="cm-operator">%=</span> <span class="cm-number">4</span> &nbsp; &nbsp; &nbsp;<span class="cm-comment"># c = c % 4 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"取余赋值: c %= 4 -&gt; c = </span>{<span class="cm-variable">c</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">c</span> <span class="cm-operator">**=</span> <span class="cm-number">3</span> &nbsp; &nbsp; <span class="cm-comment"># c = c ** 3 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"幂运算赋值: c **= 3 -&gt; c = </span>{<span class="cm-variable">c</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 4. 位运算符 </span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">m</span> <span class="cm-operator">=</span> <span class="cm-number">12</span> &nbsp; &nbsp; &nbsp;<span class="cm-comment"># 二进制: 1100 &nbsp;</span></span><br><span role="presentation"><span class="cm-variable">n</span> <span class="cm-operator">=</span> <span class="cm-number">10</span> &nbsp; &nbsp; &nbsp;<span class="cm-comment"># 二进制: 1010 &nbsp;</span></span><br><span class="cm-comment"># 利用bin(number) 可以得到number的二进制</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"m = </span>{<span class="cm-variable">m</span>}<span class="cm-string"> (二进制: </span>{<span class="cm-builtin">bin</span>(<span class="cm-variable">m</span>)}<span class="cm-string">), n = </span>{<span class="cm-variable">n</span>}<span class="cm-string"> (二进制: </span>{<span class="cm-builtin">bin</span>(<span class="cm-variable">n</span>)}<span class="cm-string">)"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"按位与: m &amp; n = </span>{<span class="cm-variable">m</span>}<span class="cm-string"> &amp; </span>{<span class="cm-variable">n</span>}<span class="cm-string"> = </span>{<span class="cm-variable">m</span> <span class="cm-operator">&amp;</span> <span class="cm-variable">n</span>}<span class="cm-string"> (二进制: </span>{<span class="cm-builtin">bin</span>(<span class="cm-variable">m</span> <span class="cm-operator">&amp;</span> <span class="cm-variable">n</span>)}<span class="cm-string">)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"按位或: m | n = </span>{<span class="cm-variable">m</span>}<span class="cm-string"> | </span>{<span class="cm-variable">n</span>}<span class="cm-string"> = </span>{<span class="cm-variable">m</span> <span class="cm-operator">|</span> <span class="cm-variable">n</span>}<span class="cm-string"> (二进制: </span>{<span class="cm-builtin">bin</span>(<span class="cm-variable">m</span> <span class="cm-operator">|</span> <span class="cm-variable">n</span>)}<span class="cm-string">)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"按位异或: m ^ n = </span>{<span class="cm-variable">m</span>}<span class="cm-string"> ^ </span>{<span class="cm-variable">n</span>}<span class="cm-string"> = </span>{<span class="cm-variable">m</span> <span class="cm-operator">^</span> <span class="cm-variable">n</span>}<span class="cm-string"> (二进制: </span>{<span class="cm-builtin">bin</span>(<span class="cm-variable">m</span> <span class="cm-operator">^</span> <span class="cm-variable">n</span>)}<span class="cm-string">)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"按位取反: ~m = ~</span>{<span class="cm-variable">m</span>}<span class="cm-string"> = </span>{<span class="cm-operator">~</span><span class="cm-variable">m</span>}<span class="cm-string"> (二进制: </span>{<span class="cm-builtin">bin</span>(<span class="cm-operator">~</span><span class="cm-variable">m</span> <span class="cm-operator">&amp;</span> <span class="cm-number">0b1111</span>)}<span class="cm-string">)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"左移: m &lt;&lt; 2 = </span>{<span class="cm-variable">m</span>}<span class="cm-string"> &lt;&lt; 2 = </span>{<span class="cm-variable">m</span> <span class="cm-operator">&lt;&lt;</span> <span class="cm-number">2</span>}<span class="cm-string"> (二进制: </span>{<span class="cm-builtin">bin</span>(<span class="cm-variable">m</span> <span class="cm-operator">&lt;&lt;</span> <span class="cm-number">2</span>)}<span class="cm-string">)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"右移: m &gt;&gt; 2 = </span>{<span class="cm-variable">m</span>}<span class="cm-string"> &gt;&gt; 2 = </span>{<span class="cm-variable">m</span> <span class="cm-operator">&gt;&gt;</span> <span class="cm-number">2</span>}<span class="cm-string"> (二进制: </span>{<span class="cm-builtin">bin</span>(<span class="cm-variable">m</span> <span class="cm-operator">&gt;&gt;</span> <span class="cm-number">2</span>)}<span class="cm-string">)"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 5. 逻辑运算符 这里需要注意和别的语言的区别,python的与或非使用的为 and or not 而不是&amp; | !</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">p</span> <span class="cm-operator">=</span> <span class="cm-keyword">True</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">q</span> <span class="cm-operator">=</span> <span class="cm-keyword">False</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"p = </span>{<span class="cm-variable">p</span>}<span class="cm-string">, q = </span>{<span class="cm-variable">q</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"逻辑与: p and q = </span>{<span class="cm-variable">p</span>}<span class="cm-string"> and </span>{<span class="cm-variable">q</span>}<span class="cm-string"> = </span>{<span class="cm-variable">p</span> <span class="cm-keyword">and</span> <span class="cm-variable">q</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"逻辑或: p or q = </span>{<span class="cm-variable">p</span>}<span class="cm-string"> or </span>{<span class="cm-variable">q</span>}<span class="cm-string"> = </span>{<span class="cm-variable">p</span> <span class="cm-keyword">or</span> <span class="cm-variable">q</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"逻辑非: not p = not </span>{<span class="cm-variable">p</span>}<span class="cm-string"> = </span>{<span class="cm-keyword">not</span> <span class="cm-variable">p</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 6. 成员运算符和身份运算符 </span><br><span class="cm-comment"># 主要用于高级变量类型，检查是否在集合内，in 和 not in</span><br><span class="cm-comment"># 元组，列表，集合，字典都可以使用，对于字典而言，检查的是key不是value</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">num_list</span> <span class="cm-operator">=</span> [<span class="cm-number">1</span>, <span class="cm-number">2</span>, <span class="cm-number">3</span>, <span class="cm-number">4</span>, <span class="cm-number">5</span>] &nbsp;</span><br><span role="presentation"><span class="cm-variable">num1</span> <span class="cm-operator">=</span> <span class="cm-number">10</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">num2</span> <span class="cm-operator">=</span> <span class="cm-number">10</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"列表: </span>{<span class="cm-variable">num_list</span>}<span class="cm-string">, num1 = </span>{<span class="cm-variable">num1</span>}<span class="cm-string">, num2 = </span>{<span class="cm-variable">num2</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"成员运算符 in: 3 in num_list = </span>{<span class="cm-number">3</span> <span class="cm-keyword">in</span> <span class="cm-variable">num_list</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"成员运算符 not in: 6 not in num_list = </span>{<span class="cm-number">6</span> <span class="cm-keyword">not</span> <span class="cm-keyword">in</span> <span class="cm-variable">num_list</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"身份运算符 is: num1 is num2 = </span>{<span class="cm-variable">num1</span> <span class="cm-keyword">is</span> <span class="cm-variable">num2</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"身份运算符 is not: num1 is not num_list = </span>{<span class="cm-variable">num1</span> <span class="cm-keyword">is</span> <span class="cm-keyword">not</span> <span class="cm-variable">num_list</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 7. 运算符优先级示例 &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"\n【7】运算符优先级示例"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">result1</span> <span class="cm-operator">=</span> <span class="cm-number">10</span> <span class="cm-operator">+</span> <span class="cm-number">5</span> <span class="cm-operator">*</span> <span class="cm-number">3</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">result2</span> <span class="cm-operator">=</span> (<span class="cm-number">10</span> <span class="cm-operator">+</span> <span class="cm-number">5</span>) <span class="cm-operator">*</span> <span class="cm-number">3</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">result3</span> <span class="cm-operator">=</span> <span class="cm-number">2</span> <span class="cm-operator">**</span> <span class="cm-number">3</span> <span class="cm-operator">*</span> <span class="cm-number">4</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">result4</span> <span class="cm-operator">=</span> <span class="cm-number">2</span> <span class="cm-operator">**</span> (<span class="cm-number">3</span> <span class="cm-operator">*</span> <span class="cm-number">4</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"乘法优先于加法: 10 + 5 * 3 = </span>{<span class="cm-variable">result1</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"括号改变优先级: (10 + 5) * 3 = </span>{<span class="cm-variable">result2</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"幂运算优先于乘法: 2 ** 3 * 4 = </span>{<span class="cm-variable">result3</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"括号改变优先级: 2 ** (3 * 4) = </span>{<span class="cm-variable">result4</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment"># 8. 不同数字类型之间的运算 &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"\n【8】不同数字类型之间的运算"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">int_num</span> <span class="cm-operator">=</span> <span class="cm-number">10</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">float_num</span> <span class="cm-operator">=</span> <span class="cm-number">3.5</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">complex_num</span> <span class="cm-operator">=</span> <span class="cm-number">2</span> <span class="cm-operator">+</span> <span class="cm-number">3j</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"整数: </span>{<span class="cm-variable">int_num</span>}<span class="cm-string">, 浮点数: </span>{<span class="cm-variable">float_num</span>}<span class="cm-string">, 复数: </span>{<span class="cm-variable">complex_num</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"整数 + 浮点数: </span>{<span class="cm-variable">int_num</span>}<span class="cm-string"> + </span>{<span class="cm-variable">float_num</span>}<span class="cm-string"> = </span>{<span class="cm-variable">int_num</span> <span class="cm-operator">+</span> <span class="cm-variable">float_num</span>}<span class="cm-string"> (类型: </span>{<span class="cm-builtin">type</span>(<span class="cm-variable">int_num</span> <span class="cm-operator">+</span> <span class="cm-variable">float_num</span>)}<span class="cm-string">)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"整数 + 复数: </span>{<span class="cm-variable">int_num</span>}<span class="cm-string"> + </span>{<span class="cm-variable">complex_num</span>}<span class="cm-string"> = </span>{<span class="cm-variable">int_num</span> <span class="cm-operator">+</span> <span class="cm-variable">complex_num</span>}<span class="cm-string"> (类型: </span>{<span class="cm-builtin">type</span>(<span class="cm-variable">int_num</span> <span class="cm-operator">+</span> <span class="cm-variable">complex_num</span>)}<span class="cm-string">)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"浮点数 * 复数: </span>{<span class="cm-variable">float_num</span>}<span class="cm-string"> * </span>{<span class="cm-variable">complex_num</span>}<span class="cm-string"> = </span>{<span class="cm-variable">float_num</span> <span class="cm-operator">*</span> <span class="cm-variable">complex_num</span>}<span class="cm-string"> (类型: </span>{<span class="cm-builtin">type</span>(<span class="cm-variable">float_num</span> <span class="cm-operator">*</span> <span class="cm-variable">complex_num</span>)}<span class="cm-string">)"</span>)</span></pre>
<h3 class="md-end-block md-heading"><span class="md-plain">类型转换</span></h3>
<p class="md-end-block md-p"><span class="md-plain">Python中，字符串，浮点数，整数，布尔类型之间都可以转换</span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">需要转化为字符串，使用</span><span class="md-pair-s" spellcheck="false"><code>str()</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">需要转化为整数，使用</span><span class="md-pair-s" spellcheck="false"><code>int()</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">需要转化为浮点数，使用</span><span class="md-pair-s" spellcheck="false"><code>float()</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">需要转化为布尔类型，使用</span><span class="md-pair-s" spellcheck="false"><code>bool()</code></span> <span class="md-plain">需要注意，上述的这些属于内置工具函数，而不是别的语言中的强转，但也要注意</span> <span class="md-plain">浮点数字符串不能</span><span class="md-pair-s" spellcheck="false"><code>int()</code></span><span class="md-plain">; </span> <strong><span class="md-plain">非0数字和非空字符串(包括空格组成的字符串)</span></strong><span class="md-plain"> 使用</span><span class="md-pair-s" spellcheck="false"><code>bool()</code></span><span class="md-plain">返回</span><span class="md-pair-s" spellcheck="false"><code>True</code></span><span class="md-plain">;</span> <span class="md-plain">0和串(</span><span class="md-pair-s" spellcheck="false"><code>""</code></span><span class="md-plain">)使用</span><span class="md-pair-s" spellcheck="false"><code>bool()</code></span><span class="md-plain">返回</span><span class="md-pair-s" spellcheck="false"><code>False</code></span></p>
</li>
</ul>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">str_v</span> <span class="cm-operator">=</span> <span class="cm-string">"123.123"</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">float</span>(<span class="cm-variable">str_v</span>)) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">bool</span>(<span class="cm-variable">str_v</span>)) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">number1</span> <span class="cm-operator">=</span> <span class="cm-number">99</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">number2</span> <span class="cm-operator">=</span> <span class="cm-number">0</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">str</span>(<span class="cm-variable">number1</span>)) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">str</span>(<span class="cm-variable">number2</span>)) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">bool</span>(<span class="cm-variable">number2</span>)) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">bool_v1</span> <span class="cm-operator">=</span> <span class="cm-keyword">True</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">bool_v2</span> <span class="cm-operator">=</span> <span class="cm-keyword">False</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">str</span>(<span class="cm-variable">bool_v1</span>)) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">str</span>(<span class="cm-variable">bool_v2</span>)) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">int</span>(<span class="cm-variable">bool_v1</span>)) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">int</span>(<span class="cm-variable">bool_v2</span>))</span><br><span role="presentation">​</span></pre>
<h2 class="md-end-block md-heading"><span class="md-plain">高级变量类型</span></h2>
<h3 class="md-end-block md-heading"><span class="md-plain">Set集合</span></h3>
<p class="md-end-block md-p"><span class="md-plain">Set集合的特点：无序 ； 不可重复 ；可以使用</span><span class="md-pair-s" spellcheck="false"><code>-</code></span><span class="md-plain">；</span><span class="md-pair-s" spellcheck="false"><code>|</code></span><span class="md-plain">；</span><span class="md-pair-s" spellcheck="false"><code>&amp;</code></span><span class="md-plain">进行集合的运算；不能修改，只能删除和新增</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">set_1</span> <span class="cm-operator">=</span> {<span class="cm-string">"湖"</span>,<span class="cm-string">"南"</span>,<span class="cm-string">"科"</span>,<span class="cm-string">"专"</span>,<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>} &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">set_1</span>) <span class="cm-comment"># 输出 {1, 2, 3, '南', '专', '湖', '科'}  体现无序</span></span><br><span role="presentation"><span class="cm-variable">set_2</span> <span class="cm-operator">=</span> <span class="cm-builtin">set</span>([<span class="cm-string">"湖"</span>,<span class="cm-string">"南"</span>,<span class="cm-string">"科"</span>,<span class="cm-string">"科"</span>,<span class="cm-number">4</span>,<span class="cm-number">5</span>,<span class="cm-number">6</span>]) &nbsp;<span class="cm-comment"># 类似上面的类型转换，可有元组得到</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">set_2</span>) <span class="cm-comment"># 输出 {4, 5, 6, '南', '湖', '科'} 体现自动去重</span></span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">set_1</span><span class="cm-operator">-</span><span class="cm-variable">set_2</span>) <span class="cm-comment"># set1中有但是set2中没有的 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">set_1</span><span class="cm-operator">|</span><span class="cm-variable">set_2</span>) <span class="cm-comment"># set1或set2中的所有元素 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">set_1</span><span class="cm-operator">&amp;</span><span class="cm-variable">set_2</span>) <span class="cm-comment"># set1和set2中的交集元素</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">我们可以使用 </span><span class="md-pair-s" spellcheck="false"><code>in 和 not in</code></span><span class="md-plain">判断元素是否存在于集合中</span></p>
<p class="md-end-block md-p"><span class="md-plain">可以通过</span><span class="md-pair-s" spellcheck="false"><code>list(set)或者tuple(set)</code></span><span class="md-plain">的方式将集合转换为列表或者元组，从而实现遍历和查找元素</span> <strong><span class="md-plain">如果要修改元素，因为不支持修改，我们只能先删除再添加</span></strong><span class="md-plain">，删除我们常用的语法是</span><span class="md-pair-s" spellcheck="false"><code>remove()和discard()</code></span><span class="md-plain">（前者删除不存在的元素的时候会报错，后者不会更加安全），新增则用</span><span class="md-pair-s" spellcheck="false"><code>add()</code></span></p>
<p class="md-end-block md-p"><span class="md-plain">集合在深度学习的过程中遇到的概率比较低，会被Pands替代，所以不过多讲述</span></p>
<h3 class="md-end-block md-heading"><span class="md-plain">List列表</span></h3>
<p class="md-end-block md-p"><span class="md-plain">Python中的列表就可以比作Java中的ArrayList，边长的数组，可重复，有序，下标访问，只是基于Python动态语言的特性，一个List中可以包含任何数据类型，不存在类型限制，也可以List套List</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">list_v</span> <span class="cm-operator">=</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-string">"ZhangSan"</span>,<span class="cm-keyword">True</span>,[<span class="cm-number">4</span>,<span class="cm-number">5</span>,<span class="cm-number">6</span>]] &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>)</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>[<span class="cm-number">0</span>])</span></pre>
<p class="md-end-block md-p"><span class="md-plain">虽然List没有限制，但是实际上底层List还是会需要存储每个类型的数据类型，导致数组其实占用空间增大了。在NumPy中我们会用NumPy数组（仅仅接纳一种数据类型的列表）代替List，提升运行速度，降低内存压力</span></p>
<p class="md-end-block md-p"><span class="md-plain">List支持负数下标索引，我们知道0索引位置对应的List的第一个元素，那么-1其实就是倒数第一个元素，以此类推，-2就是倒数第二个元素</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">list_v</span> <span class="cm-operator">=</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-string">"ZhangSan"</span>,<span class="cm-keyword">True</span>,[<span class="cm-number">4</span>,<span class="cm-number">5</span>,<span class="cm-number">6</span>]] &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>[<span class="cm-operator">-</span><span class="cm-number">1</span>],<span class="cm-variable">list_v</span>[<span class="cm-operator">-</span><span class="cm-number">2</span>],<span class="cm-variable">list_v</span>[<span class="cm-operator">-</span><span class="cm-number">3</span>]) &nbsp;</span><br><span role="presentation"><span class="cm-variable">list_v</span>[<span class="cm-operator">-</span><span class="cm-number">2</span>]<span class="cm-operator">=</span><span class="cm-number">100</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>[<span class="cm-operator">-</span><span class="cm-number">1</span>],<span class="cm-variable">list_v</span>[<span class="cm-operator">-</span><span class="cm-number">2</span>],<span class="cm-variable">list_v</span>[<span class="cm-operator">-</span><span class="cm-number">3</span>])</span></pre>
<p class="md-end-block md-p"><span class="md-plain">Python的List有着和Golang的数组一样的切片的特性，切片就是列表一部分，可以利用</span><span class="md-pair-s" spellcheck="false"><code>[start:end]</code></span><span class="md-plain">的形式截取列表中的 start~end 之间的部分，</span><span class="md-pair-s" spellcheck="false"><code>注意左闭右开</code></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">list_v</span> <span class="cm-operator">=</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-string">"ZhangSan"</span>,<span class="cm-keyword">True</span>,[<span class="cm-number">4</span>,<span class="cm-number">5</span>,<span class="cm-number">6</span>]] &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>[<span class="cm-number">1</span>:]) <span class="cm-comment"># 第二个元素到最后 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>[:]) <span class="cm-comment"># 全部元素 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>[<span class="cm-number">1</span>:<span class="cm-number">2</span>]) <span class="cm-comment"># 只有第二个元素 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>[:<span class="cm-number">5</span>]) <span class="cm-comment"># 前四个元素 等同于 print(list_v[:-1])</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">除此之外，Python的切片还能支持间隔切片。当我们明确隔n个元素采样一次的时候，可以使用</span><span class="md-pair-s" spellcheck="false"><code>[star:end:n]</code></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">list_v</span> <span class="cm-operator">=</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-number">4</span>,<span class="cm-number">5</span>,<span class="cm-number">6</span>,<span class="cm-number">7</span>,<span class="cm-number">8</span>,<span class="cm-number">9</span>,<span class="cm-number">10</span>,<span class="cm-number">11</span>,<span class="cm-number">12</span>,<span class="cm-number">13</span>,<span class="cm-number">14</span>,<span class="cm-number">15</span>] &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>[<span class="cm-number">1</span>:<span class="cm-operator">-</span><span class="cm-number">1</span>:<span class="cm-number">2</span>]) <span class="cm-comment"># [2, 4, 6, 8, 10, 12, 14]</span></span></pre>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>特别注意，Python的切片和原列表对象是完全独立的，也就是两个列表了实际上，所以对于几百万数据的大列表进行切片操作，对于主机而言是一个很大的考验，这点要和Go的切片区分开来，Go中的切片和原来的是共享的</code></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">list_v</span> <span class="cm-operator">=</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-number">4</span>,<span class="cm-number">5</span>,<span class="cm-number">6</span>,<span class="cm-number">7</span>,<span class="cm-number">8</span>,<span class="cm-number">9</span>,<span class="cm-number">10</span>,<span class="cm-number">11</span>,<span class="cm-number">12</span>,<span class="cm-number">13</span>,<span class="cm-number">14</span>,<span class="cm-number">15</span>] &nbsp;</span><br><span role="presentation"><span class="cm-variable">list_v2</span> <span class="cm-operator">=</span> <span class="cm-variable">list_v</span>[<span class="cm-number">1</span>:<span class="cm-operator">-</span><span class="cm-number">1</span>:<span class="cm-number">2</span>] &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v2</span>) &nbsp; <span class="cm-comment"># [2, 4, 6, 8, 10, 12, 14]</span></span><br><span role="presentation"><span class="cm-variable">list_v2</span>[<span class="cm-number">0</span>] <span class="cm-operator">=</span> <span class="cm-number">99</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v2</span>) &nbsp;<span class="cm-comment"># [99, 4, 6, 8, 10, 12, 14]</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>) <span class="cm-comment"># [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">在NumPy的数组中这个就被舍弃了，NumPy中的切片和原数组是共享的，修改切片也会修改原数组，如果真的需要完全拷贝一份新的，就是借助</span><span class="md-pair-s" spellcheck="false"><code>copy()</code></span><span class="md-plain">函数</span></p>
<p class="md-end-block md-p"><span class="md-plain">List可以通过</span><span class="md-pair-s" spellcheck="false"><code>append()</code></span><span class="md-plain">函数添加元素，也可以通过加法和乘法扩充和复制</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">list_v</span> <span class="cm-operator">=</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-number">4</span>] &nbsp;</span><br><span role="presentation"><span class="cm-variable">list_v</span>.<span class="cm-property">append</span>(<span class="cm-number">5</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span>) &nbsp;<span class="cm-comment">#12345</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span><span class="cm-operator">+</span>[<span class="cm-number">6</span>]) &nbsp; <span class="cm-comment">#123456</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span><span class="cm-operator">+</span>[<span class="cm-number">6</span>,<span class="cm-number">7</span>,<span class="cm-number">8</span>]) &nbsp;<span class="cm-comment">#12345678</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">list_v</span><span class="cm-operator">*</span><span class="cm-number">2</span>) <span class="cm-comment">#1234512345</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">列表还有一个列表推导式，可以了解和学习一下</span> <span class="md-plain">正常的我们创建由已知列表开平方得到新列表，我们需要借助</span><span class="md-pair-s" spellcheck="false"><code>for循环</code></span><span class="md-plain">，代码如下</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">result</span> <span class="cm-operator">=</span> []</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">i</span> <span class="cm-keyword">in</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-number">4</span>]:</span><br><span role="presentation">    <span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-variable">result</span><span class="cm-operator">+</span>[<span class="cm-variable">i</span><span class="cm-operator">**</span><span class="cm-number">2</span>]</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">result</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-plain">使用列表推导式之后可以这么写</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">result</span> <span class="cm-operator">=</span> [<span class="cm-variable">i</span><span class="cm-operator">**</span><span class="cm-number">2</span> <span class="cm-keyword">for</span> <span class="cm-variable">i</span> <span class="cm-keyword">in</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-number">4</span>]]</span></pre>
<p class="md-end-block md-p"><span class="md-plain">也还是加一点的</span><span class="md-pair-s" spellcheck="false"><code>if判断</code></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">result</span> <span class="cm-operator">=</span> []</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">i</span> <span class="cm-keyword">in</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-number">4</span>]:</span><br><span role="presentation">    <span class="cm-keyword">if</span> <span class="cm-variable">i</span><span class="cm-operator">&lt;</span><span class="cm-number">3</span>:</span><br><span role="presentation">        <span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-variable">result</span><span class="cm-operator">+</span>[<span class="cm-variable">i</span><span class="cm-operator">**</span><span class="cm-number">2</span>]</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">result</span>)</span><br><span role="presentation">​</span><br><span class="cm-comment"># 列表推导式写法</span><br><span role="presentation"><span class="cm-variable">result</span> <span class="cm-operator">=</span> [<span class="cm-variable">i</span><span class="cm-operator">**</span><span class="cm-number">2</span> <span class="cm-keyword">for</span> <span class="cm-variable">i</span> <span class="cm-keyword">in</span> [<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-number">4</span>] <span class="cm-keyword">if</span> <span class="cm-variable">i</span><span class="cm-operator">&lt;</span><span class="cm-number">3</span>]</span></pre>
<h3 class="md-end-block md-heading"><span class="md-plain">tuple元组</span></h3>
<p class="md-end-block md-p"><span class="md-plain">元组的特点：有序；可重复；单个元素不可修改但是可重新赋值替换；下标访问</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">tuple_age</span> <span class="cm-operator">=</span> (<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>) &nbsp;</span><br><span class="cm-comment"># 这种创建方式极其简单方便</span><br><span class="cm-comment"># 同时也可以看到，python中对于类型的要求没那么强</span><br><span role="presentation"><span class="cm-variable">tuple_name</span> <span class="cm-operator">=</span> <span class="cm-string">"张三"</span>,<span class="cm-string">"李四"</span>,<span class="cm-number">99</span> &nbsp;<span class="cm-variable">tuple_null</span> <span class="cm-operator">=</span> () &nbsp;<span class="cm-comment"># 创建空元组</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">tuple_age</span>) &nbsp;</span><br><span class="cm-comment"># tuple_age[0]=10 在运行的时候会报错</span><br><span role="presentation"><span class="cm-variable">tuple_age</span> <span class="cm-operator">=</span> <span class="cm-number">3</span>,<span class="cm-number">3</span>,<span class="cm-number">3</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">tuple_age</span>[<span class="cm-number">0</span>]) <span class="cm-comment"># 下标访问单个元素</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">tuple_name</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">tuple_null</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-plain">除此之外，可以利用元组进行批量赋值，或者说就是Python的批量赋值的特性</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">a</span>,<span class="cm-variable">b</span>,<span class="cm-variable">c</span> <span class="cm-operator">=</span> <span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">e</span>,<span class="cm-variable">f</span>,<span class="cm-variable">g</span> <span class="cm-operator">=</span> (<span class="cm-number">11</span>,<span class="cm-number">12.2</span>,<span class="cm-number">13.33</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">a</span>,<span class="cm-variable">b</span>,<span class="cm-variable">c</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">e</span>,<span class="cm-variable">f</span>,<span class="cm-variable">g</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-plain">当有多个数值的列表，</span><strong><span class="md-plain">然后你只需要部分数值的时候，你可以利用元组的快捷构造的特性进行截取</span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">result1</span>,<span class="cm-variable">result2</span>,<span class="cm-operator">*</span><span class="cm-variable">other</span>,<span class="cm-variable">result3</span> <span class="cm-operator">=</span><span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>,<span class="cm-number">4</span>,<span class="cm-number">5</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">result1</span>) &nbsp;<span class="cm-comment">#1</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">result2</span>) &nbsp;<span class="cm-comment">#2</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">other</span>) &nbsp;<span class="cm-comment">#[3,4]</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">result3</span>) <span class="cm-comment">#5</span></span></pre>
<h3 class="md-end-block md-heading"><span class="md-plain">dict字典</span></h3>
<p class="md-end-block md-p"><span class="md-plain">一个Key-Value结构，不可重复，重复会覆盖；无序 ；Key唯一 ； Value可以为任何数据类型，Key只能是不可变的元素（数字，字符串，布尔，元组，不可变集合）</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">dict_v</span> <span class="cm-operator">=</span> { &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"name"</span>:<span class="cm-string">"zhangsan"</span>, &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"age"</span>:<span class="cm-number">18</span>, &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-number">1</span>:(<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>), &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-number">2</span>:[<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>], &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-number">3</span>:{<span class="cm-number">1</span>,<span class="cm-number">2</span>,<span class="cm-number">3</span>}, &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"4"</span>:<span class="cm-keyword">True</span> &nbsp;</span><br><span role="presentation">} &nbsp;</span><br><span class="cm-comment"># {'name': 'zhangsan', 'age': 18, 1: (1, 2, 3), 2: [1, 2, 3], 3: {1, 2, 3}, '4': True}</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">dict_v</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-plain">字典添加元素，可以直接通过</span><span class="md-pair-s" spellcheck="false"><code>dict[newKey]=newValue</code></span><span class="md-plain">的方式进行新增</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">dict_v</span> <span class="cm-operator">=</span> { &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"name"</span>:<span class="cm-string">"zhangsan"</span>, &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"age"</span>:<span class="cm-number">18</span>, &nbsp;</span><br><span role="presentation">} &nbsp;</span><br><span role="presentation"><span class="cm-variable">dict_v</span>[<span class="cm-string">"newKey"</span>] <span class="cm-operator">=</span> <span class="cm-string">"newValue"</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">dict_v</span>)</span></pre>
<h3 class="md-end-block md-heading"><span class="md-plain">类型转换</span></h3>
<p class="md-end-block md-p"><span class="md-plain">集合，列表，元组，字典之间也可以进行转换，用到如下四个函数</span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">转化为集合使用</span><span class="md-pair-s" spellcheck="false"><code>set()</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">转化为元组使用</span><span class="md-pair-s" spellcheck="false"><code>tuple()</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">转化为列表使用</span><span class="md-pair-s" spellcheck="false"><code>list()</code></span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">转化为字典使用</span><span class="md-pair-s" spellcheck="false"><code>dist()</code></span> <span class="md-plain">这里就展示图片，不附带代码了，可自行尝试，需要注意的是，其余的转化为字典的时候，因为字典是双列的，所以肯定还会需要一个单列集合，这个时候就需要借助</span><span class="md-pair-s" spellcheck="false"><code>zip(A,B)</code></span><span class="md-plain">函数，该函数会将入参A和B进行一一对应，然后让A[0]作为Key，B[0]作为Value从而组成dict[0]，这里需要注意，传入的A和B如果是集合这种无序的，那么会造成匹配不一样按照预期一样</span> <span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260128213156572.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/20260128213156572.png" alt="image.png"></span></p>
</li>
</ul>
<h2 class="md-end-block md-heading"><span class="md-plain">流程控制</span></h2>
<h3 class="md-end-block md-heading"><span class="md-plain">if语句</span></h3>
<p class="md-end-block md-p"><span class="md-plain">在Python中，</span><span class="md-pair-s" spellcheck="false"><code>if语句</code></span><span class="md-plain">的使用格式为如下，需要注意：</span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Python中的函数，循环，if条件判断等都不会使用end来表示代码块的结束</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Python中使用缩进来表示代码块的范围</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">每一个判断条件的最后都有一个冒号，不要遗漏</span></p>
</li>
</ul>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">if</span> <span class="cm-variable">判断条件</span>:</span><br><span role="presentation">    <span class="cm-variable">逻辑处理一</span></span><br><span role="presentation"><span class="cm-keyword">elif</span> <span class="cm-variable">判断条件</span>:</span><br><span role="presentation">    <span class="cm-variable">逻辑处理二</span></span><br><span role="presentation"><span class="cm-keyword">else</span>:</span><br><span role="presentation">    <span class="cm-variable">逻辑处理三</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">简单的分数区间判断案例</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">source</span> <span class="cm-operator">=</span> <span class="cm-number">85</span> &nbsp;</span><br><span role="presentation"><span class="cm-keyword">if</span> <span class="cm-variable">source</span> <span class="cm-operator">==</span> <span class="cm-number">100</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">"满昏"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">elif</span> <span class="cm-variable">source</span> <span class="cm-operator">&gt;=</span> <span class="cm-number">90</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">"优秀"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">elif</span> <span class="cm-variable">source</span> <span class="cm-operator">&gt;=</span> <span class="cm-number">60</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">"及格"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">else</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">"不及格"</span>)</span></pre>
<h3 class="md-end-block md-heading"><span class="md-plain">循环语句</span></h3>
<p class="md-end-block md-p"><span class="md-plain">Python中for循环的基本使用如下</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">变量名</span> <span class="cm-keyword">in</span> <span class="cm-variable">可迭代对象</span>:</span><br><span role="presentation">    <span class="cm-variable">循环体逻辑</span></span><br>    <br>    <br><span class="cm-comment"># 遍历列表，依次取出每个元素 </span><br><span role="presentation"><span class="cm-variable">fruits</span> <span class="cm-operator">=</span> [<span class="cm-string">"苹果"</span>, <span class="cm-string">"香蕉"</span>, <span class="cm-string">"橙子"</span>] </span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">fruit</span> <span class="cm-keyword">in</span> <span class="cm-variable">fruits</span>: </span><br><span role="presentation">    <span class="cm-builtin">print</span>(<span class="cm-string">f"我喜欢吃：</span>{<span class="cm-variable">fruit</span>}<span class="cm-string">"</span>) </span><br><span class="cm-comment"># 输出结果： # 我喜欢吃：苹果 # 我喜欢吃：香蕉 # 我喜欢吃：橙子</span></pre>
<p class="md-end-block md-p"><span class="md-plain">如果需要计数式循环，即例如Java中</span><span class="md-pair-s" spellcheck="false"><code>for(int i=0;i&lt;10;i++)</code></span><span class="md-plain">,可以借助</span><span class="md-pair-s" spellcheck="false"><code>range(n)</code></span><span class="md-plain">，该函数会创建一个0~n-1的整数序列供for循环使用,需要注意，</span><span class="md-pair-s" spellcheck="false"><code>range()函数也是左闭右开的区间范围</code></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span class="cm-comment"># 用法1：range(n) &rarr; 生成 0 ~ n-1 的整数序列 &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">i</span> <span class="cm-keyword">in</span> <span class="cm-builtin">range</span>(<span class="cm-number">5</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-variable">i</span>) &nbsp;<span class="cm-comment"># 输出：0 1 2 3 4 &nbsp;</span></span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment cm-error"># 用法2：range(start, end) &rarr; 生成 start ~ end-1 的整数序列（左闭右开） &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">i</span> <span class="cm-keyword">in</span> <span class="cm-builtin">range</span>(<span class="cm-number">2</span>, <span class="cm-number">7</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-variable">i</span>) &nbsp;<span class="cm-comment"># 输出：2 3 4 5 6 &nbsp;</span></span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment cm-error"># 用法3：range(start, end, step) &rarr; 步长为step，生成等差序列 &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">i</span> <span class="cm-keyword">in</span> <span class="cm-builtin">range</span>(<span class="cm-number">0</span>, <span class="cm-number">10</span>, <span class="cm-number">2</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-variable">i</span>) &nbsp;<span class="cm-comment"># 输出：0 2 4 6 8（偶数） &nbsp;</span></span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment cm-error"># 示例：通过索引遍历列表 &nbsp;</span><br><span role="presentation"><span class="cm-variable">fruits</span> <span class="cm-operator">=</span> [<span class="cm-string">"苹果"</span>, <span class="cm-string">"香蕉"</span>, <span class="cm-string">"橙子"</span>] &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">i</span> <span class="cm-keyword">in</span> <span class="cm-builtin">range</span>(<span class="cm-builtin">len</span>(<span class="cm-variable">fruits</span>)): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"索引</span>{<span class="cm-variable">i</span>}<span class="cm-string">：</span>{<span class="cm-variable">fruits</span>[<span class="cm-variable">i</span>]}<span class="cm-string">"</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>for循环</code></span><span class="md-plain">也常用于高级变量类型的遍历</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span class="cm-comment"># 1. 遍历列表 (List)print("\n【1】遍历列表 (List)") &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">fruits</span> <span class="cm-operator">=</span> [<span class="cm-string">"苹果"</span>, <span class="cm-string">"香蕉"</span>, <span class="cm-string">"橙子"</span>, <span class="cm-string">"葡萄"</span>, <span class="cm-string">"草莓"</span>] &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"列表内容: </span>{<span class="cm-variable">fruits</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"方法1: 直接遍历元素"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">fruit</span> <span class="cm-keyword">in</span> <span class="cm-variable">fruits</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  - </span>{<span class="cm-variable">fruit</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin cm-error">print</span>(<span class="cm-string">"\n方法2: 使用enumerate()获取索引和值"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">index</span>, <span class="cm-variable">fruit</span> <span class="cm-keyword">in</span> <span class="cm-builtin">enumerate</span>(<span class="cm-variable">fruits</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  索引: </span>{<span class="cm-variable">index</span>}<span class="cm-string">, 值: </span>{<span class="cm-variable">fruit</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin cm-error">print</span>(<span class="cm-string">"\n方法3: 使用range(len())遍历索引"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">i</span> <span class="cm-keyword">in</span> <span class="cm-builtin">range</span>(<span class="cm-builtin">len</span>(<span class="cm-variable">fruits</span>)): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  索引: </span>{<span class="cm-variable">i</span>}<span class="cm-string">, 值: </span>{<span class="cm-variable">fruits</span>[<span class="cm-variable">i</span>]}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment cm-error"># 2. 遍历元组 (Tuple)print("\n【2】遍历元组 (Tuple)") &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">colors</span> <span class="cm-operator">=</span> (<span class="cm-string">"红色"</span>, <span class="cm-string">"绿色"</span>, <span class="cm-string">"蓝色"</span>, <span class="cm-string">"黄色"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"元组内容: </span>{<span class="cm-variable">colors</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"方法1: 直接遍历元素"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">color</span> <span class="cm-keyword">in</span> <span class="cm-variable">colors</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  - </span>{<span class="cm-variable">color</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin cm-error">print</span>(<span class="cm-string">"\n方法2: 使用enumerate()获取索引和值"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">index</span>, <span class="cm-variable">color</span> <span class="cm-keyword">in</span> <span class="cm-builtin">enumerate</span>(<span class="cm-variable">colors</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  索引: </span>{<span class="cm-variable">index</span>}<span class="cm-string">, 值: </span>{<span class="cm-variable">color</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment cm-error"># 3. 遍历集合 (Set)print("\n【3】遍历集合 (Set)") &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">numbers</span> <span class="cm-operator">=</span> {<span class="cm-number">1</span>, <span class="cm-number">2</span>, <span class="cm-number">3</span>, <span class="cm-number">4</span>, <span class="cm-number">5</span>, <span class="cm-number">3</span>, <span class="cm-number">2</span>} &nbsp;<span class="cm-comment"># 注意：集合会自动去除重复元素 &nbsp;</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"集合内容: </span>{<span class="cm-variable">numbers</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"方法1: 直接遍历元素"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">num</span> <span class="cm-keyword">in</span> <span class="cm-variable">numbers</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  - </span>{<span class="cm-variable">num</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin cm-error">print</span>(<span class="cm-string">"\n方法2: 对集合排序后遍历"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">num</span> <span class="cm-keyword">in</span> <span class="cm-builtin">sorted</span>(<span class="cm-variable">numbers</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  - </span>{<span class="cm-variable">num</span>}<span class="cm-string"> (排序后)"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment cm-error"># 4. 遍历字典 (Dictionary)print("\n【4】遍历字典 (Dictionary)") &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">student_scores</span> <span class="cm-operator">=</span> { &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"张三"</span>: <span class="cm-number">95</span>, &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"李四"</span>: <span class="cm-number">87</span>, &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"王五"</span>: <span class="cm-number">92</span>, &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"赵六"</span>: <span class="cm-number">78</span> &nbsp;</span><br><span role="presentation">} &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"字典内容: </span>{<span class="cm-variable">student_scores</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"方法1: 遍历键(keys)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">name</span> <span class="cm-keyword">in</span> <span class="cm-variable">student_scores</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  学生: </span>{<span class="cm-variable">name</span>}<span class="cm-string">, 分数: </span>{<span class="cm-variable">student_scores</span>[<span class="cm-variable">name</span>]}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin cm-error">print</span>(<span class="cm-string">"\n方法2: 明确遍历键(keys)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">name</span> <span class="cm-keyword">in</span> <span class="cm-variable">student_scores</span>.<span class="cm-property">keys</span>(): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  学生: </span>{<span class="cm-variable">name</span>}<span class="cm-string">, 分数: </span>{<span class="cm-variable">student_scores</span>[<span class="cm-variable">name</span>]}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin cm-error">print</span>(<span class="cm-string">"\n方法3: 遍历值(values)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">score</span> <span class="cm-keyword">in</span> <span class="cm-variable">student_scores</span>.<span class="cm-property">values</span>(): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  分数: </span>{<span class="cm-variable">score</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin cm-error">print</span>(<span class="cm-string">"\n方法4: 同时遍历键和值(items)"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">name</span>, <span class="cm-variable">score</span> <span class="cm-keyword">in</span> <span class="cm-variable">student_scores</span>.<span class="cm-property">items</span>(): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  学生: </span>{<span class="cm-variable">name</span>}<span class="cm-string">, 分数: </span>{<span class="cm-variable">score</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment cm-error"># 5. 嵌套数据结构的遍历 &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"\n【5】嵌套数据结构的遍历"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">nested_data</span> <span class="cm-operator">=</span> { &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"班级A"</span>: [<span class="cm-number">85</span>, <span class="cm-number">90</span>, <span class="cm-number">78</span>], &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"班级B"</span>: [<span class="cm-number">92</span>, <span class="cm-number">88</span>, <span class="cm-number">84</span>], &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"班级C"</span>: [<span class="cm-number">79</span>, <span class="cm-number">85</span>, <span class="cm-number">91</span>] &nbsp;</span><br><span role="presentation">} &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"遍历嵌套字典和列表"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">class_name</span>, <span class="cm-variable">scores</span> <span class="cm-keyword">in</span> <span class="cm-variable">nested_data</span>.<span class="cm-property">items</span>(): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f" &nbsp;</span>{<span class="cm-variable">class_name</span>}<span class="cm-string">:"</span>) &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> <span class="cm-variable">i</span>, <span class="cm-variable">score</span> <span class="cm-keyword">in</span> <span class="cm-builtin">enumerate</span>(<span class="cm-variable">scores</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f" &nbsp;  学生</span>{<span class="cm-variable">i</span><span class="cm-operator">+</span><span class="cm-number">1</span>}<span class="cm-string">: </span>{<span class="cm-variable">score</span>}<span class="cm-string">分"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span class="cm-comment cm-error"># 6. 使用条件语句的遍历 &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"\n【6】带条件判断的遍历"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"-"</span> <span class="cm-operator">*</span> <span class="cm-number">40</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">mixed_list</span> <span class="cm-operator">=</span> [<span class="cm-number">1</span>, <span class="cm-string">"hello"</span>, <span class="cm-number">3.14</span>, <span class="cm-string">"world"</span>, <span class="cm-number">42</span>, <span class="cm-keyword">True</span>, <span class="cm-string">"python"</span>] &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">f"混合列表: </span>{<span class="cm-variable">mixed_list</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"只打印字符串类型的元素:"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">item</span> <span class="cm-keyword">in</span> <span class="cm-variable">mixed_list</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> <span class="cm-builtin">isinstance</span>(<span class="cm-variable">item</span>, <span class="cm-builtin">str</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  字符串: </span>{<span class="cm-variable">item</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin cm-error">print</span>(<span class="cm-string">"\n只打印数字类型的元素:"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-keyword">for</span> <span class="cm-variable">item</span> <span class="cm-keyword">in</span> <span class="cm-variable">mixed_list</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> <span class="cm-builtin">isinstance</span>(<span class="cm-variable">item</span>, (<span class="cm-builtin">int</span>, <span class="cm-builtin">float</span>)): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">f"  数字: </span>{<span class="cm-variable">item</span>}<span class="cm-string">"</span>) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin cm-error">print</span>(<span class="cm-string">"\n"</span> <span class="cm-operator">+</span> <span class="cm-string">"="</span><span class="cm-operator">*</span><span class="cm-number">60</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"Python for循环遍历演示完成！"</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"="</span><span class="cm-operator">*</span><span class="cm-number">60</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>while循环</code></span><span class="md-plain">的基本格式如下</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">while</span> <span class="cm-variable">条件</span>:</span><br><span role="presentation">    <span class="cm-variable">循环体</span></span></pre>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>break和continue</code></span><span class="md-plain">的使用和其他语言一致，这里不过多讲述</span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">函数</span></h2>
<p class="md-end-block md-p"><span class="md-plain">Python中的函数关键字为</span><span class="md-pair-s" spellcheck="false"><code>def</code></span><span class="md-plain">，其格式如下</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">def</span> <span class="cm-def">函数名</span>(<span class="cm-variable">入参列表</span>):</span><br><span role="presentation">    <span class="cm-string">""" 文档说明字符串 """</span></span><br><span role="presentation">    <span class="cm-variable">函数体</span></span><br><span role="presentation">    <span class="cm-keyword">return</span> <span class="cm-variable">反参</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">首先我们来解释一下这个一开始文档说明字符串，这个需要搭配</span><span class="md-pair-s" spellcheck="false"><code>方法名.__doc__</code></span><span class="md-plain">进行使用，当你在这个方法写了文档说明字符串的时候，你使用</span><span class="md-pair-s" spellcheck="false"><code>方法名.__doc__</code></span><span class="md-plain">就能打印出这些内容，相当于做了方法说明文档，当然，这个不是必须的，如果不写，调用</span><span class="md-pair-s" spellcheck="false"><code>.__doc__</code></span><span class="md-plain">会返回</span><span class="md-pair-s" spellcheck="false"><code>None</code></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">def</span> <span class="cm-def">add</span>(<span class="cm-variable">x</span>, <span class="cm-variable">y</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">"""计算x+y"""</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">x</span> <span class="cm-operator">+</span> <span class="cm-variable">y</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">add</span>.<span class="cm-property">__doc__</span>) <span class="cm-comment"># 输出 计算x+y</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">函数的入参和反参可以是七种数据类型中的任意一种，并且Python的入参是值传递，也就是说函数入参是形参，不会对外部数据造成影响</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">def</span> <span class="cm-def">change</span>(<span class="cm-variable">v</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">v</span><span class="cm-operator">=</span><span class="cm-variable">v</span><span class="cm-operator">+</span><span class="cm-number">1</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable cm-error">x</span> <span class="cm-operator">=</span> <span class="cm-number">10</span> &nbsp;</span><br><span role="presentation"><span class="cm-variable">change</span>(<span class="cm-variable">x</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">x</span>) <span class="cm-comment"># 依旧是10</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">Python的函数可以多个入参和多个反参,多个反参可以利用多个变量接收，可以当作一个整体进行接收</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">def</span> <span class="cm-def">my_counter</span>(<span class="cm-variable">a</span>,<span class="cm-variable">b</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">a</span><span class="cm-operator">+</span><span class="cm-variable">b</span>,<span class="cm-variable">a</span><span class="cm-operator">-</span><span class="cm-variable">b</span>,<span class="cm-variable">a</span><span class="cm-operator">*</span><span class="cm-variable">b</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable">a</span>,<span class="cm-variable">b</span>,<span class="cm-variable">c</span><span class="cm-operator">=</span> <span class="cm-variable">my_counter</span>(<span class="cm-number">1</span>,<span class="cm-number">2</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"和为"</span>,<span class="cm-variable">a</span>,<span class="cm-string">"差为"</span>,<span class="cm-variable">b</span>,<span class="cm-string">"积为"</span>,<span class="cm-variable">c</span>) &nbsp;</span><br><span role="presentation"><span class="cm-variable">tuple_v</span> <span class="cm-operator">=</span> <span class="cm-variable">my_counter</span>(<span class="cm-number">1</span>,<span class="cm-number">2</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-string">"和为"</span>,<span class="cm-variable">tuple_v</span>[<span class="cm-number">0</span>],<span class="cm-string">"差为"</span>,<span class="cm-variable">tuple_v</span>[<span class="cm-number">1</span>],<span class="cm-string">"积为"</span>,<span class="cm-variable">tuple_v</span>[<span class="cm-number">2</span>])</span></pre>
<p class="md-end-block md-p"><span class="md-plain">需要传入可变数量的参数的时候，可以利用元组拆分法，利用</span><span class="md-pair-s" spellcheck="false"><code>*name</code></span><span class="md-plain">作为入参 </span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">def</span> <span class="cm-def">my_counter</span>(<span class="cm-operator">*</span><span class="cm-variable">args</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-number">0</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> <span class="cm-variable">arg</span> <span class="cm-keyword">in</span> <span class="cm-variable">args</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">result</span> <span class="cm-operator">+=</span> <span class="cm-variable">arg</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">result</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">my_counter</span>(<span class="cm-number">1</span>, <span class="cm-number">2</span>, <span class="cm-number">3</span>, <span class="cm-number">4</span>, <span class="cm-number">5</span>)) &nbsp;<span class="cm-comment">#15</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">my_counter</span>(<span class="cm-number">1</span>, <span class="cm-number">2</span>, <span class="cm-number">3</span>)) <span class="cm-comment"># 6</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">自然，也可以同时指定一个入参和剩下的不确定入参，需要注意的是，</span><span class="md-pair-s" spellcheck="false"><code>可变参数只能作为最后一个参数且一个函数只能有一个可变参数，虽然编译器不会报错，但是运行时会出错，因为无法确定边界</code></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">def</span> <span class="cm-def">my_counter</span>(<span class="cm-variable">off</span>,<span class="cm-operator">*</span><span class="cm-variable">args</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-number">0</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> <span class="cm-variable">arg</span> <span class="cm-keyword">in</span> <span class="cm-variable">args</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">result</span> <span class="cm-operator">+=</span> <span class="cm-variable">arg</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">result</span><span class="cm-operator">+</span><span class="cm-variable">off</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">my_counter</span>(<span class="cm-operator">-</span><span class="cm-number">10</span>,<span class="cm-number">1</span>, <span class="cm-number">2</span>, <span class="cm-number">3</span>, <span class="cm-number">4</span>, <span class="cm-number">5</span>)) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">my_counter</span>(<span class="cm-number">10</span>,<span class="cm-number">1</span>, <span class="cm-number">2</span>, <span class="cm-number">3</span>))</span></pre>
<p class="md-end-block md-p"><span class="md-plain">上面通过</span><span class="md-pair-s" spellcheck="false"><code>*name</code></span><span class="md-plain">实现不确定入参，可以理解为创建了一个name的列表然后允许我们一直往里面加东西，除此之外，还可以利用</span><span class="md-pair-s" spellcheck="false"><code>**name</code></span><span class="md-plain">的方式，作为可变参数的键值对/字典</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">def</span> <span class="cm-def">plusDict</span>(<span class="cm-variable">a</span>,<span class="cm-variable">b</span>,<span class="cm-operator">**</span><span class="cm-variable">dict_v</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">""" 加入对Java和Golang的描述，也可以自定义加新的描述键值对 """</span> &nbsp; &nbsp;<span class="cm-variable">dict_v</span>[<span class="cm-string">"Java"</span>] <span class="cm-operator">=</span> <span class="cm-variable">a</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">dict_v</span>[<span class="cm-string">"Golang"</span>] <span class="cm-operator">=</span> <span class="cm-variable">b</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">dict_v</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">plusDict</span>( <span class="cm-string">"CRUD工程师"</span>,<span class="cm-string">"云原生的宠儿"</span>,<span class="cm-variable">Python</span><span class="cm-operator">=</span><span class="cm-string">"AI时代大哥大"</span>,<span class="cm-variable">Rust</span><span class="cm-operator">=</span><span class="cm-string">"高大上"</span>) )</span></pre>
<p class="md-end-block md-p"><span class="md-plain">在入参的时候，可以指定入参名从而不一定需要按照顺序传入参数</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">def</span> <span class="cm-def">my_counter</span>(<span class="cm-variable">a</span>, <span class="cm-variable">b</span>, <span class="cm-variable">c</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">a</span> <span class="cm-operator">+</span> <span class="cm-variable">b</span> <span class="cm-operator">*</span> <span class="cm-variable">c</span> &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">my_counter</span>(<span class="cm-number">1</span>,<span class="cm-variable">c</span><span class="cm-operator">=</span><span class="cm-number">2</span>,<span class="cm-variable">b</span><span class="cm-operator">=</span><span class="cm-number">3</span>))</span></pre>
<p class="md-end-block md-p"><span class="md-plain">在定义函数的时候，可以指定入参默认值，从而当不传入的时候，可以使用默认值</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">def</span> <span class="cm-def">yourSchool</span>(<span class="cm-variable">name</span>,<span class="cm-variable">level</span><span class="cm-operator">=</span><span class="cm-string">"本科"</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">"your school "</span> <span class="cm-operator">+</span> <span class="cm-variable">name</span> <span class="cm-operator">+</span> <span class="cm-string">"is a "</span> <span class="cm-operator">+</span> <span class="cm-variable">level</span> ) &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-variable cm-error">yourSchool</span>(<span class="cm-string">"HNUST"</span>) &nbsp;<span class="cm-comment"># 不会报错</span></span><br><span role="presentation"><span class="cm-variable">yourSchool</span>(<span class="cm-string">"HNUST"</span>,<span class="cm-string">"985"</span>)</span></pre>
<h2 class="md-end-block md-heading"><span class="md-plain">类</span></h2>
<h3 class="md-end-block md-heading"><span class="md-plain">定义和普通使用</span></h3>
<p class="md-end-block md-p"><span class="md-plain">类的定义我们就不多说了，Python中的类一样具备封装，继承，多态的特性(据说在深度学习领域类一般是用来封装参数传递，其余特性用的少)，并且在Python中其实也是一切皆对象，就连基本数据类型int也是</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">a</span> <span class="cm-operator">=</span> <span class="cm-number">1</span></span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-builtin">type</span>(<span class="cm-variable">a</span>)) <span class="cm-comment"># 输出：&lt;class 'int'&gt;</span></span></pre>
<p class="md-end-block md-p"><span class="md-plain">Python中类中的函数称之为方法，且约定俗成类的名称一般大写，Python的每个类必须包含一个</span><span class="md-pair-s" spellcheck="false"><code>__init__</code></span><span class="md-plain">的初始化方法（简单理解为有参构造方法）</span> <span class="md-pair-s" spellcheck="false"><code>init</code></span><span class="md-plain">方法内的入参通过赋值就可以让类对象拥有成员a,b，也可以直接不通过入参来定义成员，这些成员具备默认值</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">class</span> <span class="cm-def">Counter</span>: &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">""" 一个可以进行加减法的计算器 """</span> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">def</span> <span class="cm-def">__init__</span>(<span class="cm-variable-2">self</span>, <span class="cm-variable">a</span>, <span class="cm-variable">b</span>): &nbsp;<span class="cm-comment"># 用于完成初始化的特殊方法 &nbsp;</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-2">self</span>.<span class="cm-property">a</span> <span class="cm-operator">=</span> <span class="cm-variable">a</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-2">self</span>.<span class="cm-property">b</span> <span class="cm-operator">=</span> <span class="cm-variable">b</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-2">self</span>.<span class="cm-property">name</span> <span class="cm-operator">=</span> <span class="cm-string">"超级计算机"</span></span><br><span role="presentation"> &nbsp;</span><br><span class="cm-null cm-error"> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">def</span> <span class="cm-def">plus</span>(<span class="cm-variable-2">self</span>): &nbsp;<span class="cm-comment"># 类中的普通方法 &nbsp;</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable-2">self</span>.<span class="cm-property">a</span> <span class="cm-operator">+</span> <span class="cm-variable-2">self</span>.<span class="cm-property">b</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">def</span> <span class="cm-def">minus</span>(<span class="cm-variable-2">self</span>): &nbsp;<span class="cm-comment"># 类中的普通方法 &nbsp;</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable-2">self</span>.<span class="cm-property">a</span> <span class="cm-operator">-</span> <span class="cm-variable-2">self</span>.<span class="cm-property">b</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">def</span> <span class="cm-def">zwjs</span>(<span class="cm-variable-2">self</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">"欢迎使用"</span>, <span class="cm-variable-2">self</span>.<span class="cm-property">name</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-plain">可以看到，无论是自定义普通方法还是init初始化方法，都有一个</span><span class="md-pair-s" spellcheck="false"><code>self参数</code></span><span class="md-plain">，这个参数用于链接类内部和外部的信息，大家可以简单的理解为</span><span class="md-pair-s" spellcheck="false"><code>this指针</code></span><span class="md-plain">，而且从上面也可也看到，Python的类的成员属性貌似可以不要定义，会根据</span><span class="md-pair-s" spellcheck="false"><code>__init__方法</code></span><span class="md-plain">自动定义在</span><span class="md-pair-s" spellcheck="false"><code>self中</code></span><span class="md-plain">，需要的话直接去</span><span class="md-pair-s" spellcheck="false"><code>self</code></span><span class="md-plain">中拿就行了</span></p>
<p class="md-end-block md-p"><span class="md-plain">类定义完成后，就可以创建类的对象以及调用其成员方法和成员属性</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-variable">counter1</span> <span class="cm-operator">=</span> <span class="cm-variable">Counter</span>(<span class="cm-number">1</span>,<span class="cm-number">2</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">counter1</span>.<span class="cm-property">a</span>, <span class="cm-variable">counter1</span>.<span class="cm-property">b</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">counter1</span>.<span class="cm-property">plus</span>()) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">counter1</span>.<span class="cm-property">minus</span>()) &nbsp;</span><br><span role="presentation"><span class="cm-variable">counter1</span>.<span class="cm-property">zwjs</span>()</span></pre>
<h3 class="md-end-block md-heading"><span class="md-plain">继承</span></h3>
<p class="md-end-block md-p"><span class="md-plain">利用继承，可以让子类继承父类的属性和方法</span> <span class="md-plain">Python的继承的表现形式为</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">class</span> <span class="cm-def">类名</span>(<span class="cm-variable">父类名</span>):</span></pre>
<p class="md-end-block md-p"><span class="md-plain">在Python中还能多继承</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">class</span> <span class="cm-def">类名</span>(<span class="cm-variable">fu1</span>,<span class="cm-variable">fu2</span>)</span></pre>
<p class="md-end-block md-p"><span class="md-plain">子类需要在</span><span class="md-pair-s" spellcheck="false"><code>__init__</code></span><span class="md-plain">方法中调用</span><span class="md-pair-s" spellcheck="false"><code>super()函数</code></span><span class="md-plain">从而实现继承属性和方法，对于父类中已有属性和方法子类中再次定义的话就会覆盖重写</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="python" spellcheck="false"><span role="presentation"><span class="cm-keyword">class</span> <span class="cm-def">Counter2</span>(<span class="cm-variable">Counter</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-string">""" 可以进行加减乘除的计算器 """</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">def</span> <span class="cm-def">__init__</span>(<span class="cm-variable-2">self</span>, <span class="cm-variable">a</span>, <span class="cm-variable">b</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-builtin">super</span>().<span class="cm-property">__init__</span>(<span class="cm-variable">a</span>, <span class="cm-variable">b</span>) &nbsp;<span class="cm-comment"># 这个不写其实也没事，不写的话就不能使用父类的东西，只要不使用父类的东西就不会报错</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-2">self</span>.<span class="cm-property">name</span> <span class="cm-operator">=</span> <span class="cm-string">"超级计算机二代"</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-null cm-error"> &nbsp; &nbsp;</span><span class="cm-keyword">def</span> <span class="cm-def">multiply</span>(<span class="cm-variable-2">self</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable-2">self</span>.<span class="cm-property">a</span> <span class="cm-operator">*</span> <span class="cm-variable-2">self</span>.<span class="cm-property">b</span> &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">def</span> <span class="cm-def">divide</span>(<span class="cm-variable-2">self</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable-2">self</span>.<span class="cm-property">a</span> <span class="cm-operator">/</span> <span class="cm-variable-2">self</span>.<span class="cm-property">b</span> &nbsp;</span><br><span role="presentation"> &nbsp;</span><br><span role="presentation"><span class="cm-null cm-error"> &nbsp; &nbsp;</span><span class="cm-keyword">def</span> <span class="cm-def">zwjs</span>(<span class="cm-variable-2">self</span>): &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-builtin">print</span>(<span class="cm-string">"您好,欢迎使用"</span>, <span class="cm-variable-2">self</span>.<span class="cm-property">name</span>)</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"><span class="cm-variable">counter</span> <span class="cm-operator">=</span> <span class="cm-variable">Counter2</span>(<span class="cm-number">1</span>, <span class="cm-number">2</span>) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">counter</span>.<span class="cm-property">plus</span>()) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">counter</span>.<span class="cm-property">minus</span>()) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">counter</span>.<span class="cm-property">multiply</span>()) &nbsp;</span><br><span role="presentation"><span class="cm-builtin">print</span>(<span class="cm-variable">counter</span>.<span class="cm-property">divide</span>()) &nbsp;</span><br><span role="presentation"><span class="cm-variable">counter</span>.<span class="cm-property">zwjs</span>()</span></pre>
</div>]]></description>
    <pubDate>Wed, 28 Jan 2026 23:00:55 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=57</guid>
</item>
<item>
    <title>Nacos补档-客户端心跳同步问题.md</title>
    <link>http://8.154.40.120:24180/?post=56</link>
    <description><![CDATA[<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">我们今天来讨论一下Nacos客户端的心跳同步的一个问题</span></p>
<p class="md-end-block md-p"><span class="md-plain">我们知道如下几个点</span></p>
<ol class="ol-list">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Nacos客户端会和服务端进行心跳同步</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Nacos服务端在集群环境下采用单个责任节点+多个从节点的方式管理，责任节点负责负责健康检查，心跳处理等操作，从节点仅仅担任数据同步工作</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">一个微服务实例连接集群的条件下，请求可能会因为负载策略发送到任意一个Nacos节点上</span></p>
</li>
</ol>
<p class="md-end-block md-p"><span class="md-plain">那么综合上述，我们可以引出一个问题：</span><strong><span class="md-plain">如果客户端的心跳请求发送到了非责任节点会怎么处理？</span></strong></p>
<p class="md-end-block md-p"><span class="md-plain">这个地方可以参考Nacos仓库下面的一个issue</span></p>
<p class="md-end-block md-p"><span class="md-meta-i-c  md-link"><a href="https://github.com/alibaba/nacos/issues/5363" target="_blank" rel="noopener"><span class="md-plain">server端收到client beat请求后，不更新last beat时间，导致实际被判定为unhealthy &middot; Issue #5363 &middot; alibaba/nacos</span></a></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" spellcheck="false"><span role="presentation">针对上述问题，我们来举个例子：假设现在我们有三个集群节点，member1、member2、member3，又假设 member1 是负责 sotck-service 健康检查任务的节点。</span><br><span role="presentation">​</span><br><span role="presentation">stock-serivce 客户端发送心跳请求的时候，是随机发送到集群中某一个节点上的，假设 stock-service 没有给 member1 发送心跳请求，如果 lastBeat 属性不同步给集群节点，那么 member1 在做健康检查的时候，是不是会判断 stock-service 为不健康的实例？</span></pre>
<p class="md-end-block md-p"><span class="md-plain">我们可以看一下issue上大佬们关于这个问题的讨论与回复</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220202300.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220202300.png" alt="image-20260124220202300"></span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220213185.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220213185.png" alt="image-20260124220213185"></span></p>
<p class="md-end-block md-p"><span class="md-plain">按照大佬的意思就是：</span><span class="md-pair-s" spellcheck="false"><code>心跳请求应该是随机发送到集群中某一台上，然后通过distro协议转发到该服务的责任节点去统一更新实例心跳时间。且心跳时间不会进行同步。</code></span></p>
<p class="md-end-block md-p"><span class="md-plain">我们可以自己扒一下源码来了解一下</span></p>
<p class="md-end-block md-p"><span class="md-plain">我们来看到服务端对于心跳的处理这一块，可以看到有一个</span><span class="md-pair-s" spellcheck="false"><code>@CanDistro</code></span><span class="md-plain">的注解</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220613615.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220613615.png" alt="image-20260124220613615"></span></p>
<p class="md-end-block md-p"><span class="md-plain">这个注解是一个自定义注解，但是他的作用好像不是很明了，我们可以看一下哪个地方用到了这个注解，发现这里有个Filter过滤器中用到了</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220733729.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220733729.png" alt="image-20260124220733729"></span></p>
<p class="md-end-block md-p"><span class="md-plain">我们来看一下这个过滤器，可以看到最终其匹配的URL是所有的</span><span class="md-pair-s" spellcheck="false"><code>/v1/ns/*</code></span><span class="md-plain">的路径</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220851564.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124220851564.png" alt="image-20260124220851564"></span></p>
<p class="md-end-block md-p"><span class="md-plain">接下来我们就可以来看一下Filter中的过滤逻辑，代码很长，但是我们主要看一下重要的部分，即有我写了注释的那一行其实就行了</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">doFilter</span>(<span class="cm-variable">ServletRequest</span> <span class="cm-variable">servletRequest</span>, <span class="cm-variable">ServletResponse</span> <span class="cm-variable">servletResponse</span>, <span class="cm-variable">FilterChain</span> <span class="cm-variable">filterChain</span>)</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">throws</span> <span class="cm-variable">IOException</span>, <span class="cm-variable">ServletException</span> {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">ReuseHttpRequest</span> <span class="cm-variable">req</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">ReuseHttpServletRequest</span>((<span class="cm-variable">HttpServletRequest</span>) <span class="cm-variable">servletRequest</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">HttpServletResponse</span> <span class="cm-variable">resp</span> <span class="cm-operator">=</span> (<span class="cm-variable">HttpServletResponse</span>) <span class="cm-variable">servletResponse</span>;</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">urlString</span> <span class="cm-operator">=</span> <span class="cm-variable">req</span>.<span class="cm-variable">getRequestURI</span>();</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">StringUtils</span>.<span class="cm-variable">isNotBlank</span>(<span class="cm-variable">req</span>.<span class="cm-variable">getQueryString</span>())) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">urlString</span> <span class="cm-operator">+=</span> <span class="cm-string">"?"</span> <span class="cm-operator">+</span> <span class="cm-variable">req</span>.<span class="cm-variable">getQueryString</span>();</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">path</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">URI</span>(<span class="cm-variable">req</span>.<span class="cm-variable">getRequestURI</span>()).<span class="cm-variable">getPath</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">serviceName</span> <span class="cm-operator">=</span> <span class="cm-variable">req</span>.<span class="cm-variable">getParameter</span>(<span class="cm-variable">CommonParams</span>.<span class="cm-variable">SERVICE_NAME</span>);</span><br><span role="presentation">​</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">StringUtils</span>.<span class="cm-variable">isBlank</span>(<span class="cm-variable">serviceName</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceName</span> <span class="cm-operator">=</span> <span class="cm-variable">req</span>.<span class="cm-variable">getParameter</span>(<span class="cm-string">"dom"</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">StringUtils</span>.<span class="cm-variable">isNotBlank</span>(<span class="cm-variable">serviceName</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceName</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceName</span>.<span class="cm-variable">trim</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Method</span> <span class="cm-variable">method</span> <span class="cm-operator">=</span> <span class="cm-variable">controllerMethodsCache</span>.<span class="cm-variable">getMethod</span>(<span class="cm-variable">req</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">method</span> <span class="cm-operator">==</span> <span class="cm-atom">null</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">NoSuchMethodException</span>(<span class="cm-variable">req</span>.<span class="cm-variable">getMethod</span>() <span class="cm-operator">+</span> <span class="cm-string">" "</span> <span class="cm-operator">+</span> <span class="cm-variable">path</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">groupName</span> <span class="cm-operator">=</span> <span class="cm-variable">req</span>.<span class="cm-variable">getParameter</span>(<span class="cm-variable">CommonParams</span>.<span class="cm-variable">GROUP_NAME</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">StringUtils</span>.<span class="cm-variable">isBlank</span>(<span class="cm-variable">groupName</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">groupName</span> <span class="cm-operator">=</span> <span class="cm-variable">Constants</span>.<span class="cm-variable">DEFAULT_GROUP</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation">​</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">groupedServiceName</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceName</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">StringUtils</span>.<span class="cm-variable">isNotBlank</span>(<span class="cm-variable">serviceName</span>) <span class="cm-operator">&amp;&amp;</span> <span class="cm-operator">!</span><span class="cm-variable">serviceName</span>.<span class="cm-variable">contains</span>(<span class="cm-variable">Constants</span>.<span class="cm-variable">SERVICE_INFO_SPLITER</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">groupedServiceName</span> <span class="cm-operator">=</span> <span class="cm-variable">groupName</span> <span class="cm-operator">+</span> <span class="cm-variable">Constants</span>.<span class="cm-variable">SERVICE_INFO_SPLITER</span> <span class="cm-operator">+</span> <span class="cm-variable">serviceName</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span class="cm-comment">// 判断是否带有CanDistro注解 &amp;&amp; responsible方法即判断是否为责任节点</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">method</span>.<span class="cm-variable">isAnnotationPresent</span>(<span class="cm-variable">CanDistro</span>.<span class="cm-keyword">class</span>) <span class="cm-operator">&amp;&amp;</span> <span class="cm-operator">!</span><span class="cm-variable">distroMapper</span>.<span class="cm-variable">responsible</span>(<span class="cm-variable">groupedServiceName</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">userAgent</span> <span class="cm-operator">=</span> <span class="cm-variable">req</span>.<span class="cm-variable">getHeader</span>(<span class="cm-variable">HttpHeaderConsts</span>.<span class="cm-variable">USER_AGENT_HEADER</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">StringUtils</span>.<span class="cm-variable">isNotBlank</span>(<span class="cm-variable">userAgent</span>) <span class="cm-operator">&amp;&amp;</span> <span class="cm-variable">userAgent</span>.<span class="cm-variable">contains</span>(<span class="cm-variable">UtilsAndCommons</span>.<span class="cm-variable">NACOS_SERVER_HEADER</span>)) {</span><br><span role="presentation">​</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">SRV_LOG</span>.<span class="cm-variable">error</span>(<span class="cm-string">"receive invalid redirect request from peer {}"</span>, <span class="cm-variable">req</span>.<span class="cm-variable">getRemoteAddr</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">resp</span>.<span class="cm-variable">sendError</span>(<span class="cm-variable">HttpServletResponse</span>.<span class="cm-variable">SC_BAD_REQUEST</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-string">"receive invalid redirect request from peer "</span> <span class="cm-operator">+</span> <span class="cm-variable">req</span>.<span class="cm-variable">getRemoteAddr</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 得到目标的真正的责任节点</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">final</span> <span class="cm-variable-3">String</span> <span class="cm-variable">targetServer</span> <span class="cm-operator">=</span> <span class="cm-variable">distroMapper</span>.<span class="cm-variable">mapSrv</span>(<span class="cm-variable">groupedServiceName</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">headerList</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">ArrayList</span><span class="cm-operator">&lt;&gt;</span>(<span class="cm-number">16</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Enumeration</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">headers</span> <span class="cm-operator">=</span> <span class="cm-variable">req</span>.<span class="cm-variable">getHeaderNames</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">while</span> (<span class="cm-variable">headers</span>.<span class="cm-variable">hasMoreElements</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">headerName</span> <span class="cm-operator">=</span> <span class="cm-variable">headers</span>.<span class="cm-variable">nextElement</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">headerList</span>.<span class="cm-variable">add</span>(<span class="cm-variable">headerName</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">headerList</span>.<span class="cm-variable">add</span>(<span class="cm-variable">req</span>.<span class="cm-variable">getHeader</span>(<span class="cm-variable">headerName</span>));</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">final</span> <span class="cm-variable-3">String</span> <span class="cm-variable">body</span> <span class="cm-operator">=</span> <span class="cm-variable">IoUtils</span>.<span class="cm-variable">toString</span>(<span class="cm-variable">req</span>.<span class="cm-variable">getInputStream</span>(), <span class="cm-variable">Charsets</span>.<span class="cm-variable">UTF_8</span>.<span class="cm-variable">name</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">final</span> <span class="cm-variable">Map</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span>, <span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">paramsValue</span> <span class="cm-operator">=</span> <span class="cm-variable">HttpClient</span>.<span class="cm-variable">translateParameterMap</span>(<span class="cm-variable">req</span>.<span class="cm-variable">getParameterMap</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">RestResult</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-variable">HttpClient</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  .<span class="cm-variable">request</span>(<span class="cm-string">"http://"</span> <span class="cm-operator">+</span> <span class="cm-variable">targetServer</span> <span class="cm-operator">+</span> <span class="cm-variable">req</span>.<span class="cm-variable">getRequestURI</span>(), <span class="cm-variable">headerList</span>, <span class="cm-variable">paramsValue</span>, <span class="cm-variable">body</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">PROXY_CONNECT_TIMEOUT</span>, <span class="cm-variable">PROXY_READ_TIMEOUT</span>, <span class="cm-variable">Charsets</span>.<span class="cm-variable">UTF_8</span>.<span class="cm-variable">name</span>(), <span class="cm-variable">req</span>.<span class="cm-variable">getMethod</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">data</span> <span class="cm-operator">=</span> <span class="cm-variable">result</span>.<span class="cm-variable">ok</span>() <span class="cm-operator">?</span> <span class="cm-variable">result</span>.<span class="cm-variable">getData</span>() : <span class="cm-variable">result</span>.<span class="cm-variable">getMessage</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">WebUtils</span>.<span class="cm-variable">response</span>(<span class="cm-variable">resp</span>, <span class="cm-variable">data</span>, <span class="cm-variable">result</span>.<span class="cm-variable">getCode</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">Exception</span> <span class="cm-variable">ignore</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">SRV_LOG</span>.<span class="cm-variable">warn</span>(<span class="cm-string">"[DISTRO-FILTER] request failed: "</span> <span class="cm-operator">+</span> <span class="cm-variable">distroMapper</span>.<span class="cm-variable">mapSrv</span>(<span class="cm-variable">groupedServiceName</span>)</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-operator">+</span> <span class="cm-variable">urlString</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  } <span class="cm-keyword">else</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">OverrideParameterRequestWrapper</span> <span class="cm-variable">requestWrapper</span> <span class="cm-operator">=</span> <span class="cm-variable">OverrideParameterRequestWrapper</span>.<span class="cm-variable">buildRequest</span>(<span class="cm-variable">req</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">requestWrapper</span>.<span class="cm-variable">addParameter</span>(<span class="cm-variable">CommonParams</span>.<span class="cm-variable">SERVICE_NAME</span>, <span class="cm-variable">groupedServiceName</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">filterChain</span>.<span class="cm-variable">doFilter</span>(<span class="cm-variable">requestWrapper</span>, <span class="cm-variable">resp</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">AccessControlException</span> <span class="cm-variable">e</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">resp</span>.<span class="cm-variable">sendError</span>(<span class="cm-variable">HttpServletResponse</span>.<span class="cm-variable">SC_FORBIDDEN</span>, <span class="cm-string">"access denied: "</span> <span class="cm-operator">+</span> <span class="cm-variable">ExceptionUtil</span>.<span class="cm-variable">getAllExceptionMsg</span>(<span class="cm-variable">e</span>));</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">NoSuchMethodException</span> <span class="cm-variable">e</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">resp</span>.<span class="cm-variable">sendError</span>(<span class="cm-variable">HttpServletResponse</span>.<span class="cm-variable">SC_NOT_IMPLEMENTED</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-string">"no such api:"</span> <span class="cm-operator">+</span> <span class="cm-variable">req</span>.<span class="cm-variable">getMethod</span>() <span class="cm-operator">+</span> <span class="cm-string">":"</span> <span class="cm-operator">+</span> <span class="cm-variable">req</span>.<span class="cm-variable">getRequestURI</span>());</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">Exception</span> <span class="cm-variable">e</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">resp</span>.<span class="cm-variable">sendError</span>(<span class="cm-variable">HttpServletResponse</span>.<span class="cm-variable">SC_INTERNAL_SERVER_ERROR</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-string">"Server failed,"</span> <span class="cm-operator">+</span> <span class="cm-variable">ExceptionUtil</span>.<span class="cm-variable">getAllExceptionMsg</span>(<span class="cm-variable">e</span>));</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">那么可以看到，所有的标记了@CanDistro注解的请求，都会请求的时候判断：如果当前处理请求的集群节点，不是这个 service 的责任节点，那么这个过滤器就会转发到对应的责任节点上做处理。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain">那我们回到最开始的问题上：</span><strong><span class="md-pair-s" spellcheck="false"><code>lastBeat 属性是如何同步的？</code></span></strong></p>
<p class="md-end-block md-p md-focus"><strong><span class="md-plain">心跳检测在请求到达节点后就能自行检测是否责任节点上，如果不在则会直接发送给责任节点，而不是等待全体同步，所以可以理解为心跳时间是定向同步而非全体同步，心跳可能会发送到非责任节点，但是非责任节点一定会检测并且转发给责任节点，责任节点对于心跳的数据不会也没有必要去同步到别的节点上，因为别的节点完全不需要也不负责</span></strong></p>]]></description>
    <pubDate>Sat, 24 Jan 2026 22:21:40 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=56</guid>
</item>
<item>
    <title>Nacos源码学习计划-Day26-Nacos2.x-事件驱动架构</title>
    <link>http://8.154.40.120:24180/?post=55</link>
    <description><![CDATA[<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">我们前面提到了，Nacos2.X版本中出现了很多的事件驱动架构的业务处理，对于很多逻辑，例如客户端注册/下线；服务改变；服务订阅等等都是发起一个Event，然后对应的有个Handler处理器进行处理</span></p>
<h1 class="md-end-block md-heading"><span class="md-plain">整体结构分析</span></h1>
<p class="md-end-block md-p"><span class="md-plain">我们可以来看一下之前我们已经了解过的几个Event类对象，其实可以发现，都是继承自Event这个事件父类</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260123181253736.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260123181253736.png" alt="image-20260123181253736"></span></p>
<p class="md-end-block md-p"><span class="md-plain">同样的，我们再去看一下所有的Handler所在类，也可以发现，有些继承自</span><strong><span class="md-pair-s" spellcheck="false"><code>SmartSubscriber</code></span></strong><span class="md-plain">，并且</span><span class="md-pair-s" spellcheck="false"><code>SmartSubscriber</code></span><span class="md-plain">之上还有个顶级接口</span><span class="md-pair-s" spellcheck="false"><code>Subscriber</code></span><span class="md-plain">，就是订阅者，在这里定义了顶级抽象方法</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">abstract</span> <span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable">Class</span><span class="cm-operator">&lt;?</span> <span class="cm-keyword">extends</span> <span class="cm-variable">Event</span><span class="cm-operator">&gt;&gt;</span> <span class="cm-def">subscribeTypes</span>();</span></pre>
<p class="md-end-block md-p"><strong><span class="md-plain">该方法的主要作用就是返回该订阅者感兴趣的Event类型的List集合</span></strong></p>
<p class="md-end-block md-p"><span class="md-plain">除此之外在顶级接口中</span><span class="md-pair-s" spellcheck="false"><code>Subscriber</code></span><span class="md-plain">中还有一个</span><span class="md-pair-s" spellcheck="false"><code>onEvent()</code></span><span class="md-plain">方法，该方法就是当对应的Event来了之后进行处理回调的方法</span></p>
<p class="md-end-block md-p"><span class="md-plain">在每个子类的构造方法上，还会调用一个</span><span class="md-pair-s" spellcheck="false"><code>registerSubscriber</code></span><span class="md-plain">方法，该方法是</span><span class="md-pair-s" spellcheck="false"><code>NotifyCenter</code></span><span class="md-plain">中的静态方法</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">registerSubscriber</span>(<span class="cm-keyword">this</span>);</span><br><span role="presentation"><span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">registerSubscriber</span>(<span class="cm-keyword">this</span>, <span class="cm-variable">NamingEventPublisherFactory</span>.<span class="cm-variable">getInstance</span>());</span></pre>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260123181418603.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260123181418603.png" alt="image-20260123181418603"></span></p>
<p class="md-end-block md-p"><span class="md-plain">根据如上的机构，其实我们完全可以自己基于Nacos提供的事件驱动架构，自己写一些事件和订阅者进行运行</span></p>
<p class="md-end-block md-p"><span class="md-plain">如下，我们先写一个</span><span class="md-pair-s" spellcheck="false"><code>TestEvent</code></span><span class="md-plain">测试事件类</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">import</span> <span class="cm-variable">com</span>.<span class="cm-variable">alibaba</span>.<span class="cm-variable">nacos</span>.<span class="cm-variable">common</span>.<span class="cm-variable">notify</span>.<span class="cm-variable">Event</span>;</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">class</span> <span class="cm-def">TestEvent</span> <span class="cm-keyword">extends</span> <span class="cm-variable">Event</span> {</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">然后写一个对应的订阅者</span><span class="md-pair-s" spellcheck="false"><code>TestSubscriber</code></span><span class="md-plain">，实现</span><span class="md-pair-s" spellcheck="false"><code>subscribeTypes</code></span><span class="md-plain">和</span><span class="md-pair-s" spellcheck="false"><code>onEvent</code></span><span class="md-plain">方法，在构造方法中将该订阅者注册上去</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">import</span> <span class="cm-variable">com</span>.<span class="cm-variable">alibaba</span>.<span class="cm-variable">nacos</span>.<span class="cm-variable">common</span>.<span class="cm-variable">notify</span>.<span class="cm-variable">Event</span>;</span><br><span role="presentation"><span class="cm-keyword">import</span> <span class="cm-variable">com</span>.<span class="cm-variable">alibaba</span>.<span class="cm-variable">nacos</span>.<span class="cm-variable">common</span>.<span class="cm-variable">notify</span>.<span class="cm-variable">NotifyCenter</span>;</span><br><span role="presentation"><span class="cm-keyword">import</span> <span class="cm-variable">com</span>.<span class="cm-variable">alibaba</span>.<span class="cm-variable">nacos</span>.<span class="cm-variable">common</span>.<span class="cm-variable">notify</span>.<span class="cm-variable">listener</span>.<span class="cm-variable">SmartSubscriber</span>;</span><br><span role="presentation"><span class="cm-keyword">import</span> <span class="cm-variable">org</span>.<span class="cm-variable">springframework</span>.<span class="cm-variable">stereotype</span>.<span class="cm-variable">Component</span>;</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">import</span> <span class="cm-variable">java</span>.<span class="cm-variable">util</span>.<span class="cm-variable">LinkedList</span>;</span><br><span role="presentation"><span class="cm-keyword">import</span> <span class="cm-variable">java</span>.<span class="cm-variable">util</span>.<span class="cm-variable">List</span>;</span><br><span role="presentation">​</span><br><span class="cm-meta">@Component</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">class</span> <span class="cm-def">TestSubscriber</span> <span class="cm-keyword">extends</span> <span class="cm-variable">SmartSubscriber</span> {</span><br><span role="presentation">​</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-variable">TestSubscriber</span>() {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">registerSubscriber</span>(<span class="cm-keyword">this</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">​</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-meta">@Override</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable">Class</span><span class="cm-operator">&lt;?</span> <span class="cm-keyword">extends</span> <span class="cm-variable">Event</span><span class="cm-operator">&gt;&gt;</span> <span class="cm-variable">subscribeTypes</span>() {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable">Class</span><span class="cm-operator">&lt;?</span> <span class="cm-keyword">extends</span> <span class="cm-variable">Event</span><span class="cm-operator">&gt;&gt;</span> <span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">LinkedList</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">result</span>.<span class="cm-variable">add</span>(<span class="cm-variable">TestEvent</span>.<span class="cm-keyword">class</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">result</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">​</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-meta">@Override</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-variable">onEvent</span>(<span class="cm-variable">Event</span> <span class="cm-variable">event</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">System</span>.<span class="cm-variable">out</span>.<span class="cm-variable">println</span>(<span class="cm-string">"TestSubscriber onEvent"</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">我们写一个测试类，来测试一下上面的代码，可以看到，调用完</span><span class="md-pair-s" spellcheck="false"><code>publishEvent</code></span><span class="md-plain">之后，对应的订阅者的</span><span class="md-pair-s" spellcheck="false"><code>onEvent</code></span><span class="md-plain">方法也成功被调用且打印了输出</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124114757110.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124114757110.png" alt="image-20260124114757110"></span></p>
<h1 class="md-end-block md-heading"><span class="md-plain">源码分析</span></h1>
<h2 class="md-end-block md-heading"><span class="md-plain">事件发布</span></h2>
<p class="md-end-block md-p"><span class="md-plain">上面我们了解一下事件驱动架构的大致结构，我们现在就可以开始分析，我们先从事件的发布这块入手</span></p>
<p class="md-end-block md-p"><span class="md-plain">我们知道，发布事件用的是</span><span class="md-pair-s" spellcheck="false"><code>NotifyCenter.publishEvent</code></span><span class="md-plain">这个方法，我们来看下这个方法的源码</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(<span class="cm-keyword">new</span> <span class="cm-variable">TestEvent</span>());</span><br><span role="presentation">​</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">static</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">publishEvent</span>(<span class="cm-keyword">final</span> <span class="cm-variable">Event</span> <span class="cm-variable">event</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 跟主线任务</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">publishEvent</span>(<span class="cm-variable">event</span>.<span class="cm-variable">getClass</span>(), <span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">Throwable</span> <span class="cm-variable">ex</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">LOGGER</span>.<span class="cm-variable">error</span>(<span class="cm-string">"There was an exception to the message publishing : "</span>, <span class="cm-variable">ex</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">false</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-keyword">static</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">publishEvent</span>(<span class="cm-keyword">final</span> <span class="cm-variable">Class</span><span class="cm-operator">&lt;?</span> <span class="cm-keyword">extends</span> <span class="cm-variable">Event</span><span class="cm-operator">&gt;</span> <span class="cm-variable">eventType</span>, <span class="cm-keyword">final</span> <span class="cm-variable">Event</span> <span class="cm-variable">event</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">ClassUtils</span>.<span class="cm-variable">isAssignableFrom</span>(<span class="cm-variable">SlowEvent</span>.<span class="cm-keyword">class</span>, <span class="cm-variable">eventType</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">INSTANCE</span>.<span class="cm-variable">sharePublisher</span>.<span class="cm-variable">publish</span>(<span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 根据Event的类型的到事件类型Topic</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">final</span> <span class="cm-variable-3">String</span> <span class="cm-variable">topic</span> <span class="cm-operator">=</span> <span class="cm-variable">ClassUtils</span>.<span class="cm-variable">getCanonicalName</span>(<span class="cm-variable">eventType</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 根据发布事件类型获取 EventPublisher 对象，该对象中会包含订阅者信息</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 订阅者怎么来的？ 从这里可以看到肯定是从一个Map中获取的，具体的在下文章节会讲到，publisherMap其实就是一个以Event全类名为key，EventPublisher对象(该对象成员属性subscribers会保存对应的订阅者类)为value的map，</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">EventPublisher</span> <span class="cm-variable">publisher</span> <span class="cm-operator">=</span> <span class="cm-variable">INSTANCE</span>.<span class="cm-variable">publisherMap</span>.<span class="cm-variable">get</span>(<span class="cm-variable">topic</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">publisher</span> <span class="cm-operator">!=</span> <span class="cm-atom">null</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 跟主线任务</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">publisher</span>.<span class="cm-variable">publish</span>(<span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">LOGGER</span>.<span class="cm-variable">warn</span>(<span class="cm-string">"There are no [{}] publishers for this event, please register"</span>, <span class="cm-variable">topic</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">false</span>;</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span><br><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">publish</span>(<span class="cm-variable">Event</span> <span class="cm-variable">event</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">checkIsStart</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 把事件放入到了一个 阻塞队列</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">boolean</span> <span class="cm-variable">success</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-variable">queue</span>.<span class="cm-variable">offer</span>(<span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 如果没有放入成功则手动调用receiveEvent方法接收并通知Subscriber处理该事件</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">success</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">LOGGER</span>.<span class="cm-variable">warn</span>(<span class="cm-string">"Unable to plug in due to interruption, synchronize sending time, event : {}"</span>, <span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">receiveEvent</span>(<span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">可以看到，又是利用到了阻塞队列，自然我们就可以去找到队列中拿任务的逻辑</span></p>
<p class="md-end-block md-p"><span class="md-plain">可以发现就在</span><span class="md-pair-s" spellcheck="false"><code>DefaultPublisher</code></span><span class="md-plain">中，在这一段代码中，我们有看到 </span><span class="md-pair-s" spellcheck="false"><code>subscribers</code></span><span class="md-plain"> 一个 Set 集合，注意，这里并不是全部的订阅者，只是当前发布的事件对应的订阅者，并不会通知全部的订阅者哈</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">for</span> (; ; ) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">shutdown</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">break</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 从阻塞队列取数据</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">final</span> <span class="cm-variable">Event</span> <span class="cm-variable">event</span> <span class="cm-operator">=</span> <span class="cm-variable">queue</span>.<span class="cm-variable">take</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 处理事件</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">receiveEvent</span>(<span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">UPDATER</span>.<span class="cm-variable">compareAndSet</span>(<span class="cm-keyword">this</span>, <span class="cm-variable">lastEventSequence</span>, <span class="cm-variable">Math</span>.<span class="cm-variable">max</span>(<span class="cm-variable">lastEventSequence</span>, <span class="cm-variable">event</span>.<span class="cm-variable">sequence</span>()));</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">protected</span> <span class="cm-keyword">final</span> <span class="cm-variable">ConcurrentHashSet</span><span class="cm-operator">&lt;</span><span class="cm-variable">Subscriber</span><span class="cm-operator">&gt;</span> <span class="cm-variable">subscribers</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">ConcurrentHashSet</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-variable-3">void</span> <span class="cm-def">receiveEvent</span>(<span class="cm-variable">Event</span> <span class="cm-variable">event</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">final</span> <span class="cm-variable-3">long</span> <span class="cm-variable">currentEventSequence</span> <span class="cm-operator">=</span> <span class="cm-variable">event</span>.<span class="cm-variable">sequence</span>();</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">hasSubscriber</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">LOGGER</span>.<span class="cm-variable">warn</span>(<span class="cm-string">"[NotifyCenter] the {} is lost, because there is no subscriber."</span>, <span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 循环遍历通知发布事件对应的订阅者</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable">Subscriber</span> <span class="cm-variable">subscriber</span> : <span class="cm-variable">subscribers</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// Whether to ignore expiration events</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">subscriber</span>.<span class="cm-variable">ignoreExpireEvent</span>() <span class="cm-operator">&amp;&amp;</span> <span class="cm-variable">lastEventSequence</span> <span class="cm-operator">&gt;</span> <span class="cm-variable">currentEventSequence</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">LOGGER</span>.<span class="cm-variable">debug</span>(<span class="cm-string">"[NotifyCenter] the {} is unacceptable to this subscriber, because had expire"</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">event</span>.<span class="cm-variable">getClass</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">continue</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// Because unifying smartSubscriber and subscriber, so here need to think of compatibility.</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// Remove original judge part of codes.</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">//通知</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">notifySubscriber</span>(<span class="cm-variable">subscriber</span>, <span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">然后最后调用了 </span><span class="md-pair-s" spellcheck="false"><code>notifySubscriber(subscriber, event);</code></span><span class="md-plain"> 方法来通知订阅者，代码如下:</span></p>
<p class="md-end-block md-p"><span class="md-plain">通过这段代码，我们能够知道，</span><strong><span class="md-plain">最后是使用线程异步的方式</span></strong><span class="md-plain">，来通知订阅者的。 事件发布流程我们就分析完了，核心关键还是利用了 </span><span class="md-pair-s" spellcheck="false"><code>阻塞队列 + 异步任务</code></span><span class="md-plain"> 来进行实现的，那么接下来我们一起来看看，订阅者注册的流程。</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">notifySubscriber</span>(<span class="cm-keyword">final</span> <span class="cm-variable">Subscriber</span> <span class="cm-variable">subscriber</span>, <span class="cm-keyword">final</span> <span class="cm-variable">Event</span> <span class="cm-variable">event</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">LOGGER</span>.<span class="cm-variable">debug</span>(<span class="cm-string">"[NotifyCenter] the {} will received by {}"</span>, <span class="cm-variable">event</span>, <span class="cm-variable">subscriber</span>);</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 创建一个任务，就是调用订阅者的 onEvent 方法</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">final</span> <span class="cm-variable">Runnable</span> <span class="cm-variable">job</span> <span class="cm-operator">=</span> () <span class="cm-operator">-&gt;</span> <span class="cm-variable">subscriber</span>.<span class="cm-variable">onEvent</span>(<span class="cm-variable">event</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">final</span> <span class="cm-variable">Executor</span> <span class="cm-variable">executor</span> <span class="cm-operator">=</span> <span class="cm-variable">subscriber</span>.<span class="cm-variable">executor</span>();</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">executor</span> <span class="cm-operator">!=</span> <span class="cm-atom">null</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 执行任务</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">executor</span>.<span class="cm-variable">execute</span>(<span class="cm-variable">job</span>);</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">else</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">job</span>.<span class="cm-variable">run</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">Throwable</span> <span class="cm-variable">e</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">LOGGER</span>.<span class="cm-variable">error</span>(<span class="cm-string">"Event callback exception: "</span>, <span class="cm-variable">e</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<h2 class="md-end-block md-heading"><span class="md-plain">订阅者注册</span></h2>
<p class="md-end-block md-p"><span class="md-plain">看完了事件的发布，我们来看订阅者这边</span></p>
<p class="md-end-block md-p"><span class="md-plain">每个订阅者在构造方法中都需要先将自己注册，所以我们先来看注册部分的代码，也就是</span><span class="md-pair-s" spellcheck="false"><code>NotifyCenter.registerSubscriber()</code></span></p>
<p class="md-end-block md-p"><span class="md-plain">可以看到，首先这个就是调用</span><span class="md-pair-s" spellcheck="false"><code>subscribeTypes</code></span><span class="md-plain">获取当前订阅者订阅的Event类型的List列表，然后遍历这个列表，调用</span><span class="md-pair-s" spellcheck="false"><code>INSTANCE.sharePublisher.addSubscriber</code></span><span class="md-plain">封装保存或者调用</span><span class="md-pair-s" spellcheck="false"><code>addSubscriber</code></span><span class="md-plain">这个方法(两种情况的底层都将内容保存到了subscribers这个Set中) </span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">TestSubscriber</span>() {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">registerSubscriber</span>(<span class="cm-keyword">this</span>);</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">static</span> <span class="cm-variable-3">void</span> <span class="cm-def">registerSubscriber</span>(<span class="cm-keyword">final</span> <span class="cm-variable">Subscriber</span> <span class="cm-variable">consumer</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">registerSubscriber</span>(<span class="cm-variable">consumer</span>, <span class="cm-variable">DEFAULT_PUBLISHER_FACTORY</span>);</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">static</span> <span class="cm-variable-3">void</span> <span class="cm-def">registerSubscriber</span>(<span class="cm-keyword">final</span> <span class="cm-variable">Subscriber</span> <span class="cm-variable">consumer</span>, <span class="cm-keyword">final</span> <span class="cm-variable">EventPublisherFactory</span> <span class="cm-variable">factory</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// If you want to listen to multiple events, you do it separately,</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// based on subclass's subscribeTypes method return list, it can register to publisher.</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">consumer</span> <span class="cm-keyword">instanceof</span> <span class="cm-variable">SmartSubscriber</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 在这里会调用 subscribeTypes 方法，来获取我们需要监听的事件，然后进行遍历</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable">Class</span><span class="cm-operator">&lt;?</span> <span class="cm-keyword">extends</span> <span class="cm-variable">Event</span><span class="cm-operator">&gt;</span> <span class="cm-variable">subscribeType</span> : ((<span class="cm-variable">SmartSubscriber</span>) <span class="cm-variable">consumer</span>).<span class="cm-variable">subscribeTypes</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// For case, producer: defaultSharePublisher -&gt; consumer: smartSubscriber.</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">ClassUtils</span>.<span class="cm-variable">isAssignableFrom</span>(<span class="cm-variable">SlowEvent</span>.<span class="cm-keyword">class</span>, <span class="cm-variable">subscribeType</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">INSTANCE</span>.<span class="cm-variable">sharePublisher</span>.<span class="cm-variable">addSubscriber</span>(<span class="cm-variable">consumer</span>, <span class="cm-variable">subscribeType</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  } <span class="cm-keyword">else</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// For case, producer: defaultPublisher -&gt; consumer: subscriber.</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 添加订阅者</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">addSubscriber</span>(<span class="cm-variable">consumer</span>, <span class="cm-variable">subscribeType</span>, <span class="cm-variable">factory</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 这里代码省略</span></span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">接下来我们继续看</span><span class="md-pair-s" spellcheck="false"><code>addSubscriber</code></span><span class="md-plain">方法</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-keyword">static</span> <span class="cm-variable-3">void</span> <span class="cm-def">addSubscriber</span>(<span class="cm-keyword">final</span> <span class="cm-variable">Subscriber</span> <span class="cm-variable">consumer</span>, <span class="cm-variable">Class</span><span class="cm-operator">&lt;?</span> <span class="cm-keyword">extends</span> <span class="cm-variable">Event</span><span class="cm-operator">&gt;</span> <span class="cm-variable">subscribeType</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">EventPublisherFactory</span> <span class="cm-variable">factory</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">final</span> <span class="cm-variable-3">String</span> <span class="cm-variable">topic</span> <span class="cm-operator">=</span> <span class="cm-variable">ClassUtils</span>.<span class="cm-variable">getCanonicalName</span>(<span class="cm-variable">subscribeType</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">synchronized</span> (<span class="cm-variable">NotifyCenter</span>.<span class="cm-keyword">class</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 这里很关键，会创建 EventPublisher 对象，一个事件会对应一个 EventPublisher 一个对象</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">MapUtil</span>.<span class="cm-variable">computeIfAbsent</span>(<span class="cm-variable">INSTANCE</span>.<span class="cm-variable">publisherMap</span>, <span class="cm-variable">topic</span>, <span class="cm-variable">factory</span>, <span class="cm-variable">subscribeType</span>, <span class="cm-variable">ringBufferSize</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 获取事件对应的 EventPublisher 对象</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">EventPublisher</span> <span class="cm-variable">publisher</span> <span class="cm-operator">=</span> <span class="cm-variable">INSTANCE</span>.<span class="cm-variable">publisherMap</span>.<span class="cm-variable">get</span>(<span class="cm-variable">topic</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">publisher</span> <span class="cm-keyword">instanceof</span> <span class="cm-variable">ShardedEventPublisher</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  ((<span class="cm-variable">ShardedEventPublisher</span>) <span class="cm-variable">publisher</span>).<span class="cm-variable">addSubscriber</span>(<span class="cm-variable">consumer</span>, <span class="cm-variable">subscribeType</span>);</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">else</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 往 EventPublisher 对象添加订阅者信息</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">publisher</span>.<span class="cm-variable">addSubscriber</span>(<span class="cm-variable">consumer</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><strong><span class="md-plain">其中</span><span class="md-pair-s" spellcheck="false"><code>MapUtil.computeIfAbsent</code></span><span class="md-plain">的逻辑如下，可以看到，在这里会利用传入的第三个参数，调用其apply方法，利用第四个和第五个参数创建出一个EventPublisher对象，这个对象内部会存储我们的订阅者信息，也就是 </span><span class="md-pair-s" spellcheck="false"><code>subscribers</code></span><span class="md-plain"> 属性</span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@NotThreadSafe</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">static</span> <span class="cm-operator">&lt;</span><span class="cm-variable">K</span>, <span class="cm-variable">C</span>, <span class="cm-variable">V</span>, <span class="cm-variable">T</span><span class="cm-operator">&gt;</span> <span class="cm-variable">V</span> <span class="cm-def">computeIfAbsent</span>(<span class="cm-variable">Map</span><span class="cm-operator">&lt;</span><span class="cm-variable">K</span>, <span class="cm-variable">V</span><span class="cm-operator">&gt;</span> <span class="cm-variable">target</span>, <span class="cm-variable">K</span> <span class="cm-variable">key</span>, <span class="cm-variable">BiFunction</span><span class="cm-operator">&lt;</span><span class="cm-variable">C</span>, <span class="cm-variable">T</span>, <span class="cm-variable">V</span><span class="cm-operator">&gt;</span> <span class="cm-variable">mappingFunction</span>, <span class="cm-variable">C</span> <span class="cm-variable">param1</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">T</span> <span class="cm-variable">param2</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Objects</span>.<span class="cm-variable">requireNonNull</span>(<span class="cm-variable">target</span>, <span class="cm-string">"target"</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Objects</span>.<span class="cm-variable">requireNonNull</span>(<span class="cm-variable">key</span>, <span class="cm-string">"key"</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Objects</span>.<span class="cm-variable">requireNonNull</span>(<span class="cm-variable">mappingFunction</span>, <span class="cm-string">"mappingFunction"</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Objects</span>.<span class="cm-variable">requireNonNull</span>(<span class="cm-variable">param1</span>, <span class="cm-string">"param1"</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Objects</span>.<span class="cm-variable">requireNonNull</span>(<span class="cm-variable">param2</span>, <span class="cm-string">"param2"</span>);</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">V</span> <span class="cm-variable">val</span> <span class="cm-operator">=</span> <span class="cm-variable">target</span>.<span class="cm-variable">get</span>(<span class="cm-variable">key</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">val</span> <span class="cm-operator">==</span> <span class="cm-atom">null</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">V</span> <span class="cm-variable">ret</span> <span class="cm-operator">=</span> <span class="cm-variable">mappingFunction</span>.<span class="cm-variable">apply</span>(<span class="cm-variable">param1</span>, <span class="cm-variable">param2</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">target</span>.<span class="cm-variable">put</span>(<span class="cm-variable">key</span>, <span class="cm-variable">ret</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">ret</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">val</span>;</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124171514611.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260124171514611.png" alt="image-20260124171514611"></span></p>
<p class="md-end-block md-p"><span class="md-plain">在代码的最后，</span><strong><span class="md-plain">会调用 </span><span class="md-pair-s" spellcheck="false"><code>addSubscriber</code></span><span class="md-plain"> 方法往</span><span class="md-pair-s" spellcheck="false"><code>subscribers</code></span><span class="md-plain"> 属性中添加订阅者，完成注册</span></strong><span class="md-plain">。</span></p>
<h1 class="md-end-block md-heading md-focus"><span class="md-plain">总结</span></h1>
<p class="md-end-block md-p"><span class="md-plain">Nacos2.X中的事件驱动架构，整体基于Event(事件)-Subscriber(订阅者)-EventPublish(事件发布者)-NotifyCenter(事件中心)</span></p>
<p class="md-end-block md-p"><span class="md-plain">订阅者通过注册中心进行注册，通过订阅者</span><span class="md-pair-s" spellcheck="false"><code>subscribeTypes()</code></span><span class="md-plain">方法来获取其关注的Event事件类型的集合，对于非慢事件利用工厂创建对应的</span><span class="md-pair-s" spellcheck="false"><code>EventPublisher</code></span><span class="md-plain">，并且将订阅者保存到</span><span class="md-pair-s" spellcheck="false"><code>EventPublisher</code></span><span class="md-plain"> 的 </span><span class="md-pair-s" spellcheck="false"><code>subscribers</code></span><span class="md-plain"> 集合，在</span><span class="md-pair-s" spellcheck="false"><code>publisherMap</code></span><span class="md-plain">中保存事件-事件发布者的对应关系，</span><span class="md-pair-s" spellcheck="false"><code>EventPublisher</code></span><span class="md-plain"> 初始化时启动异步线程，监听阻塞队列，等待事件到来</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain">通过事件中心发布事件的时候，</span><span class="md-pair-s" spellcheck="false"><code>根据事件的类型获得其全类名作为topic</code></span><span class="md-plain">，利用</span><span class="md-pair-s" spellcheck="false"><code>topic</code></span><span class="md-plain">从</span><span class="md-pair-s" spellcheck="false"><code>publisherMap</code></span><span class="md-plain">中获取对应的</span><span class="md-pair-s" spellcheck="false"><code>EventPublisher</code></span><span class="md-plain">，利用</span><span class="md-pair-s" spellcheck="false"><code>EventPublisher</code></span><span class="md-plain md-expand">将任务写入到阻塞任务队列中</span></p>]]></description>
    <pubDate>Sat, 24 Jan 2026 17:58:50 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=55</guid>
</item>
<item>
    <title>Nacos源码学习计划-Day25-Nacos2.x-服务的注销</title>
    <link>http://8.154.40.120:24180/?post=54</link>
    <description><![CDATA[<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">今天来看服务下线的源码部分，也就是当注册到Nacos上的服务终止的时候进行的逻辑操作</span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">客户端的相关操作</span></h2>
<p class="md-end-block md-p"><span class="md-plain">怎么找到对应的代码块这边我们就不过多表述了，肯定还是主要围绕</span><span class="md-pair-s" spellcheck="false"><code>AbstractAutoServiceRegistration</code></span><span class="md-plain">这个自动注册相关的类进行的操作</span></p>
<p class="md-end-block md-p"><span class="md-plain">我们可以看到，在这个类下，有一个</span><span class="md-pair-s" spellcheck="false"><code>@PreDestroy</code></span><span class="md-plain">注解标识的销毁方法，该方法内部就是调用了</span><span class="md-pair-s" spellcheck="false"><code>stop</code></span><span class="md-plain">方法</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260121171102948.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260121171102948.png" alt="image-20260121171102948"></span></p>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>stop</code></span><span class="md-plain">方法的逻辑如下</span></p>
<p class="md-end-block md-p"><span class="md-plain">可以看到，整体内容和Nacos1.4版本的逻辑操作相差不大，对客户端保存的注册实例遍历然后依次执行一些注销前置操作，注销后置操作等等</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable">RegistrationLifecycle</span><span class="cm-operator">&lt;</span><span class="cm-variable">R</span><span class="cm-operator">&gt;&gt;</span> <span class="cm-variable">registrationLifecycles</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">ArrayList</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">stop</span>() {</span><br><span role="presentation">    <span class="cm-keyword">if</span> (<span class="cm-keyword">this</span>.<span class="cm-variable">getRunning</span>().<span class="cm-variable">compareAndSet</span>(<span class="cm-atom">true</span>, <span class="cm-atom">false</span>) <span class="cm-operator">&amp;&amp;</span> <span class="cm-variable">isEnabled</span>()) {</span><br><span role="presentation">​</span><br><span role="presentation">        <span class="cm-keyword">this</span>.<span class="cm-variable">registrationLifecycles</span>.<span class="cm-variable">forEach</span>(</span><br><span role="presentation">                <span class="cm-variable">registrationLifecycle</span> <span class="cm-operator">-&gt;</span> <span class="cm-variable">registrationLifecycle</span>.<span class="cm-variable">postProcessBeforeStopRegister</span>(<span class="cm-variable">getRegistration</span>()));</span><br><span role="presentation">        <span class="cm-variable">deregister</span>();</span><br><span role="presentation">        <span class="cm-keyword">this</span>.<span class="cm-variable">registrationLifecycles</span>.<span class="cm-variable">forEach</span>(</span><br><span role="presentation">                <span class="cm-variable">registrationLifecycle</span> <span class="cm-operator">-&gt;</span> <span class="cm-variable">registrationLifecycle</span>.<span class="cm-variable">postProcessAfterStopRegister</span>(<span class="cm-variable">getRegistration</span>()));</span><br><span role="presentation">        <span class="cm-keyword">if</span> (<span class="cm-variable">shouldRegisterManagement</span>()) {</span><br><span role="presentation">            <span class="cm-keyword">this</span>.<span class="cm-variable">registrationManagementLifecycles</span></span><br><span role="presentation">                    .<span class="cm-variable">forEach</span>(<span class="cm-variable">registrationManagementLifecycle</span> <span class="cm-operator">-&gt;</span> <span class="cm-variable">registrationManagementLifecycle</span></span><br><span role="presentation">                            .<span class="cm-variable">postProcessBeforeStopRegisterManagement</span>(<span class="cm-variable">getManagementRegistration</span>()));</span><br><span role="presentation">            <span class="cm-variable">deregisterManagement</span>();</span><br><span role="presentation">            <span class="cm-keyword">this</span>.<span class="cm-variable">registrationManagementLifecycles</span></span><br><span role="presentation">                    .<span class="cm-variable">forEach</span>(<span class="cm-variable">registrationManagementLifecycle</span> <span class="cm-operator">-&gt;</span> <span class="cm-variable">registrationManagementLifecycle</span></span><br><span role="presentation">                            .<span class="cm-variable">postProcessAfterStopRegisterManagement</span>(<span class="cm-variable">getManagementRegistration</span>()));</span><br><span role="presentation">        }</span><br><span role="presentation">        <span class="cm-keyword">this</span>.<span class="cm-variable">serviceRegistry</span>.<span class="cm-variable">close</span>();</span><br><span role="presentation">    }</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">在最后</span><span class="md-pair-s" spellcheck="false"><code>deregisterManagement()</code></span><span class="md-plain">中最后会发送一个Rpc的请求发送给服务端</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260121172019699.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260121172019699.png" alt="image-20260121172019699"></span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">服务端处理Rpc请求</span></h2>
<p class="md-end-block md-p"><span class="md-plain">依旧是先找到对应的Handler，依旧是熟悉的switch-case对于不同类型的区分处理</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260121172226490.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260121172226490.png" alt="image-20260121172226490"></span></p>
<p class="md-end-block md-p"><span class="md-plain">对应的</span><span class="md-pair-s" spellcheck="false"><code>deregisterInstance()</code></span><span class="md-plain">方法的代码如下</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable">InstanceResponse</span> <span class="cm-def">deregisterInstance</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>, <span class="cm-variable">InstanceRequest</span> <span class="cm-variable">request</span>, <span class="cm-variable">RequestMeta</span> <span class="cm-variable">meta</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">clientOperationService</span>.<span class="cm-variable">deregisterInstance</span>(<span class="cm-variable">service</span>, <span class="cm-variable">request</span>.<span class="cm-variable">getInstance</span>(), <span class="cm-variable">meta</span>.<span class="cm-variable">getConnectionId</span>());</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">InstanceResponse</span>(<span class="cm-variable">NamingRemoteConstants</span>.<span class="cm-variable">DE_REGISTER_INSTANCE</span>);</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span><br><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">deregisterInstance</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>, <span class="cm-variable">Instance</span> <span class="cm-variable">instance</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">clientId</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">ServiceManager</span>.<span class="cm-variable">getInstance</span>().<span class="cm-variable">containSingleton</span>(<span class="cm-variable">service</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">SRV_LOG</span>.<span class="cm-variable">warn</span>(<span class="cm-string">"remove instance from non-exist service: {}"</span>, <span class="cm-variable">service</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Service</span> <span class="cm-variable">singleton</span> <span class="cm-operator">=</span> <span class="cm-variable">ServiceManager</span>.<span class="cm-variable">getInstance</span>().<span class="cm-variable">getSingleton</span>(<span class="cm-variable">service</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 获取 client 对象</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Client</span> <span class="cm-variable">client</span> <span class="cm-operator">=</span> <span class="cm-variable">clientManager</span>.<span class="cm-variable">getClient</span>(<span class="cm-variable">clientId</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">clientIsLegal</span>(<span class="cm-variable">client</span>, <span class="cm-variable">clientId</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 移除 instance 信息，在这里面会操作 publishers 那个Map，以及发 ClientChangedEvent 事件，来同步集群节点</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">InstancePublishInfo</span> <span class="cm-variable">removedInstance</span> <span class="cm-operator">=</span> <span class="cm-variable">client</span>.<span class="cm-variable">removeServiceInstance</span>(<span class="cm-variable">singleton</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">client</span>.<span class="cm-variable">setLastUpdatedTime</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">!=</span> <span class="cm-variable">removedInstance</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 发布客户端注销事件</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(<span class="cm-keyword">new</span> <span class="cm-variable">ClientOperationEvent</span>.<span class="cm-variable">ClientDeregisterServiceEvent</span>(<span class="cm-variable">singleton</span>, <span class="cm-variable">clientId</span>));</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">new</span> <span class="cm-variable">MetadataEvent</span>.<span class="cm-variable">InstanceMetadataEvent</span>(<span class="cm-variable">singleton</span>, <span class="cm-variable">removedInstance</span>.<span class="cm-variable">getMetadataId</span>(), <span class="cm-atom">true</span>));</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">对于客户端注销事件的处理</span><span class="md-pair-s" spellcheck="false"><code>ClientDeregisterServiceEvent</code></span><span class="md-plain">，同样的搜索对应的handler的相关逻辑</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260121172758504.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260121172758504.png" alt="image-20260121172758504"></span></p>
<p class="md-end-block md-p"><span class="md-plain">剩下的部分也就是对于内存注册表，订阅表等的一些remove操作以及同步集群了</span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">总结-Nacos2.x注册中心部分</span></h2>
<ol class="ol-list">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">对于临时实例，会采用RPC的方式进行通讯</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">内存注册表的结构变化。由原本的嵌套式Map转化为了平铺式Map，结构更加i清晰</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">客户端查询微服务实例列表的时候，调用的是客户端订阅的请求，在服务端会有订阅表，当我们被订阅的服务发生了变动，会来通知订阅者。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p md-focus"><span class="md-plain">可以明显感觉到，2.0中的事件发布操作明显更多了</span></p>
</li>
</ol>]]></description>
    <pubDate>Wed, 21 Jan 2026 17:35:51 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=54</guid>
</item>
<item>
    <title>Nacos源码学习计划-Day24-Nacos2.x-服务端心跳健康实例检测</title>
    <link>http://8.154.40.120:24180/?post=53</link>
    <description><![CDATA[<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">今天我们的主要内容就是Nacos2.x版本下，服务端的健康检测逻辑。因为Nacos升级后使用了gRPC，导致这个部分逻辑肯定时发生了较大的变化。</span></p>
<p class="md-end-block md-p"><span class="md-plain">注意，这里只是说服务端，即服务都安如何去检测客户端的健康状态，而不是客户端的心跳以及也不是集群中对于每个节点的健康检测。</span></p>
<p class="md-end-block md-p"><span class="md-plain">我们可以来看到</span><span class="md-pair-s" spellcheck="false"><code>ConnectionManager</code></span><span class="md-plain">，在这个类的内部有一个</span><span class="md-pair-s" spellcheck="false"><code>Map</code></span><span class="md-plain">保存了所有的了所有的</span><span class="md-pair-s" spellcheck="false"><code>Connection</code></span><span class="md-plain">连接对象，连接对象有个元数据的成员对象</span><span class="md-pair-s" spellcheck="false"><code>ConnectionMeta</code></span><span class="md-plain">，他有一个成员属性为</span><span class="md-pair-s" spellcheck="false"><code>lastActiveTime(最后存活时间)</code></span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260120211513211.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260120211513211.png" alt="image-20260120211513211"></span></p>
<p class="md-end-block md-p"><span class="md-plain">在</span><span class="md-pair-s" spellcheck="false"><code>ConnectionManager</code></span><span class="md-plain">对象中，有个</span><span class="md-pair-s" spellcheck="false"><code>@PostConstruct</code></span><span class="md-plain">注解标记的</span><span class="md-pair-s" spellcheck="false"><code>start</code></span><span class="md-plain">方法，该方法会开启一个</span><span class="md-pair-s" spellcheck="false"><code>run任务</code></span><span class="md-plain">，在该方法中，</span><span class="md-pair-s" spellcheck="false"><code>遍历上述的map并且用now和元数据中的lastActiveTime进行比较判断是否存活/过时,如果发现过时了，就会放入到outDatedConnections这个Set集合中</code></span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260120212234516.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260120212234516.png" alt="image-20260120212234516"></span></p>
<p class="md-end-block md-p"><strong><span class="md-pair-s" spellcheck="false"><code>outDatedConnections</code></span><span class="md-plain">在没有超出上限的时候，不会立马被清除，而是会统一在最后进行二次判断是否真正下线，如果没有下线，则会刷新activeTime并且加入到存活对象集合中，这个过程为了保证并发安全还是用了countDownLatch，这个被称之为</span><span class="md-pair-s" spellcheck="false"><code>探活机制</code></span></strong></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260120212652183.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260120212652183.png" alt="image-20260120212652183"></span></p>
<p class="md-end-block md-p"><strong><span class="md-plain">如果上述二次请求遍历完毕后依旧集合中依旧还有数据，则调用</span><span class="md-pair-s" spellcheck="false"><code>unregister()</code></span><span class="md-plain">取消注册进行删除</span></strong></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260120212840093.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20260120212840093.png" alt="image-20260120212840093"></span></p>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>unregister</code></span><span class="md-plain">源码如下，首先会从</span><span class="md-pair-s" spellcheck="false"><code>connections</code></span><span class="md-plain">这个管理了所有的连接的Map中remove，remove的时候会返回对应的Connection连接实例信息，根据实例信息中的Ip信息操作连接数，最后通知客户端注销连接</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">synchronized</span> <span class="cm-variable-3">void</span> <span class="cm-def">unregister</span>(<span class="cm-variable-3">String</span> <span class="cm-variable">connectionId</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 移除客户端信息</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Connection</span> <span class="cm-variable">remove</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-variable">connections</span>.<span class="cm-variable">remove</span>(<span class="cm-variable">connectionId</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">remove</span> <span class="cm-operator">!=</span> <span class="cm-atom">null</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">clientIp</span> <span class="cm-operator">=</span> <span class="cm-variable">remove</span>.<span class="cm-variable">getMetaInfo</span>().<span class="cm-variable">clientIp</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">AtomicInteger</span> <span class="cm-variable">atomicInteger</span> <span class="cm-operator">=</span> <span class="cm-variable">connectionForClientIp</span>.<span class="cm-variable">get</span>(<span class="cm-variable">clientIp</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">atomicInteger</span> <span class="cm-operator">!=</span> <span class="cm-atom">null</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">int</span> <span class="cm-variable">count</span> <span class="cm-operator">=</span> <span class="cm-variable">atomicInteger</span>.<span class="cm-variable">decrementAndGet</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">count</span> <span class="cm-operator">&lt;=</span> <span class="cm-number">0</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">connectionForClientIp</span>.<span class="cm-variable">remove</span>(<span class="cm-variable">clientIp</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">remove</span>.<span class="cm-variable">close</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">REMOTE_DIGEST</span>.<span class="cm-variable">info</span>(<span class="cm-string">"[{}]Connection unregistered successfully. "</span>, <span class="cm-variable">connectionId</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 通知客户端注销连接</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">clientConnectionEventListenerRegistry</span>.<span class="cm-variable">notifyClientDisConnected</span>(<span class="cm-variable">remove</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">来看一下通知客户端注销连接的逻辑,可以看到最后是发布了一个</span><span class="md-pair-s" spellcheck="false"><code>ClientDisconnectEvent</code></span><span class="md-plain">的事件，这个事件的处理我们在上一节的内容中分析过了，客户端会注销，同步到集群，当前节点关于该链接的数据，订阅者列表，注册表信息等等也都会被删除</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-comment">// 1</span><br><span role="presentation"><span class="cm-variable">clientConnectionEventListenerRegistry</span>.<span class="cm-variable">notifyClientDisConnected</span>(<span class="cm-variable">remove</span>);</span><br><span role="presentation">​</span><br><span class="cm-comment">// 2</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">notifyClientDisConnected</span>(<span class="cm-keyword">final</span> <span class="cm-variable">Connection</span> <span class="cm-variable">connection</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable">ClientConnectionEventListener</span> <span class="cm-variable">clientConnectionEventListener</span> : <span class="cm-variable">clientConnectionEventListeners</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 客户端注销，我们看这个实现类，ConnectionBasedClientManager</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">clientConnectionEventListener</span>.<span class="cm-variable">clientDisConnected</span>(<span class="cm-variable">connection</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">Throwable</span> <span class="cm-variable">throwable</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">REMOTE</span>.<span class="cm-variable">info</span>(<span class="cm-string">"[NotifyClientDisConnected] failed for listener {}"</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">clientConnectionEventListener</span>.<span class="cm-variable">getName</span>(), <span class="cm-variable">throwable</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span class="cm-comment">// 3 ConnectionBasedClientManager 实现类</span><br><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">clientDisConnected</span>(<span class="cm-variable">Connection</span> <span class="cm-variable">connect</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">clientDisconnected</span>(<span class="cm-variable">connect</span>.<span class="cm-variable">getMetaInfo</span>().<span class="cm-variable">getConnectionId</span>());</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span class="cm-comment">// 4</span><br><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">clientDisconnected</span>(<span class="cm-variable-3">String</span> <span class="cm-variable">clientId</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">SRV_LOG</span>.<span class="cm-variable">info</span>(<span class="cm-string">"Client connection {} disconnect, remove instances and subscribers"</span>, <span class="cm-variable">clientId</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">ConnectionBasedClient</span> <span class="cm-variable">client</span> <span class="cm-operator">=</span> <span class="cm-variable">clients</span>.<span class="cm-variable">remove</span>(<span class="cm-variable">clientId</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">client</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">client</span>.<span class="cm-variable">release</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 最后发布客户端注销事件</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(<span class="cm-keyword">new</span> <span class="cm-variable">ClientEvent</span>.<span class="cm-variable">ClientDisconnectEvent</span>(<span class="cm-variable">client</span>));</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>]]></description>
    <pubDate>Tue, 20 Jan 2026 21:45:46 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=53</guid>
</item>
<item>
    <title>Nacos源码学习计划-Day23-Nacos2.x-服务实例信息变化如何同步到集群节点</title>
    <link>http://8.154.40.120:24180/?post=52</link>
    <description><![CDATA[<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">前面我们了解了Nacos2.X版本下以gRpc为客户端和服务端之间的通讯下，客户端如何注册和查询实例，以及服务端如何处理注册请求，如何将服务变更通知到订阅的客户端，也在这几个过程中加深了对于Nacos2.X版本下内存注册表的结构。</span></p>
<p class="md-end-block md-p"><span class="md-plain">在看Nacos服务端处理服务注册的时候，我们最后是探索到了</span><span class="md-pair-s" spellcheck="false"><code>AbstractClient类下的addServiceInstance()方法</code></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">addServiceInstance</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>, <span class="cm-variable">InstancePublishInfo</span> <span class="cm-variable">instancePublishInfo</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 把 instance 信息放入到 publishers 中</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">publishers</span>.<span class="cm-variable">put</span>(<span class="cm-variable">service</span>, <span class="cm-variable">instancePublishInfo</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">MetricsMonitor</span>.<span class="cm-variable">incrementInstanceCount</span>();</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 同步集群节点数据</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(<span class="cm-keyword">new</span> <span class="cm-variable">ClientEvent</span>.<span class="cm-variable">ClientChangedEvent</span>(<span class="cm-keyword">this</span>));</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">SRV_LOG</span>.<span class="cm-variable">info</span>(<span class="cm-string">"Client change for service {}, {}"</span>, <span class="cm-variable">service</span>, <span class="cm-variable">getClientId</span>());</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">在该方法的最后那个</span><span class="md-pair-s" spellcheck="false"><code>pushlishEvent()</code></span><span class="md-plain">方法中，发布了一个</span><span class="md-pair-s" spellcheck="false"><code>ClientChangedEvent</code></span><span class="md-plain">，从命名和业务逻辑来看，这一步肯定是发布客户端改变事件用于同步别的集群节点，所以我们这一篇的内容就是来了解一下这个</span></p>
<h1 class="md-end-block md-heading"><span class="md-plain">处理ClientChangedEvent</span></h1>
<p class="md-end-block md-p"><span class="md-plain">还是老样子，我们对于Event事件类型的处理，都是查找对应的处理Event的地方，利用Idea的全局搜索轻松实现</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable-3">void</span> <span class="cm-def">syncToAllServer</span>(<span class="cm-variable">ClientEvent</span> <span class="cm-variable">event</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Client</span> <span class="cm-variable">client</span> <span class="cm-operator">=</span> <span class="cm-variable">event</span>.<span class="cm-variable">getClient</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// Only ephemeral data sync by Distro, persist client should sync by raft.</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 临时实例是走 distro 协议，持久化实例走 raft 协议</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// isResponsibleClient 这里判断了只有该 client 的责任节点有权利进行集群数据同步</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">client</span> <span class="cm-operator">||</span> <span class="cm-operator">!</span><span class="cm-variable">client</span>.<span class="cm-variable">isEphemeral</span>() <span class="cm-operator">||</span> <span class="cm-operator">!</span><span class="cm-variable">clientManager</span>.<span class="cm-variable">isResponsibleClient</span>(<span class="cm-variable">client</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 客户端注销集群节点同步事件</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">event</span> <span class="cm-keyword">instanceof</span> <span class="cm-variable">ClientEvent</span>.<span class="cm-variable">ClientDisconnectEvent</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 这里有常量默认值TYPE，其数值为 Nacos:Naming:v2:ClientData </span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">DistroKey</span> <span class="cm-variable">distroKey</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">DistroKey</span>(<span class="cm-variable">client</span>.<span class="cm-variable">getClientId</span>(), <span class="cm-variable">TYPE</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">distroProtocol</span>.<span class="cm-variable">sync</span>(<span class="cm-variable">distroKey</span>, <span class="cm-variable">DataOperation</span>.<span class="cm-variable">DELETE</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 客户端改变集群节点同步事件</span></span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable">event</span> <span class="cm-keyword">instanceof</span> <span class="cm-variable">ClientEvent</span>.<span class="cm-variable">ClientChangedEvent</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">// 这里有常量默认值TYPE，其数值为 Nacos:Naming:v2:ClientData </span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">DistroKey</span> <span class="cm-variable">distroKey</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">DistroKey</span>(<span class="cm-variable">client</span>.<span class="cm-variable">getClientId</span>(), <span class="cm-variable">TYPE</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">distroProtocol</span>.<span class="cm-variable">sync</span>(<span class="cm-variable">distroKey</span>, <span class="cm-variable">DataOperation</span>.<span class="cm-variable">CHANGE</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">通过上面这一段代码我们可以得知，不管是客户端注销了，还是客户端改变了，都会来同步集群节点，它们两个都是调用了 </span><span class="md-pair-s" spellcheck="false"><code>distroProtocol.sync</code></span><span class="md-plain"> 来进行同步，唯一的区别就是参数一个是 </span><span class="md-pair-s" spellcheck="false"><code>DELETE</code></span><span class="md-plain">、一个是</span><span class="md-pair-s" spellcheck="false"><code>CHANGE</code></span><span class="md-plain">。那么我们接下来一个看下同步的具体逻辑吧，代码如下：</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">sync</span>(<span class="cm-variable">DistroKey</span> <span class="cm-variable">distroKey</span>, <span class="cm-variable">DataOperation</span> <span class="cm-variable">action</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">sync</span>(<span class="cm-variable">distroKey</span>, <span class="cm-variable">action</span>, <span class="cm-variable">DistroConfig</span>.<span class="cm-variable">getInstance</span>().<span class="cm-variable">getSyncDelayMillis</span>());</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">sync</span>(<span class="cm-variable">DistroKey</span> <span class="cm-variable">distroKey</span>, <span class="cm-variable">DataOperation</span> <span class="cm-variable">action</span>, <span class="cm-variable-3">long</span> <span class="cm-variable">delay</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 遍历集群其他节点，除开自身节点</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable">Member</span> <span class="cm-variable">each</span> : <span class="cm-variable">memberManager</span>.<span class="cm-variable">allMembersWithoutSelf</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">syncToTarget</span>(<span class="cm-variable">distroKey</span>, <span class="cm-variable">action</span>, <span class="cm-variable">each</span>.<span class="cm-variable">getAddress</span>(), <span class="cm-variable">delay</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">syncToTarget</span>(<span class="cm-variable">DistroKey</span> <span class="cm-variable">distroKey</span>, <span class="cm-variable">DataOperation</span> <span class="cm-variable">action</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">targetServer</span>, <span class="cm-variable-3">long</span> <span class="cm-variable">delay</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">DistroKey</span> <span class="cm-variable">distroKeyWithTarget</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">DistroKey</span>(<span class="cm-variable">distroKey</span>.<span class="cm-variable">getResourceKey</span>(), <span class="cm-variable">distroKey</span>.<span class="cm-variable">getResourceType</span>(),</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">targetServer</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 创建了 DistroDelayTask 任务，并且把 targetServer(即集群节点的address) 包装成 DistroKey 当作参数传入了进去</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">DistroDelayTask</span> <span class="cm-variable">distroDelayTask</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">DistroDelayTask</span>(<span class="cm-variable">distroKeyWithTarget</span>, <span class="cm-variable">action</span>, <span class="cm-variable">delay</span>);</span><br><span role="presentation"> &nbsp; <span class="cm-comment">// 往执行引擎中添加任务</span></span><br><span role="presentation"> &nbsp; <span class="cm-variable">distroTaskEngineHolder</span>.<span class="cm-variable">getDelayTaskExecuteEngine</span>().<span class="cm-variable">addTask</span>(<span class="cm-variable">distroKeyWithTarget</span>, <span class="cm-variable">distroDelayTask</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">Loggers</span>.<span class="cm-variable">DISTRO</span>.<span class="cm-variable">isDebugEnabled</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">DISTRO</span>.<span class="cm-variable">debug</span>(<span class="cm-string">"[DISTRO-SCHEDULE] {} to {}"</span>, <span class="cm-variable">distroKey</span>, <span class="cm-variable">targetServer</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">可以看到，这里最后利用</span><span class="md-pair-s" spellcheck="false"><code>distroTaskEngineHolder.getDelayTaskExecuteEngine()</code></span><span class="md-plain">去执行</span><span class="md-pair-s" spellcheck="false"><code>distroDelayTask</code></span><span class="md-plain">任务，这个地方的逻辑我们在上面就说过，实际上执行的就需要看对应的</span><span class="md-pair-s" spellcheck="false"><code>processor</code></span><span class="md-plain">，而</span><span class="md-pair-s" spellcheck="false"><code>DistroTaskEngineHolder</code></span><span class="md-plain">中的执行器就是</span><span class="md-pair-s" spellcheck="false"><code>DistroDelayTaskProcessor</code></span><span class="md-plain">，我们看看对应的处理方法</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">process</span>(<span class="cm-variable">NacosTask</span> <span class="cm-variable">task</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span>(<span class="cm-variable">task</span> <span class="cm-keyword">instanceof</span> <span class="cm-variable">DistroDelayTask</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">DistroDelayTask</span> <span class="cm-variable">distroDelayTask</span> <span class="cm-operator">=</span> (<span class="cm-variable">DistroDelayTask</span>) <span class="cm-variable">task</span>;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">DistroKey</span> <span class="cm-variable">distroKey</span> <span class="cm-operator">=</span> <span class="cm-variable">distroDelayTask</span>.<span class="cm-variable">getDistroKey</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">switch</span> (<span class="cm-variable">distroDelayTask</span>.<span class="cm-variable">getAction</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-variable">DELETE</span>:</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 删除任务</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">DistroSyncDeleteTask</span> <span class="cm-variable">syncDeleteTask</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">DistroSyncDeleteTask</span>(<span class="cm-variable">distroKey</span>, <span class="cm-variable">distroComponentHolder</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">distroTaskEngineHolder</span>.<span class="cm-variable">getExecuteWorkersManager</span>().<span class="cm-variable">addTask</span>(<span class="cm-variable">distroKey</span>, <span class="cm-variable">syncDeleteTask</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-variable">CHANGE</span>:</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-variable">ADD</span>:</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 改变、新增都会走这一段逻辑，封装为DistroSyncChangeTask</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">DistroSyncChangeTask</span> <span class="cm-variable">syncChangeTask</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">DistroSyncChangeTask</span>(<span class="cm-variable">distroKey</span>, <span class="cm-variable">distroComponentHolder</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">distroTaskEngineHolder</span>.<span class="cm-variable">getExecuteWorkersManager</span>().<span class="cm-variable">addTask</span>(<span class="cm-variable">distroKey</span>, <span class="cm-variable">syncChangeTask</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">default</span>:</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">false</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">这一段逻辑，不管是删除，还是改变和新增的操作，都是创建了一个任务，添加到了 distro 任务执行引擎，这个引擎和之前看的延时任务执行实现有点不一样。</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227211423702.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227211423702.png" alt="image-20251227211423702"></span></p>
<p class="md-end-block md-p"><span class="md-plain">从逻辑上找下去，我们首先需要看到</span><span class="md-pair-s" spellcheck="false"><code>DistroExecuteTaskExecuteEngine</code></span><span class="md-plain">中的</span><span class="md-pair-s" spellcheck="false"><code>addTask()</code></span><span class="md-plain">的逻辑</span></p>
<p class="md-end-block md-p"><span class="md-plain">可以发现，就是</span><strong><span class="md-plain">将</span><span class="md-pair-s" spellcheck="false"><code>task</code></span><span class="md-plain">加入到</span><span class="md-pair-s" spellcheck="false"><code>TaskExecuteWorker类型的数组中</code></span><span class="md-plain">，然后调用</span><span class="md-pair-s" spellcheck="false"><code>TaskExecuteWorker.process方法执行task</code></span></strong></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227212310543.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227212310543.png" alt="image-20251227212310543"></span></p>
<p class="md-end-block md-p"><span class="md-plain">然后我们来看看</span><span class="md-pair-s" spellcheck="false"><code>process</code></span><span class="md-plain">中的逻辑</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227214537141.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227214537141.png" alt="image-20251227214537141"></span></p>
<p class="md-end-block md-p"><strong><span class="md-plain">可以看到这里面的主要逻辑就是将</span><span class="md-pair-s" spellcheck="false"><code>task</code></span><span class="md-plain">放入到队列中。</span></strong></p>
<p class="md-end-block md-p"><strong><span class="md-plain">在延时任务执行引擎实现原理是有一个 Map tasks 任务池，很多对应的任务处理器，然后定时从任务池获取任务，执行任务。而这个 </span><span class="md-pair-s" spellcheck="false"><code>distroTaskEngineHolder实现原理使用阻塞队列 + 异步任务</code></span><span class="md-plain">的方式来实现的，所以最终这个任务肯定会被拿取并且执行</span></strong></p>
<h2 class="md-end-block md-heading"><span class="md-plain">DistroSyncChangeTask</span></h2>
<p class="md-end-block md-p"><span class="md-plain">我们回到</span><span class="md-pair-s" spellcheck="false"><code>DistroDelayTaskProcessor</code></span><span class="md-plain">中的</span><span class="md-pair-s" spellcheck="false"><code>process</code></span><span class="md-plain">逻辑，可以知道无论是</span><span class="md-pair-s" spellcheck="false"><code>ADD还是CHANGE</code></span><span class="md-plain">对应的都是封装为了</span><span class="md-pair-s" spellcheck="false"><code>DistroSyncChangeTask</code></span><span class="md-plain">任务类型，我们查看其设计，可以发现继承自</span><span class="md-pair-s" spellcheck="false"><code>AbstractDistroExecuteTask</code></span><span class="md-plain">，继续往上看其实就是实现了</span><span class="md-pair-s" spellcheck="false"><code>Runnable接口</code></span><span class="md-plain">，所以我们来看看对应的</span><span class="md-pair-s" spellcheck="false"><code>run</code></span><span class="md-plain">逻辑，因为我们是</span><span class="md-pair-s" spellcheck="false"><code>CHANGE</code></span><span class="md-plain">类型，所以我们这里也肯定去看</span><span class="md-pair-s" spellcheck="false"><code>DistroSyncChangeTask下的doExecuteWithCallback的逻辑</code></span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227220202242.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227220202242.png" alt="image-20251227220202242"></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">protected</span> <span class="cm-variable-3">void</span> <span class="cm-def">doExecuteWithCallback</span>(<span class="cm-variable">DistroCallback</span> <span class="cm-variable">callback</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">type</span> <span class="cm-operator">=</span> <span class="cm-variable">getDistroKey</span>().<span class="cm-variable">getResourceType</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 获取请求数据</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">DistroData</span> <span class="cm-variable">distroData</span> <span class="cm-operator">=</span> <span class="cm-variable">getDistroData</span>(<span class="cm-variable">type</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">distroData</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">DISTRO</span>.<span class="cm-variable">warn</span>(<span class="cm-string">"[DISTRO] {} with null data to sync, skip"</span>, <span class="cm-variable">toString</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// syncData 同步集群节点</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">getDistroComponentHolder</span>().<span class="cm-variable">findTransportAgent</span>(<span class="cm-variable">type</span>)</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  .<span class="cm-variable">syncData</span>(<span class="cm-variable">distroData</span>, <span class="cm-variable">getDistroKey</span>().<span class="cm-variable">getTargetServer</span>(), <span class="cm-variable">callback</span>);</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">此时我们应该去看</span><span class="md-pair-s" spellcheck="false"><code>syncData</code></span><span class="md-plain">的逻辑，该方法也是接口中的方法，需要在对应的实现类中查找对应的实现，这里有两个实现类</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227224407283.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227224407283.png" alt="image-20251227224407283"></span></p>
<p class="md-end-block md-p"><span class="md-plain">怎么知道是哪个呢？我们可以回头来看一下，</span><strong><span class="md-plain">调用</span><span class="md-pair-s" spellcheck="false"><code>syncData</code></span><span class="md-plain">的是</span><span class="md-pair-s" spellcheck="false"><code>findTransportAgent(type)</code></span><span class="md-plain">的返回指，这个返回值其实在之前的</span><span class="md-pair-s" spellcheck="false"><code>run</code></span><span class="md-plain">的逻辑中也有被调用，其返回类型是</span><span class="md-pair-s" spellcheck="false"><code>DistroTransportAgent</code></span><span class="md-plain">，而</span><span class="md-pair-s" spellcheck="false"><code>DistroClientTransportAgent</code></span><span class="md-plain">是</span><span class="md-pair-s" spellcheck="false"><code>DistroTransportAgent</code></span><span class="md-plain">的子类</span></strong></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227224351093.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251227224351093.png" alt="image-20251227224351093"></span></p>
<p class="md-end-block md-p"><span class="md-plain">看到这里我们知道了，</span><strong><span class="md-plain">接下来要看的内容是</span><span class="md-pair-s" spellcheck="false"><code>DistroClientTransportAgent的syncData()的逻辑</code></span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">syncData</span>(<span class="cm-variable">DistroData</span> <span class="cm-variable">data</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">targetServer</span>, <span class="cm-variable">DistroCallback</span> <span class="cm-variable">callback</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">isNoExistTarget</span>(<span class="cm-variable">targetServer</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">callback</span>.<span class="cm-variable">onSuccess</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 创建请求对象</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">DistroDataRequest</span> <span class="cm-variable">request</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">DistroDataRequest</span>(<span class="cm-variable">data</span>, <span class="cm-variable">data</span>.<span class="cm-variable">getType</span>());</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 找到集群节点</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Member</span> <span class="cm-variable">member</span> <span class="cm-operator">=</span> <span class="cm-variable">memberManager</span>.<span class="cm-variable">find</span>(<span class="cm-variable">targetServer</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 发送 rpc 异步请求</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">clusterRpcClientProxy</span>.<span class="cm-variable">asyncRequest</span>(<span class="cm-variable">member</span>, <span class="cm-variable">request</span>, <span class="cm-keyword">new</span> <span class="cm-variable">DistroRpcCallbackWrapper</span>(<span class="cm-variable">callback</span>, <span class="cm-variable">member</span>));</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">NacosException</span> <span class="cm-variable">nacosException</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">callback</span>.<span class="cm-variable">onFailed</span>(<span class="cm-variable">nacosException</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">所以看到这里我们差不多知道了整个</span><span class="md-pair-s" spellcheck="false"><code>某个节点上的服务实例修改后，如果通知到其余节点</code></span><span class="md-plain">的过程，捋一捋，</span><strong><span class="md-plain">一开始我们是从 </span><span class="md-pair-s" spellcheck="false"><code>ClientChangedEvent</code></span><span class="md-plain"> 事件开始分析，我们找到了处理这个事件的逻辑，调用了 </span><span class="md-pair-s" spellcheck="false"><code>sync</code></span><span class="md-plain"> 方法，在这个方法中会遍历其他集群节点进行数据同步，同步的逻辑如下。</span></strong></p>
<ol class="ol-list" start="">
<li class="md-list-item">
<p class="md-end-block md-p"><strong><span class="md-plain">先是创建了 DistroDelayTask 延迟任务，放入到了延迟任务执行引擎，由 </span><span class="md-pair-s" spellcheck="false"><code>DistroDelayTaskProcessor</code></span><span class="md-plain"> 处理器来处理。</span></strong></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><strong><span class="md-plain">在 </span><span class="md-pair-s" spellcheck="false"><code>DistroDelayTaskProcessor</code></span><span class="md-plain"> 处理器，处理的逻辑中又创建了 </span><span class="md-pair-s" spellcheck="false"><code>DistroSyncChangeTask</code></span><span class="md-plain"> 线程任务，执行引擎是先调用了 </span><span class="md-pair-s" spellcheck="false"><code>AbstractDistroExecuteTask</code></span><span class="md-plain"> 父类中的 </span><span class="md-pair-s" spellcheck="false"><code>run</code></span><span class="md-plain"> 方法，在 </span><span class="md-pair-s" spellcheck="false"><code>run</code></span><span class="md-plain"> 方法中又调用了子类的</span><span class="md-pair-s" spellcheck="false"><code>doExecuteWithCallback</code></span><span class="md-plain"> 方法。</span></strong></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><strong><span class="md-pair-s" spellcheck="false"><code>doExecuteWithCallback</code></span><span class="md-plain"> 方法中会去获取最新的微服务实例列表，通过 rpc 发起异步请求进行数据同步。</span></strong></p>
</li>
</ol>
<h1 class="md-end-block md-heading"><span class="md-plain">集群其余节点处理DistroDataRequest</span></h1>
<p class="md-end-block md-p"><span class="md-plain">上述内容中，我们已经清楚了集群节点发其同步请求，那么现在我们来看看别的集群节点如何处理这个同步请求的。我们知道，对应的请求类型是</span><span class="md-pair-s" spellcheck="false"><code>DistroDataRequest</code></span><span class="md-plain">,老规矩，利用IDEA全局搜索找到对应的Handler处理器</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251228102952496.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251228102952496.png" alt="image-20251228102952496"></span></p>
<p class="md-end-block md-p"><span class="md-plain">可以发现，无论是</span><span class="md-pair-s" spellcheck="false"><code>ADD</code></span><span class="md-plain"> 还是 </span><span class="md-pair-s" spellcheck="false"><code>CHANGE</code></span><span class="md-plain"> 还是 </span><span class="md-pair-s" spellcheck="false"><code>DELETE</code></span><span class="md-plain"> 的逻辑，最终都是执行的</span><span class="md-pair-s" spellcheck="false"><code>handlerSyncData(...)</code></span><span class="md-plain">的逻辑，跟下去整个逻辑链路大概是</span><span class="md-pair-s" spellcheck="false"><code>handleSyncData()-&gt;onReceive()-&gt;processData-&gt;handlerClientSyncData()</code></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable">DistroDataResponse</span> <span class="cm-def">handleSyncData</span>(<span class="cm-variable">DistroData</span> <span class="cm-variable">distroData</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 创建response对象</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">DistroDataResponse</span> <span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">DistroDataResponse</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// onReceive方法处理请求数据distroData</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">distroProtocol</span>.<span class="cm-variable">onReceive</span>(<span class="cm-variable">distroData</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">result</span>.<span class="cm-variable">setErrorCode</span>(<span class="cm-variable">ResponseCode</span>.<span class="cm-variable">FAIL</span>.<span class="cm-variable">getCode</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">result</span>.<span class="cm-variable">setMessage</span>(<span class="cm-string">"[DISTRO-FAILED] distro data handle failed"</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">result</span>;</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span><br><span class="cm-comment">/**</span><br><span role="presentation"> <span class="cm-comment">* Receive synced distro data, find processor to process.</span></span><br><span role="presentation"> <span class="cm-comment">*</span></span><br><span role="presentation"> <span class="cm-comment">* @param distroData Received data</span></span><br><span role="presentation"> <span class="cm-comment">* @return true if handle receive data successfully, otherwise false</span></span><br><span role="presentation"> <span class="cm-comment">*/</span></span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">onReceive</span>(<span class="cm-variable">DistroData</span> <span class="cm-variable">distroData</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">DISTRO</span>.<span class="cm-variable">info</span>(<span class="cm-string">"[DISTRO] Receive distro data type: {}, key: {}"</span>, <span class="cm-variable">distroData</span>.<span class="cm-variable">getType</span>(),</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">distroData</span>.<span class="cm-variable">getDistroKey</span>());</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">resourceType</span> <span class="cm-operator">=</span> <span class="cm-variable">distroData</span>.<span class="cm-variable">getDistroKey</span>().<span class="cm-variable">getResourceType</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 利用type找到对应的processor处理器</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">DistroDataProcessor</span> <span class="cm-variable">dataProcessor</span> <span class="cm-operator">=</span> <span class="cm-variable">distroComponentHolder</span>.<span class="cm-variable">findDataProcessor</span>(<span class="cm-variable">resourceType</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">dataProcessor</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">DISTRO</span>.<span class="cm-variable">warn</span>(<span class="cm-string">"[DISTRO] Can't find data process for received data {}"</span>, <span class="cm-variable">resourceType</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">false</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 调用处理器的processData方法处理请求数据</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">dataProcessor</span>.<span class="cm-variable">processData</span>(<span class="cm-variable">distroData</span>);</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>processData</code></span><span class="md-plain">也是接口中的方法，一样的有两个实现类，这里我们需要根据</span><span class="md-pair-s" spellcheck="false"><code>resourceType</code></span><span class="md-plain">来进行确认，这个</span><span class="md-pair-s" spellcheck="false"><code>resourceType</code></span><span class="md-plain">的值大家可以回过头看这一章的最开始那个地方，在</span><span class="md-pair-s" spellcheck="false"><code>syncToAllServer</code></span><span class="md-plain">中，也就是我们</span><span class="md-pair-s" spellcheck="false"><code>Event</code></span><span class="md-plain">创建和传入的地方，我们当前这个</span><span class="md-pair-s" spellcheck="false"><code>resourceType</code></span><span class="md-plain">和</span><span class="md-pair-s" spellcheck="false"><code>DistroKey</code></span><span class="md-plain">其实一开始就绑定了，那么其实这个</span><span class="md-pair-s" spellcheck="false"><code>resourceType</code></span><span class="md-plain">就是我们上面提到的常量</span><span class="md-pair-s" spellcheck="false"><code>TYPE</code></span></p>
<p class="md-end-block md-p"><span class="md-plain">然后这个地方通过</span><span class="md-pair-s" spellcheck="false"><code>findDataProcessor</code></span><span class="md-plain">查找</span><span class="md-pair-s" spellcheck="false"><code>DistroComponentHolder</code></span><span class="md-plain">中的Map</span><span class="md-pair-s" spellcheck="false"><code>成员dataProcessorMap</code></span><span class="md-plain">，以</span><span class="md-pair-s" spellcheck="false"><code>TYPE</code></span><span class="md-plain">为key的对应的value</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-keyword">final</span> <span class="cm-variable">Map</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span>, <span class="cm-variable">DistroDataProcessor</span><span class="cm-operator">&gt;</span> <span class="cm-variable">dataProcessorMap</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">HashMap</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">DistroDataProcessor</span> <span class="cm-def">findDataProcessor</span>(<span class="cm-variable-3">String</span> <span class="cm-variable">processType</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">dataProcessorMap</span>.<span class="cm-variable">get</span>(<span class="cm-variable">processType</span>);</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">到这里我们知道了存储在哪里以及对应的key，但是对应的value该如何查找呢？大家可以继续看到</span><span class="md-pair-s" spellcheck="false"><code>DistroComponentHolder</code></span><span class="md-plain">中，可以发现这个Map是</span><span class="md-pair-s" spellcheck="false"><code>private私有的</code></span><span class="md-plain">，那么就说明外部不能随便进行赋值，有且只能通过</span><span class="md-pair-s" spellcheck="false"><code>registerDataProcessor</code></span><span class="md-plain">方法进行赋值</span></p>
<p class="md-end-block md-p"><span class="md-plain">那么我们查找一下这个方法使用的地方，看看是否能查找到部分线索，可以发现就两个地方</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251228204246865.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251228204246865.png" alt="image-20251228204246865"></span></p>
<p class="md-end-block md-p"><span class="md-plain">我们可以分别看一下这两个地方中，因为我们需要其</span><span class="md-pair-s" spellcheck="false"><code>processType</code></span><span class="md-plain">为</span><span class="md-pair-s" spellcheck="false"><code>TYPE</code></span><span class="md-plain">，那么也就是对应的</span><span class="md-pair-s" spellcheck="false"><code>processType()</code></span><span class="md-plain">方法返回值为</span><span class="md-pair-s" spellcheck="false"><code>TYPE</code></span><span class="md-plain">，可以看到，符合条件的是</span><span class="md-pair-s" spellcheck="false"><code>DistroClientComponentRegistry</code></span><span class="md-plain">,另外一个的返回不满足条件(另外一个的就不贴图了，大家自己可以去看一下,他的Key是另外一个常量)</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251228204526868.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251228204526868.png" alt="image-20251228204526868"></span></p>
<p class="md-end-block md-p"><span class="md-plain">那么确定了对应的返回值类型是</span><span class="md-pair-s" spellcheck="false"><code>DistroClientComponentRegistry</code></span><span class="md-plain">,那么我们就可以看其中的</span><span class="md-pair-s" spellcheck="false"><code>processData()</code></span><span class="md-plain">方法的实现逻辑</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">processData</span>(<span class="cm-variable">DistroData</span> <span class="cm-variable">distroData</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">switch</span> (<span class="cm-variable">distroData</span>.<span class="cm-variable">getType</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-variable">ADD</span>:</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-variable">CHANGE</span>:</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 添加和改变走这个逻辑</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 这一步是将请求数据利用序列化器进行对应的反序列化的操作</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">ClientSyncData</span> <span class="cm-variable">clientSyncData</span> <span class="cm-operator">=</span> <span class="cm-variable">ApplicationUtils</span>.<span class="cm-variable">getBean</span>(<span class="cm-variable">Serializer</span>.<span class="cm-keyword">class</span>)</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  .<span class="cm-variable">deserialize</span>(<span class="cm-variable">distroData</span>.<span class="cm-variable">getContent</span>(), <span class="cm-variable">ClientSyncData</span>.<span class="cm-keyword">class</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">handlerClientSyncData</span>(<span class="cm-variable">clientSyncData</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-variable">DELETE</span>:</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 删除走这个逻辑</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">deleteClientId</span> <span class="cm-operator">=</span> <span class="cm-variable">distroData</span>.<span class="cm-variable">getDistroKey</span>().<span class="cm-variable">getResourceKey</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">DISTRO</span>.<span class="cm-variable">info</span>(<span class="cm-string">"[Client-Delete] Received distro client sync data {}"</span>, <span class="cm-variable">deleteClientId</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">clientManager</span>.<span class="cm-variable">clientDisconnected</span>(<span class="cm-variable">deleteClientId</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">default</span>:</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">false</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">可以看到主要的逻辑就是</span><span class="md-pair-s" spellcheck="false"><code>handlerClientSyncData</code></span><span class="md-plain">方法，而在这个方法内的主要逻辑是</span><span class="md-pair-s" spellcheck="false"><code>upgradeClient</code></span><span class="md-plain">方法</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable-3">void</span> <span class="cm-def">handlerClientSyncData</span>(<span class="cm-variable">ClientSyncData</span> <span class="cm-variable">clientSyncData</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">DISTRO</span>.<span class="cm-variable">info</span>(<span class="cm-string">"[Client-Add] Received distro client sync data {}"</span>, <span class="cm-variable">clientSyncData</span>.<span class="cm-variable">getClientId</span>());</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">clientManager</span>.<span class="cm-variable">syncClientConnected</span>(<span class="cm-variable">clientSyncData</span>.<span class="cm-variable">getClientId</span>(), <span class="cm-variable">clientSyncData</span>.<span class="cm-variable">getAttributes</span>());</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Client</span> <span class="cm-variable">client</span> <span class="cm-operator">=</span> <span class="cm-variable">clientManager</span>.<span class="cm-variable">getClient</span>(<span class="cm-variable">clientSyncData</span>.<span class="cm-variable">getClientId</span>());</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">upgradeClient</span>(<span class="cm-variable">client</span>, <span class="cm-variable">clientSyncData</span>);</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span class="cm-comment">// 传入client即客户端ID对应的客户端对象 和 clientSyncData为对应的客户端数据</span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable-3">void</span> <span class="cm-def">upgradeClient</span>(<span class="cm-variable">Client</span> <span class="cm-variable">client</span>, <span class="cm-variable">ClientSyncData</span> <span class="cm-variable">clientSyncData</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">namespaces</span> <span class="cm-operator">=</span> <span class="cm-variable">clientSyncData</span>.<span class="cm-variable">getNamespaces</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">groupNames</span> <span class="cm-operator">=</span> <span class="cm-variable">clientSyncData</span>.<span class="cm-variable">getGroupNames</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">serviceNames</span> <span class="cm-operator">=</span> <span class="cm-variable">clientSyncData</span>.<span class="cm-variable">getServiceNames</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable">InstancePublishInfo</span><span class="cm-operator">&gt;</span> <span class="cm-variable">instances</span> <span class="cm-operator">=</span> <span class="cm-variable">clientSyncData</span>.<span class="cm-variable">getInstancePublishInfos</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 同步数据集合</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Set</span><span class="cm-operator">&lt;</span><span class="cm-variable">Service</span><span class="cm-operator">&gt;</span> <span class="cm-variable">syncedService</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">HashSet</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 遍历传入参数中的所有服务信息</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable-3">int</span> <span class="cm-variable">i</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>; <span class="cm-variable">i</span> <span class="cm-operator">&lt;</span> <span class="cm-variable">namespaces</span>.<span class="cm-variable">size</span>(); <span class="cm-variable">i</span><span class="cm-operator">++</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 创建对应的Service对象</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Service</span> <span class="cm-variable">service</span> <span class="cm-operator">=</span> <span class="cm-variable">Service</span>.<span class="cm-variable">newService</span>(<span class="cm-variable">namespaces</span>.<span class="cm-variable">get</span>(<span class="cm-variable">i</span>), <span class="cm-variable">groupNames</span>.<span class="cm-variable">get</span>(<span class="cm-variable">i</span>), <span class="cm-variable">serviceNames</span>.<span class="cm-variable">get</span>(<span class="cm-variable">i</span>));</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 获取内存中保存的对应的service单例对象</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Service</span> <span class="cm-variable">singleton</span> <span class="cm-operator">=</span> <span class="cm-variable">ServiceManager</span>.<span class="cm-variable">getInstance</span>().<span class="cm-variable">getSingleton</span>(<span class="cm-variable">service</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 加入到集合中，方便后续比较</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">syncedService</span>.<span class="cm-variable">add</span>(<span class="cm-variable">singleton</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">InstancePublishInfo</span> <span class="cm-variable">instancePublishInfo</span> <span class="cm-operator">=</span> <span class="cm-variable">instances</span>.<span class="cm-variable">get</span>(<span class="cm-variable">i</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 如果从client中获取的和同步数据中的内容不一样，则发布新增事件，client需要同步新数据</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">instancePublishInfo</span>.<span class="cm-variable">equals</span>(<span class="cm-variable">client</span>.<span class="cm-variable">getInstancePublishInfo</span>(<span class="cm-variable">singleton</span>))) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">client</span>.<span class="cm-variable">addServiceInstance</span>(<span class="cm-variable">singleton</span>, <span class="cm-variable">instancePublishInfo</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">new</span> <span class="cm-variable">ClientOperationEvent</span>.<span class="cm-variable">ClientRegisterServiceEvent</span>(<span class="cm-variable">singleton</span>, <span class="cm-variable">client</span>.<span class="cm-variable">getClientId</span>()));</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 如果某个服务不在同步数据中（即不在 syncedService 集合中），则从client中移除该服务实例并发布注销事件</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable">Service</span> <span class="cm-variable">each</span> : <span class="cm-variable">client</span>.<span class="cm-variable">getAllPublishedService</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">syncedService</span>.<span class="cm-variable">contains</span>(<span class="cm-variable">each</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">client</span>.<span class="cm-variable">removeServiceInstance</span>(<span class="cm-variable">each</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">new</span> <span class="cm-variable">ClientOperationEvent</span>.<span class="cm-variable">ClientDeregisterServiceEvent</span>(<span class="cm-variable">each</span>, <span class="cm-variable">client</span>.<span class="cm-variable">getClientId</span>()));</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">在这里会解析传过来的参数，最后对于新增的数据，还是发布了 </span><span class="md-pair-s" spellcheck="false"><code>ClientRegisterServiceEvent</code></span><span class="md-plain"> 事件，这个事件我们在讲解注册源码的时候已经分析过了，这一块注册逻辑就结束了。</span></p>
<p class="md-end-block md-p"><span class="md-plain">我们会过来看看</span><span class="md-pair-s" spellcheck="false"><code>processData</code></span><span class="md-plain">中对于删除操作的逻辑处理，可以发现核心在于</span><span class="md-pair-s" spellcheck="false"><code>clientManager.clientDisconnected(deleteClientId);</code></span><span class="md-plain">一样得需要找对应的实现类</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" spellcheck="false"><span role="presentation">这个实现类怎么找到的呢，这个有点难度。</span><br><span role="presentation">从代码的角度分析的话，DistroClientComponentRegistry中的这个clientManager，本质上是来源于DistroClientComponentRegistry中的clientManager，而DistroClientComponentRegistry是个Bean对象</span><br><span role="presentation">那么我们来看看clientManager，他对应的接口类型是ClientManagerDelegate，这个ClientManagerDelegate中也同样实现了clientDisconnected这个方法</span><br><span role="presentation">ClientManagerDelegate 持有三种不同的 ClientManager 实现：</span><br><span role="presentation">ConnectionBasedClientManager：基于连接的客户端管理器</span><br><span role="presentation">EphemeralIpPortClientManager：临时IP端口客户端管理器</span><br><span role="presentation">PersistentIpPortClientManager：持久IP端口客户端管理器</span><br><span role="presentation">​</span><br><span role="presentation">而ClientManagerDelegate中对于clientDisconnected的实现如下</span><br><span role="presentation">@Override</span><br><span role="presentation">public boolean clientDisconnected(String clientId) {</span><br><span role="presentation"> &nbsp;  return getClientManagerById(clientId).clientDisconnected(clientId);</span><br><span role="presentation">}</span><br><span role="presentation">与这个方法相关联的方法如下</span><br><span role="presentation">private ClientManager getClientManagerById(String clientId) {</span><br><span role="presentation"> &nbsp;  if (isConnectionBasedClient(clientId)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  return connectionBasedClientManager;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp;  return clientId.endsWith(ClientConstants.PERSISTENT_SUFFIX) ? persistentIpPortClientManager : ephemeralIpPortClientManager;</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">private boolean isConnectionBasedClient(String clientId) {</span><br><span role="presentation"> &nbsp; return !clientId.contains(IpPortBasedClient.ID_DELIMITER);</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">也就是说，我们确定这个clientDisconnected的实现类，最终是要依靠clientId，而本质上这个clientId是客户端注册的时候生成的，比较难追踪，而且你会发现很多的相关的信息都会回到这个clientId上来</span><br><span role="presentation">​</span><br><span role="presentation">所以我们这里其实最好的方法是debug的形式，或者我们也可以从理解的角度来看，一般的客户端都是临时IP端口，所以默认为EphemeralIpPortClientManager的实现</span></pre>
<p class="md-end-block md-p"><strong><span class="md-plain">这个方法我们看 </span><span class="md-pair-s" spellcheck="false"><code>EphemeralIpPortClientManager</code></span><span class="md-plain"> 临时实例的实现类</span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" spellcheck="false"><span role="presentation">@Override</span><br><span role="presentation">public boolean clientDisconnected(String clientId) {</span><br><span role="presentation"> &nbsp;  Loggers.SRV_LOG.info("Client connection {} disconnect, remove instances and subscribers", clientId);</span><br><span role="presentation"> &nbsp;  // 移除客户端信息</span><br><span role="presentation"> &nbsp;  IpPortBasedClient client = clients.remove(clientId);</span><br><span role="presentation"> &nbsp;  if (null == client) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  return true;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp;  // 发布客户端注销事件</span><br><span role="presentation"> &nbsp;  NotifyCenter.publishEvent(new ClientEvent.ClientDisconnectEvent(client));</span><br><span role="presentation"> &nbsp;  client.release();</span><br><span role="presentation"> &nbsp;  return true;</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>ClientDisconnectEvent</code></span><span class="md-plain"> 发布客户端注销事件，会在多个地方处理该事件。</span></p>
<p class="md-end-block md-p"><strong><span class="md-plain">第一个是</span><span class="md-pair-s" spellcheck="false"><code>DistroClientDataProcessor</code></span><span class="md-plain">，客户端注销后会同步集群节点。第二个是</span><span class="md-pair-s" spellcheck="false"><code>ClientServiceIndexesManager</code></span><span class="md-plain">，移除本身节点该 client 的数据。</span></strong></p>
<p class="md-end-block md-p"><span class="md-plain">客户端注销同步集群节点没什么好说的了，和注册同步逻辑类似，我们来看下 </span><span class="md-pair-s" spellcheck="false"><code>ClientServiceIndexesManager</code></span><span class="md-plain"> 类处理注销事件，代码如下：</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" spellcheck="false"><span role="presentation">​</span><br><span role="presentation">@Override</span><br><span role="presentation">public void onEvent(Event event) {</span><br><span role="presentation"> &nbsp;  if (event instanceof ClientEvent.ClientDisconnectEvent) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  // 处理客户端注销</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  handleClientDisconnect((ClientEvent.ClientDisconnectEvent) event);</span><br><span role="presentation"> &nbsp;  } else if (event instanceof ClientOperationEvent) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  handleClientOperation((ClientOperationEvent) event);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">private void handleClientDisconnect(ClientEvent.ClientDisconnectEvent event) {</span><br><span role="presentation"> &nbsp;  Client client = event.getClient();</span><br><span role="presentation"> &nbsp;  for (Service each : client.getAllSubscribeService()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  // 移除订阅者信息 其实就是操作subscriberIndexes</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  removeSubscriberIndexes(each, client.getClientId());</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp;  for (Service each : client.getAllPublishedService()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  // 移除注册表信息 其实就是操作publisherIndexes</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  removePublisherIndexes(each, client.getClientId());</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span></pre>]]></description>
    <pubDate>Mon, 29 Dec 2025 21:47:46 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=52</guid>
</item>
<item>
    <title>Nacos源码学习计划-Day22-Nacos2.x-服务变动如何通知订阅客户端</title>
    <link>http://8.154.40.120:24180/?post=51</link>
    <description><![CDATA[<h1 class="md-end-block md-heading md-focus"><span class="md-plain md-expand">服务变更如何通知订阅服务的客户端</span></h1>
<p class="md-end-block md-p"><span class="md-plain">我们在</span><span class="md-meta-i-c  md-link"><a href="/?post=47" target="_blank" rel="noopener"><span class="md-plain">Nacos源码学习计划-Day20-Nacos2.x-服务端处理客户端gRPC注册请求 </span></a></span><span class="md-plain">的最后面有看到，服务端处理gRPC的注册请求的最后，即</span><span class="md-pair-s" spellcheck="false"><code>addPublisherIndexes()</code></span><span class="md-plain">的最后一行，可以看到是发布了一个</span><span class="md-pair-s" spellcheck="false"><code>ServiceChangedEvent</code></span><span class="md-plain">服务变更的事件</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable-3">void</span> <span class="cm-def">addPublisherIndexes</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">clientId</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 把 clientId 写入注册表</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">publisherIndexes</span>.<span class="cm-variable">computeIfAbsent</span>(<span class="cm-variable">service</span>, (<span class="cm-variable">key</span>) <span class="cm-operator">-&gt;</span> <span class="cm-keyword">new</span> <span class="cm-variable">ConcurrentHashSet</span><span class="cm-operator">&lt;&gt;</span>());</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">publisherIndexes</span>.<span class="cm-variable">get</span>(<span class="cm-variable">service</span>).<span class="cm-variable">add</span>(<span class="cm-variable">clientId</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 发布服务改变事件</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(<span class="cm-keyword">new</span> <span class="cm-variable">ServiceEvent</span>.<span class="cm-variable">ServiceChangedEvent</span>(<span class="cm-variable">service</span>, <span class="cm-atom">true</span>));</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">今天就围绕这个来看看，服务变更后是如何通知对应的订阅的客户端的</span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">初看ServiceChangedEvent</span></h2>
<p class="md-end-block md-p"><span class="md-plain">老规矩，对于Event事件处理，优先需要找到对应的handler处理器，还是利用IDEA的全局搜索能轻松找到</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224142710962.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224142710962.png" alt="image-20251224142710962"></span></p>
<p class="md-end-block md-p"><span class="md-plain">看到这个地方，大家可以发现，</span><strong><span class="md-plain">这个</span><span class="md-pair-s" spellcheck="false"><code>if</code></span><span class="md-plain">部分的逻辑其实就是将对应的</span><span class="md-pair-s" spellcheck="false"><code>ServiceChangedEvent</code></span><span class="md-plain">封装成为</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTask类型</code></span><span class="md-plain">，然后调用</span><span class="md-pair-s" spellcheck="false"><code>delayTaskEngine.addTask()</code></span><span class="md-plain">方法，将其放入到任务队列中去执行</span></strong></p>
<p class="md-end-block md-p"><span class="md-plain">如果有阅读了上一节内容的UU就会发现，</span><strong><span class="md-plain">在这里Nacos使用这个的 </span><span class="md-pair-s" spellcheck="false"><code>delayTaskEngine.addTask()</code></span><span class="md-plain">,在上一节的最后，也就是</span><span class="md-pair-s" spellcheck="false"><code>ClientSubscribeServiceEvent客户端订阅服务事件</code></span><span class="md-plain">的处理中也是利用这个，这个其实是Nacos2.X中很重要的延迟任务处理引擎</span></strong></p>
<p class="md-end-block md-p"><span class="md-plain">所以接下来，我们来好好分析一下这个玩意儿</span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">PushDelayTaskExecuteEngine</span></h2>
<p class="md-end-block md-p"><span class="md-plain">延迟任务执行引擎，顾名思义是执行延迟任务，可以往执行引擎中添加任务，然后该任务会被延时执行。</span></p>
<p class="md-end-block md-p"><span class="md-plain">这个</span><strong><span class="md-pair-s" spellcheck="false"><code>delayTaskEngine</code></span><span class="md-plain">的数据类型是</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTaskExecuteEngine</code></span></strong><span class="md-plain">,我们可以先来查看一下他的相关的继承和实现关系</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224144945653.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224144945653.png" alt="image-20251224144945653"></span></p>
<p class="md-end-block md-p"><span class="md-plain">既然我们知道了最终会将任务放入到一个任务队列中，那么我们尝试找找从队列中取任务的逻辑</span></p>
<p class="md-end-block md-p"><span class="md-plain">但是实际搜索会发现，好像并没有这种一个一个拿的相关逻辑</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224155809825.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224155809825.png" alt="image-20251224155809825"></span></p>
<p class="md-end-block md-p"><span class="md-plain">这个时候我们再回来看看整个</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTaskExecuteEngine</code></span><span class="md-plain">的代码，通过方法命名和整个组织的结构来看，</span><strong><span class="md-plain">可以猜测到，整个逻辑任务的实现可能依靠的是</span><span class="md-pair-s" spellcheck="false"><code>processTasks()</code></span><span class="md-plain">这个方法，但是整个链路逻辑还是不是很明了，从代码来看，我们需要网上再去看其父类是如何实现的</span></strong></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224160527597.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224160527597.png" alt="image-20251224160527597"></span></p>
<p class="md-end-block md-p"><span class="md-plain">我们来看其父类</span><span class="md-pair-s" spellcheck="false"><code>NacosDelayTaskExecuteEngine</code></span><span class="md-plain">的逻辑</span></p>
<p class="md-end-block md-p"><span class="md-plain">可以从父类的构造方法中看到，构建的时候会利用内部的线程池成员开启</span><span class="md-pair-s" spellcheck="false"><code>ProcessRunnable 线程任务</code></span><span class="md-plain">，而这个线程任务的底层逻辑就是执行</span><span class="md-pair-s" spellcheck="false"><code>processTasks()</code></span><span class="md-plain">方法</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224163346630.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251224163346630.png" alt="image-20251224163346630"></span></p>
<p class="md-end-block md-p"><span class="md-plain">这个方法在子类中是有重写的，但是我们去看子类中的实现，</span><strong><span class="md-plain">就是加了一定的判断后再调用父类中的</span><span class="md-pair-s" spellcheck="false"><code>processTasks方法</code></span><span class="md-plain">，所以我们直接来看父类中的实现即可</span></strong></p>
<p class="md-end-block md-p"><span class="md-plain">可以看到，</span><strong><span class="md-plain">首先从tasks中获取到所有的任务key进行遍历，通过 taskKey 获取到具体的任务之后，再通过 taskKey 获取对应的处理器，最终调用处理器(通过</span><span class="md-pair-s" spellcheck="false"><code>getProcessor()方法获取</code></span><span class="md-plain">)的</span><span class="md-pair-s" spellcheck="false"><code>process()</code></span><span class="md-plain">处理方法</span></strong></p>
<p class="md-end-block md-p"><strong><span class="md-plain">从</span><span class="md-pair-s" spellcheck="false"><code>getProcessor()</code></span><span class="md-plain">的逻辑可以看到，如果给对应的</span><span class="md-pair-s" spellcheck="false"><code>key</code></span><span class="md-plain">即特定的</span><span class="md-pair-s" spellcheck="false"><code>task</code></span><span class="md-plain">设置了特定的</span><span class="md-pair-s" spellcheck="false"><code>processor处理器</code></span><span class="md-plain">，则会保存在</span><span class="md-pair-s" spellcheck="false"><code>taskProcessors</code></span><span class="md-plain">这个Map中，如果没有则使用默认的处理器，在顶级抽象接口中为</span><span class="md-pair-s" spellcheck="false"><code>NacosTaskProcessor</code></span><span class="md-plain">，而</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTaskExecuteEngine</code></span><span class="md-plain">的构造方法中调用了set方法将这个处理器设置为了</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTaskProcessor</code></span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation">​</span><br><span class="cm-comment">// 任务池</span><br><span role="presentation"><span class="cm-keyword">protected</span> <span class="cm-keyword">final</span> <span class="cm-variable">ConcurrentHashMap</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">Object</span>, <span class="cm-variable">AbstractDelayTask</span><span class="cm-operator">&gt;</span> <span class="cm-variable">tasks</span>;</span><br><span role="presentation">​</span><br><span class="cm-comment">// 这个不是NacosDelayTaskExecuteEngine的，而是来自顶级抽象类AbstractNacosTaskExecuteEngine  存储了特定的task-processor处理器的映射关系</span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-keyword">final</span> <span class="cm-variable">ConcurrentHashMap</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">Object</span>, <span class="cm-variable">NacosTaskProcessor</span><span class="cm-operator">&gt;</span> <span class="cm-variable">taskProcessors</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">ConcurrentHashMap</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation">​</span><br><span class="cm-comment">/**</span><br><span role="presentation"> <span class="cm-comment">* process tasks in execute engine.</span></span><br><span role="presentation"> <span class="cm-comment">*/</span></span><br><span role="presentation"><span class="cm-keyword">protected</span> <span class="cm-variable-3">void</span> <span class="cm-def">processTasks</span>() {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 获取全部的任务，进行遍历</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Collection</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">Object</span><span class="cm-operator">&gt;</span> <span class="cm-variable">keys</span> <span class="cm-operator">=</span> <span class="cm-variable">getAllTaskKeys</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable-3">Object</span> <span class="cm-variable">taskKey</span> : <span class="cm-variable">keys</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 通过任务 key，获取具体的任务，并且从任务池中移除掉</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">AbstractDelayTask</span> <span class="cm-variable">task</span> <span class="cm-operator">=</span> <span class="cm-variable">removeTask</span>(<span class="cm-variable">taskKey</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">task</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">continue</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 通过任务 key 获取对应的处理器</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NacosTaskProcessor</span> <span class="cm-variable">processor</span> <span class="cm-operator">=</span> <span class="cm-variable">getProcessor</span>(<span class="cm-variable">taskKey</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">processor</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">getEngineLog</span>().<span class="cm-variable">error</span>(<span class="cm-string">"processor not found for task, so discarded. "</span> <span class="cm-operator">+</span> <span class="cm-variable">task</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">continue</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 调用处理器的处理方法</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">processor</span>.<span class="cm-variable">process</span>(<span class="cm-variable">task</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">retryFailedTask</span>(<span class="cm-variable">taskKey</span>, <span class="cm-variable">task</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">Throwable</span> <span class="cm-variable">e</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">getEngineLog</span>().<span class="cm-variable">error</span>(<span class="cm-string">"Nacos task execute error "</span>, <span class="cm-variable">e</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">retryFailedTask</span>(<span class="cm-variable">taskKey</span>, <span class="cm-variable">task</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span><br><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">NacosTaskProcessor</span> <span class="cm-def">getProcessor</span>(<span class="cm-variable-3">Object</span> <span class="cm-variable">key</span>) {</span><br><span role="presentation"> &nbsp;<span class="cm-comment">// 如果处理器中没有对应的 key，那么就返回默认的任务处理器</span></span><br><span role="presentation"> &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">taskProcessors</span>.<span class="cm-variable">containsKey</span>(<span class="cm-variable">key</span>) <span class="cm-operator">?</span> <span class="cm-variable">taskProcessors</span>.<span class="cm-variable">get</span>(<span class="cm-variable">key</span>) : <span class="cm-variable">defaultTaskProcessor</span>;</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">而我们整个任务的存储结构</span><span class="md-pair-s" spellcheck="false"><code>tasks</code></span><span class="md-plain">这个map中的元素，就是来自于我们上面类似</span><span class="md-pair-s" spellcheck="false"><code>ServiceChangedEvent</code></span><span class="md-plain">的各种事件处理代码中，会通过</span><span class="md-pair-s" spellcheck="false"><code>addTask()</code></span><span class="md-plain">方法放入到</span><span class="md-pair-s" spellcheck="false"><code>tasks</code></span><span class="md-plain">中从而完成任务的放入</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">addTask</span>(<span class="cm-variable-3">Object</span> <span class="cm-variable">key</span>, <span class="cm-variable">AbstractDelayTask</span> <span class="cm-variable">newTask</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">lock</span>.<span class="cm-variable">lock</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 往任务池添加任务</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">AbstractDelayTask</span> <span class="cm-variable">existTask</span> <span class="cm-operator">=</span> <span class="cm-variable">tasks</span>.<span class="cm-variable">get</span>(<span class="cm-variable">key</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">!=</span> <span class="cm-variable">existTask</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">newTask</span>.<span class="cm-variable">merge</span>(<span class="cm-variable">existTask</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">tasks</span>.<span class="cm-variable">put</span>(<span class="cm-variable">key</span>, <span class="cm-variable">newTask</span>);</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">finally</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">lock</span>.<span class="cm-variable">unlock</span>();</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span></pre>
<h2 class="md-end-block md-heading"><span class="md-plain">再看ServiceChangedEvent</span></h2>
<p class="md-end-block md-p"><span class="md-plain">OK，分析完了这个延迟执行引擎，我们继续回到</span><span class="md-pair-s" spellcheck="false"><code>ServiceChangedEvent</code></span><span class="md-plain">的处理逻辑中来</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">onEvent</span>(<span class="cm-variable">Event</span> <span class="cm-variable">event</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">upgradeJudgement</span>.<span class="cm-variable">isUseGrpcFeatures</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">event</span> <span class="cm-keyword">instanceof</span> <span class="cm-variable">ServiceEvent</span>.<span class="cm-variable">ServiceChangedEvent</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 如果服务变动，会通知该 service 所有的订阅者，更新本地缓存</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">ServiceEvent</span>.<span class="cm-variable">ServiceChangedEvent</span> <span class="cm-variable">serviceChangedEvent</span> <span class="cm-operator">=</span> (<span class="cm-variable">ServiceEvent</span>.<span class="cm-variable">ServiceChangedEvent</span>) <span class="cm-variable">event</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Service</span> <span class="cm-variable">service</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceChangedEvent</span>.<span class="cm-variable">getService</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">delayTaskEngine</span>.<span class="cm-variable">addTask</span>(<span class="cm-variable">service</span>, <span class="cm-keyword">new</span> <span class="cm-variable">PushDelayTask</span>(<span class="cm-variable">service</span>, <span class="cm-variable">PushConfig</span>.<span class="cm-variable">getInstance</span>().<span class="cm-variable">getPushTaskDelay</span>()));</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable">event</span> <span class="cm-keyword">instanceof</span> <span class="cm-variable">ServiceEvent</span>.<span class="cm-variable">ServiceSubscribedEvent</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 如果服务被一个客户端订阅，则只推送该客户端</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">ServiceEvent</span>.<span class="cm-variable">ServiceSubscribedEvent</span> <span class="cm-variable">subscribedEvent</span> <span class="cm-operator">=</span> (<span class="cm-variable">ServiceEvent</span>.<span class="cm-variable">ServiceSubscribedEvent</span>) <span class="cm-variable">event</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Service</span> <span class="cm-variable">service</span> <span class="cm-operator">=</span> <span class="cm-variable">subscribedEvent</span>.<span class="cm-variable">getService</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">delayTaskEngine</span>.<span class="cm-variable">addTask</span>(<span class="cm-variable">service</span>, <span class="cm-keyword">new</span> <span class="cm-variable">PushDelayTask</span>(<span class="cm-variable">service</span>, <span class="cm-variable">PushConfig</span>.<span class="cm-variable">getInstance</span>().<span class="cm-variable">getPushTaskDelay</span>(),</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">subscribedEvent</span>.<span class="cm-variable">getClientId</span>()));</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">我们知道了，</span><strong><span class="md-plain">这个</span><span class="md-pair-s" spellcheck="false"><code>入参event</code></span><span class="md-plain">会被封装为一个</span><span class="md-pair-s" spellcheck="false"><code>Service</code></span><span class="md-plain">对象，并且作为</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTask</code></span><span class="md-plain">最终被</span><span class="md-pair-s" spellcheck="false"><code>delayTaskEngine</code></span><span class="md-plain">中的</span><span class="md-pair-s" spellcheck="false"><code>defaultTaskProcessor</code></span><span class="md-plain">的</span><span class="md-pair-s" spellcheck="false"><code>process()</code></span><span class="md-plain">方法处理</span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" spellcheck="false"><span role="presentation">因为没有触发addProcessor()方法，所以不会针对这个event设置特定的processor，所以这里肯定是直接使用defaultTaskProcessor</span></pre>
<p class="md-end-block md-p"><span class="md-plain">因为</span><span class="md-pair-s" spellcheck="false"><code>delayTaskEngine</code></span><span class="md-plain">中的</span><span class="md-pair-s" spellcheck="false"><code>defaultTaskProcessor</code></span><span class="md-plain">的类型是</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTaskProcessor</code></span><span class="md-plain">，所以我们直接来看这个处理器的</span><span class="md-pair-s" spellcheck="false"><code>process()</code></span><span class="md-plain">方法</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">process</span>(<span class="cm-variable">NacosTask</span> <span class="cm-variable">task</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 任务类型转换</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">PushDelayTask</span> <span class="cm-variable">pushDelayTask</span> <span class="cm-operator">=</span> (<span class="cm-variable">PushDelayTask</span>) <span class="cm-variable">task</span>;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Service</span> <span class="cm-variable">service</span> <span class="cm-operator">=</span> <span class="cm-variable">pushDelayTask</span>.<span class="cm-variable">getService</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 提交 PushExecuteTask 线程任务</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">NamingExecuteTaskDispatcher</span>.<span class="cm-variable">getInstance</span>()</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  .<span class="cm-variable">dispatchAndExecuteTask</span>(<span class="cm-variable">service</span>, <span class="cm-keyword">new</span> <span class="cm-variable">PushExecuteTask</span>(<span class="cm-variable">service</span>, <span class="cm-variable">executeEngine</span>, <span class="cm-variable">pushDelayTask</span>));</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">可以看到，</span><strong><span class="md-plain">这个</span><span class="md-pair-s" spellcheck="false"><code>最初的入参event转换成了PushDelayTask后又被转化为了PushExecuteTask</code></span><span class="md-plain">，这个就是我们熟知的任务类型了，那么接下来我们可以直接去看他对应的</span><span class="md-pair-s" spellcheck="false"><code>run()</code></span><span class="md-plain">方法</span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">run</span>() {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 从注册表获取当前 service 最新的实例列表数据</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">PushDataWrapper</span> <span class="cm-variable">wrapper</span> <span class="cm-operator">=</span> <span class="cm-variable">generatePushData</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">ClientManager</span> <span class="cm-variable">clientManager</span> <span class="cm-operator">=</span> <span class="cm-variable">delayTaskEngine</span>.<span class="cm-variable">getClientManager</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 获取客户端id，也就是 rpc 客户端连接 id</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable-3">String</span> <span class="cm-variable">each</span> : <span class="cm-variable">getTargetClientIds</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 获取客户端，通知服务变动</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Client</span> <span class="cm-variable">client</span> <span class="cm-operator">=</span> <span class="cm-variable">clientManager</span>.<span class="cm-variable">getClient</span>(<span class="cm-variable">each</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">client</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// means this client has disconnect</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">continue</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 获取客户端的订阅者</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Subscriber</span> <span class="cm-variable">subscriber</span> <span class="cm-operator">=</span> <span class="cm-variable">client</span>.<span class="cm-variable">getSubscriber</span>(<span class="cm-variable">service</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 回调客户端</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">delayTaskEngine</span>.<span class="cm-variable">getPushExecutor</span>().<span class="cm-variable">doPushWithCallback</span>(<span class="cm-variable">each</span>, <span class="cm-variable">subscriber</span>, <span class="cm-variable">wrapper</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">new</span> <span class="cm-variable">NamingPushCallback</span>(<span class="cm-variable">each</span>, <span class="cm-variable">subscriber</span>, <span class="cm-variable">wrapper</span>.<span class="cm-variable">getOriginalData</span>(), <span class="cm-variable">delayTask</span>.<span class="cm-variable">isPushToAll</span>()));</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">Exception</span> <span class="cm-variable">e</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Loggers</span>.<span class="cm-variable">PUSH</span>.<span class="cm-variable">error</span>(<span class="cm-string">"Push task for service"</span> <span class="cm-operator">+</span> <span class="cm-variable">service</span>.<span class="cm-variable">getGroupedServiceName</span>() <span class="cm-operator">+</span> <span class="cm-string">" execute failed "</span>, <span class="cm-variable">e</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">delayTaskEngine</span>.<span class="cm-variable">addTask</span>(<span class="cm-variable">service</span>, <span class="cm-keyword">new</span> <span class="cm-variable">PushDelayTask</span>(<span class="cm-variable">service</span>, <span class="cm-number">1000L</span>));</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span class="cm-comment">// 通过Service的信息(命名空间，group，cluster集群信息等等)和元数据（是否临时实例，保护阈值，路由选择器等等）得到PushDataWrapper</span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable">PushDataWrapper</span> <span class="cm-def">generatePushData</span>() {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">ServiceInfo</span> <span class="cm-variable">serviceInfo</span> <span class="cm-operator">=</span> <span class="cm-variable">delayTaskEngine</span>.<span class="cm-variable">getServiceStorage</span>().<span class="cm-variable">getPushData</span>(<span class="cm-variable">service</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">ServiceMetadata</span> <span class="cm-variable">serviceMetadata</span> <span class="cm-operator">=</span> <span class="cm-variable">delayTaskEngine</span>.<span class="cm-variable">getMetadataManager</span>().<span class="cm-variable">getServiceMetadata</span>(<span class="cm-variable">service</span>).<span class="cm-variable">orElse</span>(<span class="cm-atom">null</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">PushDataWrapper</span>(<span class="cm-variable">serviceMetadata</span>, <span class="cm-variable">serviceInfo</span>);</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable">Collection</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-def">getTargetClientIds</span>() {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 通过 pushToAll 这个参数来控制是否推送给全部的 client</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 如果为 true 推送 service 全部的 client，如果为 false，需要指定 targetClients 参数，只推送该参数里面的 client</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">delayTask</span>.<span class="cm-variable">isPushToAll</span>() <span class="cm-operator">?</span> <span class="cm-variable">delayTaskEngine</span>.<span class="cm-variable">getIndexesManager</span>().<span class="cm-variable">getAllClientsSubscribeService</span>(<span class="cm-variable">service</span>)</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  : <span class="cm-variable">delayTask</span>.<span class="cm-variable">getTargetClients</span>();</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">Collection</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-def">getAllClientsSubscribeService</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 从订阅表获取 clientId</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">subscriberIndexes</span>.<span class="cm-variable">containsKey</span>(<span class="cm-variable">service</span>) <span class="cm-operator">?</span> <span class="cm-variable">subscriberIndexes</span>.<span class="cm-variable">get</span>(<span class="cm-variable">service</span>) : <span class="cm-keyword">new</span> <span class="cm-variable">ConcurrentHashSet</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">这里补充一下，对于</span><span class="md-pair-s" spellcheck="false"><code>isPushToAll()</code></span><span class="md-plain">底层就是判断成员</span><span class="md-pair-s" spellcheck="false"><code>pushToAll为true还是false</code></span><span class="md-plain">，这个成员的数值取决于任务类型，我们知道在最外层的时候，会通过</span><span class="md-pair-s" spellcheck="false"><code>switch-case</code></span><span class="md-plain">区分</span><span class="md-pair-s" spellcheck="false"><code>event</code></span><span class="md-plain">的类型从而包装成不同类型的</span><span class="md-pair-s" spellcheck="false"><code>task</code></span><span class="md-plain">，虽然都是包装成为</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTask</code></span><span class="md-plain">,但是用到的构造方法不同，所以在初始化的时候这个</span><span class="md-pair-s" spellcheck="false"><code>pushToAll</code></span><span class="md-plain">的值就不一样</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable">Set</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">targetClients</span>;</span><br><span role="presentation">​</span><br><span class="cm-comment">//  服务变动事件，所使用的构造方法,可以看到pushToAll设置为true，就是要通知给所有的订阅了该服务的客户端</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">PushDelayTask</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>, <span class="cm-variable-3">long</span> <span class="cm-variable">delay</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-variable">service</span> <span class="cm-operator">=</span> <span class="cm-variable">service</span>;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">pushToAll</span> <span class="cm-operator">=</span> <span class="cm-atom">true</span>;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">targetClients</span> <span class="cm-operator">=</span> <span class="cm-atom">null</span>;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">setTaskInterval</span>(<span class="cm-variable">delay</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">setLastProcessTime</span>(<span class="cm-variable">System</span>.<span class="cm-variable">currentTimeMillis</span>());</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span class="cm-comment">// 服务订阅事件使用的构造方法</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">PushDelayTask</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>, <span class="cm-variable-3">long</span> <span class="cm-variable">delay</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">targetClient</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-variable">service</span> <span class="cm-operator">=</span> <span class="cm-variable">service</span>;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-variable">pushToAll</span> <span class="cm-operator">=</span> <span class="cm-atom">false</span>;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-variable">targetClients</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">HashSet</span><span class="cm-operator">&lt;&gt;</span>(<span class="cm-number">1</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 把 clientId 添加到 targetClients 当中，这个 clientId 就是发起服务订阅的客户端ID</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-variable">targetClients</span>.<span class="cm-variable">add</span>(<span class="cm-variable">targetClient</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">setTaskInterval</span>(<span class="cm-variable">delay</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">setLastProcessTime</span>(<span class="cm-variable">System</span>.<span class="cm-variable">currentTimeMillis</span>());</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">我们回到</span><span class="md-pair-s" spellcheck="false"><code>run</code></span><span class="md-plain">中的逻辑，在最后通过</span><span class="md-pair-s" spellcheck="false"><code>doPushWithCallback</code></span><span class="md-plain">这个方式发送消息给对应的client，这个方法是来自于顶层接口，有三个实现类，因为我们之前聊到过Nacos2.X都是用的Rpc的形式了，所以直接去看Rpc版的实现</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251226163801378.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251226163801378.png" alt="image-20251226163801378"></span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">doPushWithCallback</span>(<span class="cm-variable-3">String</span> <span class="cm-variable">clientId</span>, <span class="cm-variable">Subscriber</span> <span class="cm-variable">subscriber</span>, <span class="cm-variable">PushDataWrapper</span> <span class="cm-variable">data</span>, <span class="cm-variable">PushCallBack</span> <span class="cm-variable">callBack</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 这里构建的是 NotifySubscriberRequest 类型请求参数</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">pushService</span>.<span class="cm-variable">pushWithCallback</span>(<span class="cm-variable">clientId</span>, <span class="cm-variable">NotifySubscriberRequest</span>.<span class="cm-variable">buildNotifySubscriberRequest</span>(<span class="cm-variable">getServiceInfo</span>(<span class="cm-variable">data</span>, <span class="cm-variable">subscriber</span>)),</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">callBack</span>, <span class="cm-variable">GlobalExecutor</span>.<span class="cm-variable">getCallbackExecutor</span>());</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">可以看到，这里发送的请求是</span><span class="md-pair-s" spellcheck="false"><code>NotifySubscriberRequest</code></span><span class="md-plain">，那么接下来我们可以去客户端查找这个请求的处理器</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">class</span> <span class="cm-def">NamingPushRequestHandler</span> <span class="cm-keyword">implements</span> <span class="cm-variable">ServerRequestHandler</span> {</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">private</span> <span class="cm-keyword">final</span> <span class="cm-variable">ServiceInfoHolder</span> <span class="cm-variable">serviceInfoHolder</span>;</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-variable">NamingPushRequestHandler</span>(<span class="cm-variable">ServiceInfoHolder</span> <span class="cm-variable">serviceInfoHolder</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-variable">serviceInfoHolder</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceInfoHolder</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-meta">@Override</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-variable">Response</span> <span class="cm-variable">requestReply</span>(<span class="cm-variable">Request</span> <span class="cm-variable">request</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">request</span> <span class="cm-keyword">instanceof</span> <span class="cm-variable">NotifySubscriberRequest</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 类型转换</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NotifySubscriberRequest</span> <span class="cm-variable">notifyResponse</span> <span class="cm-operator">=</span> (<span class="cm-variable">NotifySubscriberRequest</span>) <span class="cm-variable">request</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 更新本地缓存数据</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceInfoHolder</span>.<span class="cm-variable">processServiceInfo</span>(<span class="cm-variable">notifyResponse</span>.<span class="cm-variable">getServiceInfo</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">NotifySubscriberResponse</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">null</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>serviceInfoHolder.processServiceInfo</code></span><span class="md-plain"> 更新本地缓存的方法，我们在上一篇文章也见过了，也就是更新 </span><span class="md-pair-s" spellcheck="false"><code>serviceInfoMap</code></span><span class="md-plain"> 中的数据即可。</span></p>
<h1 class="md-end-block md-heading"><span class="md-plain">总结</span></h1>
<ol class="ol-list">
<li class="md-list-item">
<p class="md-end-block md-p"><strong><span class="md-plain">事件触发</span></strong><span class="md-plain">：服务端处理完服务注册等操作后，会发布</span><span class="md-pair-s" spellcheck="false"><code>ServiceChangedEvent</code></span><span class="md-plain">（服务变更事件）或</span><span class="md-pair-s" spellcheck="false"><code>ServiceSubscribedEvent</code></span><span class="md-plain">（服务订阅事件）。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><strong><span class="md-plain">任务封装</span></strong><span class="md-plain">：事件处理器将事件封装为</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTask</code></span><span class="md-plain">，通过</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTaskExecuteEngine</code></span><span class="md-plain">（延迟任务执行引擎）加入任务队列。其中，服务变更事件对应的任务会设置</span><span class="md-pair-s" spellcheck="false"><code>pushToAll=true</code></span><span class="md-plain">（通知所有订阅客户端），服务订阅事件则</span><span class="md-pair-s" spellcheck="false"><code>pushToAll=false</code></span><span class="md-plain">且后续操作指定目标客户端 ID。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><strong><span class="md-plain">延迟任务执行</span></strong><span class="md-plain">：</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTaskExecuteEngine</code></span><span class="md-plain">基于父类</span><span class="md-pair-s" spellcheck="false"><code>NacosDelayTaskExecuteEngine</code></span><span class="md-plain">的线程池，通过</span><span class="md-pair-s" spellcheck="false"><code>processTasks()</code></span><span class="md-plain">方法轮询处理任务，调用默认处理器</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTaskProcessor</code></span><span class="md-plain">的</span><span class="md-pair-s" spellcheck="false"><code>process()</code></span><span class="md-plain">方法。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><strong><span class="md-plain">推送任务执行</span></strong><span class="md-plain">：</span><span class="md-pair-s" spellcheck="false"><code>PushDelayTask</code></span><span class="md-plain">转换为</span><span class="md-pair-s" spellcheck="false"><code>PushExecuteTask</code></span><span class="md-plain">，该任务会获取服务最新实例数据（</span><span class="md-pair-s" spellcheck="false"><code>ServiceInfo</code></span><span class="md-plain">）和订阅客户端列表，通过 gRPC 向目标客户端发送</span><span class="md-pair-s" spellcheck="false"><code>NotifySubscriberRequest</code></span><span class="md-plain">请求。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><strong><span class="md-plain">客户端处理</span></strong><span class="md-plain">：客户端通过</span><span class="md-pair-s" spellcheck="false"><code>NamingPushRequestHandler</code></span><span class="md-plain">接收请求，调用</span><span class="md-pair-s" spellcheck="false"><code>serviceInfoHolder.processServiceInfo()</code></span><span class="md-plain">更新本地缓存，完成服务变更的同步。</span></p>
</li>
</ol>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">整个流程通过事件驱动、延迟任务调度和 gRPC 通信，实现了服务端与客户端的高效数据同步。</span></p>]]></description>
    <pubDate>Fri, 26 Dec 2025 16:46:29 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=51</guid>
</item>
<item>
    <title>Nacos源码学习计划-Day21-Nacos2.x-从服务调用链路上来加深对于内存注册表的理解</title>
    <link>http://8.154.40.120:24180/?post=50</link>
    <description><![CDATA[<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">这一章，主要是从服务调用的链路角度，</span><strong><span class="md-plain">来帮我们对于Nacos2.X版本中的</span><span class="md-pair-s" spellcheck="false"><code>内存注册表结构</code></span><span class="md-plain">的理解</span></strong><span class="md-plain">，在服务调用的链路中，客户端肯定会需要发起从服务端查询对应的服务实例，服务端也会处理这个请求从而查找内存注册表从而进行响应，这个过程中自然就会需要我们去了解内存注册表的结构</span></p>
<h1 class="md-end-block md-heading"><span class="md-plain">客户端服务查询逻辑</span></h1>
<p class="md-end-block md-p"><span class="md-plain">这里我们之前其在Nacos1.4.X版本的分析中有说过的，当时我们对于Nacos2.X版本中的实现也是有一定的提及的，所以我们不会很详细的从头到尾讲，大家可以先去回顾一下</span></p>
<p class="md-end-block md-p"><span class="md-meta-i-c  md-link"><a href="/?post=30" target="_blank" rel="noopener"><span class="md-plain">Nacos源码学习计划-Day05-服务调用时的调用链路（如何获取服务信息） - ZealSingerの博客啦~</span></a></span></p>
<p class="md-end-block md-p"><span class="md-plain">之前有提到，Nacos2.X版本中就是通过实现Spring Cloud服务发现接口，依赖于</span><span class="md-pair-s" spellcheck="false"><code>NacosServiceDiscovery</code></span><span class="md-plain">来和Nacos服务端进行交互，在</span><span class="md-pair-s" spellcheck="false"><code>NacosServiceDiscovery</code></span><span class="md-plain">中查询服务列表的操作其实就是调用的</span><span class="md-pair-s" spellcheck="false"><code>namingService().selectInstances()</code></span><span class="md-plain">，我们可以进去看看他的逻辑</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251219205150332.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251219205150332.png" alt="image-20251219205150332"></span></p>
<p class="md-end-block md-p"><span class="md-plain">这里需要注意，不要看到</span><span class="md-pair-s" spellcheck="false"><code>return语句中的selectInstances</code></span><span class="md-plain">去了，这个里面是对于</span><span class="md-pair-s" spellcheck="false"><code>serviceInfo</code></span><span class="md-plain">对象中的</span><span class="md-pair-s" spellcheck="false"><code>hosts</code></span><span class="md-plain">的健康状态的判断，用于去除不健康的数据，也就是说我们服务端直接返回的是</span><span class="md-pair-s" spellcheck="false"><code>serviceInfo</code></span><span class="md-plain">，所以我们要看这个对象的获取逻辑，也就是如下这行逻辑</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable">Instance</span><span class="cm-operator">&gt;</span> <span class="cm-def">selectInstances</span>(<span class="cm-variable-3">String</span> <span class="cm-variable">serviceName</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">groupName</span>, <span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">clusters</span>, <span class="cm-variable-3">boolean</span> <span class="cm-variable">healthy</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable-3">boolean</span> <span class="cm-variable">subscribe</span>) <span class="cm-keyword">throws</span> <span class="cm-variable">NacosException</span> {</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">ServiceInfo</span> <span class="cm-variable">serviceInfo</span>;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">clusterString</span> <span class="cm-operator">=</span> <span class="cm-variable">StringUtils</span>.<span class="cm-variable">join</span>(<span class="cm-variable">clusters</span>, <span class="cm-string">","</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 判断是否需要 订阅，默认为 true</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">subscribe</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 查询 Nacos 本地缓存数据</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceInfo</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceInfoHolder</span>.<span class="cm-variable">getServiceInfo</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusterString</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 如果本地缓存数据为空，则通过 client 对象请求服务端获取数据，这里是调用的订阅方法</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">serviceInfo</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceInfo</span> <span class="cm-operator">=</span> <span class="cm-variable">clientProxy</span>.<span class="cm-variable">subscribe</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusterString</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">else</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceInfo</span> <span class="cm-operator">=</span> <span class="cm-variable">clientProxy</span>.<span class="cm-variable">queryInstancesOfService</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusterString</span>, <span class="cm-number">0</span>, <span class="cm-atom">false</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 返回数据</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">selectInstances</span>(<span class="cm-variable">serviceInfo</span>, <span class="cm-variable">healthy</span>);</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><strong><span class="md-plain">这个</span><span class="md-pair-s" spellcheck="false"><code>subscribe</code></span><span class="md-plain">大家先知道他是客户端是否采用</span><span class="md-pair-s" spellcheck="false"><code>订阅模式</code></span><span class="md-plain">，默认情况下就是</span><span class="md-pair-s" spellcheck="false"><code>true即开启订阅模式</code></span><span class="md-plain">，该配置影响的也就是客户端查询服务实例的方式和时机，区别的体现也是就是我们上述代码中</span></strong></p>
<h2 class="md-end-block md-heading"><span class="md-plain">订阅模式下</span></h2>
<p class="md-end-block md-p"><span class="md-plain">订阅模式下，我们可以很清楚的看到，先执行的</span><span class="md-pair-s" spellcheck="false"><code>serviceInfoHolder.getServiceInfo</code></span><span class="md-plain">先查询缓存，其底层就是在客户端内存Map中进行</span><span class="md-pair-s" spellcheck="false"><code>get()操作</code></span><span class="md-plain">，然后</span><strong><span class="md-plain">再执行的</span><span class="md-pair-s" spellcheck="false"><code>clientProxy.subscribe</code></span><span class="md-plain">就是查询服务端且进行订阅，我可以来看一下这个过程的逻辑，这个subscribe是需要寻找对应的实现类的，我们可以直接查看clientProxy数据类型，就知道要看哪个了</span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">ServiceInfo</span> <span class="cm-def">subscribe</span>(<span class="cm-variable-3">String</span> <span class="cm-variable">serviceName</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">groupName</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">clusters</span>) <span class="cm-keyword">throws</span> <span class="cm-variable">NacosException</span> {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">NAMING_LOGGER</span>.<span class="cm-variable">info</span>(<span class="cm-string">"[SUBSCRIBE-SERVICE] service:{}, group:{}, clusters:{} "</span>, <span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusters</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">serviceNameWithGroup</span> <span class="cm-operator">=</span> <span class="cm-variable">NamingUtils</span>.<span class="cm-variable">getGroupedName</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">serviceKey</span> <span class="cm-operator">=</span> <span class="cm-variable">ServiceInfo</span>.<span class="cm-variable">getKey</span>(<span class="cm-variable">serviceNameWithGroup</span>, <span class="cm-variable">clusters</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 开启实例查询定时任务</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">serviceInfoUpdateService</span>.<span class="cm-variable">scheduleUpdateIfAbsent</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusters</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 会再一次查询缓存数据</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">ServiceInfo</span> <span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceInfoHolder</span>.<span class="cm-variable">getServiceInfoMap</span>().<span class="cm-variable">get</span>(<span class="cm-variable">serviceKey</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">result</span> <span class="cm-operator">||</span> <span class="cm-operator">!</span><span class="cm-variable">isSubscribed</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusters</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 如果还是为空，则会使用 grpc 来请求服务端</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-variable">grpcClientProxy</span>.<span class="cm-variable">subscribe</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusters</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 更新本地缓存</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">serviceInfoHolder</span>.<span class="cm-variable">processServiceInfo</span>(<span class="cm-variable">result</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">result</span>;</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>scheduleUpdateIfAbsent</code></span><span class="md-plain">开启了一个 </span><span class="md-pair-s" spellcheck="false"><code>实例查询定时任务</code></span><span class="md-plain">，在这个定时任务中会去更新本地缓存的数据，我们来看一下里面的逻辑</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-keyword">final</span> <span class="cm-variable">Map</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span>, <span class="cm-variable">ScheduledFuture</span><span class="cm-operator">&lt;?&gt;&gt;</span> <span class="cm-variable">futureMap</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">HashMap</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span>, <span class="cm-variable">ScheduledFuture</span><span class="cm-operator">&lt;?&gt;&gt;</span>();</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">scheduleUpdateIfAbsent</span>(<span class="cm-variable-3">String</span> <span class="cm-variable">serviceName</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">groupName</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">clusters</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 生成一个 serverKey</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">serviceKey</span> <span class="cm-operator">=</span> <span class="cm-variable">ServiceInfo</span>.<span class="cm-variable">getKey</span>(<span class="cm-variable">NamingUtils</span>.<span class="cm-variable">getGroupedName</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>), <span class="cm-variable">clusters</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 判断当前 serviceKey 是否有开启定时任务，如果有就不开启了</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">futureMap</span>.<span class="cm-variable">get</span>(<span class="cm-variable">serviceKey</span>) <span class="cm-operator">!=</span> <span class="cm-atom">null</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 加了一把同步锁，以免并发冲突</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">synchronized</span> (<span class="cm-variable">futureMap</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 加锁之后又再一次进行判断，双重检测</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">futureMap</span>.<span class="cm-variable">get</span>(<span class="cm-variable">serviceKey</span>) <span class="cm-operator">!=</span> <span class="cm-atom">null</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 最后开 UpdateTask 定时任务</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">ScheduledFuture</span><span class="cm-operator">&lt;?&gt;</span> <span class="cm-variable">future</span> <span class="cm-operator">=</span> <span class="cm-variable">addTask</span>(<span class="cm-keyword">new</span> <span class="cm-variable">UpdateTask</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusters</span>));</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">futureMap</span>.<span class="cm-variable">put</span>(<span class="cm-variable">serviceKey</span>, <span class="cm-variable">future</span>);</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">每次查询一个新的 service 都会开启这么一个定时任务，来为本地数据进行更新操作。我们再来看看这个 </span><span class="md-pair-s" spellcheck="false"><code>UpdateTask</code></span><span class="md-plain"> 做了什么事情，代码如下：</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">run</span>() {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">long</span> <span class="cm-variable">delayTime</span> <span class="cm-operator">=</span> <span class="cm-variable">DEFAULT_DELAY</span>;</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 如果没有订阅且map中不包含任务 则取消该任务</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">changeNotifier</span>.<span class="cm-variable">isSubscribed</span>(<span class="cm-variable">groupName</span>, <span class="cm-variable">serviceName</span>, <span class="cm-variable">clusters</span>) <span class="cm-operator">&amp;&amp;</span> <span class="cm-operator">!</span><span class="cm-variable">futureMap</span>.<span class="cm-variable">containsKey</span>(</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceKey</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NAMING_LOGGER</span>.<span class="cm-variable">info</span>(<span class="cm-string">"update task is stopped, service:{}, clusters:{}"</span>, <span class="cm-variable">groupedServiceName</span>, <span class="cm-variable">clusters</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">isCancel</span> <span class="cm-operator">=</span> <span class="cm-atom">true</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 从本地缓存中获取一次，如果本地缓存中为空</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">ServiceInfo</span> <span class="cm-variable">serviceObj</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceInfoHolder</span>.<span class="cm-variable">getServiceInfoMap</span>().<span class="cm-variable">get</span>(<span class="cm-variable">serviceKey</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">serviceObj</span> <span class="cm-operator">==</span> <span class="cm-atom">null</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 为空就会通过 gRPC 去查询服务端的数据</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceObj</span> <span class="cm-operator">=</span> <span class="cm-variable">namingClientProxy</span>.<span class="cm-variable">queryInstancesOfService</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusters</span>, <span class="cm-number">0</span>, <span class="cm-atom">false</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 更新本地缓存</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceInfoHolder</span>.<span class="cm-variable">processServiceInfo</span>(<span class="cm-variable">serviceObj</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 更新获取时间</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">lastRefTime</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceObj</span>.<span class="cm-variable">getLastRefTime</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 如果本地缓存不为空，会判断该本地缓存最后一次刷新的时间，是否小于等于 最后一次数据刷新时间</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">serviceObj</span>.<span class="cm-variable">getLastRefTime</span>() <span class="cm-operator">&lt;=</span> <span class="cm-variable">lastRefTime</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 小于等于的话，会重新请求服务端数据，然后更新本地缓存</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 需要注意queryInstancesOfService方法的后面两个参数 0是updPort 这个是；历史兼容问题，Nacos1.X版本中会通过upd协议进行服务端对客户端的主动推送，这个参数用于告诉服务端，客户端在哪个端口进行监听接受</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 但是Nacos2.X版本中已经全面采用gRPC了，所以这个参数其实是没有意义，所以直接传入0即可</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 最后一个boolea类型的参数，代表healthOnly，即是否只返回健康实例，如果传入false则代表无论健康还是不健康的实例都会被传回</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceObj</span> <span class="cm-operator">=</span> <span class="cm-variable">namingClientProxy</span>.<span class="cm-variable">queryInstancesOfService</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusters</span>, <span class="cm-number">0</span>, <span class="cm-atom">false</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">serviceInfoHolder</span>.<span class="cm-variable">processServiceInfo</span>(<span class="cm-variable">serviceObj</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; </span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">lastRefTime</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceObj</span>.<span class="cm-variable">getLastRefTime</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 如果为empty 则代表请求失败了 则会累计请求失败次数</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 因为我们在查询的时候设置了healthOnly参数为false，所以如果返回为空则说明从服务端的角度而言单体或者集群中满足条件的健康实例和非健康实例，这个对于客户端缓存而言的效果就是没有，所以empty可以直接和请求fail进行绑定</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">CollectionUtils</span>.<span class="cm-variable">isEmpty</span>(<span class="cm-variable">serviceObj</span>.<span class="cm-variable">getHosts</span>())) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 累计请求失败次数 实质上就是failCount这个变量递增</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">incFailCount</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 计算下一次定时任务执行的时间，这里的结果是 6s</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">delayTime</span> <span class="cm-operator">=</span> <span class="cm-variable">serviceObj</span>.<span class="cm-variable">getCacheMillis</span>() <span class="cm-operator">*</span> <span class="cm-variable">DEFAULT_UPDATE_CACHE_TIME_MULTIPLE</span>;</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 当请求成功之后，会重置错误次数为 0 </span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">resetFailCount</span>();</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">catch</span> (<span class="cm-variable">Throwable</span> <span class="cm-variable">e</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 记录请求失败的次数</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">incFailCount</span>();</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NAMING_LOGGER</span>.<span class="cm-variable">warn</span>(<span class="cm-string">"[NA] failed to update serviceName: {}"</span>, <span class="cm-variable">groupedServiceName</span>, <span class="cm-variable">e</span>);</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">finally</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">isCancel</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 这里就根据请求失败的次数，来动态调整定时任务的执行时间，防止频繁地重试</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">executor</span>.<span class="cm-variable">schedule</span>(<span class="cm-keyword">this</span>, <span class="cm-variable">Math</span>.<span class="cm-variable">min</span>(<span class="cm-variable">delayTime</span> <span class="cm-operator">&lt;&lt;</span> <span class="cm-variable">failCount</span>, <span class="cm-variable">DEFAULT_DELAY</span> <span class="cm-operator">*</span> <span class="cm-number">60</span>),</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">TimeUnit</span>.<span class="cm-variable">MILLISECONDS</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">定时任务开启之后，我们会通过</span><span class="md-pair-s" spellcheck="false"><code>subscribe</code></span><span class="md-plain">方法对服务端发起订阅查询这个RPC请求，其逻辑如下,可以发现就是发起了一个SubscribeServiceRequest的请求</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-comment">/**</span><br><span role="presentation"> <span class="cm-comment">* Execute subscribe operation.</span></span><br><span role="presentation"> <span class="cm-comment">*</span></span><br><span role="presentation"> <span class="cm-comment">* @param serviceName service name</span></span><br><span role="presentation"> <span class="cm-comment">* @param groupName &nbsp; group name</span></span><br><span role="presentation"> <span class="cm-comment">* @param clusters &nbsp;  clusters, current only support subscribe all clusters, maybe deprecated</span></span><br><span role="presentation"> <span class="cm-comment">* @return current service info of subscribe service</span></span><br><span role="presentation"> <span class="cm-comment">* @throws NacosException nacos exception</span></span><br><span role="presentation"> <span class="cm-comment">*/</span></span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">ServiceInfo</span> <span class="cm-def">doSubscribe</span>(<span class="cm-variable-3">String</span> <span class="cm-variable">serviceName</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">groupName</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">clusters</span>) <span class="cm-keyword">throws</span> <span class="cm-variable">NacosException</span> {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">SubscribeServiceRequest</span> <span class="cm-variable">request</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">SubscribeServiceRequest</span>(<span class="cm-variable">namespaceId</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">serviceName</span>, <span class="cm-variable">clusters</span>,</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-atom">true</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">SubscribeServiceResponse</span> <span class="cm-variable">response</span> <span class="cm-operator">=</span> <span class="cm-variable">requestToServer</span>(<span class="cm-variable">request</span>, <span class="cm-variable">SubscribeServiceResponse</span>.<span class="cm-keyword">class</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">redoService</span>.<span class="cm-variable">subscriberRegistered</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusters</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">response</span>.<span class="cm-variable">getServiceInfo</span>();</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p">&nbsp;</p>
<h2 class="md-end-block md-heading"><span class="md-plain">非订阅模式下</span></h2>
<p class="md-end-block md-p"><span class="md-plain">至于非订阅模式下，代码逻辑就很简单了</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-variable">serviceInfo</span> <span class="cm-operator">=</span> <span class="cm-variable">clientProxy</span>.<span class="cm-variable">queryInstancesOfService</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">clusterString</span>, <span class="cm-number">0</span>, <span class="cm-atom">false</span>);</span></pre>
<p class="md-end-block md-p"><span class="md-plain">可以看到，这个逻辑其实也是订阅模式下</span><span class="md-pair-s" spellcheck="false"><code>UpdateTask</code></span><span class="md-plain">的</span><span class="md-pair-s" spellcheck="false"><code>run</code></span><span class="md-plain">逻辑中</span><span class="md-pair-s" spellcheck="false"><code>RPC查询服务端</code></span><span class="md-plain">的方法，其实就是调用了同一个方法进行RPC查询，所以这里我们不需要多解释。</span></p>
<figure class="md-table-fig table-figure">
<table class="md-table">
<thead>
<tr class="md-end-block">
<th><span class="md-plain">方法</span></th>
<th><span class="md-plain">设计意图</span></th>
</tr>
</thead>
<tbody>
<tr class="md-end-block">
<td><span class="md-pair-s" spellcheck="false"><code>subscribe</code></span></td>
<td><span class="md-plain">以「一次订阅请求」换「后续无感知的推送更新」，减少客户端与服务端的交互次数，提升性能（订阅模式的核心价值）</span></td>
</tr>
<tr class="md-end-block">
<td><span class="md-pair-s" spellcheck="false"><code>queryInstancesOfService</code></span></td>
<td><span class="md-plain">满足「单次实时查询」场景（如手动触发的实例校验、非长连接场景），牺牲性能换实时性，同时通过降级缓存保证容错</span></td>
</tr>
</tbody>
</table>
</figure>
<h1 class="md-end-block md-heading"><span class="md-plain">服务端处理订阅查询请求</span></h1>
<p class="md-end-block md-p"><span class="md-plain">我们在分析订阅模式下的请求逻辑的时候，看到了实际上就是发出了一个</span><span class="md-pair-s" spellcheck="false"><code>SubscribeServiceRequest</code></span><span class="md-plain">的请求到服务端，老规矩，我们利用Idea的搜索功能到服务端查看一下服务端是如何处理的</span></p>
<p class="md-end-block md-p"><span class="md-plain">很轻松我们就能找到</span><span class="md-pair-s" spellcheck="false"><code>SubscribeServiceRequestHandler</code></span><span class="md-plain">这个处理器，其逻辑为</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-meta">@Secured</span>(<span class="cm-variable">action</span> <span class="cm-operator">=</span> <span class="cm-variable">ActionTypes</span>.<span class="cm-variable">READ</span>)</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">SubscribeServiceResponse</span> <span class="cm-def">handle</span>(<span class="cm-variable">SubscribeServiceRequest</span> <span class="cm-variable">request</span>, <span class="cm-variable">RequestMeta</span> <span class="cm-variable">meta</span>) <span class="cm-keyword">throws</span> <span class="cm-variable">NacosException</span> {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 从request中获取相关目标服务的数据</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">namespaceId</span> <span class="cm-operator">=</span> <span class="cm-variable">request</span>.<span class="cm-variable">getNamespace</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">serviceName</span> <span class="cm-operator">=</span> <span class="cm-variable">request</span>.<span class="cm-variable">getServiceName</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">groupName</span> <span class="cm-operator">=</span> <span class="cm-variable">request</span>.<span class="cm-variable">getGroupName</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">app</span> <span class="cm-operator">=</span> <span class="cm-variable">request</span>.<span class="cm-variable">getHeader</span>(<span class="cm-string">"app"</span>, <span class="cm-string">"unknown"</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable-3">String</span> <span class="cm-variable">groupedServiceName</span> <span class="cm-operator">=</span> <span class="cm-variable">NamingUtils</span>.<span class="cm-variable">getGroupedName</span>(<span class="cm-variable">serviceName</span>, <span class="cm-variable">groupName</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 利用参数创建一个临时service 即被调用的那个服务的Service</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Service</span> <span class="cm-variable">service</span> <span class="cm-operator">=</span> <span class="cm-variable">Service</span>.<span class="cm-variable">newService</span>(<span class="cm-variable">namespaceId</span>, <span class="cm-variable">groupName</span>, <span class="cm-variable">serviceName</span>, <span class="cm-atom">true</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 利用请求元数据 创建请求订阅的对象 即请求发起者</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Subscriber</span> <span class="cm-variable">subscriber</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Subscriber</span>(<span class="cm-variable">meta</span>.<span class="cm-variable">getClientIp</span>(), <span class="cm-variable">meta</span>.<span class="cm-variable">getClientVersion</span>(), <span class="cm-variable">app</span>, <span class="cm-variable">meta</span>.<span class="cm-variable">getClientIp</span>(),</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">namespaceId</span>, <span class="cm-variable">groupedServiceName</span>, <span class="cm-number">0</span>, <span class="cm-variable">request</span>.<span class="cm-variable">getClusters</span>());</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 获取健康的实例</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">ServiceInfo</span> <span class="cm-variable">serviceInfo</span> <span class="cm-operator">=</span> <span class="cm-variable">ServiceUtil</span>.<span class="cm-variable">selectInstancesWithHealthyProtection</span>(<span class="cm-variable">serviceStorage</span>.<span class="cm-variable">getData</span>(<span class="cm-variable">service</span>),</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">metadataManager</span>.<span class="cm-variable">getServiceMetadata</span>(<span class="cm-variable">service</span>).<span class="cm-variable">orElse</span>(<span class="cm-atom">null</span>), <span class="cm-variable">subscriber</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">request</span>.<span class="cm-variable">isSubscribe</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 如果是订阅模式 在这里进行订阅关系的绑定 当订阅关系绑定成功后 会触发服务端推送服务数据</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">clientOperationService</span>.<span class="cm-variable">subscribeService</span>(<span class="cm-variable">service</span>, <span class="cm-variable">subscriber</span>, <span class="cm-variable">meta</span>.<span class="cm-variable">getConnectionId</span>());</span><br><span role="presentation"> &nbsp;  } <span class="cm-keyword">else</span> {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">clientOperationService</span>.<span class="cm-variable">unsubscribeService</span>(<span class="cm-variable">service</span>, <span class="cm-variable">subscriber</span>, <span class="cm-variable">meta</span>.<span class="cm-variable">getConnectionId</span>());</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">SubscribeServiceResponse</span>(<span class="cm-variable">ResponseCode</span>.<span class="cm-variable">SUCCESS</span>.<span class="cm-variable">getCode</span>(), <span class="cm-string">"success"</span>, <span class="cm-variable">serviceInfo</span>);</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">主体逻辑的分析就是如上，我们现在来看看里面的几个方法</span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">selectInstancesWithHealthyProtection</span></h2>
<p class="md-end-block md-p"><span class="md-plain">这个方法的直接逻辑是获取有健康保证的实例，其方法定义如下</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-comment">/**</span><br><span role="presentation"> <span class="cm-comment">* Select instance of service info with healthy protection.</span></span><br><span role="presentation"> <span class="cm-comment">* @param serviceInfo &nbsp; &nbsp; original service info</span></span><br><span role="presentation"> <span class="cm-comment">* @param serviceMetadata service meta info</span></span><br><span role="presentation"> <span class="cm-comment">* @param subscriber subscriber</span></span><br><span role="presentation"> <span class="cm-comment">* @return new service info</span></span><br><span role="presentation"> <span class="cm-comment">*/</span></span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-keyword">static</span> <span class="cm-variable">ServiceInfo</span> <span class="cm-def">selectInstancesWithHealthyProtection</span>(<span class="cm-variable">ServiceInfo</span> <span class="cm-variable">serviceInfo</span>, <span class="cm-variable">ServiceMetadata</span> <span class="cm-variable">serviceMetadata</span>, <span class="cm-variable">Subscriber</span> <span class="cm-variable">subscriber</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">selectInstancesWithHealthyProtection</span>(<span class="cm-variable">serviceInfo</span>, <span class="cm-variable">serviceMetadata</span>, <span class="cm-variable">subscriber</span>.<span class="cm-variable">getCluster</span>(), <span class="cm-atom">false</span>, <span class="cm-atom">false</span>, <span class="cm-variable">subscriber</span>.<span class="cm-variable">getIp</span>());</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">该方法的第一个参数是</span><span class="md-pair-s" spellcheck="false"><code>ServiceInfo</code></span><span class="md-plain">对象，这个参数的获取是通过</span><span class="md-pair-s" spellcheck="false"><code>serviceStorage.getData(service)</code></span><span class="md-plain">获取到的，我们来看一下他的逻辑</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-keyword">final</span> <span class="cm-variable">ConcurrentMap</span><span class="cm-operator">&lt;</span><span class="cm-variable">Service</span>, <span class="cm-variable">ServiceInfo</span><span class="cm-operator">&gt;</span> <span class="cm-variable">serviceDataIndexes</span>;</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">ServiceInfo</span> <span class="cm-def">getData</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 判断缓存中是否有数据，如果有直接取缓存数据，缓存没有就调用 getPushData(service) </span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">serviceDataIndexes</span>.<span class="cm-variable">containsKey</span>(<span class="cm-variable">service</span>) <span class="cm-operator">?</span> <span class="cm-variable">serviceDataIndexes</span>.<span class="cm-variable">get</span>(<span class="cm-variable">service</span>) : <span class="cm-variable">getPushData</span>(<span class="cm-variable">service</span>);</span><br><span role="presentation">}</span><br><span role="presentation">​</span></pre>
<p class="md-end-block md-p"><span class="md-plain">我们第一次查，肯定是没有缓存的，也就是肯定走后面的</span><span class="md-pair-s" spellcheck="false"><code>getPushData()</code></span><span class="md-plain">方法</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">ServiceInfo</span> <span class="cm-def">getPushData</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// emptyServiceInfo的作用是将Service对象转化为ServiceInfo对象，注意这个result目前其hosts是空的</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">ServiceInfo</span> <span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-variable">emptyServiceInfo</span>(<span class="cm-variable">service</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">ServiceManager</span>.<span class="cm-variable">getInstance</span>().<span class="cm-variable">containSingleton</span>(<span class="cm-variable">service</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">result</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// getAllInstancesFromIndex方法赋值hosts</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">result</span>.<span class="cm-variable">setHosts</span>(<span class="cm-variable">getAllInstancesFromIndex</span>(<span class="cm-variable">service</span>));</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">serviceDataIndexes</span>.<span class="cm-variable">put</span>(<span class="cm-variable">service</span>, <span class="cm-variable">result</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">result</span>;</span><br><span role="presentation">}</span><br><span role="presentation"> &nbsp; </span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable">Instance</span><span class="cm-operator">&gt;</span> <span class="cm-def">getAllInstancesFromIndex</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Set</span><span class="cm-operator">&lt;</span><span class="cm-variable">Instance</span><span class="cm-operator">&gt;</span> <span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">HashSet</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Set</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">clusters</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">HashSet</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable-3">String</span> <span class="cm-variable">each</span> : <span class="cm-variable">serviceIndexesManager</span>.<span class="cm-variable">getAllClientsRegisteredService</span>(<span class="cm-variable">service</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Optional</span><span class="cm-operator">&lt;</span><span class="cm-variable">InstancePublishInfo</span><span class="cm-operator">&gt;</span> <span class="cm-variable">instancePublishInfo</span> <span class="cm-operator">=</span> <span class="cm-variable">getInstanceInfo</span>(<span class="cm-variable">each</span>, <span class="cm-variable">service</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">instancePublishInfo</span>.<span class="cm-variable">isPresent</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Instance</span> <span class="cm-variable">instance</span> <span class="cm-operator">=</span> <span class="cm-variable">parseInstance</span>(<span class="cm-variable">service</span>, <span class="cm-variable">instancePublishInfo</span>.<span class="cm-variable">get</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">result</span>.<span class="cm-variable">add</span>(<span class="cm-variable">instance</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">clusters</span>.<span class="cm-variable">add</span>(<span class="cm-variable">instance</span>.<span class="cm-variable">getClusterName</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// cache clusters of this service</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">serviceClusterIndex</span>.<span class="cm-variable">put</span>(<span class="cm-variable">service</span>, <span class="cm-variable">clusters</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">LinkedList</span><span class="cm-operator">&lt;&gt;</span>(<span class="cm-variable">result</span>);</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">其中</span><span class="md-pair-s" spellcheck="false"><code>getAllInstancesFromIndex</code></span><span class="md-plain">方法很关键，分析如下</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable">List</span><span class="cm-operator">&lt;</span><span class="cm-variable">Instance</span><span class="cm-operator">&gt;</span> <span class="cm-def">getAllInstancesFromIndex</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 这里的 service 参数对应我们需要查询的服务 stock-service</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Set</span><span class="cm-operator">&lt;</span><span class="cm-variable">Instance</span><span class="cm-operator">&gt;</span> <span class="cm-variable">result</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">HashSet</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Set</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-variable">clusters</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">HashSet</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation"> &nbsp; &nbsp;</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// each = clientId，通过 service 获取对应 service 全部的 ClientId</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">for</span> (<span class="cm-variable-3">String</span> <span class="cm-variable">each</span> : <span class="cm-variable">serviceIndexesManager</span>.<span class="cm-variable">getAllClientsRegisteredService</span>(<span class="cm-variable">service</span>)) {</span><br><span role="presentation"> &nbsp; </span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 通过 clientId 查找对应的 Instance 信息</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Optional</span><span class="cm-operator">&lt;</span><span class="cm-variable">InstancePublishInfo</span><span class="cm-operator">&gt;</span> <span class="cm-variable">instancePublishInfo</span> <span class="cm-operator">=</span> <span class="cm-variable">getInstanceInfo</span>(<span class="cm-variable">each</span>, <span class="cm-variable">service</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">instancePublishInfo</span>.<span class="cm-variable">isPresent</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 对象转换</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Instance</span> <span class="cm-variable">instance</span> <span class="cm-operator">=</span> <span class="cm-variable">parseInstance</span>(<span class="cm-variable">service</span>, <span class="cm-variable">instancePublishInfo</span>.<span class="cm-variable">get</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">result</span>.<span class="cm-variable">add</span>(<span class="cm-variable">instance</span>);</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">clusters</span>.<span class="cm-variable">add</span>(<span class="cm-variable">instance</span>.<span class="cm-variable">getClusterName</span>());</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp;  }</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 缓存该 service 对应有哪些 集群</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">serviceClusterIndex</span>.<span class="cm-variable">put</span>(<span class="cm-variable">service</span>, <span class="cm-variable">clusters</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">LinkedList</span><span class="cm-operator">&lt;&gt;</span>(<span class="cm-variable">result</span>);</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><strong><span class="md-pair-s" spellcheck="false"><code>getAllClientsRegisteredService</code></span><span class="md-plain">的底层其实就是我们上一节分析过的内容，从</span><span class="md-pair-s" spellcheck="false"><code>publisherIndexes</code></span><span class="md-plain">这个Map中获取对应</span><span class="md-pair-s" spellcheck="false"><code>clientId集合</code></span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-comment">// 这个map保存的是&lt;Service,Set&lt;clientId&gt;&gt;的对照关系</span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-keyword">final</span> <span class="cm-variable">ConcurrentMap</span><span class="cm-operator">&lt;</span><span class="cm-variable">Service</span>, <span class="cm-variable">Set</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;&gt;</span> <span class="cm-variable">publisherIndexes</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">ConcurrentHashMap</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-keyword">final</span> <span class="cm-variable">ConcurrentMap</span><span class="cm-operator">&lt;</span><span class="cm-variable">Service</span>, <span class="cm-variable">Set</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;&gt;</span> <span class="cm-variable">subscriberIndexes</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">ConcurrentHashMap</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation">​</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable">Collection</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span><span class="cm-operator">&gt;</span> <span class="cm-def">getAllClientsRegisteredService</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>) {</span><br><span role="presentation">    <span class="cm-keyword">return</span> <span class="cm-variable">publisherIndexes</span>.<span class="cm-variable">containsKey</span>(<span class="cm-variable">service</span>) <span class="cm-operator">?</span> <span class="cm-variable">publisherIndexes</span>.<span class="cm-variable">get</span>(<span class="cm-variable">service</span>) : <span class="cm-keyword">new</span> <span class="cm-variable">ConcurrentHashSet</span><span class="cm-operator">&lt;&gt;</span>();</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">接下来的逻辑也很明显了，</span><strong><span class="md-plain">拿到对应的</span><span class="md-pair-s" spellcheck="false"><code>clientId</code></span><span class="md-plain">之后就可以查找client从而获得对应的实例信息（之前有提到过这两者之间的关系），也就是</span><span class="md-pair-s" spellcheck="false"><code>getInstanceInfo</code></span><span class="md-plain">的逻辑</span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable">Optional</span><span class="cm-operator">&lt;</span><span class="cm-variable">InstancePublishInfo</span><span class="cm-operator">&gt;</span> <span class="cm-def">getInstanceInfo</span>(<span class="cm-variable-3">String</span> <span class="cm-variable">clientId</span>, <span class="cm-variable">Service</span> <span class="cm-variable">service</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 通过 clientId 获取每一个对应的 client 连接对象</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Client</span> <span class="cm-variable">client</span> <span class="cm-operator">=</span> <span class="cm-variable">clientManager</span>.<span class="cm-variable">getClient</span>(<span class="cm-variable">clientId</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">client</span>) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">Optional</span>.<span class="cm-variable">empty</span>();</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 在 client 对象中，获取实例信息，getInstancePublishInfo的底层就是从publishers这个Map中获取，这个Map中维护了Service和Instance的对照关系</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">Optional</span>.<span class="cm-variable">ofNullable</span>(<span class="cm-variable">client</span>.<span class="cm-variable">getInstancePublishInfo</span>(<span class="cm-variable">service</span>));</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">到这里，我们整个查找的过程差不多接近尾声了，这个通过Service查找到对应的Instance的流程和我们上一节提到的内存表的设计关系是完全一致的</span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">绑定订阅关系</span></h2>
<p class="md-end-block md-p"><span class="md-plain">查找到有健康保障的实例时候，就需要绑定订阅关系了</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">if</span> (<span class="cm-variable">request</span>.<span class="cm-variable">isSubscribe</span>()) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 添加订阅者，如果被订阅的服务有变动，需要通知订阅者</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">clientOperationService</span>.<span class="cm-variable">subscribeService</span>(<span class="cm-variable">service</span>, <span class="cm-variable">subscriber</span>, <span class="cm-variable">meta</span>.<span class="cm-variable">getConnectionId</span>());</span><br><span role="presentation">} <span class="cm-keyword">else</span> {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">clientOperationService</span>.<span class="cm-variable">unsubscribeService</span>(<span class="cm-variable">service</span>, <span class="cm-variable">subscriber</span>, <span class="cm-variable">meta</span>.<span class="cm-variable">getConnectionId</span>());</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-pair-s" spellcheck="false"><code>subscribeService</code></span><span class="md-plain">的代码逻辑如下</span></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span class="cm-comment">/**</span><br><span role="presentation"> <span class="cm-comment">* 订阅者列表</span></span><br><span role="presentation"> <span class="cm-comment">*/</span></span><br><span role="presentation"><span class="cm-keyword">protected</span> <span class="cm-keyword">final</span> <span class="cm-variable">ConcurrentHashMap</span><span class="cm-operator">&lt;</span><span class="cm-variable">Service</span>, <span class="cm-variable">Subscriber</span><span class="cm-operator">&gt;</span> <span class="cm-variable">subscribers</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">ConcurrentHashMap</span><span class="cm-operator">&lt;&gt;</span>(<span class="cm-number">16</span>, <span class="cm-number">0.75f</span>, <span class="cm-number">1</span>);</span><br><span role="presentation">​</span><br><span role="presentation">​</span><br><span class="cm-comment">/**</span><br><span role="presentation"> <span class="cm-comment">* 添加订阅者</span></span><br><span role="presentation"> <span class="cm-comment">* @param service &nbsp;  service 被查询的对象</span></span><br><span role="presentation"> <span class="cm-comment">* @param subscriber subscribe 订阅者 对应请求调用者</span></span><br><span role="presentation"> <span class="cm-comment">* @param clientId &nbsp; id of client &nbsp; 维护请求调用者和Nacos之间的关系</span></span><br><span role="presentation"> <span class="cm-comment">*/</span></span><br><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-def">subscribeService</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>, <span class="cm-variable">Subscriber</span> <span class="cm-variable">subscriber</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">clientId</span>) {</span><br><span role="presentation">​</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 被调用者对象/服务对象</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Service</span> <span class="cm-variable">singleton</span> <span class="cm-operator">=</span> <span class="cm-variable">ServiceManager</span>.<span class="cm-variable">getInstance</span>().<span class="cm-variable">getSingletonIfExist</span>(<span class="cm-variable">service</span>).<span class="cm-variable">orElse</span>(<span class="cm-variable">service</span>);</span><br><span role="presentation">​</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 订阅者/发其请求者</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">Client</span> <span class="cm-variable">client</span> <span class="cm-operator">=</span> <span class="cm-variable">clientManager</span>.<span class="cm-variable">getClient</span>(<span class="cm-variable">clientId</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable">clientIsLegal</span>(<span class="cm-variable">client</span>, <span class="cm-variable">clientId</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span>;</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">//  订阅者/发其请求者 对应的 Client 对象中，添加订阅者订阅者信息</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">client</span>.<span class="cm-variable">addServiceSubscriber</span>(<span class="cm-variable">singleton</span>, <span class="cm-variable">subscriber</span>);</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">client</span>.<span class="cm-variable">setLastUpdatedTime</span>();</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 发布客户端订阅事件</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(<span class="cm-keyword">new</span> <span class="cm-variable">ClientOperationEvent</span>.<span class="cm-variable">ClientSubscribeServiceEvent</span>(<span class="cm-variable">singleton</span>, <span class="cm-variable">clientId</span>));</span><br><span role="presentation">}</span><br><span role="presentation">​</span><br><span role="presentation">​</span><br><span class="cm-meta">@Override</span><br><span role="presentation"><span class="cm-keyword">public</span> <span class="cm-variable-3">boolean</span> <span class="cm-def">addServiceSubscriber</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>, <span class="cm-variable">Subscriber</span> <span class="cm-variable">subscriber</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// 添加 服务者-订阅者 之间的映射关系内存缓存map</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-atom">null</span> <span class="cm-operator">==</span> <span class="cm-variable">subscribers</span>.<span class="cm-variable">put</span>(<span class="cm-variable">service</span>, <span class="cm-variable">subscriber</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">MetricsMonitor</span>.<span class="cm-variable">incrementSubscribeCount</span>();</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span><br><span role="presentation">}</span></pre>
<p class="md-end-block md-p"><span class="md-plain">可以看到如上代码的作用，</span><strong><span class="md-plain">主要就是发布了通知事件且在 订阅者的client对象中添加了</span><span class="md-pair-s" spellcheck="false"><code>key=服务对象；value=订阅者</code></span><span class="md-plain">的缓存内容</span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" spellcheck="false"><span role="presentation">这里需要注意一下ConcurrentHashMap&lt;Service, Subscriber&gt; subscribers 他是属于client层面的，也就是每个client中的一份，而不是staic或者说不是全局的</span><br><span role="presentation">​</span><br><span role="presentation">怎么理解呢，就是如果大家先不这么理解，可能会有UU认为就是，这个map是一对一的关系，表示的是服务者-订阅者，那么是不是代表一个服务只能被一个订阅者订阅呢？然后我们来分析这个问题</span><br><span role="presentation">首先从业务层面而言，这个理解肯定不对，我们有两个客户端clientA和clientB，然后我们有个远端服务ServiceC，A和B进行服务调用的时候肯定都会需要查询服务C，按照默认设置都是订阅模式，那么肯定需要A和B同时都能动态感知ServiceC的变化，也就是需要A和B都订阅C，事实上我们Nacos中自然是能这么使用的。所以从业务角度而言，肯定需要支持被多个订阅者订阅、</span><br><span role="presentation">​</span><br><span role="presentation">然后从代码层面来看，就需要结合我们所说的，这个Map是每个client层面的，也就是说ServiceC-clientB这个属于clientB中的map，ServiceC-clientA这个属于clientA中的map，ServiceC-clientA标识cleintA订阅了ServiceC，如果我们还有别的微服务，A同样需要监听，也就会出现ServiceC1-clientA，ServiceC2-clientA，ServiceC3-clientA...</span><br><span role="presentation">​</span><br><span role="presentation">那么我们所说的，一个ServiceC对于所有的订阅他的client是如何存储的呢？订阅功能的需求而言，肯定会需要统一维护，当出现变更的时候才能快速的进行通知，这个其实就是我们之前的ConcurrentMap&lt;Service, Set&lt;String&gt;&gt; subscriberIndexes这种类型的map，保存的是&lt;Service,Set&lt;clientId&gt;&gt;的对照关系</span></pre>
<h2 class="md-end-block md-heading"><span class="md-plain">ClientSubscribeServiceEvent</span></h2>
<p class="md-end-block md-p"><span class="md-plain">在上述逻辑的最后呢，发布了</span><span class="md-pair-s" spellcheck="false"><code>ClientSubscribeServiceEvent</code></span><span class="md-plain">这个事件，我们来分析一下这个地方，同样的，通过Idea的全局搜索，我们可以找到处理这个事件的地方，其实也是我们一开始进行RPC注册时候处理注册事件的地方，我们先来稍微的看一下</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251220203053250.png"><img src="https://zealsinger-book-bucket.oss-cn-hangzhou.aliyuncs.com/img/image-20251220203053250.png" alt="image-20251220203053250"></span></p>
<p class="md-end-block md-p"><strong><span class="md-pair-s" spellcheck="false"><code>addSubscriberIndexes</code></span><span class="md-plain">具体逻辑如下</span></strong><span class="md-plain">，如果是第一次订阅的客户端，</span><strong><span class="md-plain">会发布</span><span class="md-pair-s" spellcheck="false"><code>ServiceSubscribedEvent</code></span><span class="md-plain">服务订阅事件，这个我们放在下一个章节来讲解</span></strong></p>
<pre class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" spellcheck="false"><span role="presentation"><span class="cm-keyword">private</span> <span class="cm-variable-3">void</span> <span class="cm-def">addSubscriberIndexes</span>(<span class="cm-variable">Service</span> <span class="cm-variable">service</span>, <span class="cm-variable-3">String</span> <span class="cm-variable">clientId</span>) {</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-variable">subscriberIndexes</span>.<span class="cm-variable">computeIfAbsent</span>(<span class="cm-variable">service</span>, (<span class="cm-variable">key</span>) <span class="cm-operator">-&gt;</span> <span class="cm-keyword">new</span> <span class="cm-variable">ConcurrentHashSet</span><span class="cm-operator">&lt;&gt;</span>());</span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-comment">// Fix #5404,只有第一次放入的时候需要通知</span></span><br><span role="presentation"> &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">subscriberIndexes</span>.<span class="cm-variable">get</span>(<span class="cm-variable">service</span>).<span class="cm-variable">add</span>(<span class="cm-variable">clientId</span>)) {</span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 发布订阅事件</span></span><br><span role="presentation"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">NotifyCenter</span>.<span class="cm-variable">publishEvent</span>(<span class="cm-keyword">new</span> <span class="cm-variable">ServiceEvent</span>.<span class="cm-variable">ServiceSubscribedEvent</span>(<span class="cm-variable">service</span>, <span class="cm-variable">clientId</span>));</span><br><span role="presentation"> &nbsp;  }</span><br><span role="presentation">}</span></pre>]]></description>
    <pubDate>Sat, 20 Dec 2025 20:34:27 +0800</pubDate>
    <dc:creator>ZealSinger</dc:creator>
    <guid>http://8.154.40.120:24180/?post=50</guid>
</item></channel>
</rss>