<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Hycer&apos;s Blog</title><description>不止于技术</description><link>https://fuwari.vercel.app/</link><language>zh_CN</language><item><title>Cloudflare Tunnel优选域名加速</title><link>https://fuwari.vercel.app/posts/cloudflaretunnel%E4%BC%98%E9%80%89%E5%9F%9F%E5%90%8D%E5%8A%A0%E9%80%9F/cloudflaretunnel%E4%BC%98%E9%80%89%E5%9F%9F%E5%90%8D%E5%8A%A0%E9%80%9F/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/cloudflaretunnel%E4%BC%98%E9%80%89%E5%9F%9F%E5%90%8D%E5%8A%A0%E9%80%9F/cloudflaretunnel%E4%BC%98%E9%80%89%E5%9F%9F%E5%90%8D%E5%8A%A0%E9%80%9F/</guid><description>利用优选域名加速Cloudflare Tunnel访问</description><pubDate>Tue, 03 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Cloudflare Tunnel优选域名加速&lt;/h1&gt;
&lt;h2&gt;环境要求&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.已部署好Cloudflare Tuunel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.Cloudflare账户下拥有两个域名，一个主访问域名，一个加速域名&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本文我以&lt;code&gt;hycer.cn&lt;/code&gt;为主域名，&lt;code&gt;436464.cloud&lt;/code&gt;为加速域名为例&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;注意：优选域名加速只会降低访问延迟，并不会增加网络带宽&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;操作步骤&lt;/h2&gt;
&lt;h3&gt;为Tunnel应用绑定域名&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.本文以加速内网nas服务为例，为nas服务绑定&lt;code&gt;nas.hycer.cn&lt;/code&gt;、&lt;code&gt;cf-nas.436464.cloud&lt;/code&gt;的两个域名，前者将作为最终访问服务的域名，也就是主访问域名，后者作为加速域名，实际通过该域名访问隧道的访问。在Tunnel的“已发布应用程序路由”中可以绑定域名，绑定成功后会自动添加DNS解析，如下图。确保两个域名都可以访问应用。
&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;配置回退源和自定义主机名&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.来到&lt;strong&gt;加速域名&lt;/strong&gt;，进入“SSL/TLS”的自定义主机名界面，将加速域名添加为回退源，确保回退源的状态为有效
&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.点击添加“自定义主机名”按钮，配置自定义主机名。自定义主机名为主访问域名，自定义源服务器为加速域名
&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.添加后，Cloudflare会验证主机名状态和证书状态，确保我们拥有该域名的控制权。按照要求在主域名中配置TXT解析即可
&lt;img src=&quot;image-3.png&quot; alt=&quot;alt text&quot; /&gt;
&lt;img src=&quot;image-4.png&quot; alt=&quot;alt text&quot; /&gt;
配置解析后可能会显示“待定（错误）”的报错，此处为bug，删除TXT解析重新添加即可。若仍不行，可以先将主域名&lt;code&gt;nas.hycer.cn&lt;/code&gt;的解析CNAME到加速域名上
&lt;img src=&quot;image-5.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;确保主机名状态和证书状态均为有效即可
&lt;img src=&quot;image-6.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;配置优选域名&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;来到&lt;strong&gt;加速域名&lt;/strong&gt;的DNS记录，配置一个CNAME解析到&lt;a href=&quot;https://www.wetest.vip/page/cloudflare/cname.html&quot;&gt;优选域名&lt;/a&gt;，优选域名可自行网上查找;此处以将&lt;code&gt;speedup.436464.cloud&lt;/code&gt;解析到优选域名&lt;code&gt;youxuan.cf.090227.xyz&lt;/code&gt;为例。&lt;strong&gt;注意关闭小黄云，取消代理&lt;/strong&gt;
&lt;img src=&quot;image-7.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;主域名指向优选域名&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;最后回到&lt;strong&gt;主域名&lt;/strong&gt;的DNS记录，将原指向Tunnel的&lt;code&gt;nas.hycer.cn&lt;/code&gt;指向加速域名&lt;code&gt;speedup.436464.cloud&lt;/code&gt;，&lt;strong&gt;注意关闭小黄云，取消代理&lt;/strong&gt;.至此Tunnel的优选域名加速配置完成
&lt;img src=&quot;image-8.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;加速验证&lt;/h3&gt;
&lt;p&gt;分别ping直连域名&lt;code&gt;cf-nas.436464.cloud&lt;/code&gt;和加速后的主域名&lt;code&gt;nas.hycer.cn&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;加速前：
&lt;img src=&quot;image-9.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;加速后：
&lt;img src=&quot;image-10.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;核心原理解析&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;域名角色&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;主访问域名&lt;/td&gt;
&lt;td&gt;nas.aaa.com&lt;/td&gt;
&lt;td&gt;用户实际访问的域名，最后指向优选线路&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;回退源域名（Tunnel 域名）&lt;/td&gt;
&lt;td&gt;cf-nas.bbb.com&lt;/td&gt;
&lt;td&gt;绑定真实 Cloudflare Tunnel，作为回源锚点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;优选域名&lt;/td&gt;
&lt;td&gt;ip.ccc.com&lt;/td&gt;
&lt;td&gt;指向优选 IP / 优选域名，负责加速&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;最终流量路径
&lt;ul&gt;
&lt;li&gt;1.用户访问 &lt;code&gt;nas.aaa.com&lt;/code&gt; → DNS 解析到 &lt;code&gt;ip.ccc.com&lt;/code&gt;（优选线路）。&lt;/li&gt;
&lt;li&gt;2.流量到达 Cloudflare 优选节点，请求头Host为 &lt;code&gt;nas.aaa.com&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;3.Cloudflare 识别为 SaaS 自定义主机名，内部回源到 &lt;code&gt;cf-nas.bbb.com&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;4.&lt;code&gt;cf-nas.bbb.com&lt;/code&gt; 绑定的Tunnel将流量转发到内网NAS。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Linux通过mdadm组建软RAID</title><link>https://fuwari.vercel.app/posts/linux%E9%80%9A%E8%BF%87mdadm%E7%BB%84%E5%BB%BA%E8%BD%AFraid/linux%E9%80%9A%E8%BF%87mdadm%E7%BB%84%E5%BB%BA%E8%BD%AFraid/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/linux%E9%80%9A%E8%BF%87mdadm%E7%BB%84%E5%BB%BA%E8%BD%AFraid/linux%E9%80%9A%E8%BF%87mdadm%E7%BB%84%E5%BB%BA%E8%BD%AFraid/</guid><description>Linux通过mdadm组建RAID 0、RAID 1、 RAID 5、 RAID 10实验</description><pubDate>Fri, 14 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;实验环境&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Ubuntu24.04 4C4G虚拟机&lt;/li&gt;
&lt;li&gt;数据盘类型覆盖：scsi，virtio&lt;/li&gt;
&lt;li&gt;mdadm版本：4.3-1ubuntu2.1&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;实验前准备&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;挂载数据盘：根据组建的RAID级别挂载相应数量的数据盘&lt;/li&gt;
&lt;li&gt;安装mdadm：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install mdadm
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;RAID 0&lt;/h2&gt;
&lt;h3&gt;组建RAID 0&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.挂载两块virtio格式容量50GB的硬盘&lt;/li&gt;
&lt;li&gt;2.创建RAID 0阵列&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mdadm -C /dev/md0 -l 0 -n 2 /dev/vdb /dev/vdc

# -C --create参数，创建RAID阵列
# /dev/md0 为组建RAID后的设备名
# -l --level参数，创建的RAID级别
# -n --raid-devices= 参数，组建RAID的设备数及具体设备路径
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建好后lsblk应为如下的状态：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3.检查RAID状态&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mdadm --detail /dev/md0
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;root@ubuntu:~# mdadm --detail /dev/md0
/dev/md0:
           Version : 1.2
     Creation Time : Fri Nov 14 02:53:01 2025
        Raid Level : raid0                  # RAID级别
        Array Size : 104790016 (99.94 GiB 107.30 GB)
      Raid Devices : 2
     Total Devices : 2
       Persistence : Superblock is persistent

       Update Time : Fri Nov 14 02:53:01 2025
             State : clean
    Active Devices : 2
   Working Devices : 2
    Failed Devices : 0
     Spare Devices : 0

            Layout : original
        Chunk Size : 512K

Consistency Policy : none

              Name : ubuntu:0  (local to host ubuntu)
              UUID : ad31c8f6:e775ff93:17548363:43669993
            Events : 0

    Number   Major   Minor   RaidDevice State       # RAID成员及状态
       0     253       16        0      active sync   /dev/vdb
       1     253       32        1      active sync   /dev/vdc
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;检验&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.挂载一块50GB的virtio硬盘用于做对比实验&lt;/li&gt;
&lt;li&gt;2.格式化并挂载文件系统&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 格式化RAID设备和对照组硬盘为ext4格式
mkfs.ext4 /dev/md0
mkfs.ext4 /dev/vdd

# 创建两个挂载点 并挂载文件系统
mkdir /mnt/raid0 /mnt/noraid0

mount /dev/md0 /mnt/raid0/
mount /dev/vdd /mnt/noraid0/
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.写入测试&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 使用dd命令顺序写入8GB文件，观察写入速度
dd if=/dev/zero of=/mnt/noraid0/testfile bs=1G count=8 oflag=direct

dd if=/dev/zero of=/mnt/raid0/testfile bs=1G count=8 oflag=direct
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以发现速度提升几乎翻倍&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4.读取测试&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 使用dd命令顺序读取刚才写入的文件，观察读取速度
dd if=/mnt/noraid0/testfile of=/dev/null bs=1G count=8 iflag=direct

dd if=/mnt/raid0/testfile of=/dev/null bs=1G count=8 iflag=direct
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;读取速度也是大幅度提升&lt;/p&gt;
&lt;h2&gt;RAID 1&lt;/h2&gt;
&lt;h3&gt;组建RAID 1&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.挂载两块scsi格式容量50GB的硬盘&lt;/li&gt;
&lt;li&gt;2.创建RAID 1阵列&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mdadm -C /dev/md1 -l 1 -n 2 /dev/sda /dev/sdb 
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.检查RAID状态&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 在刚创建RAID 1后使用 watch 指令每隔1秒更新查看mdstat文件
watch -n 1 &quot;cat /proc/mdstat&quot; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-3.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;代表两块硬盘正在同步数据&lt;/p&gt;
&lt;p&gt;同步完成后&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-4.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[UU]&lt;/code&gt;代表两块硬盘都处于UP状态，RAID 1阵列已准备就绪&lt;/p&gt;
&lt;h3&gt;检验&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.格式化并挂载文件系统&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 格式化RAID设备和对照组硬盘为ext4格式，并挂载文件系统
mkfs.ext4 /dev/md1
mkdir /mnt/raid1
mount /dev/md1 /mnt/raid1/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-5.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到实际容量为两块硬盘容量之和的50%&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2.冗余测试&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建初始文件&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;cd /mnt/raid1
# 使用dd命令写入500M初始文件文件并记录md5值至文件中
dd if=/dev/zero of=base_file.bin bs=1M count=500 status=progress

md5sum base_file.bin &amp;gt; /tmp/base_file_original.md5
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;创建持续写入脚本stress_write.sh&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
STRESS_DIR=&quot;/mnt/raid1&quot;
LOG_FILE=&quot;/tmp/stress_io.log&quot;

echo &quot;$(date): Starting continuous write process...&quot; | tee -a $LOG_FILE

counter=1
while true; do
    # 创建一个不断增长的文件，并每次写入都刷新
    echo &quot;This is a continuous write stream at $(date). Loop counter: $counter&quot; &amp;gt;&amp;gt; $STRESS_DIR/continuous_write.log
    sync # 强制写入磁盘
    dd if=/dev/urandom of=$STRESS_DIR/random_data_$counter.bin bs=1M count=10 status=none
    ((counter++))
    # 输出状态点
    if (( $counter % 5 == 0 )); then
        echo &quot;$(date): Written $counter files so far...&quot; | tee -a $LOG_FILE
    fi
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;赋予脚本执行权&lt;code&gt;chmod +x stress_wirte.sh&lt;/code&gt;
运行脚本&lt;code&gt;./stress_write.sh&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新开终端窗口，观察RAID状态&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;watch -n 1 &apos;cat /proc/mdstat &amp;amp;&amp;amp; echo &quot;---&quot; &amp;amp;&amp;amp; mdadm --detail /dev/md1 | grep -E &quot;State|Devices&quot;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-6.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新开终端窗口，观察系统io监控&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;watch -n 1 &apos;iostat -xd 1 1 /dev/sda /dev/sdb | tail -n +3&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-7.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将磁盘sdb标记为故障并移除&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mdadm /dev/md1 --fail /dev/sdb
mdadm /dev/md1 --remove /dev/sdb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-12.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;可以看到设备虽处于降级状态，但剩余设备仍在工作
&lt;img src=&quot;image-8.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;验证数据可访问性&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对比初始文件的md5值，验证文件并无损坏&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-9.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重新添加sdb&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mdadm /dev/md1 --add /dev/sdb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-10.png&quot; alt=&quot;alt text&quot; /&gt;
可以看到正在重建同步数据&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;等待重建完成后再次检查初始文件的md5值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-11.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;RAID 5&lt;/h2&gt;
&lt;h3&gt;组建RAID 5&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.挂载3块250G的scsi硬盘&lt;/li&gt;
&lt;li&gt;2.创建RAID 5阵列&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mdadm -C /dev/md0 -l 5 -n 3 /dev/sda /dev/sdb /dev/sdc
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;watch -n 1 &quot;cat /proc/mdstat&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看同步进度&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-13.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;同步完成&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-14.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;检验&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.准备测试数据&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mkfs.ext4 /dev/md0
mkdir /mnt/raid5
mount /dev/md0 /mnt/raid5

# 创建校验文件
dd if=/dev/urandom of=/mnt/raid5/test_file.bin bs=1M count=2000 status=progress
md5sum /mnt/raid5/test_file.bin &amp;gt; /tmp/raid5_original.md5
cat /tmp/raid5_original.md5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-15.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以发现md0挂载的实际容量为（磁盘数-1）*单个磁盘容量&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2.移除硬盘sdb&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mdadm /dev/md0 --fail /dev/sdb
mdadm /dev/md0 --remove /dev/sdb

# 检查状态，现在是降级状态
mdadm --detail /dev/md0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-16.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3.在降级状态下验证数据可访问性&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;md5sum /mnt/raid5/test_file.bin

cat /tmp/raid5_original.md5

echo &quot;RAID 5 is working in degraded mode!&quot; | tee /mnt/raid5/degraded_test.txt
cat /mnt/raid5/degraded_test.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-17.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4.重新添加sdb&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mdadm /dev/md0 --add /dev/sdb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次校验md5值&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-18.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;5.读写性能测试（挂载一块500GB scis类型硬盘并挂载为ext4文件系统）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 测试顺序写入8G文件
dd if=/dev/zero of=/mnt/noraid5/largefile.bin bs=1G count=8 oflag=direct status=progress

dd if=/dev/zero of=/mnt/raid5/largefile.bin bs=1G count=8 oflag=direct status=progress

# 测试顺序读取8G文件
dd if=/mnt/noraid5/largefile.bin of=/dev/null bs=1G count=8 iflag=direct status=progress

dd if=/mnt/raid5/largefile.bin of=/dev/null bs=1G count=8 iflag=direct status=progress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-19.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可见RAID 5因需要奇偶校验导致写入速度反而比没有组RAID的硬盘还要慢；但读取速度远高于未组RAID的普通硬盘&lt;/p&gt;
</content:encoded></item><item><title>MSTE教材学习</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mste%E6%95%99%E6%9D%90%E5%AD%A6%E4%B9%A0/mste%E6%95%99%E6%9D%90%E5%AD%A6%E4%B9%A0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mste%E6%95%99%E6%9D%90%E5%AD%A6%E4%B9%A0/mste%E6%95%99%E6%9D%90%E5%AD%A6%E4%B9%A0/</guid><description>杭州宏杉学习笔记-MSTE教材学习</description><pubDate>Wed, 12 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;存储概述&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;磁盘阵列架构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;软件RAID：通过软件实现RAID功能，不需要额外的硬件设备，但是会耗费大量计算资源&lt;/li&gt;
&lt;li&gt;硬件RAID卡：由RAID卡提供RAID计算所需的资源，不会占用系统资源。但主机空间有限，一台主机无法连接很多硬盘；无法满足多个服务器需要共享存储资源的场景&lt;/li&gt;
&lt;li&gt;单控制器磁盘阵列：将RAID保护的功能从主机转移到主机外，实现存储与计算分离。通过SCSI、SAS、FC、IP等接口把更多主机同时连接到外部存储器上。&lt;/li&gt;
&lt;li&gt;双控制器磁盘阵列：单控制器存储本身存在单点故障，通过双控来提高存储的冗余性；通常两个控制器之间需要传递以下信息：控制器间的控制信息；控制器之间的心跳信息（HA检测用）；数据的跨控制器传输。&lt;/li&gt;
&lt;li&gt;多控制器磁盘阵列：通过将更多的控制器组织起来一起提供存储服务，可以获得更强的性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;补充常见的RAID类型：RAID-0，RAID-1，RAID-5，RAID-6，RAID-10：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RAID-0（条带化）：至少需要两块硬盘。数据被分割成多个“条带”，然后并发地、均匀地写入到所有硬盘中。写数据时将数据分为N份，以独立的方式实现N块磁盘的读写，将N份数据并发写入磁盘中，性能非常高，没有冗余，任何一块硬盘发生故障，所有数据都会损坏且无法恢复&lt;/li&gt;
&lt;li&gt;RAID-1：至少需要两块硬盘。将同一份数据无差别的写两份到磁盘（将磁盘分为两份，一份工作，一份镜像），有镜像冗余&lt;/li&gt;
&lt;li&gt;RAID-10：至少需要4块硬盘。首先基于RAID-1模式将磁盘分为两份，随后将这两份RAID-1以RAID-0模式组合起来，既保证了效率又保证了安全，有镜像冗余&lt;/li&gt;
&lt;li&gt;RAID-5：至少需要3块硬盘，数据分布存储（类似RAID-0）+ 奇偶校验信息（用于恢复数据）轮流存储在不同的硬盘上，单盘冗余&lt;/li&gt;
&lt;li&gt;RAID-6：类似与RAID-6，但是至少需要4块硬盘，双硬盘冗余&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多控制器架构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scale-up：纵向扩展，直接扩展部件。按照功能不同将控制器分为CPU、缓存、前端接口和后端接口等模块，这些模块可以通过高速背板互相访问。当性能不满足应用要求和负载时，在背板中加入更多的模块以此来提高存储系统的性能。&lt;/li&gt;
&lt;li&gt;scale-out：横向扩展，这种方式的思想是分布式计算，通过增加标准的、相对廉价的节点到同一个集群中，来线性地提升整个系统的性能和容量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储器控制器架构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RISC：精简指令集计算机，设计简单，速度快。单由于标准化程度差，发展比较缓慢；性能不高。&lt;/li&gt;
&lt;li&gt;CISC：复杂指令集计算机，主要是Intel、AMD的x86处理器。已成为中断存储产品的主流首选，逐步应用于高端产品。&lt;/li&gt;
&lt;li&gt;ASIC：专用集成电路，专为存储的RAID处理而设计，实现复杂，架构封闭，可移植性差，多采用CISC+ASIC混合架构。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SAS技术&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SAS全称Serial Attached SCSI（串行连接SCSI），是由SCSI演变而来，将SCSI的并行改为了串行&lt;/li&gt;
&lt;li&gt;SAS在物理连接器上与SATA保持兼容，但功能和协议更强大。&lt;/li&gt;
&lt;li&gt;单根SAS2.0速率可以达到6Gb，SAS4.0速率可以达到22.5Gb，并且支持双端口聚合。&lt;/li&gt;
&lt;li&gt;SAS支持全双工通信，意味着它可以同时读取和写入操作，与SATA的半双工相比，效率翻倍。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;写缓存镜像：将写缓存数据做两个拷贝，分别放在主缓存和镜像缓存中，系统工作时会同时向两个缓存中写入数据。有写缓存镜像时，数据的写入需要经过一下步骤。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.主机数据写入主控制器缓存；&lt;/li&gt;
&lt;li&gt;2.将缓存数据复制到对端控制器；&lt;/li&gt;
&lt;li&gt;3.对端控制器接受完整数据后返回确认消息；&lt;/li&gt;
&lt;li&gt;4.源控制器返回主机写成功，写操作完毕。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缓存掉电保护技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.外置UPS（不间断电源）：当出现供电故障时UPS可以保证磁盘阵列正常工作&lt;/li&gt;
&lt;li&gt;2.内置电池：当控制器出现供电故障时为控制器持续供电&lt;/li&gt;
&lt;li&gt;3.非易失性存储器：在控制器内部内置一个小硬盘或者小容量的非易失性缓存，当断电时将缓存内的数据临时写入小硬盘或者是非易失性的缓存里，等系统恢复时再将数据读回缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;RAID及CRAID技术&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;RAID中文全称独立磁盘冗余阵列，或简称磁盘阵列。初衷是为了组合小的廉价磁盘来代替大的昂贵磁盘，同时希望磁盘失效时不会令数据受损。它将多个独立的物理磁盘按照某种方式组合起来，形成一个虚拟的磁盘，作为一个独立的存储资源出现。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAID 0&lt;/td&gt;
&lt;td&gt;数据条带化，无校验&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 1&lt;/td&gt;
&lt;td&gt;数据镜像，无校验&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 2&lt;/td&gt;
&lt;td&gt;海明码错误校验及校正&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 3&lt;/td&gt;
&lt;td&gt;数据条带化读写，校验信息存放于专用硬盘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 4&lt;/td&gt;
&lt;td&gt;单次写数据采用单个硬盘，校验信息存放于专用硬盘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 5&lt;/td&gt;
&lt;td&gt;数据条带化，校验信息分布式存放&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 6&lt;/td&gt;
&lt;td&gt;数据条带化，分布式校验并提供两级冗余&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 01&lt;/td&gt;
&lt;td&gt;先做RAID 0，后做RAID 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 10&lt;/td&gt;
&lt;td&gt;先做RAID 1，后做RAID 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 50&lt;/td&gt;
&lt;td&gt;先做RAID 5，后做RAID 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;常见RAID级别比较&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RAID组织数据的方式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.分区：一组地址连续的存储块，单个磁盘可以有一个或多个分区&lt;/li&gt;
&lt;li&gt;2.条块：分区可以进一步细分为更小的段，被称为条块。&lt;strong&gt;是条带的元素&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;3.条带：&lt;strong&gt;分区和条块是在单个磁盘上进行的&lt;/strong&gt;。条带是&lt;strong&gt;磁盘阵列&lt;/strong&gt;中的两个或多个分区上的一组&lt;strong&gt;位置相关&lt;/strong&gt;的条块。&quot;位置相关&quot;意味着，每个分区上的第一条块属于第一条带，每个分区上的第二条块属于第二条带...组成条带的&lt;strong&gt;条块大小&lt;/strong&gt;也被称为&lt;strong&gt;条带深度&lt;/strong&gt;，每个条带&lt;strong&gt;跨越的磁盘数&lt;/strong&gt;被称为&lt;strong&gt;条带宽度&lt;/strong&gt;，条带宽度*条带深度就是每个条带的裸容量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RAID5的三种写方式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.整条写：如果一次硬盘写操作刚好映射满一个条带，则相应的校验值可根据新的数据块直接计算出来，该过程可在RAID的缓存中直接完成。不需要任何多余的读操作，是RAID5中效率最高的一种写操作&lt;/li&gt;
&lt;li&gt;2.重构写：如果一次写硬盘的数据包括一个条带的多数个数据块时，则产生新的校验信息时必须先将没有更新的数据读到缓存中，计算出新的校验信息，然后再将新数据与新校验信息一起写到磁盘中。相比于整条写多了一个读旧数据的操作&lt;/li&gt;
&lt;li&gt;3.读改写：如果一次写操作只更新了条带中极小部分的数据，为了计算新的校验值，需要读出条带中原来的数据，然后将计算的新的校验数据和新数据一起写到磁盘中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同步：创建完RAID组后，立即将所有数据盘中的数据进行异或算法，得到的结果写入校验盘，这个过程就叫同步&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重建：当RAID组中的某一块盘发生故障时，用一块空的热备盘（能随时顶替故障盘的备份盘）替换该故障盘，此时就需要通过RAID组中其他数据盘和校验盘将故障盘的数据恢复给热备盘，达到重建的功能。重建不影响存取操作的连续性，但会影响性能&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RAID机制冗余方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;镜像冗余&lt;/li&gt;
&lt;li&gt;校验冗余&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CRAID（Chinese-RAID）：是宏杉科技以中国命名的一项存储专利技术，帮助用户可以放心地大量采用SATA硬盘同时避免硬盘频繁故障带来的数据丢失风险&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CRAID原理：
&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cell是灵活的数据单元，是存储资源管理的基本单元&lt;/li&gt;
&lt;li&gt;在传统RAID中，LUN，RAID，Disk这3者之间是捆绑关系，当RAID组中有磁盘坏块，传统技术的处理是直接踢盘，热备盘顶替，如果没有热备盘，RAID就会处于降级状态&lt;/li&gt;
&lt;li&gt;CRAID是以cell为核心，在创建RAID的时候按照cell为基本单位进行划分，并在此基础上去创建LUN。破除了LUN，RAID，Disk之间的捆绑关系。（也就是多了一层虚拟化）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CRAID和RAID重建方式比较&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传统RAID5，磁盘出现数据坏块，整个磁盘不可用，被踢出，如果有热备盘，热备盘顶上，
进行全盘重建，这个过程需要花费较长时间。而CRAID技术，CRAID5中数据盘出现数据坏块即其中的一个Cell故障，由预留的Cell替换，且只重建坏块的数据，Cell中正常的数据进行拷贝，大大缩短了重建时间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>超融合等保环境部署</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E8%B6%85%E8%9E%8D%E5%90%88%E7%AD%89%E4%BF%9D%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2/%E8%B6%85%E8%9E%8D%E5%90%88%E7%AD%89%E4%BF%9D%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E8%B6%85%E8%9E%8D%E5%90%88%E7%AD%89%E4%BF%9D%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2/%E8%B6%85%E8%9E%8D%E5%90%88%E7%AD%89%E4%BF%9D%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2/</guid><description>超融合等保环境部署</description><pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;新增sudo用户和修改ssh端口&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.从已部署好的MCloud_V3.4.3环境中取用脚本，路径&lt;code&gt;/home/mcloud/security&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;create_sudo_user.sh # 创建sudo 用户
disable_root_login.sh # 是禁止root用户登录
ssh_port_add.sh # 添加ssh端口
ssh_port_remove.sh # 移除ssh端口
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.使用脚本&lt;code&gt;create_sudo_user.sh&lt;/code&gt;新增两个用户&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;create_sudo_user.sh mcloud wvu7G55U
create_sudo_user.sh mdbs wvu7G55U     # mdbs用户为部署MDBS时必须使用的用户
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.使用脚本添加新ssh端口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh_port_add.sh 20022 # 新增20022端口
ssh_port_remove.sh 22 20022 # 移除原22端口

ssh_port_add.sh 22 # 防止后续操作可能造成防火墙问题，建议再将22端口先打开

# 添加防火墙规则
iptables -I INPUT -p tcp --dport 20022 -m comment --comment &quot;new ssh port&quot; -j ACCEPT

# 停止并禁用firewalld
systemctl stop firewalld
systemctl disable firewalld
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;脚本部署MDBS&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;部署MDBS过程中使用&lt;code&gt;mdbs&lt;/code&gt;用户操作&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.&lt;strong&gt;在每个节点&lt;/strong&gt;新增&lt;code&gt;/etc/ms_ssh_port.conf&lt;/code&gt;文件并写入添加的ssh端口号&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vi /etc/ms_ssh_port.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.将MDBS部署包拷进路径&lt;code&gt;/home/mdbs/MDBS_V5.2.0/&lt;/code&gt;下
完整部署包应包含以下文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config.yaml
host.conf
install_mdbs.sh
MD5.txt
MDBS_V5.2.0-openEuler-x86_64.zip # MDBS部署文件，以部署的版本为准
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.修改&lt;code&gt;config.yaml&lt;/code&gt;文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vi config.yaml

# 新增
ansible_port: 20022 # 注意格式一致

# 其余配置按实际情况修改
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.修改&lt;code&gt;host.conf&lt;/code&gt;文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vi host.conf

# 配置好节点信息

# 填写mdbs用户的信息
ansible_user=mdbs
absible_ssh_pass=wvu7G55U
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.执行安装脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sh install_mdbs.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安装过程中可能会因iptables规则消失
需将规则写入配置文件中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vi /etc/sysconfig/iptables
# 在:OUTPUT ACCEPT下面新增

-A INPUT -ptcpm state--state NEWm tcp --dport 20022 -j ACCEPT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;17619022205773.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;随后重启iptables&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart iptables
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就可以再次执行安装脚本了&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;脚本部署MCloud&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.使用非root用户将MCloud的压缩包上传进用户目录(/home/mcloud)下&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.创建安装目录 并将压缩包移动进安装目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /opt/deploymdbsandmcloud/mcloud/tar/
sudo mv MCloud_V3.4.3.tgz /opt/deploymdbsandmcloud/mcloud/tar/
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.解压安装包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/deploymdbsandmcloud/mcloud/tar/
sudo tar -zxvf MCloud_V3.4.3.tgz
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.修改&lt;code&gt;mcloud-install.config&lt;/code&gt;文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd MCloud_V3.4.3
vi mcloud-install.config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据实际情况修改配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
&quot;virtualIp&quot;: &quot;&quot;,  # VIP
&quot;myIp&quot;: &quot;172.20.2.29&quot;,           # 本节点管理IP ，按照实际环境修改
&quot;mySshUser&quot;: &quot;root&quot;,            # 本节点ssh用户，默认root
&quot;mySshPass&quot;: &quot;wvu7G55U&quot;,     # 本节点ssh密码，默认密码wvu7G55U，按照实际环境修改
&quot;peerIp&quot;: &quot;&quot;, 
&quot;peerSshUser&quot;: &quot;&quot;, 
&quot;peerSshPass&quot;: &quot;&quot;,
&quot;dbRootPass&quot;: &quot;Macro3&quot;,   # 数据库密码，默认设置Macro3
&quot;virtualRouterId&quot;: &quot;66&quot;,     # 虚拟路由ID，建议设置为66
&quot;interface&quot;: &quot;bond0&quot;,    # 管理网口名称，与网络配置章节3.3.2保持一致，本文档以bond0为例
&quot;timeServer&quot;: &quot;172.20.2.29&quot;,      # NTP服务器IP，建议填写本节点管理IP
&quot;gateway&quot;: &quot;172.20.0.254&quot;,        # 网关地址，按照实际环境修改
&quot;replicationIps&quot;: [[&quot;&quot;, &quot;&quot;], [&quot;&quot;, &quot;&quot;]], # 备用ip，有ip异常了，就会切到别的ip。可不填
&quot;mySshPort&quot;: &quot;22&quot;,         # 本节点ssh端口，默认22
&quot;peerSshPort&quot;: &quot;&quot; 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.执行安装脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo sh mcloud-install.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;脚本部署仲裁者&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;步骤同脚本部署MDBS，仲裁者节点也需要使用非root用户-mdbs用户部署&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>ArchLinux和Win11双系统安装</title><link>https://fuwari.vercel.app/posts/arch-linux%E5%92%8Cwin%E5%8F%8C%E7%B3%BB%E7%BB%9F%E5%AE%89%E8%A3%85/archlinux%E5%92%8Cwin%E5%8F%8C%E7%B3%BB%E7%BB%9F%E5%AE%89%E8%A3%85/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/arch-linux%E5%92%8Cwin%E5%8F%8C%E7%B3%BB%E7%BB%9F%E5%AE%89%E8%A3%85/archlinux%E5%92%8Cwin%E5%8F%8C%E7%B3%BB%E7%BB%9F%E5%AE%89%E8%A3%85/</guid><description>ArchLinux和Win11双系统安装</description><pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本文采用VMWare虚拟机环境进行双系统的安装&lt;/p&gt;
&lt;h2&gt;Win11系统安装&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;正常安装windows系统，将需要安装ArchLiunx的分区分出来，方便后续安装&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ArchLinux系统安装&lt;/h2&gt;
&lt;h3&gt;进入ArchLinux安装系统&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.win11虚拟机的cdrom挂载上ArchLinux的安装镜像
&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.重启虚拟机，按&lt;code&gt;ESC&lt;/code&gt;进入BootManager页面，选择从CDROM启动&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;连接网络（使用有线连接可跳过）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.输入&lt;code&gt;iwctl&lt;/code&gt;进入网络管理界面&lt;/li&gt;
&lt;li&gt;2.&lt;code&gt;device list&lt;/code&gt; 列出无线网卡设备名，比如无线网卡 &lt;code&gt;wlan0&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;如果设备的Powered字段为off，则需要启用无线设备。输入&lt;code&gt;rfkill unblock wifi&lt;/code&gt;来解锁无线网卡&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3.&lt;code&gt;station wlan0 scan&lt;/code&gt; 扫描网络&lt;/li&gt;
&lt;li&gt;4.&lt;code&gt;station wlan0 get-networks&lt;/code&gt; 列出所有 wifi 网络&lt;/li&gt;
&lt;li&gt;5.&lt;code&gt;station wlan0 connect wifi-name&lt;/code&gt; 进行连接，注意这里无法输入中文。回车后输入密码即可&lt;/li&gt;
&lt;li&gt;6.&lt;code&gt;exit&lt;/code&gt; 连接成功后退出&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;磁盘分区&lt;/h3&gt;
&lt;p&gt;ArchLinux至少需要如下3个分区&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/ # 根目录
/boot # boot分区
/swap # swap分区
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.使用&lt;code&gt;lsblk&lt;/code&gt;查看当前分区情况
&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;
硬盘&lt;code&gt;nvme0n1&lt;/code&gt;为安装ArchLinux系统的目标硬盘，其中1~4分区为windows系统使用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.使用&lt;code&gt;cfdisk /dev/nvme0n1&lt;/code&gt;进入分区操作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.选中&lt;code&gt;free space&lt;/code&gt;,使用&lt;code&gt;New&lt;/code&gt;选项新建分区
&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.分区大小为1G，用作boot分区&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.重复分区操作，将swap分区划分出来，并将剩余空间用作根分区
&lt;img src=&quot;image-3.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.选中&lt;code&gt;Write&lt;/code&gt;回车，输入&lt;code&gt;yes&lt;/code&gt;保存更改，最后&lt;code&gt;Quit&lt;/code&gt;推出&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7.格式化分区，依次执行以下指令，将boot分区格式化为&lt;code&gt;fat32&lt;/code&gt;，swap分区格式化为&lt;code&gt;linuxswap&lt;/code&gt;，根目录格式化为&lt;code&gt;ext4&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkfs.vfat -F32 /dev/nvme0n1p5
mkswap /dev/nvme0n1p6
mkfs.ext4 /dev/nvme0n1p7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-4.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;8.挂载根目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mount /dev/nvme0n1p7 /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;9.采用Linux和Windows独立boot分区，创建boot目录并挂载boot分区&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /mnt/boot
mount /dev/nvme0n1p5 /mnt/boot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-5.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;10.启用swap分区&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;swapon /dev/nvme0n1p6
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;11.使用&lt;code&gt;lsblk&lt;/code&gt;检查分区
&lt;img src=&quot;image-6.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;pacman换源&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.使用&lt;code&gt;vim&lt;/code&gt;编辑器修改&lt;code&gt;/etc/pacman.d/mirrorlist&lt;/code&gt;文件。将&lt;code&gt;pacman&lt;/code&gt;软件仓库源更换为国内软件仓库镜像源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/pacman.d/mirrorlist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推荐将如下软件源配置在最前&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch # 中国科学技术大学开源镜像站
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch # 清华大学开源软件镜像站
Server = https://repo.huaweicloud.com/archlinux/$repo/os/$arch # 华为开源镜像站
Server = http://mirror.lzu.edu.cn/archlinux/$repo/os/$arch # 兰州大学开源镜像站
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-7.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.更新软件源&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -Syy
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安装系统&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.安装基本系统&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacstrap /mnt base base-devel linux-zen linux-zen-headers linux-firmware vim nano e2fsprogs ntfs-3g
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这里使用的linux-zen内核&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果提示GPG证书错误，可能是因为使用的不是最新的镜像文件，可以通过更新 &lt;code&gt;archlinux-keyring&lt;/code&gt; 解决此问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -S archlinux-keyring
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.安装其余软件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacstrap /mnt networkmanager vim sudo zsh zsh-completions
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;pacstrap: Arch Linux 的专用安装脚本，用于将软件包安装到指定目录&lt;/li&gt;
&lt;li&gt;/mnt: 新系统的根目录挂载点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;并让networkmanager开机自启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl enable NetworkManager
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.生产&lt;code&gt;fstab&lt;/code&gt;文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;genfstab -U -p /mnt &amp;gt;&amp;gt; /mnt/etc/fstab
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.检查&lt;code&gt;fstab&lt;/code&gt;文件
&lt;img src=&quot;image-8.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.切换并进入新系统&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;arch-chroot /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-9.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;系统初始配置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.设置root密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.添加隶属于wheel组的普通用户，并设置密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;useradd -m -G wheel -s /bin/zsh hycer # 添加普通用户hycer并指定使用的终端为zsh
passwd hycer # 设置密码
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.修改sudoers，使普通用户能临时获取root权限&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/sudoers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到&lt;code&gt;# %wheel ALL=（ALL）ALL&lt;/code&gt;并取消注释
&lt;img src=&quot;image-10.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.根据自己的CPU安装对应平台的微码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -S intel-ucode
pacman -S amd-ucode
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.安装Grub引导，提供双系统选择入口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -S grub efibootmgr os-prober
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.安装Grub配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=Arch --recheck
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7.修改Grub配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/default/grub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到&lt;code&gt;#GRUB_DISABLE_OS_PROBER=false&lt;/code&gt;并取消注释
&lt;img src=&quot;image-11.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;8.生成Grub配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grub-mkconfig -o /boot/grub/grub.cfg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;若引导了 win10，则输出应该包含倒数第二行：
&lt;img src=&quot;image-12.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;没有也没关系，后面重启进入系统后再次生成配置文件即可&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;9.配置语言和区域&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/locale.gen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分别找到如下字符，取消注释&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#en_US.UTF-8 UTF-8
#zh_CN.UTF-8 UTF-8
#zh_TW.UTF-8 UTF-8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;刷新区域信息,并设置语言&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;locale-gen

echo LANG=en_US.UTF-8 &amp;gt; /etc/locale.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;10.修改时区&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安装kde&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.安装kde&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -S plasma sddm konsole dolphin ark gwenview
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;过程中的选项采用默认即可&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.设置ssdm开机自启&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl enable sddm
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.安装字体&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -S wqy-microhei
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.安装输入法和浏览器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -S firefox
pacman -S fcitx5-im fcitx5-chinese-addons fcitx5-rime
pacman -S wget
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入法需设置环境变量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/environment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加入以下几行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GTK_IM_MODULE=fcitx
QT_IM_MODULE=fcitx
XMODIFIERS=@im=fcitx
SDL_IM_MODULE=fcitx
GLFW_IM_MODULE=ibus
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.退出当前系统，并重启,即可进入新系统&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exit
reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.别忘记重新写入Grub配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo grub-config -o /boot/grub/grub.cfg
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>KVM虚拟机xml配置文件详解</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/kvm%E8%99%9A%E6%8B%9F%E6%9C%BAxml%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/kvm%E8%99%9A%E6%8B%9F%E6%9C%BAxml%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/kvm%E8%99%9A%E6%8B%9F%E6%9C%BAxml%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/kvm%E8%99%9A%E6%8B%9F%E6%9C%BAxml%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/</guid><description>杭州宏杉学习笔记-KVM虚拟机xml配置文件详解</description><pubDate>Mon, 29 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;在对应物理机上使用&lt;code&gt;virsh dumpxml [虚拟机UUID]&lt;/code&gt;即可查看虚拟机的xml配置文件&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;根标签&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;domain type=&apos;kvm&apos; id=&apos;34&apos; xmlns:qemu=&apos;http://libvirt.org/schemas/domain/qemu/1.0&apos;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;domain&amp;gt;&lt;/code&gt;: 定义虚拟机的根元素。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;type=&apos;kvm&apos;&lt;/code&gt;: 使用KVM虚拟化技术。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;id=&apos;34&apos;&lt;/code&gt;: 该虚拟机在Libvirt守护进程中的运行时ID，重启后会变化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xmlns:qemu&lt;/code&gt;: 引入QEMU命名空间，用于添加QEMU特有的命令行参数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;元数据与标识&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;name&amp;gt;578625568c7e41ccb827ecb9e01e3460&amp;lt;/name&amp;gt;
&amp;lt;uuid&amp;gt;57862556-8c7e-41cc-b827-ecb9e01e3460&amp;lt;/uuid&amp;gt;
&amp;lt;description&amp;gt;hml-1004-rd8-u&amp;lt;/description&amp;gt;
&amp;lt;metadata xmlns:mc=&quot;http://www.macrosan.com/&quot;&amp;gt;
  &amp;lt;mc:mcloud&amp;gt;
    &amp;lt;internalId&amp;gt;609&amp;lt;/internalId&amp;gt;
    &amp;lt;hostManagementIp&amp;gt;172.20.2.208&amp;lt;/hostManagementIp&amp;gt;
  &amp;lt;/mc:mcloud&amp;gt;
&amp;lt;/metadata&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt;: 虚拟机的唯一名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;uuid&amp;gt;&lt;/code&gt;: 虚拟机的全局唯一标识符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;description&amp;gt;&lt;/code&gt;: 描述。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;metadata&amp;gt;&lt;/code&gt;: 用于存储自定义元数据。这里是mcloud管理平台的信息，包括内部ID和管理主机IP。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;资源分配&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;memory unit=&apos;KiB&apos;&amp;gt;4194304&amp;lt;/memory&amp;gt;
&amp;lt;currentMemory unit=&apos;KiB&apos;&amp;gt;4194304&amp;lt;/currentMemory&amp;gt;
&amp;lt;vcpu placement=&apos;static&apos;&amp;gt;4&amp;lt;/vcpu&amp;gt;
&amp;lt;cputune&amp;gt;
  &amp;lt;shares&amp;gt;512&amp;lt;/shares&amp;gt;
&amp;lt;/cputune&amp;gt;
&amp;lt;resource&amp;gt;
  &amp;lt;partition&amp;gt;/machine&amp;lt;/partition&amp;gt;
&amp;lt;/resource&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;memory&amp;gt;&lt;/code&gt;: 虚拟机最大可使用的内存大小（4GiB）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;currentMemory&amp;gt;&lt;/code&gt;: 虚拟机&lt;strong&gt;启动时分配的内存大小&lt;/strong&gt;，通常与&lt;code&gt;&amp;lt;memory&amp;gt;&lt;/code&gt;相同，但可配置ballooning驱动进行动态调整。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;vcpu&amp;gt;&lt;/code&gt;: &lt;strong&gt;虚拟CPU数量&lt;/strong&gt;。placement=&apos;static&apos;表示CPU数量在运行时不可改变。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;cputune&amp;gt;&lt;/code&gt;: CPU调度调优。
&lt;code&gt;&amp;lt;shares&amp;gt;512&amp;lt;/shares&amp;gt;&lt;/code&gt;: 相对权重，用于在主机CPU资源紧张时决定各虚拟机的CPU时间分配比例。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;resource&amp;gt;&lt;/code&gt;: 资源分区设置，用于cgroups管理。/machine表示此虚拟机位于根分区。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;系统信息 (SMBIOS)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;sysinfo type=&apos;smbios&apos;&amp;gt;
  &amp;lt;system&amp;gt;
    &amp;lt;entry name=&apos;serial&apos;&amp;gt;aa1c84f6-8768-4abc-ad79-d2c96189dac1&amp;lt;/entry&amp;gt;
  &amp;lt;/system&amp;gt;
  &amp;lt;chassis&amp;gt;
    &amp;lt;entry name=&apos;asset&apos;&amp;gt;www.mcloud.io&amp;lt;/entry&amp;gt;
  &amp;lt;/chassis&amp;gt;
&amp;lt;/sysinfo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;sysinfo&amp;gt;&lt;/code&gt;: 模拟物理主机的SMBIOS/DMI信息(用于描述虚拟机的信息)，供虚拟机内操作系统读取。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;system&amp;gt;&lt;/code&gt;：系统序列号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;chassis&amp;gt;&lt;/code&gt;：机箱资产标签&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在虚拟机中可以通过&lt;code&gt;dmidecode&lt;/code&gt;查看到相应的系统信息
&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;操作系统与启动设置&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;os&amp;gt;
  &amp;lt;type arch=&apos;x86_64&apos; machine=&apos;pc-q35-6.2&apos;&amp;gt;hvm&amp;lt;/type&amp;gt;
  &amp;lt;loader readonly=&apos;yes&apos; type=&apos;pflash&apos;&amp;gt;/usr/share/edk2/ovmf/OVMF.fd&amp;lt;/loader&amp;gt;
  &amp;lt;nvram template=&apos;/usr/share/edk2/ovmf/OVMF.fd&apos;&amp;gt;/var/lib/libvirt/qemu/nvram/578625568c7e41ccb827ecb9e01e3460.fd&amp;lt;/nvram&amp;gt;
  &amp;lt;bootmenu enable=&apos;yes&apos;/&amp;gt;
  &amp;lt;smbios mode=&apos;sysinfo&apos;/&amp;gt;
&amp;lt;/os&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;os&amp;gt;&lt;/code&gt;: 操作系统相关配置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;type&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;arch=&apos;x86_64&apos;: 目标CPU架构。&lt;/li&gt;
&lt;li&gt;machine=&apos;pc-q35-6.2&apos;: 模拟的机器类型（主板型号）。
&lt;ul&gt;
&lt;li&gt;q35:较新的平台，模拟的是 Intel 的 Q35 芯片组，支持更多现代特性。
提供更好的硬件虚拟化支持，例如更好的 PCIe 设备支持、SATA 和更高效的内存管理。
支持 UEFI 启动，适合运行现代操作系统，尤其是 Windows 10、Windows 11 或 Linux 现代发行版。&lt;/li&gt;
&lt;li&gt;i440fx:老旧平台，类似于早期的 Intel 440FX 芯片组。
它支持较老的硬件和设备模型，兼容性较强，适合运行较旧的操作系统或软件（比如 Windows XP、Windows 7 等）。
适合需要较低兼容性要求的系统。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;hvm: 完全虚拟化。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;loader&amp;gt;&lt;/code&gt;: UEFI固件文件路径，readonly=&apos;yes&apos;表示使用只读副本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;nvram&amp;gt;&lt;/code&gt;: UEFI变量存储文件路径，基于模板创建。
&lt;strong&gt;legacy引导模式的虚拟机无上述两项标签&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;bootmenu enable=&apos;yes&apos;/&amp;gt;&lt;/code&gt;: 启动时显示引导菜单。类似以下界面
&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;smbios mode=&apos;sysinfo&apos;/&amp;gt;&lt;/code&gt;: 使用上面&lt;code&gt;&amp;lt;sysinfo&amp;gt;&lt;/code&gt;标签中定义的SMBIOS信息。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;虚拟硬件特性&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;features&amp;gt;
  &amp;lt;acpi/&amp;gt;
  &amp;lt;apic/&amp;gt;
  &amp;lt;pae/&amp;gt;
  &amp;lt;ioapic driver=&apos;kvm&apos;/&amp;gt;
&amp;lt;/features&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;features&amp;gt;&lt;/code&gt;: 启用虚拟硬件的ACPI（电源管理）、APIC（高级可编程中断控制器）、PAE（物理地址扩展）等特性。ioapic driver=&apos;kvm&apos;指定使用KVM的内置I/O APIC。&lt;/p&gt;
&lt;h2&gt;CPU模型与特性&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;cpu mode=&apos;custom&apos; match=&apos;exact&apos; check=&apos;full&apos;&amp;gt;
  &amp;lt;model fallback=&apos;forbid&apos;&amp;gt;qemu64&amp;lt;/model&amp;gt;
  &amp;lt;topology sockets=&apos;1&apos; dies=&apos;1&apos; clusters=&apos;1&apos; cores=&apos;4&apos; threads=&apos;1&apos;/&amp;gt;
  &amp;lt;feature policy=&apos;require&apos; name=&apos;x2apic&apos;/&amp;gt;
  &amp;lt;feature policy=&apos;require&apos; name=&apos;hypervisor&apos;/&amp;gt;
  &amp;lt;feature policy=&apos;require&apos; name=&apos;lahf_lm&apos;/&amp;gt;
  &amp;lt;feature policy=&apos;disable&apos; name=&apos;svm&apos;/&amp;gt;
&amp;lt;/cpu&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;cpu&amp;gt;&lt;/code&gt;: 详细的CPU模拟配置。
&lt;ul&gt;
&lt;li&gt;mode=&apos;custom&apos; match=&apos;exact&apos;: 严格匹配自定义的CPU模型。与云主机的设置的CPU模式有关
&lt;ul&gt;
&lt;li&gt;兼容模式：custom&lt;/li&gt;
&lt;li&gt;物理机匹配模式：host-model&lt;/li&gt;
&lt;li&gt;直通模式：host-passthrough&lt;/li&gt;
&lt;li&gt;云平台修改CPU模式，兼容模式和物理机匹配模式在&lt;code&gt;/run/libvirt/qemu/UUID.xml&lt;/code&gt;(使用dumpxml指令查看的也是该文件，该文件为云主机运行时的配置)均显示为&lt;code&gt;custom&lt;/code&gt;；但云主机的实际配置&lt;code&gt;/etc/libvirt/qemu/UUID.xml&lt;/code&gt;(该文件为云主机持久化的配置文件，也就是定义配置文件)正常显示为对应的模式；预配置的是host-model（模拟与宿主机CPU功能集兼容的虚拟CPU），但是实际启动的时候，会生成一个自定义CPU配置（即custom模式），其中包含与宿主机兼容的特性子集。如下图
&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;check=&apos;full&apos;: 进行完整的CPU特性兼容性检查。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;model fallback=&apos;forbid&apos;&amp;gt;qemu64&amp;lt;/model&amp;gt;&lt;/code&gt;: 使用qemu64基础CPU模型，如果主机不支持则禁止启动（fallback=&apos;forbid&apos;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;topology&amp;gt;&lt;/code&gt;: CPU拓扑结构&lt;/strong&gt;：1个插槽，4个核心，单线程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;feature&amp;gt;&lt;/code&gt;: 明确要求或禁用特定的CPU特性。例如，禁用了AMD的SVM虚拟化扩展。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;设备列表 (核心配置)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;devices&amp;gt;
  &amp;lt;emulator&amp;gt;/usr/libexec/qemu-kvm&amp;lt;/emulator&amp;gt;
  &amp;lt;disk type=&apos;block&apos; device=&apos;disk&apos; snapshot=&apos;external&apos;&amp;gt;
  &amp;lt;driver name=&apos;qemu&apos; type=&apos;raw&apos; cache=&apos;none&apos; io=&apos;native&apos;/&amp;gt;
  &amp;lt;source dev=&apos;/dev/disk/by-id/dm-uuid-mpath-3600b34213a21e9f2bc149b0000000678&apos;/&amp;gt;
  &amp;lt;target dev=&apos;vda&apos; bus=&apos;virtio&apos;/&amp;gt;
  &amp;lt;boot order=&apos;1&apos;/&amp;gt;
  &amp;lt;/disk&amp;gt;

  &amp;lt;disk type=&apos;block&apos; device=&apos;disk&apos; snapshot=&apos;external&apos;&amp;gt;
  &amp;lt;target dev=&apos;vdb&apos; bus=&apos;virtio&apos;/&amp;gt;
  &amp;lt;/disk&amp;gt;

  &amp;lt;disk type=&apos;block&apos; device=&apos;cdrom&apos;&amp;gt;
  &amp;lt;target dev=&apos;hdc&apos; bus=&apos;sata&apos;/&amp;gt;
  &amp;lt;readonly/&amp;gt;
  &amp;lt;/disk&amp;gt;

  &amp;lt;controller type=&apos;scsi&apos; index=&apos;0&apos; model=&apos;virtio-scsi&apos;&amp;gt;
  &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x04&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
 &amp;lt;/controller&amp;gt;
 ....
&amp;lt;/devices&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;devices&amp;gt;&lt;/code&gt;: 包含所有虚拟设备的定义。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;emulator&amp;gt;&lt;/code&gt;: QEMU-KVM模拟器的完整路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1. 存储设备 (Disk Devices)&lt;/h3&gt;
&lt;h4&gt;磁盘&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;disk type=&apos;block&apos; device=&apos;disk&apos; snapshot=&apos;external&apos;&amp;gt;
  &amp;lt;driver name=&apos;qemu&apos; type=&apos;raw&apos; cache=&apos;none&apos; io=&apos;native&apos;/&amp;gt;
  &amp;lt;source dev=&apos;/dev/disk/by-id/dm-uuid-mpath-3600b34213a21e9f2bc149b0000000678&apos; index=&apos;2&apos;/&amp;gt;
  &amp;lt;backingStore/&amp;gt;
  &amp;lt;target dev=&apos;vda&apos; bus=&apos;virtio&apos;/&amp;gt;
  &amp;lt;boot order=&apos;1&apos;/&amp;gt;
  &amp;lt;alias name=&apos;virtio-disk0&apos;/&amp;gt;
  &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x08&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
&amp;lt;/disk&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;disk&amp;gt;&lt;/code&gt;: 定义磁盘设备&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;type=&apos;block&apos;: 使用块设备（非文件）&lt;/li&gt;
&lt;li&gt;device=&apos;disk&apos;: 设备类型为硬盘&lt;/li&gt;
&lt;li&gt;snapshot=&apos;external&apos;: 快照由外部存储管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;driver&amp;gt;&lt;/code&gt;: 驱动配置&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;name=&apos;qemu&apos;: 使用 QEMU 驱动&lt;/li&gt;
&lt;li&gt;type=&apos;raw&apos;: 磁盘格式&lt;/li&gt;
&lt;li&gt;cache=&apos;none&apos;: 直写模式，保证数据安全&lt;/li&gt;
&lt;li&gt;io=&apos;native&apos;: 使用 Linux 原生 AIO&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;: 源设备&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dev=...: 主机上的多路径设备路径&lt;/li&gt;
&lt;li&gt;index=&apos;2&apos;: 设备索引号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;backingStore/&amp;gt;&lt;/code&gt;: 后备存储（空表示无）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;target&amp;gt;&lt;/code&gt;: 目标设备&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dev=&apos;vda&apos;: 在虚拟机中显示为 vda&lt;/li&gt;
&lt;li&gt;bus=&apos;virtio&apos;: 使用 VirtIO 总线&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;boot order=&apos;1&apos;/&amp;gt;&lt;/code&gt;: 启动顺序为第一&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;alias&amp;gt;&lt;/code&gt;: 设备别名，用于内部引用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;address&amp;gt;&lt;/code&gt;: PCI 地址&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;domain=&apos;0x0000&apos;: PCI 域&lt;/li&gt;
&lt;li&gt;bus=&apos;0x08&apos;: PCI 总线号&lt;/li&gt;
&lt;li&gt;slot=&apos;0x00&apos;: PCI 插槽号&lt;/li&gt;
&lt;li&gt;function=&apos;0x0&apos;: PCI 功能号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;CD-ROM 设备&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;disk type=&apos;block&apos; device=&apos;cdrom&apos;&amp;gt;
  &amp;lt;driver name=&apos;qemu&apos; type=&apos;raw&apos;/&amp;gt;
  &amp;lt;source dev=&apos;/dev/disk/by-id/dm-uuid-mpath-3600b34213a21e9f2bccab40000000669&apos; index=&apos;1&apos;/&amp;gt;
  &amp;lt;backingStore/&amp;gt;
  &amp;lt;target dev=&apos;hdc&apos; bus=&apos;sata&apos;/&amp;gt;
  &amp;lt;readonly/&amp;gt;
  &amp;lt;alias name=&apos;sata0-0-1&apos;/&amp;gt;
  &amp;lt;address type=&apos;drive&apos; controller=&apos;0&apos; bus=&apos;0&apos; target=&apos;0&apos; unit=&apos;1&apos;/&amp;gt;
&amp;lt;/disk&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;device=&apos;cdrom&apos;: 设备类型为光驱&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;readonly/&amp;gt;&lt;/code&gt;: 只读设备&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;address type=&apos;drive&apos;&amp;gt;&lt;/code&gt;: IDE/SATA 地址格式
&lt;ul&gt;
&lt;li&gt;controller=&apos;0&apos;: 控制器索引&lt;/li&gt;
&lt;li&gt;bus=&apos;0&apos;: 总线号&lt;/li&gt;
&lt;li&gt;target=&apos;0&apos;: 目标号&lt;/li&gt;
&lt;li&gt;unit=&apos;1&apos;: 单元号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.控制器 (Controllers)&lt;/h3&gt;
&lt;h4&gt;SCSI 控制器&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;controller type=&apos;scsi&apos; index=&apos;0&apos; model=&apos;virtio-scsi&apos;&amp;gt;
  &amp;lt;alias name=&apos;scsi0&apos;/&amp;gt;
  &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x04&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
&amp;lt;/controller&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;type=&apos;scsi&apos;: SCSI 控制器
model=&apos;virtio-scsi&apos;: VirtIO SCSI 模型
index=&apos;0&apos;: 控制器索引号&lt;/p&gt;
&lt;h4&gt;PCIe 根端口控制器&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;controller type=&apos;pci&apos; index=&apos;3&apos; model=&apos;pcie-root-port&apos;&amp;gt;
  &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
  &amp;lt;target chassis=&apos;3&apos; port=&apos;0x10&apos;/&amp;gt;
  &amp;lt;alias name=&apos;pci.3&apos;/&amp;gt;
  &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x02&apos; function=&apos;0x0&apos; multifunction=&apos;on&apos;/&amp;gt;
&amp;lt;/controller&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;model=&apos;pcie-root-port&apos;: PCIe 根端口&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;target&amp;gt;&lt;/code&gt;: 目标配置
&lt;ul&gt;
&lt;li&gt;chassis=&apos;3&apos;: 机箱编号&lt;/li&gt;
&lt;li&gt;port=&apos;0x10&apos;: 端口号&lt;/li&gt;
&lt;li&gt;multifunction=&apos;on&apos;: 多功能设备&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.&lt;strong&gt;网络设备 (Network Interface)&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;interface type=&apos;bridge&apos;&amp;gt;
  &amp;lt;mac address=&apos;fa:b1:f0:c3:34:66&apos;/&amp;gt;
  &amp;lt;source bridge=&apos;br_bond1&apos;/&amp;gt;
  &amp;lt;target dev=&apos;vnic609.0&apos;/&amp;gt;
  &amp;lt;model type=&apos;virtio&apos;/&amp;gt;
  &amp;lt;mtu size=&apos;1500&apos;/&amp;gt;
  &amp;lt;alias name=&apos;net0&apos;/&amp;gt;
  &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x03&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
&amp;lt;/interface&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;type=&apos;bridge&apos;: 桥接模式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;mac&amp;gt;&lt;/code&gt;: MAC 地址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;: 源网络
&lt;ul&gt;
&lt;li&gt;bridge=&apos;br_bond1&apos;: 主机网桥名称&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;target&amp;gt;&lt;/code&gt;: 目标设备
&lt;ul&gt;
&lt;li&gt;dev=&apos;vnic609.0&apos;: 虚拟网卡设备名&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;model&amp;gt;&lt;/code&gt;: 网卡模型
type=&apos;virtio&apos;: VirtIO 高性能网卡&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;mtu&amp;gt;&lt;/code&gt;: 最大传输单元
size=&apos;1500&apos;: 1500 字节&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.串行与控制台 (Serial &amp;amp; Console)&lt;/h3&gt;
&lt;h4&gt;串行端口&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;serial type=&apos;pty&apos;&amp;gt;
  &amp;lt;source path=&apos;/dev/pts/0&apos;/&amp;gt;
  &amp;lt;target type=&apos;isa-serial&apos; port=&apos;0&apos;&amp;gt;
    &amp;lt;model name=&apos;isa-serial&apos;/&amp;gt;
  &amp;lt;/target&amp;gt;
  &amp;lt;alias name=&apos;serial0&apos;/&amp;gt;
&amp;lt;/serial&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;type=&apos;pty&apos;: 使用伪终端&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;: 源终端
&lt;ul&gt;
&lt;li&gt;path=&apos;/dev/pts/0&apos;: 伪终端路径&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;target&amp;gt;&lt;/code&gt;: 目标类型
&lt;ul&gt;
&lt;li&gt;type=&apos;isa-serial&apos;: ISA 串行端口&lt;/li&gt;
&lt;li&gt;port=&apos;0&apos;: 端口号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;控制台&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;console type=&apos;pty&apos; tty=&apos;/dev/pts/0&apos;&amp;gt;
  &amp;lt;source path=&apos;/dev/pts/0&apos;/&amp;gt;
  &amp;lt;target type=&apos;serial&apos; port=&apos;0&apos;/&amp;gt;
  &amp;lt;alias name=&apos;serial0&apos;/&amp;gt;
&amp;lt;/console&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tty=&apos;/dev/pts/0&apos;: 关联的 TTY 设备&lt;/p&gt;
&lt;h3&gt;5.通信通道 (Channels)&lt;/h3&gt;
&lt;h4&gt;Guest Agent 通道&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;channel type=&apos;unix&apos;&amp;gt;
  &amp;lt;source mode=&apos;bind&apos; path=&apos;/var/lib/libvirt/qemu/578625568c7e41ccb827ecb9e01e3460&apos;/&amp;gt;
  &amp;lt;target type=&apos;virtio&apos; name=&apos;org.qemu.guest_agent.0&apos; state=&apos;connected&apos;/&amp;gt;
  &amp;lt;alias name=&apos;channel0&apos;/&amp;gt;
  &amp;lt;address type=&apos;virtio-serial&apos; controller=&apos;0&apos; bus=&apos;0&apos; port=&apos;1&apos;/&amp;gt;
&amp;lt;/channel&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;type=&apos;unix&apos;: Unix socket 通信&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;: 源socket&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mode=&apos;bind&apos;: 绑定模式&lt;/li&gt;
&lt;li&gt;path=...: socket 文件路径&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;target&amp;gt;&lt;/code&gt;: 目标&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;name=&apos;org.qemu.guest_agent.0&apos;: QEMU Guest Agent 服务名&lt;/li&gt;
&lt;li&gt;state=&apos;connected&apos;: 连接状态&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.输入设备 (Input Devices)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input type=&apos;tablet&apos; bus=&apos;usb&apos;&amp;gt;
  &amp;lt;alias name=&apos;input0&apos;/&amp;gt;
  &amp;lt;address type=&apos;usb&apos; bus=&apos;0&apos; port=&apos;1&apos;/&amp;gt;
&amp;lt;/input&amp;gt;
&amp;lt;input type=&apos;mouse&apos; bus=&apos;ps2&apos;&amp;gt;
  &amp;lt;alias name=&apos;input1&apos;/&amp;gt;
&amp;lt;/input&amp;gt;
&amp;lt;input type=&apos;keyboard&apos; bus=&apos;ps2&apos;&amp;gt;
  &amp;lt;alias name=&apos;input2&apos;/&amp;gt;
&amp;lt;/input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;type=&apos;tablet&apos;: 图形输入板（绝对定位）
type=&apos;mouse&apos;: 鼠标
type=&apos;keyboard&apos;: 键盘
bus=&apos;usb&apos; / bus=&apos;ps2&apos;: 总线类型&lt;/p&gt;
&lt;h3&gt;7.图形显示 (Graphics &amp;amp; Video)&lt;/h3&gt;
&lt;h4&gt;VNC 图形&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;graphics type=&apos;vnc&apos; port=&apos;5900&apos; autoport=&apos;yes&apos; listen=&apos;0.0.0.0&apos;&amp;gt;
  &amp;lt;listen type=&apos;address&apos; address=&apos;0.0.0.0&apos;/&amp;gt;
&amp;lt;/graphics&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;type=&apos;vnc&apos;: VNC 协议
port=&apos;5900&apos;: 端口号
autoport=&apos;yes&apos;: 自动分配端口
listen=&apos;0.0.0.0&apos;: 监听所有地址&lt;/p&gt;
&lt;h4&gt;视频卡&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;video&amp;gt;
  &amp;lt;model type=&apos;cirrus&apos; vram=&apos;16384&apos; heads=&apos;1&apos; primary=&apos;yes&apos;/&amp;gt;
  &amp;lt;alias name=&apos;video0&apos;/&amp;gt;
  &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x01&apos; function=&apos;0x0&apos;/&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;model&amp;gt;&lt;/code&gt;: 显卡模型
&lt;ul&gt;
&lt;li&gt;type=&apos;cirrus&apos;: Cirrus Logic 显卡&lt;/li&gt;
&lt;li&gt;vram=&apos;16384&apos;: 16MB 显存&lt;/li&gt;
&lt;li&gt;heads=&apos;1&apos;: 单显示器&lt;/li&gt;
&lt;li&gt;primary=&apos;yes&apos;: 主显示设备&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.USB 重定向 (USB Redirection)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;redirdev bus=&apos;usb&apos; type=&apos;spicevmc&apos;&amp;gt;
  &amp;lt;alias name=&apos;redir0&apos;/&amp;gt;
  &amp;lt;address type=&apos;usb&apos; bus=&apos;3&apos; port=&apos;1&apos;/&amp;gt;
&amp;lt;/redirdev&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;bus=&apos;usb&apos;: USB 总线
type=&apos;spicevmc&apos;: SPICE 虚拟通道类型
作用: 将客户端 USB 设备重定向到虚拟机&lt;/p&gt;
&lt;h3&gt;9.内存气球 (Memory Balloon)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;memballoon model=&apos;virtio&apos;&amp;gt;
  &amp;lt;stats period=&apos;10&apos;/&amp;gt;
  &amp;lt;alias name=&apos;balloon0&apos;/&amp;gt;
  &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x09&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
&amp;lt;/memballoon&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;model=&apos;virtio&apos;: VirtIO 内存气球设备&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;stats period=&apos;10&apos;/&amp;gt;&lt;/code&gt;: 每10秒统计内存使用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;作用: 动态调整虚拟机内存大小&lt;/p&gt;
&lt;h3&gt;10.安全标签&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;seclabel type=&apos;none&apos; model=&apos;none&apos;/&amp;gt;
  &amp;lt;seclabel type=&apos;dynamic&apos; model=&apos;dac&apos; relabel=&apos;yes&apos;&amp;gt;
    &amp;lt;label&amp;gt;+0:+0&amp;lt;/label&amp;gt;
    &amp;lt;imagelabel&amp;gt;+0:+0&amp;lt;/imagelabel&amp;gt;
  &amp;lt;/seclabel&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;定义了两种安全标签。第二个使用DAC（自主访问控制），+0:+0表示使用当前用户的UID和GID来访问资源。&lt;/p&gt;
&lt;h3&gt;11.QEMU命令行扩展&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;qemu:commandline&amp;gt;
    &amp;lt;qemu:arg value=&apos;-qmp&apos;/&amp;gt;
    &amp;lt;qemu:arg value=&apos;unix:/var/lib/libvirt/qemu/mcloud/578625568c7e41ccb827ecb9e01e3460.sock,server,nowait&apos;/&amp;gt;
  &amp;lt;/qemu:commandline&amp;gt;
&amp;lt;qemu:commandline&amp;gt;: 添加额外的QEMU参数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加了-qmp参数，用于开启QEMU Machine Protocol监控，通过Unix Socket通信。&lt;/p&gt;
&lt;h2&gt;完整的xml配置文件&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;domain type=&apos;kvm&apos; id=&apos;34&apos; xmlns:qemu=&apos;http://libvirt.org/schemas/domain/qemu/1.0&apos;&amp;gt;
  &amp;lt;name&amp;gt;578625568c7e41ccb827ecb9e01e3460&amp;lt;/name&amp;gt;
  &amp;lt;uuid&amp;gt;57862556-8c7e-41cc-b827-ecb9e01e3460&amp;lt;/uuid&amp;gt;
  &amp;lt;description&amp;gt;hml-1004-rd8-u&amp;lt;/description&amp;gt;
  &amp;lt;metadata xmlns:mc=&quot;http://www.macrosan.com/&quot;&amp;gt;
    &amp;lt;mc:mcloud&amp;gt;
      &amp;lt;internalId&amp;gt;609&amp;lt;/internalId&amp;gt;
      &amp;lt;hostManagementIp&amp;gt;172.20.2.208&amp;lt;/hostManagementIp&amp;gt;
    &amp;lt;/mc:mcloud&amp;gt;
  &amp;lt;/metadata&amp;gt;
  &amp;lt;memory unit=&apos;KiB&apos;&amp;gt;4194304&amp;lt;/memory&amp;gt;
  &amp;lt;currentMemory unit=&apos;KiB&apos;&amp;gt;4194304&amp;lt;/currentMemory&amp;gt;
  &amp;lt;vcpu placement=&apos;static&apos;&amp;gt;4&amp;lt;/vcpu&amp;gt;
  &amp;lt;cputune&amp;gt;
    &amp;lt;shares&amp;gt;512&amp;lt;/shares&amp;gt;
  &amp;lt;/cputune&amp;gt;
  &amp;lt;resource&amp;gt;
    &amp;lt;partition&amp;gt;/machine&amp;lt;/partition&amp;gt;
  &amp;lt;/resource&amp;gt;
  &amp;lt;sysinfo type=&apos;smbios&apos;&amp;gt;
    &amp;lt;system&amp;gt;
      &amp;lt;entry name=&apos;serial&apos;&amp;gt;aa1c84f6-8768-4abc-ad79-d2c96189dac1&amp;lt;/entry&amp;gt;
    &amp;lt;/system&amp;gt;
    &amp;lt;chassis&amp;gt;
      &amp;lt;entry name=&apos;asset&apos;&amp;gt;www.mcloud.io&amp;lt;/entry&amp;gt;
    &amp;lt;/chassis&amp;gt;
  &amp;lt;/sysinfo&amp;gt;
  &amp;lt;os&amp;gt;
    &amp;lt;type arch=&apos;x86_64&apos; machine=&apos;pc-q35-6.2&apos;&amp;gt;hvm&amp;lt;/type&amp;gt;
    &amp;lt;loader readonly=&apos;yes&apos; type=&apos;pflash&apos;&amp;gt;/usr/share/edk2/ovmf/OVMF.fd&amp;lt;/loader&amp;gt;
    &amp;lt;nvram template=&apos;/usr/share/edk2/ovmf/OVMF.fd&apos;&amp;gt;/var/lib/libvirt/qemu/nvram/578625568c7e41ccb827ecb9e01e3460.fd&amp;lt;/nvram&amp;gt;
    &amp;lt;bootmenu enable=&apos;yes&apos;/&amp;gt;
    &amp;lt;smbios mode=&apos;sysinfo&apos;/&amp;gt;
  &amp;lt;/os&amp;gt;
  &amp;lt;features&amp;gt;
    &amp;lt;acpi/&amp;gt;
    &amp;lt;apic/&amp;gt;
    &amp;lt;pae/&amp;gt;
    &amp;lt;ioapic driver=&apos;kvm&apos;/&amp;gt;
  &amp;lt;/features&amp;gt;
  &amp;lt;cpu mode=&apos;custom&apos; match=&apos;exact&apos; check=&apos;full&apos;&amp;gt;
    &amp;lt;model fallback=&apos;forbid&apos;&amp;gt;qemu64&amp;lt;/model&amp;gt;
    &amp;lt;topology sockets=&apos;1&apos; dies=&apos;1&apos; clusters=&apos;1&apos; cores=&apos;4&apos; threads=&apos;1&apos;/&amp;gt;
    &amp;lt;feature policy=&apos;require&apos; name=&apos;x2apic&apos;/&amp;gt;
    &amp;lt;feature policy=&apos;require&apos; name=&apos;hypervisor&apos;/&amp;gt;
    &amp;lt;feature policy=&apos;require&apos; name=&apos;lahf_lm&apos;/&amp;gt;
    &amp;lt;feature policy=&apos;disable&apos; name=&apos;svm&apos;/&amp;gt;
  &amp;lt;/cpu&amp;gt;
  &amp;lt;clock offset=&apos;utc&apos;/&amp;gt;
  &amp;lt;on_poweroff&amp;gt;destroy&amp;lt;/on_poweroff&amp;gt;
  &amp;lt;on_reboot&amp;gt;restart&amp;lt;/on_reboot&amp;gt;
  &amp;lt;on_crash&amp;gt;restart&amp;lt;/on_crash&amp;gt;
  &amp;lt;devices&amp;gt;
    &amp;lt;emulator&amp;gt;/usr/libexec/qemu-kvm&amp;lt;/emulator&amp;gt;
    &amp;lt;disk type=&apos;block&apos; device=&apos;disk&apos; snapshot=&apos;external&apos;&amp;gt;
      &amp;lt;driver name=&apos;qemu&apos; type=&apos;raw&apos; cache=&apos;none&apos; io=&apos;native&apos;/&amp;gt;
      &amp;lt;source dev=&apos;/dev/disk/by-id/dm-uuid-mpath-3600b34213a21e9f2bc149b0000000678&apos; index=&apos;2&apos;/&amp;gt;
      &amp;lt;backingStore/&amp;gt;
      &amp;lt;target dev=&apos;vda&apos; bus=&apos;virtio&apos;/&amp;gt;
      &amp;lt;boot order=&apos;1&apos;/&amp;gt;
      &amp;lt;alias name=&apos;virtio-disk0&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x08&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/disk&amp;gt;
    &amp;lt;disk type=&apos;block&apos; device=&apos;disk&apos; snapshot=&apos;external&apos;&amp;gt;
      &amp;lt;driver name=&apos;qemu&apos; type=&apos;raw&apos; cache=&apos;none&apos; io=&apos;native&apos;/&amp;gt;
      &amp;lt;source dev=&apos;/dev/disk/by-id/dm-uuid-mpath-3600b34213a21e9f2bcc4210000000857&apos; index=&apos;3&apos;/&amp;gt;
      &amp;lt;backingStore/&amp;gt;
      &amp;lt;target dev=&apos;vdb&apos; bus=&apos;virtio&apos;/&amp;gt;
      &amp;lt;alias name=&apos;virtio-disk1&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x0a&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/disk&amp;gt;
    &amp;lt;disk type=&apos;block&apos; device=&apos;cdrom&apos;&amp;gt;
      &amp;lt;driver name=&apos;qemu&apos; type=&apos;raw&apos;/&amp;gt;
      &amp;lt;source dev=&apos;/dev/disk/by-id/dm-uuid-mpath-3600b34213a21e9f2bccab40000000669&apos; index=&apos;1&apos;/&amp;gt;
      &amp;lt;backingStore/&amp;gt;
      &amp;lt;target dev=&apos;hdc&apos; bus=&apos;sata&apos;/&amp;gt;
      &amp;lt;readonly/&amp;gt;
      &amp;lt;alias name=&apos;sata0-0-1&apos;/&amp;gt;
      &amp;lt;address type=&apos;drive&apos; controller=&apos;0&apos; bus=&apos;0&apos; target=&apos;0&apos; unit=&apos;1&apos;/&amp;gt;
    &amp;lt;/disk&amp;gt;
    &amp;lt;controller type=&apos;scsi&apos; index=&apos;0&apos; model=&apos;virtio-scsi&apos;&amp;gt;
      &amp;lt;alias name=&apos;scsi0&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x04&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;sata&apos; index=&apos;0&apos;&amp;gt;
      &amp;lt;alias name=&apos;ide&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x1f&apos; function=&apos;0x2&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;0&apos; model=&apos;pcie-root&apos;&amp;gt;
      &amp;lt;alias name=&apos;pcie.0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;1&apos; model=&apos;dmi-to-pci-bridge&apos;&amp;gt;
      &amp;lt;model name=&apos;i82801b11-bridge&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.1&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x1e&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;2&apos; model=&apos;pci-bridge&apos;&amp;gt;
      &amp;lt;model name=&apos;pci-bridge&apos;/&amp;gt;
      &amp;lt;target chassisNr=&apos;2&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.2&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x01&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;3&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;3&apos; port=&apos;0x10&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.3&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x02&apos; function=&apos;0x0&apos; multifunction=&apos;on&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;4&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;4&apos; port=&apos;0x11&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.4&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x02&apos; function=&apos;0x1&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;5&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;5&apos; port=&apos;0x12&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.5&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x02&apos; function=&apos;0x2&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;6&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;6&apos; port=&apos;0x13&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.6&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x02&apos; function=&apos;0x3&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;7&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;7&apos; port=&apos;0x14&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.7&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x02&apos; function=&apos;0x4&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;8&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;8&apos; port=&apos;0x15&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.8&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x02&apos; function=&apos;0x5&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;9&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;9&apos; port=&apos;0x16&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.9&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x02&apos; function=&apos;0x6&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;10&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;10&apos; port=&apos;0x17&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.10&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x02&apos; function=&apos;0x7&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;11&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;11&apos; port=&apos;0x18&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.11&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x03&apos; function=&apos;0x0&apos; multifunction=&apos;on&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;12&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;12&apos; port=&apos;0x19&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.12&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x03&apos; function=&apos;0x1&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;13&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;13&apos; port=&apos;0x1a&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.13&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x03&apos; function=&apos;0x2&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;14&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;14&apos; port=&apos;0x1b&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.14&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x03&apos; function=&apos;0x3&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;15&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;15&apos; port=&apos;0x1c&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.15&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x03&apos; function=&apos;0x4&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;16&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;16&apos; port=&apos;0x1d&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.16&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x03&apos; function=&apos;0x5&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;17&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;17&apos; port=&apos;0x1e&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.17&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x03&apos; function=&apos;0x6&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;18&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;18&apos; port=&apos;0x1f&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.18&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x03&apos; function=&apos;0x7&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;19&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;19&apos; port=&apos;0x20&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.19&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x04&apos; function=&apos;0x0&apos; multifunction=&apos;on&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;20&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;20&apos; port=&apos;0x21&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.20&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x04&apos; function=&apos;0x1&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;21&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;21&apos; port=&apos;0x22&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.21&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x04&apos; function=&apos;0x2&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;22&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;22&apos; port=&apos;0x23&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.22&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x04&apos; function=&apos;0x3&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;23&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;23&apos; port=&apos;0x24&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.23&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x04&apos; function=&apos;0x4&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;24&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;24&apos; port=&apos;0x25&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.24&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x04&apos; function=&apos;0x5&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;25&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;25&apos; port=&apos;0x26&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.25&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x04&apos; function=&apos;0x6&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;26&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;26&apos; port=&apos;0x27&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.26&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x04&apos; function=&apos;0x7&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;27&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;27&apos; port=&apos;0x28&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.27&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x05&apos; function=&apos;0x0&apos; multifunction=&apos;on&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;28&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;28&apos; port=&apos;0x29&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.28&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x05&apos; function=&apos;0x1&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;29&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;29&apos; port=&apos;0x2a&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.29&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x05&apos; function=&apos;0x2&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;pci&apos; index=&apos;30&apos; model=&apos;pcie-root-port&apos;&amp;gt;
      &amp;lt;model name=&apos;pcie-root-port&apos;/&amp;gt;
      &amp;lt;target chassis=&apos;30&apos; port=&apos;0x2b&apos;/&amp;gt;
      &amp;lt;alias name=&apos;pci.30&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x05&apos; function=&apos;0x3&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;usb&apos; index=&apos;0&apos; model=&apos;piix3-uhci&apos;&amp;gt;
      &amp;lt;alias name=&apos;usb&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x02&apos; slot=&apos;0x01&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;usb&apos; index=&apos;1&apos; model=&apos;ehci&apos;&amp;gt;
      &amp;lt;alias name=&apos;usb1&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x02&apos; slot=&apos;0x02&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;usb&apos; index=&apos;2&apos; model=&apos;nec-xhci&apos;&amp;gt;
      &amp;lt;alias name=&apos;usb2&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x05&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;usb&apos; index=&apos;3&apos; model=&apos;ehci&apos;&amp;gt;
      &amp;lt;alias name=&apos;usb3&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x02&apos; slot=&apos;0x03&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;usb&apos; index=&apos;4&apos; model=&apos;nec-xhci&apos;&amp;gt;
      &amp;lt;alias name=&apos;usb4&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x06&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;controller type=&apos;virtio-serial&apos; index=&apos;0&apos;&amp;gt;
      &amp;lt;alias name=&apos;virtio-serial0&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x07&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/controller&amp;gt;
    &amp;lt;interface type=&apos;bridge&apos;&amp;gt;
      &amp;lt;mac address=&apos;fa:b1:f0:c3:34:66&apos;/&amp;gt;
      &amp;lt;source bridge=&apos;br_bond1&apos;/&amp;gt;
      &amp;lt;target dev=&apos;vnic609.0&apos;/&amp;gt;
      &amp;lt;model type=&apos;virtio&apos;/&amp;gt;
      &amp;lt;mtu size=&apos;1500&apos;/&amp;gt;
      &amp;lt;alias name=&apos;net0&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x03&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/interface&amp;gt;
    &amp;lt;serial type=&apos;pty&apos;&amp;gt;
      &amp;lt;source path=&apos;/dev/pts/0&apos;/&amp;gt;
      &amp;lt;target type=&apos;isa-serial&apos; port=&apos;0&apos;&amp;gt;
        &amp;lt;model name=&apos;isa-serial&apos;/&amp;gt;
      &amp;lt;/target&amp;gt;
      &amp;lt;alias name=&apos;serial0&apos;/&amp;gt;
    &amp;lt;/serial&amp;gt;
    &amp;lt;console type=&apos;pty&apos; tty=&apos;/dev/pts/0&apos;&amp;gt;
      &amp;lt;source path=&apos;/dev/pts/0&apos;/&amp;gt;
      &amp;lt;target type=&apos;serial&apos; port=&apos;0&apos;/&amp;gt;
      &amp;lt;alias name=&apos;serial0&apos;/&amp;gt;
    &amp;lt;/console&amp;gt;
    &amp;lt;channel type=&apos;unix&apos;&amp;gt;
      &amp;lt;source mode=&apos;bind&apos; path=&apos;/var/lib/libvirt/qemu/578625568c7e41ccb827ecb9e01e3460&apos;/&amp;gt;
      &amp;lt;target type=&apos;virtio&apos; name=&apos;org.qemu.guest_agent.0&apos; state=&apos;connected&apos;/&amp;gt;
      &amp;lt;alias name=&apos;channel0&apos;/&amp;gt;
      &amp;lt;address type=&apos;virtio-serial&apos; controller=&apos;0&apos; bus=&apos;0&apos; port=&apos;1&apos;/&amp;gt;
    &amp;lt;/channel&amp;gt;
    &amp;lt;channel type=&apos;spicevmc&apos;&amp;gt;
      &amp;lt;target type=&apos;virtio&apos; name=&apos;com.redhat.spice.0&apos; state=&apos;connected&apos;/&amp;gt;
      &amp;lt;alias name=&apos;channel1&apos;/&amp;gt;
      &amp;lt;address type=&apos;virtio-serial&apos; controller=&apos;0&apos; bus=&apos;0&apos; port=&apos;2&apos;/&amp;gt;
    &amp;lt;/channel&amp;gt;
    &amp;lt;input type=&apos;tablet&apos; bus=&apos;usb&apos;&amp;gt;
      &amp;lt;alias name=&apos;input0&apos;/&amp;gt;
      &amp;lt;address type=&apos;usb&apos; bus=&apos;0&apos; port=&apos;1&apos;/&amp;gt;
    &amp;lt;/input&amp;gt;
    &amp;lt;input type=&apos;mouse&apos; bus=&apos;ps2&apos;&amp;gt;
      &amp;lt;alias name=&apos;input1&apos;/&amp;gt;
    &amp;lt;/input&amp;gt;
    &amp;lt;input type=&apos;keyboard&apos; bus=&apos;ps2&apos;&amp;gt;
      &amp;lt;alias name=&apos;input2&apos;/&amp;gt;
    &amp;lt;/input&amp;gt;
    &amp;lt;graphics type=&apos;vnc&apos; port=&apos;5900&apos; autoport=&apos;yes&apos; listen=&apos;0.0.0.0&apos;&amp;gt;
      &amp;lt;listen type=&apos;address&apos; address=&apos;0.0.0.0&apos;/&amp;gt;
    &amp;lt;/graphics&amp;gt;
    &amp;lt;video&amp;gt;
      &amp;lt;model type=&apos;cirrus&apos; vram=&apos;16384&apos; heads=&apos;1&apos; primary=&apos;yes&apos;/&amp;gt;
      &amp;lt;alias name=&apos;video0&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x00&apos; slot=&apos;0x01&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/video&amp;gt;
    &amp;lt;redirdev bus=&apos;usb&apos; type=&apos;spicevmc&apos;&amp;gt;
      &amp;lt;alias name=&apos;redir0&apos;/&amp;gt;
      &amp;lt;address type=&apos;usb&apos; bus=&apos;3&apos; port=&apos;1&apos;/&amp;gt;
    &amp;lt;/redirdev&amp;gt;
    &amp;lt;redirdev bus=&apos;usb&apos; type=&apos;spicevmc&apos;&amp;gt;
      &amp;lt;alias name=&apos;redir1&apos;/&amp;gt;
      &amp;lt;address type=&apos;usb&apos; bus=&apos;3&apos; port=&apos;2&apos;/&amp;gt;
    &amp;lt;/redirdev&amp;gt;
    &amp;lt;redirdev bus=&apos;usb&apos; type=&apos;spicevmc&apos;&amp;gt;
      &amp;lt;alias name=&apos;redir2&apos;/&amp;gt;
      &amp;lt;address type=&apos;usb&apos; bus=&apos;4&apos; port=&apos;1&apos;/&amp;gt;
    &amp;lt;/redirdev&amp;gt;
    &amp;lt;redirdev bus=&apos;usb&apos; type=&apos;spicevmc&apos;&amp;gt;
      &amp;lt;alias name=&apos;redir3&apos;/&amp;gt;
      &amp;lt;address type=&apos;usb&apos; bus=&apos;4&apos; port=&apos;2&apos;/&amp;gt;
    &amp;lt;/redirdev&amp;gt;
    &amp;lt;memballoon model=&apos;virtio&apos;&amp;gt;
      &amp;lt;stats period=&apos;10&apos;/&amp;gt;
      &amp;lt;alias name=&apos;balloon0&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x09&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/memballoon&amp;gt;
  &amp;lt;/devices&amp;gt;
  &amp;lt;seclabel type=&apos;none&apos; model=&apos;none&apos;/&amp;gt;
  &amp;lt;seclabel type=&apos;dynamic&apos; model=&apos;dac&apos; relabel=&apos;yes&apos;&amp;gt;
    &amp;lt;label&amp;gt;+0:+0&amp;lt;/label&amp;gt;
    &amp;lt;imagelabel&amp;gt;+0:+0&amp;lt;/imagelabel&amp;gt;
  &amp;lt;/seclabel&amp;gt;
  &amp;lt;qemu:commandline&amp;gt;
    &amp;lt;qemu:arg value=&apos;-qmp&apos;/&amp;gt;
    &amp;lt;qemu:arg value=&apos;unix:/var/lib/libvirt/qemu/mcloud/578625568c7e41ccb827ecb9e01e3460.sock,server,nowait&apos;/&amp;gt;
  &amp;lt;/qemu:commandline&amp;gt;
&amp;lt;/domain&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Ubuntu设置允许root用户ssh登录</title><link>https://fuwari.vercel.app/posts/ubuntu%E8%AE%BE%E7%BD%AE%E5%85%81%E8%AE%B8root%E7%94%A8%E6%88%B7ssh%E7%99%BB%E5%BD%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/ubuntu%E8%AE%BE%E7%BD%AE%E5%85%81%E8%AE%B8root%E7%94%A8%E6%88%B7ssh%E7%99%BB%E5%BD%95/</guid><description>Ubuntu设置允许root用户ssh登录</description><pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;1.设置root用户密码&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo passwd root
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.修改sshd_config文件&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改下面配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#PermitRootLogin prohibit-password
#PasswordAuthentication yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PermitRootLogin yes
PasswordAuthentication yes
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.重启ssh服务&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo service sshd restart
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>nfs搭建</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/nfs%E6%90%AD%E5%BB%BA/nfs%E6%90%AD%E5%BB%BA/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/nfs%E6%90%AD%E5%BB%BA/nfs%E6%90%AD%E5%BB%BA/</guid><description>杭州宏杉学习笔记-nfs搭建</description><pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;CentOs&lt;/h2&gt;
&lt;h3&gt;nfs服务端&lt;/h3&gt;
&lt;h4&gt;1.安装nfs&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;yum install -y nfs-utils rpcbind
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;查看是否安装成功：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;rpm -qa nfs-utils
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;查看rpc服务是否自动启动&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;ss -tuln | grep 111
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.设置开机启动nfs相关服务&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;systemctl enable rpcbind
systemctl enable nfs-server
systemctl enable nfs-idmap
systemctl enable nfs-lock
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.启动nfs服务&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;systemctl start rpcbind
systemctl start nfs-server
systemctl start nfs-idmap
systemctl start nfs-lock
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4.创建共享目录并修改权限&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mkdir /nfs
chmod 777 /nfs
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5.修改配置文件&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;/nfs *(rw,sync,no_root_squash)&quot; &amp;gt;&amp;gt; /etc/exports
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ubuntu&lt;/h2&gt;
&lt;h3&gt;nfs服务端&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.安装nfs服务器&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install nfs-kernel-server
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.创建共享目录&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir /nfs
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.修改nfs服务器配置文件&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo sudo /etc/exports
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;指定nfs服务器共享目录及其属性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/nfs *(rw,sync,no_root_squash)

# * 允许所有网段访问，也可以使用具体IP
# rw 读写权力
# sync 数据同步写入内存和硬盘
# no_root_squash 允许root用户访问
# no_subtree_check 不检查父目录的权限
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;4.重启nfs服务&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart nfs-kernel-server
# 或者
sudo service nfs-kernel-server restart
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;5.查看nfs服务器的共享目录&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;showmount -e localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;nfs客户端&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.安装nfs客户端&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install nfs-common
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.挂载nfs服务端共享目录&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mount -t nfs 192.168.1.100:/nfs /mnt/nfs # 将192.168.1.100:/nfs挂载到/mnt/nfs目录下
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.卸载共享目录&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;umount /mnt/nfs
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>性能测试</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/</guid><description>杭州宏杉学习笔记-性能测试笔记</description><pubDate>Fri, 05 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;I/O命令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I/O命令基本属性包括：读写类型（Read/Write),命令块大小(cmdBlockSize)&lt;/li&gt;
&lt;li&gt;I/O命令性能指标：命令延时(cmdRespTime)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;读&lt;/code&gt;操作为前台操作，&lt;code&gt;写&lt;/code&gt;操作为后台操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;I/O模型&lt;/h3&gt;
&lt;p&gt;三个基本属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;平均块大小（AvgBlockSize）：I/O请求序列中所有请求的块大小的平均值，一般单位是B、KB、MB。通常简称为块大小（BlockSize）&lt;/li&gt;
&lt;li&gt;读写比例（ReadPct/WritePct）：读和写请求再I/O请求序列中的比例，可以用IOPS或MBPS来衡量&lt;/li&gt;
&lt;li&gt;随机度分布（SeqPct/RandPct)：顺序和随机请求在I/O请求序列中所占的比例，随机一般指均匀分布&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;I/O队列&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;img.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;I/O模型性能指标&lt;/h3&gt;
&lt;p&gt;特定I/O模型以及特定压力大小下I/O处理能力&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;吞吐量（MBPS）：每秒传输的数据量&lt;/li&gt;
&lt;li&gt;每秒请求数（IOPS）：每秒处理的I/O请求数&lt;/li&gt;
&lt;li&gt;平均延时（AvgRespTime）：I/O请求处理平均耗时，单位多用ms表示&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关联关系：&lt;/p&gt;
&lt;p&gt;$$$
MBPS = IOPS * BlockSize
$$$&lt;/p&gt;
&lt;p&gt;$$$
IOPS = QueDepth / RespTime
$$$&lt;/p&gt;
&lt;p&gt;$$$
MBPS = QueDepth * BlockSize / RespTime
$$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能影响的因素：CPU，内存，磁盘性能，网络性能，系统多层队列处理机制&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;vdbench&lt;/h3&gt;
&lt;p&gt;vdbench是一款用于基准测试存储产品的磁盘io负载生成器。&lt;/p&gt;
&lt;h4&gt;使用说明&lt;/h4&gt;
&lt;p&gt;vdbench的使用需要操作系统安装java运行环境，同时采用命令行的方式进行测试。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通常，可以使用下方命令进行测试：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;./vdbench -f &amp;lt;vdbench_file&amp;gt; -o &amp;lt;output_file&amp;gt;

# -f 指定测试负载参数文件，-o 指定测试结果输出目录，默认为当前目录
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;负载参数文件&lt;/h5&gt;
&lt;p&gt;vdbench按照&lt;strong&gt;一定的顺序&lt;/strong&gt;来解析文件，对于磁盘测试：General，HD，SD，WD和RD；对于文件系统测试：General，HS，FSD，FWD和RD
例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# General（通用定义）
dedupratio=30
journal=yes
report=host_detail
# HD（主机定义）
hd=hd1,system=localhost,vdbench=/home/vdbench,user=root,jvms=8
# FSD（文件系统存储定义）
fsd=fsd1,anchor=Y:\nasPerf,depth=2,width=3,files=1000,size=(64k,40,128k,30,512k,30)
# FWD（文件系统工作负载定义）
fwd=fwd1,fsd=fsd*,operation=read,xfersize=8k,fileio=random,fileselect=random,threads=32
# RD（运行定义）
rd=rd1,fwd=fwd1,fwdrate=max,format=yes,elapsed=300,interval=1
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;裸盘测试参数&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;1.General 通用定义&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# General parameters

compratio=nn # 定义写入磁盘的数据压缩比
dedupratio= nn # 重删比，默认为1 （全部数据块都是不同的）
dedupunit= nn # 重删数据块的大小，默认128KB
dedupsets= # 多少个重删数据，默认5%。比如1024个128K的SD，5%就是51个重删数据，也就是把重删的那一部分数据分成51组，每一组中的数据都是一样的。 
concatenatesds=yes #联合测试，把所有SD定义的存储当作一个LUN测试。
create_anchors=yes #在FSD定义中，生成根目录

ios_per_jvm=nnnnnn # 每一个java虚拟机每秒可运行的最大IO操作，默认为100,000
loop=nn # 重复运行测试多少次repeat nn times
loop=nn[s|m|h] # 在多少时间后，重复测试
pattern= # 重新定义生成的数据模型
port=nn # 重新指定Java端口
showlba=yes # 收集数据，用于showlba分析
messagescan=no # 不浏览、打印系统日志
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.HD 主机定义&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# HD（主机定义）
hd=default,vdbench=/home/vdbench,shell=ssh,user=root # 该行定义的参数为下面所有主机的全局参数，如果某个主机有单独的参数则会覆盖该行参数
hd=hd1,system=localhost,vdbench=/root/vdbench
hd=hd2,system=172.0.73.23
hd=hd3,system=172.0.73.24

# 常用参数说明
# hd=default 设置全局参数
# hd=localhost 设置当前主机参数
# hd=hd1 添加主机参数
# system=xxx 指定主机ip
# vdbench=xxx 添加主机的vdbench路径
# jvms=nn 指定java虚拟机数量
# shell=xxx 指定通信协议，以控制远程主机开启vdbench
# user=xxx 指定远程主机登录用户名
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.SD 存储定义，设置需要测试的磁盘、lvm卷和文件相关参数&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# SD
sd=sd1,host=hd1,lun=/dev/mapper/mpathb,openflags=o_direct,thread=8 
# 定义了一个名为sd1的测试目标。它使用8个并发线程，通过Direct I/O的方式，对连接在 hd1 主机上、由多路径软件管理的名为 /dev/mapper/mpathb的LUN，进行性能压测。
sd=sd2,host=hd2,lun=/dev/mapper/mpathc,openflags=o_direct,thread=8

# 常用参数说明
# sd=default # 设置全局参数
# sd=name # 设置该sd的别名
# dedupratio= # 参考General中的定义，除了dedupunit必须在General中定义外，其余的重删参数都可以给特定的SD定义。
# dedupsets=
# deduphotsets=
# dedupflipflop=
# hitarea=nn # 读命中大小，默认1m。配合rhpct、whpct使用
# host=name # 指定该SD在哪台主机，如果不设置 默认在当前主机
# journal=xxx # 指定检验日志目录
# lun=lun_name # 指定磁盘或文件
# openflags=(flag,..) # 通过特定的方式打开LUN或文件，绕过缓存常用的参数；常见参数：linux：o_direct or directio,o_dsync,o_rsync,o_sync,fsync;windows:directio,o_dsync,o_rsync,o_sync
# range=(nn,mm) # 指定测试的范围，默认是全盘
# size=nn # 用于工作负载的文件/磁盘大小（非必需）
# threads=nn # 该SD的最大并发数（outstanding I/O），默认为8
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;4.WD 工作负载定义&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;wd=wd1,sd=(sd1,sd2),rdpct=100,xfersize=4k

# 常用参数说明
# wd=default # 设置工作负载全局参数
# wd=name # 设置该工作负载的别名
# sd=xx # 指定该工作负载用于那个存储定义（sd）
# host=host_label # 指定哪个主机运行该工作负载，默认为本地主机
# iorate=nn # 设置该工作负载的固定IOPS
# openflags=(flag,..) # 通过特定的方式打开lun或文件
# priority=nn # 该工作负载的优先级，=1 最高，往后递降
# range=(nn,nn) # 限定该工作负载在SD上的作用范围
# rdpct=nn # 读占比，默认100
# rhpct=nn # 读命中占比，默认0
# seekpct=nn # 随机IO占比，默认seekpct=100或seekpct=random
# skew=nn # 该工作负载占总IO的比例，也就是可以规定不同的工作负载比例
# streams=(nn,mm) # 在同一个设备（lun/文件）上建立多个顺序IO流
# stride=(min,max) # To allow for skip-sequential I/O.
# threads=nn # IO并发数，只有在联合SD的情况下能使用（见SD concatenation)
# whpct=nn # 写命中占比，默认为0
# xfersize=nn # IO块大小，默认4K
# xfersize=(n,m,n,m,..) # 指定IO块大小的分布
# xfersize=(min,max,align) # 在(min,max)的范围内随机生成IO块大小
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;5.RD 运行定义&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;rd=run1,wd=(wd1,wd2),iorate=1000,elapsed=60,interval=5
# 定义了一个名为run1的测试任务：同时启动wd1和wd2两种工作负载，整体的I/O速率限制在1000IOPS。持续运行60秒，且每隔5秒就会在屏幕上打印一次性能统计信息。

# 常用参数说明
# rd=default # 设置rd全局参数
# rd=name # rd别名
# wd=xx # 该RD作用于哪个工作负载
# sd=xxx # 该RD作用于哪些SDs（可选）
# interval=nn # 打印间隔
# elapsed=nn # 运行时间，默认30秒
# maxdata=nn. # 本次测试的最大文件大小。当elapsed或者maxdata达到设定值，IO都会停止
# openflags=xxxx # 通过特定的方式打开文件或LUN
# warmup=nn # 预热时间
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;文件系统测试参数&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;1.FSD 文件系统定义&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;fsd=fsd1,anchor=/dir1,depth=2,width=2,files=2,size=128k

# 常用参数说明
# fsd=name # 设置FSD别名
# fsd=default # FSD全局参数
# anchor=/dir/ # 测试目录
# depth=nn # 目录深度
# distribution=all # 每个目录都生成文件，默认只在最底层目录生成文件
# width=nn # 设置每层级多少个目录
# files=nn # 每个最底层目录生成多少个文件
# openflags=(flag,..) # 通过特定方式处理IO请求
# shared=yes/no # 是否共享FSD给不同的Slaves或主机。文件数量很大的时候，为了减少slaves间关于文件状态信息的交流，可以使用 shared ，让每个slaves使用属于自己的那一部分文件。
# sizes=(nn,nn,…..) # 文件大小
# totalsize=nnn # 总文件大小
# journal=dir # 数据检验日志目录
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.FWD 文件系统工作负载定义&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;fwd=fwd1,fsd=fsd1,operation=read,xfersize=4k,fileio=sequential,fileselect=random,threads=2

# 常用参数说明
# fwd=name # FWD别名
# fwd=default # FWD全局参数
# fsd=(xx,….) # 该工作负载作用于哪些FSD（文件系统定义）
# fileio=(random.shared) # 允许多个线程共用一个文件
# fileio=(seq,delete) # 顺序IO，当第一次写入时，先删除文件
# fileio=random # 随机文件IO
# fileio=sequential # 顺序文件IO
# fileselect=random/seq # 如何选择一个目录或文件
# host=host_label # 该工作负载作用于哪个主机
# operation=xxxx # 规定对文件的操作
# # mkdir, rmdir, create, delete, open, close, read, write, access, getattr and setattr    
# rdpct=nn # 混合读写中的读占比（只能用于  operation=read 和 operation=write）
# skew=nn # 该工作负载占比
# threads=nn # 线程数 （文件数需要大于线程数）
# xfersize=(nn,…) # 读写文件时的IO块大小
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.RD （文件系统独有的参数）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;rd=rd1,fwd=fwd1,fwdrate=max,format=yes,elapsed=10,interval=1

# 常用参数说明
# fwd=(xx,yy,..) # 该运行定义作用于哪个工作负载
# fwdrate=nn # 每秒的操作数
# format=yes/no/only/restart/clean/once/directories # 是否重新创建（格式化）XXX
# operations=xx # 覆盖FWD中的文件操作
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;测试示例&lt;/h5&gt;
&lt;p&gt;测试参数：&lt;/p&gt;
&lt;p&gt;云主机3块盘，每块盘8线程，读占比70%，io速率尽可能最大，测试2小时，每5s打印结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 3lun_4k
hd=default,vdbench=/root/vdbench50406,user=root,shell=ssh
hd=host1,system=localhost

sd=sd1,lun=/dev/vdb,openflags=directio,threads=8
sd=sd2,lun=/dev/vdc,openflags=directio,threads=8
sd=sd3,lun=/dev/vdd,openflags=directio,threads=8

wd=wd1,sd=sd*,rdpct=70,xfersize=4k
rd=run1,wd=wd1,iorate=max,elapsed=2h,interval=5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nohup ./vdbench -f 3lun_4k -vr &amp;amp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;观察测试结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tail -f nohup.out
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看测试报告：summary.html&lt;/p&gt;
&lt;h3&gt;Unixbench(服务器跑分软件)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;UnixBench是一个类 Unix系统（Unix、BSD、Linux）下的开源性能测试工具，被广泛用于测试Linux系统主机的性能。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UnixBench的主要测试项有：系统调用、读写、进程、图形化测试、2D、3D、管道、运算、C库等系统基准性能提供测试数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;工具下载地址：&lt;a href=&quot;https://download.csdn.net/download/qq_37330657/90014475?spm=1001.2014.3001.5503&quot;&gt;https://download.csdn.net/download/qq_37330657/90014475?spm=1001.2014.3001.5503&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;运行测试&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;测试系统可能缺失&lt;code&gt;perl&lt;/code&gt;、&lt;code&gt;gcc&lt;/code&gt;等依赖，需手动通过包管理器安装依赖&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;测试指令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./Run -c 1 -c 128
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-c&lt;/code&gt;为并发数，该指令会运行单核和128核（全核）两轮测试&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;测试输出解析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 x Dhrystone 2 using register variables 1 2 3 4 5 6 7 8 9 10
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 x&lt;/code&gt;:单核测试&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Dhrystone 2 using register variables&lt;/code&gt;:测试项目名称&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1 2 3...&lt;/code&gt;:样本测试进度，测试结果为运行多次样本取平均值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;测试指标解析&lt;/h4&gt;
&lt;h4&gt;1.CPU 计算性能&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Dhrystone 2 using register variables: 衡量整数运算性能，适用于 CPU 计算能力对比。&lt;/li&gt;
&lt;li&gt;Double-Precision Whetstone: 测试浮点运算能力，适用于科学计算。&lt;/li&gt;
&lt;li&gt;Execl Throughput: 衡量系统执行 &lt;code&gt;execl()&lt;/code&gt; 的能力，反映进程创建与管理性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.文件 I/O 性能&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;File Copy (1024 bufsize, 2000 maxblocks): 测试文件复制性能，涉及 I/O 读写速度。&lt;/li&gt;
&lt;li&gt;File Copy (256 bufsize, 500 maxblocks): 小块文件复制性能。&lt;/li&gt;
&lt;li&gt;File Copy (4096 bufsize, 8000 maxblocks): 大块文件复制性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.进程与线程管理&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Pipe Throughput: 评估进程间通信（IPC）管道的吞吐能力。&lt;/li&gt;
&lt;li&gt;Pipe-based Context Switching: 测试进程上下文切换的效率。&lt;/li&gt;
&lt;li&gt;Process Creation: 评估进程创建的开销。&lt;/li&gt;
&lt;li&gt;Shell Scripts (1 concurrent): 单个 shell 脚本执行能力。&lt;/li&gt;
&lt;li&gt;Shell Scripts (8 concurrent): 8 个 shell 并行执行能力。&lt;/li&gt;
&lt;li&gt;System Call Overhead: 评估系统调用的开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;输出结果示例&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Benchmark Run: 三 10月 22 2025 10:21:35 - 10:53:01  # 测试时间
128 CPUs in system; running 128 parallel copies of tests  # 128核测试

Dhrystone 2 using register variables     1307112026.1 lps   (10.0 s, 7 samples) # 项目的具体数据 整数运算13.07亿次/秒
Double-Precision Whetstone                   434592.7 MWIPS (10.0 s, 7 samples)
Execl Throughput                               6792.8 lps   (29.7 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks        104257.4 KBps  (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks           26708.5 KBps  (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks        376215.1 KBps  (30.0 s, 2 samples)
Pipe Throughput                            14919076.5 lps   (10.0 s, 7 samples)
Pipe-based Context Switching                4045172.9 lps   (10.0 s, 7 samples)
Process Creation                              25810.9 lps   (30.0 s, 2 samples)
Shell Scripts (1 concurrent)                  50317.4 lpm   (60.1 s, 2 samples)
Shell Scripts (8 concurrent)                   7971.9 lpm   (60.3 s, 2 samples)
System Call Overhead                        3563258.8 lps   (10.0 s, 7 samples)

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0 1307112026.1 112006.2
Double-Precision Whetstone                       55.0     434592.7  79016.8
Execl Throughput                                 43.0       6792.8   1579.7
File Copy 1024 bufsize 2000 maxblocks          3960.0     104257.4    263.3
File Copy 256 bufsize 500 maxblocks            1655.0      26708.5    161.4
File Copy 4096 bufsize 8000 maxblocks          5800.0     376215.1    648.6
Pipe Throughput                               12440.0   14919076.5  11992.8
Pipe-based Context Switching                   4000.0    4045172.9  10112.9
Process Creation                                126.0      25810.9   2048.5
Shell Scripts (1 concurrent)                     42.4      50317.4  11867.3
Shell Scripts (8 concurrent)                      6.0       7971.9  13286.5
System Call Overhead                          15000.0    3563258.8   2375.5
                                                                   ========
System Benchmarks Index Score                                        4261.3
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BASELINE&lt;/code&gt;: 固定的参考值，通常来自于1990年代在一台特定标准机器上运行测试得到的分数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RESULT&lt;/code&gt;: 测试结果&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INDEX&lt;/code&gt;：单项得分 RESULT ÷ BASELINE&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Index Score&lt;/code&gt;：综合得分 ⁿ√(INDEX₁ × INDEX₂ × INDEX₃ × ... × INDEXₙ)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;常见问题&lt;/h4&gt;
&lt;h5&gt;测试结果缺失全核数据&lt;/h5&gt;
&lt;p&gt;常见的原因是处理器线程数大于默认的16，这种情况下，解决方案是修改&lt;code&gt;Run&lt;/code&gt;文件内的&lt;code&gt;maxCopies&lt;/code&gt;数值，确保该数值大于或等于处理器线程数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Configure the categories to which tests can belong.
my $testCats = {
  &apos;system&apos;    =&amp;gt; { &apos;name&apos; =&amp;gt; &quot;System Benchmarks&quot;, &apos;maxCopies&apos; =&amp;gt; 16 },
  &apos;2d&apos;        =&amp;gt; { &apos;name&apos; =&amp;gt; &quot;2D Graphics Benchmarks&quot;, &apos;maxCopies&apos; =&amp;gt; 1 },
  &apos;3d&apos;        =&amp;gt; { &apos;name&apos; =&amp;gt; &quot;3D Graphics Benchmarks&quot;, &apos;maxCopies&apos; =&amp;gt; 1 },
  &apos;misc&apos;      =&amp;gt; { &apos;name&apos; =&amp;gt; &quot;Non-Index Benchmarks&quot;, &apos;maxCopies&apos; =&amp;gt; 16 },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Configure the categories to which tests can belong.
my $testCats = {
  &apos;system&apos;    =&amp;gt; { &apos;name&apos; =&amp;gt; &quot;System Benchmarks&quot;, &apos;maxCopies&apos; =&amp;gt; 128 },
  &apos;2d&apos;        =&amp;gt; { &apos;name&apos; =&amp;gt; &quot;2D Graphics Benchmarks&quot;, &apos;maxCopies&apos; =&amp;gt; 128 },
  &apos;3d&apos;        =&amp;gt; { &apos;name&apos; =&amp;gt; &quot;3D Graphics Benchmarks&quot;, &apos;maxCopies&apos; =&amp;gt; 128 },
  &apos;misc&apos;      =&amp;gt; { &apos;name&apos; =&amp;gt; &quot;Non-Index Benchmarks&quot;, &apos;maxCopies&apos; =&amp;gt; 128 },
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>分布式块存储</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E5%88%86%E5%B8%83%E5%BC%8F%E5%9D%97%E5%AD%98%E5%82%A8/%E5%88%86%E5%B8%83%E5%BC%8F%E5%9D%97%E5%AD%98%E5%82%A8/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E5%88%86%E5%B8%83%E5%BC%8F%E5%9D%97%E5%AD%98%E5%82%A8/%E5%88%86%E5%B8%83%E5%BC%8F%E5%9D%97%E5%AD%98%E5%82%A8/</guid><description>杭州宏杉学习笔记-分布式块存储</description><pubDate>Wed, 27 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;MDBS（MacroSAN Distributed Block Storage）：宏杉分布式块存储，通过组织服务器的本地硬盘，提供高性能和高可靠性的存储业务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储池：存储池是一种逻辑结构，将多个磁盘组成一个存储池，然后进行逻辑划分，主要用于数据隔离。存储池安全级别支持硬盘级（默认支持）、节点级、机柜级&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;卷：卷是存储池的逻辑划分，通常一个卷对应一个逻辑磁盘，卷大小必须小于等于存储池大小&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RCache（读缓存）：每个节点固定划分512MB内存作为卷的读缓存，单个Cache块大小为64KB。支持对单个卷启用禁用RCache。RCache能有效提升性能&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SSD读写缓存：选用SSD（NVMe/SAS/SATA）作为缓存盘，每块SSD盘均匀划分出N个区，每个分区对应一个HDD的数据盘，用作数据盘的读写缓存。目前2U服务器暂定N=6，即1块SSD对应6块HDD，SSD划分6个分区&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OSD：每个OSD对应一个HDD硬盘，运行在服务器上，是一个管理HDD的服务进程，负责存储数据、读写数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;读IO流程(&lt;em&gt;仅可能从本地副本读取数据&lt;/em&gt;)：
&lt;img src=&quot;img.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;从内存读Cache中获取数据，若命中，则直接返回数据，否则继续下一步&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;从SSD Cache中获取数据，若命中，则返回数据，并且如果该IO数据达到一定的读取次数将被缓存到RCache中；否则继续下一步&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;从HDD硬盘中获取数据，返回数据，如果该IO数据达到一定的读取次数将被缓存到SSD Cache中&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;写IO流程(&lt;em&gt;所有写IO均需经过内存读缓存，若有写命中读，则将对应读数据置为无效（旧数据已过时）&lt;/em&gt;)：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小IO：将小IO缓存在SSD Cache后，完成本节点的写操作。&lt;strong&gt;当SSD写缓存中的数据达到一定量或者一定周期后，批量将数据写入HDD&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;大IO：直接透写入HDD&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多副本：MDBS利用多副本机制保证数据可靠性，同一份数据可以复制保存为2~3个副本；创建存储池时，可选择副本数量（2或3），以及数据可读写策略（1/3，2/3）：指在决策过程中，需要多少个副本达成一致才能进行下一步。
如2/3，一次写操作必须至少成功写入2个副本，这次写操作才会被系统认为是成功的。一次读操作必须至少从2个副本读取到数据，并比对一致后，才能将结果返回给用户&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;精简配置：可创建容量大于存储池物理容量的逻辑卷，以满足特殊的容量规划需求，再按&lt;strong&gt;需分配空间&lt;/strong&gt;，提升空间的利用率&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;快照：将用户的卷数据在某个时间点的状态保存下来，后续可恢复数据。MDBS快照数据在存储时采用ROW（Redirect on write）写时重定向机制，不会造成源卷性能下降
&lt;img src=&quot;img_1.png&quot; alt=&quot;img_1.png&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建快照时会将源卷拷贝一份，快照卷指向源卷相同的存储空间&lt;/li&gt;
&lt;li&gt;当源卷发生写入时，源卷会将原来的存储空间拷贝出一份用于写入并替代原存储空间（COW，copy on write 写时拷贝）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>3浪潮集群MDBS&amp;MCloud环境搭建笔记</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mcloud%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/3%E6%B5%AA%E6%BD%AE%E9%9B%86%E7%BE%A4%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mcloud%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/3%E6%B5%AA%E6%BD%AE%E9%9B%86%E7%BE%A4%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</guid><description>杭州宏杉学习笔记-3浪潮集群MDBS&amp;MCloud环境搭建笔记</description><pubDate>Sun, 17 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一.Deployer装机&lt;/h2&gt;
&lt;p&gt;1.使用U盘给3台浪潮服务器装机Deployer_V1.0.18,系统：Deployer-openEuler-22.03-LTS-SP4-x86_64.iso
2.检查系统版本：cat /etc/os-release
&lt;img src=&quot;img.png&quot; alt=&quot;img.png&quot; /&gt;
3.装机完成后检查网卡，所有服务器网卡名称应相同
&lt;img src=&quot;img_1.png&quot; alt=&quot;img_1.png&quot; /&gt;
其中enp94s为GE网卡做管理网，enp26s为10GE网卡做业务/迁移网，enp59s为25GE网卡做存储网&lt;/p&gt;
&lt;h2&gt;二.交换机侧配置&lt;/h2&gt;
&lt;p&gt;1.登录管理网交换机配置管理网端口聚合组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;configure terminal # 进入全局配置模式
interface eth-0-10 # 进入网口配置模式
channel-group 10 mode active # 创建端口聚合组
exit
# ...其余两个网口配置方式相同

interface agg 10 # 进入端口聚合组配置模式
mlag 10 # 创建MLAG组
exit
# ... 剩余两个MLAG组配置方式相同
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.2交换机同理&lt;/p&gt;
&lt;p&gt;2.登录业务网交换机配置业务网端口聚合组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;configure terminal # 进入全局配置模式
interface eth-0-28 # 进入网口配置模式
switchport mode trunk # 设置端口 trunk
channel-group 28 mode active # 创建端口聚合组
exit
# ...其余两个网口配置方式相同

interface agg 28 # 进入端口聚合组配置模式
mlag 28 # 创建MLAG组
exit
# ... 剩余两个MLAG组配置方式相同
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.2交换机同理&lt;/p&gt;
&lt;p&gt;3.登录存储网交换机配置存储网端口vlan，巨帧支持和聚合组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;configure terminal # 进入全局配置模式

vlan 204 # 创建vlan 204

interface range eth-0-25 - 30 # 进入范围端口配置模式
switchport access vlan 204 # 将端口加入vlan 204
jumboframe enable # 允许巨帧
exit

interface eth-0-25 # 进入网口配置模式
channel-group 25 mode active # 创建端口聚合组
exit
interface eth-0-26 # 进入网口配置模式
channel-group 25 mode active
exit

interface eth-0-27 # 进入网口配置模式
channel-group 27 mode active # 创建端口聚合组
exit
interface eth-0-28 # 进入网口配置模式
channel-group 27 mode active
exit

interface eth-0-29 # 进入网口配置模式
channel-group 29 mode active # 创建端口聚合组
exit
interface eth-0-30 # 进入网口配置模式
channel-group 30 mode active
exit
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三.服务器配置&lt;/h2&gt;
&lt;p&gt;1.检查系统版本（cat /etc/version),分区大小，磁盘数量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /etc/version
lsblk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.执行网络初始化mc_first_config_network&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mc_first_config_network
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.管理网配置bond0，并将管理网口加入bond0，配置管理网ip，网关和子网掩码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mc_bond_create_del -c bond0 # 创建bond0
mc_bond_add_del_nic -a bond0 enp94s0f0 # 添加管理网口
mc_bond_add_del_nic -a bond0 en94s0f1
# 其余两台服务器执行相同指令

# 每台服务器一个管理网ip，如：
mc_config_nic_and_bond_ip -i bond0 172.20.2.204 255.255.0.0 y 172.20.0.254 # 配置管理网ip

mc_config_nic_and_bond_ip -i bond0 172.20.2.206 255.255.0.0 y 172.20.0.254 # 配置管理网ip

mc_config_nic_and_bond_ip -i bond0 172.20.2.208 255.255.0.0 y 172.20.0.254 # 配置管理网ip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.存储网配置bond2，并将存储网口加入bond2，配置存储网ip和子网掩码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mc_bond_create_del -c bond2 # 创建bond0
mc_bond_add_del_nic -a bond2 enp59s0f0np0 # 添加存储网口
mc_bond_add_del_nic -a bond2 enp59s0f1np1
# 其余两台服务器执行相同指令

# 每台服务器一个存储网ip，如：
mc_config_nic_and_bond_ip -i bond2 192.168.2.4 255.255.0.0 y 172.20.0.254 # 配置存储网ip

mc_config_nic_and_bond_ip -i bond2 192.168.2.6 255.255.0.0 y 172.20.0.254 # 配置存储网ip

mc_config_nic_and_bond_ip -i bond2 192.168.2.8 255.255.0.0 y 172.20.0.254 # 配置存储网ip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5.业务网配置bond1，并将业务网口加入bond1&lt;/p&gt;
&lt;p&gt;6.迁移网复用业务网，配置迁移网ip和子网掩码&lt;/p&gt;
&lt;h2&gt;四.部署MCloud和MDBS&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;img_2.png&quot; alt=&quot;img_2.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;img_3.png&quot; alt=&quot;img_3.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;img_4.png&quot; alt=&quot;img_4.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;img_5.png&quot; alt=&quot;img_5.png&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>MCloud云平台</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mcloud%E4%BA%91%E5%B9%B3%E5%8F%B0/mcloud%E4%BA%91%E5%B9%B3%E5%8F%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mcloud%E4%BA%91%E5%B9%B3%E5%8F%B0/mcloud%E4%BA%91%E5%B9%B3%E5%8F%B0/</guid><description>杭州宏杉学习笔记-MCloud云平台相关功能和知识</description><pubDate>Mon, 04 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.性能优化工具包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;windows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内部监控Agent：用于获取云主机的CPU，内存和磁盘容量的监控数据&lt;/li&gt;
&lt;li&gt;QGA：云主机与宿主机交互的应用程序，不依赖网络&lt;/li&gt;
&lt;li&gt;Virtio驱动：优化云主机与宿主机的I/O性能，包括硬盘驱动,网卡驱动，内存驱动，pci设备驱动等&lt;/li&gt;
&lt;li&gt;Cloudbase-Init：win的初始化工具，实现导入UserData等定制功能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;linux：内部监控Agent，QGA&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.Linux云主机依赖性能优化工具的功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;内部监控：内部监控agent获取CPU，内存，磁盘等数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在线修改配置：QGA实现的云主机与物理机之间的数据交互，不需要依赖网络&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改主机名&lt;/li&gt;
&lt;li&gt;在线修改IP地址&lt;/li&gt;
&lt;li&gt;在线修改MAC地址&lt;/li&gt;
&lt;li&gt;修改云主机密码&lt;/li&gt;
&lt;li&gt;同步配置（网卡IP地址，子网掩码，网关，DNS，MTU等）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;故障检测：要求云主机内部有&lt;code&gt;pvpanic&lt;/code&gt;模块(&lt;code&gt;lsmod | grep pvpanic&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.系统镜像格式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;iso：光盘映像，通常通过光盘或USB引导启动，如操作系统安装盘。iso只读，无法直接修改&lt;/li&gt;
&lt;li&gt;raw：原始的磁盘镜像，未经加工的裸磁盘格式（相应的性能也就比较高），接近物理磁盘，占用的空间与实际使用的空间一致&lt;/li&gt;
&lt;li&gt;qcow2(qemu copy-on-write)：一种虚拟化镜像格式，支持快照和加密&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.关于云主机系统镜像（iso,raw,qcow2,vmdk）格式和主存储类型(mdbs,sharesan,sharedblock，local)与云主机根盘格式(raw,qcow2)有什么关系？&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;主存储类型&lt;/th&gt;
&lt;th&gt;镜像格式&lt;/th&gt;
&lt;th&gt;根云盘格式&lt;/th&gt;
&lt;th&gt;数据云盘格式&lt;/th&gt;
&lt;th&gt;快照格式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;iso,qcow2,raw&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SharedBlock&lt;/td&gt;
&lt;td&gt;iso,qcow2,raw&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NFS&lt;/td&gt;
&lt;td&gt;iso,qcow2&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NFS&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MStor&lt;/td&gt;
&lt;td&gt;iso,raw&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MStor&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ShareSAN&lt;/td&gt;
&lt;td&gt;iso,raw&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ShareSAN&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;td&gt;qcow2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;local&lt;/code&gt;，&lt;code&gt;shareblock&lt;/code&gt;类型的主存储，镜像&lt;strong&gt;不论格式&lt;/strong&gt;，根云盘都为&lt;strong&gt;qcow2&lt;/strong&gt;类型（对于local主存储，若用raw格式的镜像去创建云主机，根云盘会先为raw，最后转为qcow2。因为raw不支持快照等虚拟机的功能）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;mdbs&lt;/code&gt;和&lt;code&gt;sharesan&lt;/code&gt;类型的主存储，则根系统镜像格式有关，若系统镜像为iso或raw格式，根云盘格式为raw；若系统镜像格式为qcow2，则根云盘格式为qcow2&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.创建镜像中，本地上传和URL上传有什么区别？为什么URL上传会比本地上传快？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本地上传：镜像文件会先上传到当前管理节点的临时路径下，随后再通过这个临时路径上传到镜像服务器&lt;/li&gt;
&lt;li&gt;URL上传：直接通过URL上传到镜像服务器。因此比本地上传快。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.云盘的停用功能：已挂载云主机的云盘停用后对云主机的使用没有影响，停用的云盘无法在加载云盘中选中，也无法创建云盘镜像&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7.MCloud云平台物理机停用后，不影响物理机上原有资源，但无法申请新资源。若云主机属于LocalStorage类型的主存储和已停用的物理机，则&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;（1）该物理机上的云主机不支持在线修改计算规格，不能申请新资源&lt;/li&gt;
&lt;li&gt;（2）“运行中”状态云主机可以重启、暂停、恢复，但不支持关闭电源后开启云主机(关闭电源后再重启会重新分配物理机)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;8.云主机创建后有3种启动策略：&lt;code&gt;InstantStart&lt;/code&gt;,&lt;code&gt;JustCreate&lt;/code&gt;,&lt;code&gt;Createstopped&lt;/code&gt;。云平台默认&lt;code&gt;InstantStart&lt;/code&gt;策略，创建云主机时和全局配置中并无该参数的配置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;9.云主机CPU的三种模式：（修改CPU模式后，需重启云主机才能生效,在&lt;strong&gt;创建集群&lt;/strong&gt;时，也可以&lt;em&gt;指定集群的云主机CPU模式&lt;/em&gt;）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1）兼容模式：通用标准虚拟化CPU，兼容性好，但是无法提供最优性能。迁移场景推荐设置该模式&lt;/li&gt;
&lt;li&gt;2）物理机匹配模式：虚拟化内核软件模拟与物理机服务器CPU接近或一致的CPU型号，相对于兼容模式性能更好，但迁移时兼容性较差&lt;/li&gt;
&lt;li&gt;3）直通模式：将物理机CPU型号和大部分功能透传给云主机，能提供最优性能，但迁移时兼容性很差&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;10.FT容错云主机：可实现CPU状态、内存数据、硬盘数据的实时同步，为主云主机在其他物理机上创建一台配置完全相同的备用云主机，当主云主机发生故障时将立即触发切换，由备用云主机支撑业务运行。适用于效率要求不高，但业务不允许中断的场景&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;11.FT容错云主机仅支持使用qcow2格式的镜像创建&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;12.FT容错云主机仅支持使用Local主存储创建，不支持挂载数据盘，可关机后对根盘扩容&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;13.IP分配策略中顺序分配与循环分配的区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;顺序分配：系统按ip从小到大排序，从第一个可用IP开始分配，&lt;strong&gt;中途释放的ip会在下次分配时使用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;循环分配：系统按ip从小到大排序，从第一个可用IP开始分配，&lt;strong&gt;中途释放的ip会在将现有空闲ip分配完一轮后再次使用&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;14.修改后需重启云主机的功能：故障检测，USB重定向，计算规格在线修改（全局设置），加载默认网卡，修改控制台密码&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;15.共享云盘仅支持MDBS，ShareSAN，SharedBlock类型的主存储，且共享云盘仅支持&lt;strong&gt;scsi&lt;/strong&gt;总线类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;16.ide总线类型的云盘不支持在线挂卸载，且每台云主机至多挂载&lt;strong&gt;3&lt;/strong&gt;块ide总线类型的云盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;17.更改主存储时迁移数据的方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MCloud迁移：基于云平台的数据拷贝能力执行迁移，适用于通用场景，迁移时会占用云平台的计算资源，有一定耗时。不支持保留快照&lt;/li&gt;
&lt;li&gt;MStor迁移：基于MStor主存储的跨池迁移卷功能实现数据快速迁移，仅适用于&lt;strong&gt;源和目标位于同一MStor主存储&lt;/strong&gt;的迁移场景。支持保留快照&lt;/li&gt;
&lt;li&gt;ShareSAN迁移：基于ShareSAN的NDM（Non-interrupt Data Migration，无中断数据迁移）功能实现数据快速迁移（ShareSAN需要有NDM License），仅适用于&lt;strong&gt;源和目标位于同一ShareSAN主存储&lt;/strong&gt;的迁移场景。不支持保留快照&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;18.未安装性能优化工具以及安装了性能优化工具的legacy云主机主板类型为&lt;strong&gt;i440fx&lt;/strong&gt;，安装了性能优化工具的uefi云主机主板类型为&lt;strong&gt;q35&lt;/strong&gt;.q35类型的主板不支持ide总线类型的云盘，会启动报错：&lt;code&gt;IDE controllers are unsupported for this QEMU binary or machine type&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;19.ide根盘的云主机在安装性能优化工具重启后，ide类型的盘会变为virtio类型的盘（包括根盘）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;20.windows云主机需安装性能优化工具后才能识别scsi和virtio类型的云盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;21.云平台资源预留：&lt;/p&gt;
&lt;p&gt;$$
集群可用CPU资源=（CPU总颗数&lt;em&gt;单颗CPU超线程数-(M+ 12&lt;/em&gt;n)*1）*超分率 *0.8
$$&lt;/p&gt;
&lt;p&gt;M为集群所有数据盘数，n为计算存储节点数&lt;/p&gt;
&lt;p&gt;$$
单节点可用内存 = 总内存 - 26GB  - 缓存分区数 * 2GB – 数据盘占用内存
$$&lt;/p&gt;
&lt;p&gt;26GB为操作系统8GB+10GMDBS基础服务+8GMCloud云平台管理，数据盘占用=数据盘数*N（数据盘容量&amp;lt;=8TB,N=4GB，否则N=5GB）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;22.仲裁者的作用：避免脑裂问题，确保数据一致性。当两个数据中心之间的网络链路中断，但每个站点的存储系统都正常运行时，两个站点都认为对方故障，同时对外提供读写服务，导致数据不一致。仲裁者此时就需要判断哪个站点应继续服务，哪个站点应暂停服务。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>MCloud常用指令</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mcloud%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4/mcloud%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mcloud%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4/mcloud%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4/</guid><description>杭州宏杉学习笔记-MCloud常用指令</description><pubDate>Wed, 30 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;mcloud-bin&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;获取mcloud服务运行状态及版本信息&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mcloud-bin status
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;启动、重启、停止mcloud服务&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mcloud-bin start
mcloud-bin restart
mcloud-bin stop
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;检查管理节点数据库状态(检查双管理节点数据库不一致)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mcloud-bin db-stauts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查看mcloud配置&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mcloud-bin show_configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改mcloud配置（修改配置后需重启所有管理节点的mcloud服务）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mcloud-bin configure [配置名]=[value]
# 例如修改从管理节点的ssh端口
mcloud-bin configure peer.sshPort=20023
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;mcloud-cli&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;登录（若未做特殊声明，下述的指令均为在登录的基础上执行）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;login withAccount accountName=admin password=password
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;集群开启防火墙：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;globalConfig update name=reject.all.ports category=iptables value=true
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;开启导出镜像端口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;globalConfig update category=iptables name=mcloud.allow.ports value=8080,60
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改numa值（对应计算规格在线修改）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;globalConfig update category=vm name=numa value=[true/false]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对接MacroSAN存储时，若ODSP的版本低于V3.0.19需要修改LUN类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若MacroSAN存储上存储池为全HDD Pool，需要修改LUN类型为Thick&lt;/li&gt;
&lt;li&gt;其他情况修改LUN类型为Thin&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;globalConfig update category=macrosan name=san.lun.mode value=[Thick/Thin]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;以FC的方式对接MacroSAN时，需要配置FC端口的黑名单&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;globalConfig update category=storageDevice name=FC.storage.blacklist value=50:0b:34:20:03:a2:1e:05,50:0b:34:20:03:a2:06:05(物理端口地址)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设置物理机保留内存&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;以平台为单位(MCloud平台内所有物理机计算保留内存容量一致)：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gloalConfig update category=kvm name=reservedMemory value=16G
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;以集群为单位(MCloud存在多个集群，单个集群内所有物理机计算保留内存容量一致，不同集群间物理机计算保留内存容量不一致)：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource updateConfig resourceUuid=[集群uuid] category=kvm name=reservedMemory value=16G
resource queryConfig resourceUuid=[集群uuid] category=kvm name=reservedMemory  // 查询
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;以物理机为单位&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource updateConfig resourceUuid=[物理机uuid] category=kvm name=reservedMemory value=16G
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;开启全局容错能力(搭建孪星双节点)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;globalConfig update category=ft name=ft.cluster.enable value=true
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;云盘更改主存储后，SAN对应的lun名称/mdbs对应卷名称都会改变，查询更改后的名称&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemTag query tag~=overwrite sortBy=createDate sortDirection=desc resourceUuid=原云主机云盘uuid
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;开启混盘配置（根盘和数据盘可以跨主存储）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;globalConfig update category=vm name=attach.mixed.volume value=true
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;关机修改云主机计算规格&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vm changeOffering instanceOfferingUuid=(计算规格uuid) vmInstanceUuid=(云主机uuid)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查询云主机&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vm query uuid=[云主机uuid]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过查询云主机，可以获取到云主机的硬盘的vid（installpath:mstor://Pool-2/2/&lt;strong&gt;21&lt;/strong&gt;），对应wwn：600b342d430615785202de&lt;strong&gt;0000000021&lt;/strong&gt;。可在mdbs上直接搜索wwn后几位的vid搜索到卷&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-3.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;云盘更改主存储后，SAN对应的lun名称/mdbs对应卷名称都会改变，查询更改后的名称也可以通过上述方法查询&lt;/p&gt;
&lt;h2&gt;MDBS CLI&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;登录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 元数据节点
mcli
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过卷的uuid查询卷信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;volume mgt query --name [uuid]

mdbs&amp;gt; volume mgt query --name fcf4563947634cfab5edf4fc87273760
Command succeed.
=================================================
name:fcf4563947634cfab5edf4fc87273760 # name即对应uuid
vid:133 # 对应卷installpath: mstor://Pool-2/2/133
capacity(MB):40960
used_cap(MB):3268.0
size_kbyte:41943040
used_kbyte:3346432
status:0
qos:0
pid:2 # 对应卷installpath: mstor://Pool-2/2/133
stripe_size(KB):64
stripe_width:2
strategy_id: 0
related_snap: NA
lun_type: clone
rcache: 1
master_proxy: 1
snap_num: 0
use_type: AP
create_timestamp: 2025-11-14T16:05:30.497+08:00
delete_timestamp: 0
wwn: 600b342d4306157852fd580000000133 # 0000000133对应vid
sn: 00b342d43061578528fd580000000133
nguid: d43061578528fd5800b3420000000133
mr_rate: 0
verify: 0
desc: N/A
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;virsh&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;列出运行中的云主机&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;virsh list
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;列出所有云主机&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;virsh list --all
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;查看云主机&lt;strong&gt;当前配置&lt;/strong&gt;信息(实时配置)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;virsh dumpxml [UUID]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;交互式地查看云主机的&lt;strong&gt;持久化配置&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;virsh edit [UUID]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;云主机打流量更改主存储，任务超时报错，云主机仍在迁移中状态&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;virsh domjobinfo 云主机uuid
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SAN&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;查询SAN设备HA状态&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 进入odsp-cli
/odsp/bin/odsp-cli
# 获取高可用状态
ha mgt getstatus
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>MCloud网络知识</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mclouod%E7%BD%91%E7%BB%9C%E7%9F%A5%E8%AF%86%E7%AC%94%E8%AE%B0/mcloud%E7%BD%91%E7%BB%9C%E7%9F%A5%E8%AF%86%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mclouod%E7%BD%91%E7%BB%9C%E7%9F%A5%E8%AF%86%E7%AC%94%E8%AE%B0/mcloud%E7%BD%91%E7%BB%9C%E7%9F%A5%E8%AF%86%E7%AC%94%E8%AE%B0/</guid><description>杭州宏杉学习笔记-MCloud网络知识</description><pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;相关术语&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;广播域：网络中能接收到同一广播消息的所有设备的集合，任何一个设备发送的广播帧都会被该域的其他设备接收（三层交换机，路由器）&lt;/li&gt;
&lt;li&gt;冲突域：网络中共享同一带宽，可能会发生数据冲突的区域，同一时间只能有一个设备发送数据（交换机，网桥）&lt;/li&gt;
&lt;li&gt;二层网络：二层广播域（交换机），仅通过ARP广播获取MAC地址实现通讯&lt;/li&gt;
&lt;li&gt;三层网络：云主机使用的网络配置，包括ip范围，网关，dns，子网掩码等，数据传送通过ip转发&lt;/li&gt;
&lt;li&gt;共有网络：一般为可直接访问互联网的网络&lt;/li&gt;
&lt;li&gt;私有网络：云主机之间连接和使用的内部网络&lt;/li&gt;
&lt;li&gt;扁平网络：与物理机网络直通，也可以直接访问互联网&lt;/li&gt;
&lt;li&gt;VPC网络：云主机使用的私有网络，可通过VPC路由器访问互联网&lt;/li&gt;
&lt;li&gt;弹性IP：基于NAT，将一个网络地址转换为另一个网络地址（地址映射）&lt;/li&gt;
&lt;li&gt;虚拟IP：数据包会被发送到虚拟IP，再路由至云主机&lt;/li&gt;
&lt;li&gt;Underlay网络：相对于Overlay网络，物理网络成为Underlay网络&lt;/li&gt;
&lt;li&gt;Overlay网络：相对于Underlay网络，运行在物理网络之上的虚拟逻辑网络称为Overlay网络&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;TCP/IP五层网络模型&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层数&lt;/th&gt;
&lt;th&gt;结构&lt;/th&gt;
&lt;th&gt;主要设备及协议&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;应用层&lt;/td&gt;
&lt;td&gt;HTTP，FTP，SMTP，DHCP等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;传输层&lt;/td&gt;
&lt;td&gt;TCP，UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;网络层&lt;/td&gt;
&lt;td&gt;IP，ARP（三层交换机，路由器）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;数据链路层&lt;/td&gt;
&lt;td&gt;网桥，交换机&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;物理层&lt;/td&gt;
&lt;td&gt;集线器（HUB），中继器&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Linux虚拟网络设备&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tun设备是一个三层设备，读取和写入IP数据包&lt;/li&gt;
&lt;li&gt;Tap设备是一个二层设备，读取和写入MAC数据帧，与真实的物理网卡能力接近&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;img.png&quot; alt=&quot;img.png&quot; /&gt;
虚拟机上的eth[x],为虚拟网卡，对应宿主机上的vnic[x]（Tap设备）,它们为一对设备，共同完成虚拟机与外部的通信&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bridge网桥：同物理交换机差不多，维护一个转发数据库根据MAC地址转发报文。物理设备和虚拟设备都可以桥接到bridge上，接收数据时，由bridge决定数据的走向，但是发送数据时不会被转发到bridge上，会寻找下一个发送出口
&lt;img src=&quot;img_1.png&quot; alt=&quot;img_1.png&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Veth pair&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;veth是虚拟以太网卡的缩写（Virtual Ethernet）。veth设备总是成对的，所以被称为veth pair。&lt;/li&gt;
&lt;li&gt;veth pair一端发送的数据会在另一端接收&lt;/li&gt;
&lt;li&gt;veth pair常用于将两端放在不同的network namespace里进行跨network namespace之间的通信&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Network Namespace(网络命名空间)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Linux内核提供的一种网络隔离机制，用于将不同的网络设备、IP地址、路由表、防火墙规则等网络资源隔离到独立的命名空间中&lt;/li&gt;
&lt;li&gt;同一个网络设备只能位于一个命名空间中，不同的网络命名空间中的设备可以通过veth pair进行桥接&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;VLAN&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;虚拟局域网，是将一个物理的局域网在逻辑上划分成多个广播域&lt;/li&gt;
&lt;li&gt;可以实现相同的vlan在同一广播域，不同vlan内的报文在数据传输时是相互隔离的&lt;/li&gt;
&lt;li&gt;用vlan可以划分不同的用户使用不同的网段，便于流量管理&lt;/li&gt;
&lt;li&gt;vlan的数量一共2^12=&lt;strong&gt;4096&lt;/strong&gt;个vlanID（0~4095）其中0和4095为系统保留，所以vlanID范围为1~4094&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;VXLAN&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;虚拟可扩展的局域网，通过三层网络搭建的虚拟的二层网络&lt;/li&gt;
&lt;li&gt;vxlan可突破vlan4096个子网的数量限制，能够支持2^24个子网&lt;/li&gt;
&lt;li&gt;VNI是每个VXLAN的标识，24位整数，最大值为2^24-2=16777214&lt;/li&gt;
&lt;li&gt;tunnel，隧道，是一个逻辑上的概念，一种虚拟通道，VXLAN通信双方（虚拟机之间）都认为自己在直接通信，并不知道底层网络&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;PVLAN&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一种网络隔离技术，在传统的VLAN基础上提供更细粒度的隔离，同一个VLAN内部隔离不同的实例&lt;/li&gt;
&lt;li&gt;一个PVLAN下只有一个隔离VLAN&lt;/li&gt;
&lt;li&gt;PVLAN的优势在于精细的访问控制，节省IP资源，提高安全保护&lt;/li&gt;
&lt;li&gt;可用于同一个三层网络下的vm之间相互隔离&lt;/li&gt;
&lt;li&gt;创建二层网络时，可开关PVLAN，用于设置是否在云平台中实现 PVLAN 的隔离 VLAN（Isolated VLAN）功能，通常与物理交换机的 PVLAN 功能配合使用&lt;/li&gt;
&lt;li&gt;二层网络开启后PVLAN后，将实现二层网络内的隔离效果，使用该二层网络创建的三层网络内的云主机之间默认也无法通信。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二层网络&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;L2NoVlanNetwork:表示相关的物理机对应的网络设备不设置VLAN，如果交换机端口设置了VLAN，则需要在交换机端配置Access模式&lt;/li&gt;
&lt;li&gt;L2VlanNetwork：表示相关的物理机对应的网络设备需要设置VLAN，需在物理机接入的交换机端进行Trunk设置&lt;/li&gt;
&lt;li&gt;如果集群已加载二层网络，但物理机不存在此二层设备（二层网络绑定的网卡不存在），则物理机不能添加进入该集群&lt;/li&gt;
&lt;li&gt;如果集群未加载二层网络，物理机也不存在此二层设备，则物理机不能添加进入该集群&lt;/li&gt;
&lt;li&gt;如果物理机存在此二层设备，但设备接线于集群内其他物理机接线不一样，则会导致创建出来的云主机ip无法正常工作&lt;/li&gt;
&lt;li&gt;删除二层网络时，其对应的三层网络将被删除，使用此三层网络的云主机的网卡也将被删除&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三层网络&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;不推荐多个三层网络使用同一个二层网络，否则可能会出现DHCP服务不可用的情况&lt;/li&gt;
&lt;li&gt;云主机加载三层网络会自动在物理机上创建一个虚拟网卡（vnic）attach到对应的网桥上面&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;路由资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;MCloud封装了VPC路由器的镜像，只为云路由提供服务，云路由的镜像不能直接用于创建云主机&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;镜像上传地址：
x86：http://管理节点IP:8080/mcloud/static/mcloud-repo/x86_64/c78/mcloud-tools/MCloud-vRouter-openEuler-22.03.qcow2
arm：http://管理节点IP:8080/mcloud/static/mcloud-repo/aarch64/openeuler22/mcloud-tools/MCloud-vRouter-openEuler-22.03-aarch64.qcow2&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建路由规格时，管理网可以公有网络和系统网络&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VPC路由器为公有网络/VPC网络提供分布式DHCP、DNS、SNAT、弹性IP、端口转发、负载均衡、安全组等网络服务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设置网卡QOS，能通过VPC路由器设置网卡的Qos，限制网卡的上行和下行带宽&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多公网SNAT（Source Network Address Translation)，VPC路由器加载的默认公有网络默认开启SNAT&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SNAT：源IP地址转换，将&lt;strong&gt;内部VPC网络&lt;/strong&gt;中发向公网的数据通过&lt;strong&gt;NAT转化&lt;/strong&gt;，将数据包中源地址转成VPC路由器对应&lt;strong&gt;公网网卡的IP地址&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;默认公有网络开启SNAT后，VPC网络中云主机可以直接访问互联网&lt;/li&gt;
&lt;li&gt;非默认公有网络开启SNAT后，VPC网络中云主机可通过SNAT访问非默认网络中的云主机&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;源进源出：由VPC路由器基本属性中开关。当VPC路由器配置多条公有网络时，将每个公网所触发的外部请求对应的内部响应原路返回，确保数据进出一致。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;组播数据传输使用UDP协议，接收的云主机防火墙应启用IGMP协议&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;安全组&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;安全组支持的协议包括&lt;code&gt;tcp、udp、icmp、all&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;安全组由作用方向、作用对象、协议、端口、优先级（数字越小，优先级越高）组成。为云主机提供三层网络的安全控制，能对tcp、udp、icmp等数据包进行过滤&lt;/li&gt;
&lt;li&gt;入方向：数据包从外部进入云主机&lt;/li&gt;
&lt;li&gt;出方向：数据包从云主机往外部发送&lt;/li&gt;
&lt;li&gt;网卡绑定安全组后才会受安全规则的限制，一张网卡可以绑定多个安全组&lt;/li&gt;
&lt;li&gt;网卡加入安全组后，除安全规则规定外，默认允许其他所有出方向流量，拒绝其他所有入方向流量&lt;/li&gt;
&lt;li&gt;VPC防火墙管控南北向流量，作用于整个VPC，部署在VPC路由器上。安全组作用于云主机虚拟网卡，侧重保护VPC内部东西向通信安全，部署在云主机上。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;虚拟IP&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;公有网络创建VIP&lt;/th&gt;
&lt;th&gt;VPC网络创建VIP&lt;/th&gt;
&lt;th&gt;扁平网络创建VIP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;为扁平网络提供&lt;/td&gt;
&lt;td&gt;弹性IP，负载均衡&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;弹性IP，负载均衡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;为VPC网络提供&lt;/td&gt;
&lt;td&gt;弹性IP，端口转发，负载均衡，IPsec隧道&lt;/td&gt;
&lt;td&gt;负载均衡&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;云路由器成功创建后，系统使用路由器加载的三层网络会自动创建虚拟ip。数据包会发送到虚拟ip，再路由至主机网络&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;弹性IP&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;弹性IP基于NAT，将一个网络的IP地址转换成另一个网络的IP地址，可将对公网的访问直接关联到内部私有的云主机IP&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;端口转发&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;基于VPC路由提供的三层转发服务，可以将指定公网ip+端口转发到云主机对应协议的端口上&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;负载均衡&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;将VIP的访问流量分发到后端服务器上，自动检测隔离不可用的后端服务器，提高业务的服务能力和可用性&lt;/li&gt;
&lt;li&gt;性能共享型：通过VPC路由器提供负载均衡服务，访问流量经由VPC路由器分发给后端服务器。当VPC路由器上承载多业务运行，负载均衡服务需与其他业务共享VPC路由器性能&lt;/li&gt;
&lt;li&gt;性能独享型：通过负载均衡实例提供负载均衡服务，访问流量经由负载均衡实例分发给后端服务器。负载实例是一个定制的云主机，负载均衡服务独享该实例性能。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>自动化测试</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/</guid><description>杭州宏杉学习笔记-自动化测试</description><pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;自动化测试环境&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;jdk1.8[https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html]&lt;/li&gt;
&lt;li&gt;maven3.9.3[https://maven.apache.org/]&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;项目结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;mcloud_test/Test
├── lib     // 项目本地依赖jar包
│   ├── acl-0.0.1.jar
│   ├ ...
├── pom.xml     // 项目 pom.xml
├── ssl     // ssl相关工具
│   ├── DefaultSSLVerifier.java     // 默认ssl验证
│   ├── Readme.md
│   ├── ZSClient.java       // zstack ssl客户端
│   └── ZSConfig.java       // zstack ssl配置
├── test        // 主测试模块
│   ├── pom.xml     // 测试模块pom.xml
│   ├── src
│   │   └── test
│   │       ├── java ...
│   │       │    └── test
│   │       │        ├── api    // MCloud服务api
│   │       │        │   ├── Api.java
│   │       │        │   ├─ ...
│   │       │        ├── constant       // 常量定义
│   │       │        │   ├── ResourceConstant.java
│   │       │        │   ├─ ...
│   │       │        ├── deployer       // 部署器（环境配置）相关
│   │       │        │   ├── AbstractDeployer.java
│   │       │        │   ├─ ...*.java
│   │       │        │   ├── schema     // 部署器配置模型
│   │       │        │   │   ├── AccountConfig.java
│   │       │        │   │   ├─ ...
│   │       │        ├── factory        // 工厂类
│   │       │        │   ├── BackupStorageFactory.java
│   │       │        │   ├─ ...
│   │       │        ├── FT     // 容错(Fault Tolerance)测试
│   │       │        │   ├── FtTest.java        // 测试基类
│   │       │        │   ├── host       // 物理机容错测试
│   │       │        │   │   ├── TestAllHostAlternatePowerOff.java      // 物理机交替掉电测试
│   │       │        │   │   ├─ ...
│   │       │        │   ├── synchronized_netcard       // 同步网卡测试
│   │       │        │   │   ├── TestAllHostAlternateDownABNetcard.java     // 物理机网卡交替关闭测试
│   │       │        │   │   ├─ ...
│   │       │        │   ├── Test1.java     // 测试用例前置，用于加载deployerXML配置文件
│   │       │        │   ├── TestSuite.java     // 测试用例套件
│   │       │        ├── HA     // 高可用(High Availability)相关测试
│   │       │        │   ├── HaTest.java        // 高可用测试基类
│   │       │        │   ├── host       // 物理机相关测试
│   │       │        │   │   ├── TestAllHostAlternatePowerOffOn.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── l2_netcard // L2网卡相关测试
│   │       │        │   │   ├── TestAllHostDownNetCard.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── Test1.java
│   │       │        │   └── TestSuite.java
│   │       │        ├── imageshare_mcloud      // 不同存储之间迁移云盘？
│   │       │        │   ├── image
│   │       │        │   │   ├── Test1.java
│   │       │        │   │   ├─ ...
│   │       │        │   └── vm
│   │       │        │       ├── LocalToMStor.java
│   │       │        │       ├─ ...
│   │       │        ├── local_mcloud       // Local存储的MCloud测试
│   │       │        │   ├── clonevm        // 克隆VM
│   │       │        │   │   ├── Test1.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── image      // 系统镜像相关测试
│   │       │        │   │   ├── Test1.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── instanceoffering       // 计算规格相关测试
│   │       │        │   │   ├── Test1.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── isovm      // 云主机iso相关测试
│   │       │        │   │   ├── Test1.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── migratevm      // 云主机迁移测试
│   │       │        │   │   ├── Test1.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── vm     // 云主机相关测试
│   │       │        │   │   ├── Test1.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── vmsnap     // 云主机快照相关测试
│   │       │        │   │   ├── Test1.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── volume     // 云盘相关测试
│   │       │        │   │   ├── Test1.java
│   │       │        │   │   ├─ ...
│   │       │        │   └── volumesnap // 云盘快照相关测试
│   │       │        │       ├── Test1.java
│   │       │        │       ├─ ...
│   │       │        ├── macrosan_mcloud_sdas       // 双活存储相关测试
│   │       │        │   ├── clonevm
│   │       │        │   ├── isovm
│   │       │        │   ├── migratevm
│   │       │        │   ├── repvm
│   │       │        │   ├── vm
│   │       │        │   ├── vmsnap
│   │       │        │   └── volume
│   │       │        ├── mdbs_mcloud        // MDBS相关测试
│   │       │        ├── psMigrate      // 迁移功能测试（更改物理机，更改主存储，更改物理机和主存储）
│   │       │        ├── sharedBlock_mcloud     // sharedBlock存储相关测试
│   │       │        ├── upgrade        // 升级测试
│   │       │        │   ├── before
│   │       │        │   │   ├── TestAttachVirtioDataVolumeWithRunningVm.java
│   │       │        │   │   ├─ ...
│   │       │        │   ├── Test1.java
│   │       │        │   ├─ ...
│   │       │        ├── util       // 工具类
│   │       │        │   ├── CommandUtil.java
│   │       │        │   ├─ ...
│   │       │        ├── BaseTest.java  // 测试基类
│   │       │        ├── CaseExceptionInterceptor.java      // 测试异常拦截器
│   │       │        ├── TestDeployer.java      // 以下5个文件未被使用
│   │       │        ├── package-info.java     
│   │       │        ├── TestScriptRunner.java
│   │       │        ├── UnitTestSuiteConfig.java
│   │       │        └── UnitTestSuite.java
│   │       └── resources
│   │           ├── caseRunShell        // 测试用例执行脚本，带base的为开发使用，用例数量较少
│   │           │   ├── case_run_base.sh
│   │           │   ├── case_run_ft.sh
│   │           │   ├── case_run_ha.sh
│   │           │   ├── case_run_image_share.sh
│   │           │   ├── case_run_migrate.sh
│   │           │   ├── case_run_nfs_base.sh
│   │           │   ├── case_run_nfs.sh
│   │           │   ├── case_run_san_sdas.sh
│   │           │   ├── case_run_sblk_base.sh
│   │           │   ├── case_run_sblk.sh
│   │           │   ├── case_run_sftp_base.sh
│   │           │   ├── case_run_sftp.sh
│   │           │   ├── case_run.sh
│   │           │   └── case_run_upgrade.sh
│   │           ├── deployerXml     // 测试用例配置文件
│   │           │   ├── ft
│   │           │   │   └── ft.xml
│   │           │   ├─ ...
│   │           ├── env     // 测试环境配置文件
│   │           ├── junit-platform.properties
│   │           ├── log4j2.xml
│   │           ├── mcloud.properties   // MCloud相关配置文件
│   │           ├── META-INF
│   │           │   ├── metafile.properties
│   │           │   └── services
│   │           │       └── org.junit.jupiter.api.extension.Extension
│   │           ├── psMigrateCrossPool.csv
│   │           ├── psMigrate.csv
│   │           ├── UnitTestSuiteConfig.xml
│   │           ├── unitTestSuiteXml
│   │           │   ├── AccountManager.xml
│   │           │   └── Vm.xml
│   │           ├── virsh_login         // 云主机功能相关脚本
│   │           │   ├── attach_guest_tools.sh
│   │           │   ├─ ...
│   │           └── xsd
│   │               ├── deployer
│   │               │   ├── compute.xsd
│   │               │   ├─ ...
│   │               └── UnitTestSuite.xsd

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用说明&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;根据需要自动化测试的环境，对照文档《自动化教程》代码分类表格，确定执行的脚本和需要修改的代码。（如测试local主存储的脚本为case_run_sftp_base.sh，需要修改env文件和deployerXml/nfs_mcloud/nfs_mcloud.xml文件）&lt;/li&gt;
&lt;li&gt;env文件保存的为环境配置，例如管理节点IP，物理节点ip，bmcip，主存储类型，二层网络网卡名等&lt;/li&gt;
&lt;li&gt;deployerXml文件夹下的xml文件为环境中的资源配置，例如创建的计算规格参数，云盘规格参数，区域，集群和物理机，二三层网络配置，上传的镜像资源等&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;用例说明&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;以local主存储为例&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;镜像相关（image）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;共计8个用例&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;用例套件资源初始化（Test1）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.删除所有资源缓存（资源通过api查询到后会缓存在本地的HashMap中），若env环境配置文件中&lt;code&gt;deleteAllResource&lt;/code&gt;为&lt;code&gt;true&lt;/code&gt;(默认为false），则会删除云平台上的所有测试资源&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ResourceUtil.deleteAllResource(api);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.加载&lt;code&gt;deployerXml&lt;/code&gt;配置文件,并初始化deployerXml中的测试资源（创建计算规格参数，云盘规格参数，区域，集群和物理机，二三层网络配置，上传的镜像资源等）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Deployer deployer = new Deployer(&quot;deployerXml/nfs_mcloud/nfs_mcloud.xml&quot;);
deployer.build();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.通过获取主存储类型验证资源是否初始化成功，资源不存在则api会抛异常&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Test
  public void test() throws InterruptedException, ApiSenderException, IOException {
      ResourceUtil.getPrimaryStorageFromType(TypeConstant.NFS_PRIMARY_STORAGE_TYPE);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;创建两个RAW格式镜像：TinyLinux-1、TinyLinux-2（TestCreateRAWImage）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.&lt;code&gt;setUp&lt;/code&gt;准备api（获取admin账户session），删除所有测试云主机及绑定的数据盘&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@BeforeEach
public void setUp() throws Exception {
    logger.debug(&quot;\n\n---------- {} is running now ----------\n&quot;, this.getClass().getName());
    api.prepare();
    api.deleteVmAndVolume();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.获取主存储和镜像服务器的信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PrimaryStorageInventory primaryStorage = ResourceUtil.getPrimaryStorageFromEnv();

BackupStorageInventory backupStorage = getBackupStorageFromType(ResourceConstant.SFTP_STORAGE_NAME);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.获取镜像1和镜像2的信息,通过断言判断镜像状态是否为&lt;code&gt;Ready&lt;/code&gt;和&lt;code&gt;Enabled&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ImageInventory image1Info = ResourceUtil.getImage(ResourceConstant.TEST_IMAGE_NAME + &quot;-1&quot;);
String img1Uuid = image1Info.getUuid();
assertEquals(&quot;Ready&quot;, image1Info.getStatus());
assertEquals(&quot;Enabled&quot;, image1Info.getState());

ImageInventory image2Info = ResourceUtil.getImage(ResourceConstant.TEST_IMAGE_NAME + &quot;-2&quot;);
String img2Uuid = image2Info.getUuid();
assertEquals(&quot;Ready&quot;, image2Info.getStatus());
assertEquals(&quot;Enabled&quot;, image2Info.getState());
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.等待30s，防止初始化资源后未更新数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.调用&lt;code&gt;sync&lt;/code&gt;api重新查询镜像信息，通过断言镜像大小不为0判断镜像是否创建成功&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;image1Info = api.syncImageSize(img1Uuid);
image2Info = api.syncImageSize(img2Uuid);

assertNotEquals(0, image1Info.getActualSize());
assertNotEquals(0, image2Info.getActualSize());
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;使用云盘根盘创建镜像，使用该云盘镜像创建云主机（TestCreatVmFromRootVolumeTemplate）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.&lt;code&gt;setUp&lt;/code&gt;准备api（获取admin账户session），删除所有测试云主机及绑定的数据盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.获取各资源实例（集群，三层网络，云盘规格，计算规格，镜像服务器，主存储,镜像）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ClusterInventory cluster = getCluster();
L3NetworkInventory l3Network = api.queryL3Network(&quot;name&quot;, &quot;TestL3Network1&quot;);
DiskOfferingInventory rootDiskOffering = api.queryDiskOffering(&quot;name&quot;, &quot;TestRootDiskOffering&quot;);
InstanceOfferingInventory instanceOffering = api.queryInstanceOffering(&quot;name&quot;, &quot;TestInstanceOffering1&quot;);
HostInventory host1 = ResourceUtil.queryHost1();
ImageInventory image = api.queryImage(&quot;name&quot;, &quot;TinyLinux-1&quot;);

PrimaryStorageInventory primaryStorage = ResourceUtil.getPrimaryStorageFromEnv();

BackupStorageInventory backupStorage = getBackupStorageFromType(ResourceConstant.SFTP_STORAGE_NAME);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.使用上述资源创建一台云主机TestCase-vm1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;VmInstanceInventory vmInstance1 = api.createVmInstance(&quot;vm1&quot;, instanceOffering.getUuid(), image.getUuid(),
  l3Network.getUuid(), rootDiskOffering.getUuid(), cluster.getUuid(), &quot;this is a vm&quot;,
  &quot;InstantStart&quot;, null);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.使用创建的云主机的根盘创建云盘规格TestRootDiskOffering&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ImageInventory rootVolumeTemplate = api.createRootVolumeTemplateFromRootVolume(&quot;My Root Volume Template&quot;,
  vmInstance1.getRootVolumeUuid(), asList(backupStorage.getUuid()), &quot;Linux&quot;, false);
String rootVolumeTemplateUuid = rootVolumeTemplate.getUuid();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.使用该云盘规格创建一台云主机TestCase-vm2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;VmInstanceInventory vmInstance2 = api.createVmInstance(&quot;vm2&quot;, instanceOffering.getUuid(), rootVolumeTemplateUuid,
  l3Network.getUuid(), cluster.getUuid(), &quot;InstantStart&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.通过云主机UUID查询云主机状态，断言判断状态是否为运行中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vmInstance2 = api.queryVm(&quot;uuid&quot;, vmInstance2.getUuid());
assertEquals(VmInstanceState.Running.toString(), vmInstance2.getState());
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7.重启云主机，再次断言判断状态是否为运行中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api.rebootVmInstance(vmInstance2.getUuid());
vmInstance2 = api.queryVm(&quot;name&quot;, &quot;vm2&quot;);
assertEquals(VmInstanceState.Running.toString(), vmInstance2.getState());
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;测试单个镜像的删除与恢复&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.&lt;code&gt;setUp&lt;/code&gt;准备api（获取admin账户session），删除所有测试云主机及绑定的数据盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.获取所需资源实例（集群，三层网络，计算规格，镜像服务器，主存储,镜像-TinyLinux-1）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.使用上述镜像创建一台云主机TestCase-vm1,并断言确保创建成功&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.从镜像服务器中删除该镜像&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;List&amp;lt;String&amp;gt; backupList = Collections.singletonList(backupStorage.getUuid());

api.deleteImage(imgUuid, backupList);
image = api.queryImage(&quot;uuid&quot;, imgUuid);
logger.debug(image.getState());
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.恢复镜像&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api.recoverImage(imgUuid, backupList);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.再次使用该镜像创建云主机TestCase-vm2，并断言确保创建成功&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7.两台云主机关机后再开机，并断言确保状态正常&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api.stopVmInstance(vmInstance1.getUuid());
api.stopVmInstance(vmInstance2.getUuid());  
VmInstanceInventory vm11 = api.queryVm(&quot;name&quot;, &quot;vm1&quot;);
assertEquals(VmInstanceState.Stopped.toString(), vm11.getState());  
VmInstanceInventory vm22 = api.queryVm(&quot;name&quot;, &quot;vm2&quot;);
assertEquals(VmInstanceState.Stopped.toString(), vm22.getState());  
api.startVmInstance(vmInstance1.getUuid());
api.startVmInstance(vmInstance2.getUuid()); 
vmInstance1 = api.queryVm(&quot;name&quot;, &quot;vm1&quot;);
assertEquals(VmInstanceState.Running.toString(), vmInstance1.getState()); 
vmInstance2 = api.queryVm(&quot;name&quot;, &quot;vm2&quot;);
assertEquals(VmInstanceState.Running.toString(), vmInstance2.getState());
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;测试删除多个镜像后恢复&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.&lt;code&gt;setUp&lt;/code&gt;准备api（获取admin账户session），删除所有测试云主机及绑定的数据盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.获取所需资源实例（集群，三层网络，计算规格，镜像服务器，主存储,镜像-TinyLinux-1/2/3）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.使用镜像TinyLinux-2/3分别创建两台云主机vm1和vm2，并执行重启操作，断言确保状态正常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.删除3个镜像，并使用断言确保镜像状态为已删除&lt;code&gt;Deleted&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api.deleteImage(img1Uuid, backupList);
api.deleteImage(img2Uuid, backupList);
api.deleteImage(img3Uuid, backupList);

image1 = api.queryImage(&quot;uuid&quot;, img1Uuid);
logger.debug(&quot;image1 state : {}&quot;, image1.getState());
logger.debug(&quot;image1 status : {}&quot;, image1.getStatus());
assertEquals(&quot;Deleted&quot;, image1.getStatus());

image2 = api.queryImage(&quot;uuid&quot;, img2Uuid);
logger.debug(&quot;image2 state : {}&quot;, image2.getState());
logger.debug(&quot;image2 status : {}&quot;, image2.getStatus());
assertEquals(&quot;Deleted&quot;, image2.getStatus());

image3 = api.queryImage(&quot;uuid&quot;, img3Uuid);
logger.debug(&quot;image3 state : {}&quot;, image3.getState());
logger.debug(&quot;image3 status : {}&quot;, image3.getStatus());
assertEquals(&quot;Deleted&quot;, image3.getStatus());
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.恢复3个镜像，并断言确保镜像状态为&lt;code&gt;Ready&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;测试更新镜像名称和描述&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.&lt;code&gt;setUp&lt;/code&gt;准备api（获取admin账户session），删除所有测试云主机及绑定的数据盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.获取所需资源实例（集群，三层网络，计算规格，镜像服务器，主存储,镜像-TinyLinux-1）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.使用上述镜像创建一台云主机TestCase-vm1,并断言确保创建成功&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.重命名镜像名为&lt;code&gt;Image-new&lt;/code&gt;，描述为&lt;code&gt;new description&lt;/code&gt;，断言更新后的名称和描述&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;image = api.updateImage(imgUuid, &quot;Image-new&quot;, &quot;Linux&quot;, &quot;new description&quot;);
imgUuid = image.getUuid();
logger.debug(&quot;------------Update image---------------&quot;);
logger.debug(&quot;image uuid : {}&quot;, imgUuid);
logger.debug(&quot;image name : {}&quot;, image.getName());
assertEquals(&quot;Image-new&quot;, image.getName());
logger.debug(&quot;image description : {}&quot;, image.getDescription());
assertEquals(&quot;new description&quot;, image.getDescription());
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.使用更新后的镜像创建一个云主机vm2，并断言确保创建成功，再重启，断言确保状态正确&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.将镜像名称改回&lt;code&gt;TinyLinux-1&lt;/code&gt;，并断言确保修改成功&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;测试停用和启用多个镜像&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.&lt;code&gt;setUp&lt;/code&gt;准备api（获取admin账户session），删除所有测试云主机及绑定的数据盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.获取所需资源实例（集群，三层网络，计算规格，镜像服务器，主存储,镜像-TinyLinux-1/2）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.使用镜像TinyLinux-1/2分别创建两台云主机vm1和vm2，断言确保状态正常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.停用2个镜像&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api.changeImageState(img1Uuid, ImageStateEvent.disable);
api.changeImageState(img2Uuid, ImageStateEvent.disable);
TimeUnit.SECONDS.sleep(5);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.使用&lt;code&gt;TinyLinux-1&lt;/code&gt;创建云主机vm3，并断言确保创建报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;boolean hasException = false;
try {
    api.createVmInstance(&quot;vm3&quot;, instanceOffering.getUuid(), img1Uuid, l3Network.getUuid(),
            cluster.getUuid(), &quot;InstantStart&quot;);
} catch (Exception e) {
    hasException = true;
}
assertTrue(hasException);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.启用2个镜像&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api.changeImageState(img1Uuid, ImageStateEvent.enable);
api.changeImageState(img2Uuid, ImageStateEvent.enable);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7.使用两个镜像分别创建vm3和vm4，并断言确保没有报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hasException = false;
try {
    api.createVmInstance(&quot;vm3&quot;, instanceOffering.getUuid(), img1Uuid, l3Network.getUuid(),
            cluster.getUuid(), &quot;InstantStart&quot;);
} catch (Exception e) {
    hasException = true;
}
assertFalse(hasException);
try {
    api.createVmInstance(&quot;vm4&quot;, instanceOffering.getUuid(), img2Uuid, l3Network.getUuid(),
            cluster.getUuid(), &quot;InstantStart&quot;);
} catch (Exception e) {
    hasException = true;
}
assertFalse(hasException);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;8.查询并断言两个vm的运行状态，重启两台vm后再次断言确保状态正常&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;测试停用和启用单个镜像&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;1.&lt;code&gt;setUp&lt;/code&gt;准备api（获取admin账户session），删除所有测试云主机及绑定的数据盘&lt;/li&gt;
&lt;li&gt;2.获取所需资源实例（集群，三层网络，计算规格，镜像服务器，主存储,镜像-TinyLinux-1）&lt;/li&gt;
&lt;li&gt;3.使用镜像TinyLinux-1创建云主机vm1，断言确保状态正常&lt;/li&gt;
&lt;li&gt;4.停用镜像&lt;/li&gt;
&lt;li&gt;5.使用&lt;code&gt;TinyLinux-1&lt;/code&gt;创建云主机vm3，并断言确保创建报错&lt;/li&gt;
&lt;li&gt;6.启用镜像&lt;/li&gt;
&lt;li&gt;7.再次使用镜像创建vm3，并断言确保没有报错&lt;/li&gt;
&lt;li&gt;8.查询并断言vm的运行状态，重启vm后再次断言确保状态正常&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;测试彻底删除镜像&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.&lt;code&gt;setUp&lt;/code&gt;准备api（获取admin账户session），删除所有测试云主机及绑定的数据盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.获取所需资源实例（集群，三层网络，计算规格，镜像服务器，主存储,镜像-TinyLinux-3）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.使用上述镜像创建一台云主机vm1，并断言确保创建成功&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.将镜像彻底删除&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api.deleteImage(imgUuid, backupList);
image = api.queryImage(&quot;uuid&quot;, imgUuid);
logger.debug(&quot;image state : {}&quot;, image.getState());
//彻底删除
api.expungeImage(imgUuid, backupList);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.查询镜像信息，断言确保镜像数据为&lt;code&gt;Null&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;image = api.queryImage(&quot;uuid&quot;, imgUuid);
assertNull(image);
logger.debug(&quot;---------------------------&quot;);
logger.debug(&quot;image has been expunged&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>转正答辩问题参考</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E8%BD%AC%E6%AD%A3%E7%AD%94%E8%BE%A9%E9%97%AE%E9%A2%98%E5%8F%82%E8%80%83/%E8%BD%AC%E6%AD%A3%E7%AD%94%E8%BE%A9%E9%97%AE%E9%A2%98%E5%8F%82%E8%80%83/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E8%BD%AC%E6%AD%A3%E7%AD%94%E8%BE%A9%E9%97%AE%E9%A2%98%E5%8F%82%E8%80%83/%E8%BD%AC%E6%AD%A3%E7%AD%94%E8%BE%A9%E9%97%AE%E9%A2%98%E5%8F%82%E8%80%83/</guid><description>杭州宏杉学习笔记-转正答辩问题参考</description><pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;答辩问题组1&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;以&lt;strong&gt;裸金属&lt;/strong&gt;和&lt;strong&gt;v2v迁移&lt;/strong&gt;为主&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、裸金属模块有哪些功能？&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;裸金属模块包含有裸金属集群、裸金属设备、裸金属主机、部署服务器、预配置模版以及回收站这六个功能模块
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2、裸金属设备支持哪些操作？&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;裸金属设备支持启用、停用、删除、查看属性、开机、重启、关闭电源、获取硬件信息、更新IPMI信息、打开控制台
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3、QGA和DHCP有什么依赖关系吗？QGA和性能优化工具有什么关系？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;并无直接依赖关系，可以实现一些相同的功能。性能优化工具里面包含了QGA，
以修改主机名为例，安装性能优化工具之后则可以通过QGA去修改主机名
不安装性能优化工具的情况下，则可以通过DHCP去修改主机名。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4、通过南京明捷项目支持工作的经验中，可以补充那些测试用例来提高测试覆盖范围？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;①ESXI上使用的存储，增加覆盖NFS存储类型
②大规格的测试场景，例如V2V迁移测试验证源端云主机挂载2T的硬盘
③源端虚拟机的磁盘路径带特殊符号场景
④源端存储、目标端存储读写性能较差的场景覆盖
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5、windows虚拟机迁移过来后无法正常进入系统，如何排查？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;①检查源端虚拟机是否可以正常运行
②检查迁移后的云主机的状态是否为运行中
③检查虚拟机的根盘、数据盘的挂载情况、大小、格式、类型等是否和源端虚拟机保持一致
④检查迁移后的虚拟机配置，例如CPU、内存、网卡类型与源环境是否保持一致
⑤打开云主机的控制台，观察是否是启动过程卡住或是其他类型，例如蓝屏、黑屏无响应等
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;6、MCMP测试和MCloud测试的区别是什么&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;多云管平台主要是需要测试多个云平台之间的资源调度功能，同时还要验证与底层平台操作的一致性，多云管平台另外还需要测试对不同云平台的兼容性，测试复杂度高
而MCloud测试则是聚焦单一平台功能验证
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;7、 MCMP测试中如何确认是否有问题？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;①观察界面上的报错信息或者任务结果
②操作一致性检查，对比检查多云管平台上执行的操作与实际下发到底层平台上操作是否一致
③对比测试， 在多云管平台和底层平台执行同样的操作，确认是否为多云管平台独有问题
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;8、性能优化工具包含哪些？  mwatch有什么作用？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;性能优化工具包含内部监控agent、QGA、Virtio驱动、Cloudbase-Init以及Pvpanic驱动。
Virtio驱动又包含有硬盘驱动（vioscsi、viostor）、网卡驱动（NetKVM）、内存Balloon驱动、串口设备驱动（vioserial）、PCI设备驱动（virtio-pci）等
mwatch即mwatch-vm-agent服务， 是安装在云主机内部的代理，用于获取云主机CPU、内存和磁盘容量的内部监控数据
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;9、vmtools的作用是什么？ 与v2v测试过程中的什么功能强相关？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;①vmtools可以实现虚拟机的网络/存储性能优化、电源管理增强、提高虚拟硬件兼容性， 并且可以收集虚拟机详细监控信息
②在v2v迁移过程中，针对VMware的云主机，如果云主机未安装VMwareTools，系统会尝试通过vCenter执行云主机关闭电源操作。
如果云主机安装了VMware Tools，系统会通过VMwareTools引导云主机操作系统关机。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;10、裸金属模块部署服务器的限制有哪些？需要划分空间吗？推荐做法是什么？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;要求如下：
①部署服务器挂载到裸金属集群中
②部署服务器仅支持复用管理节点
③要求部署服务器有足够的存储空间，保存用于PXE部署的镜像
④要求部署服务器连接到管理网络，与MCloud云平台的管理节点连通
⑤要求部署服务器连接到部署网络，与裸金属设备连通
⑥要求部署服务器上的DHCP监听网卡连接到部署网络，并保证该部署网络上不存在其他DHCP服务
需要创建存储路径，存储路径需要有足够的空间；推荐单独创建目录，不可复用系统目录 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;答辩问题组2&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;以&lt;strong&gt;NTP&lt;/strong&gt;为主&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;1、描述一个FT问题及定位结论&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FT云主机备云主机所在节点下电重启，FT云主机业务网络会出现中断，且恢复后可能会出现再次业务网络中断。因当时开发无时间定位，
位结论。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、chrony守护进程、时间源和集群的关系&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;集群中每个物理机都有chrony守护进程，master节点中的守护进程时间源设置为外部时间源；slave节点中的守护进程时间源指向的是master,
其余物理机守护进程时间源指向的是master和slave节点
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3、时间源为什么要设置双备份，一个可以吗&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;双备份是为了当其中一个管理节点挂了的时候，其他物理机还能依靠剩下的管理节点进行时间同步，确保集群内时间的一致性。只有一个
话，就没有冗余，当这个管理节点挂掉后，集群内就无法确保时间一致
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4、两个管理节点都挂了，是否能同步&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;不能，两个节点都挂了，集群内部处于一种无leader的状态，无法确保集群内时间一致
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5、unixbench工具测试项在我们现在的工作中全面吗，重点关注哪项&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unixbench测试项覆盖比较全面，涵盖CPU性能，磁盘IO性能，内存速度等。重点可以关注CPU的整数和浮点数运算性能以及磁盘的IO性能
为这是整机计算性能测试工具，CPU，内存，存储等多方面都会影响得分，不能仅靠某项的分数判断CPU的性能
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;6、怎么解释单核和多核结果相反，是否合理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;一般在服务器CPU中，核心数越多，频率越低；核心数越少，频率则越高，单核分数更看重频率，多核分数看重核心数量。所以只针对单核
相反这个现象来讲是合理的。但测试机采用的是云平台上的云主机，测试结果会受CPU、内存、主存储、云平台上的其他云主机等因素影响，且
用例128C128G限制的影响，部分测试环境并没有达到资源独占。导致测试结果与实际结果可能存在出入
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;7、怎么结合结果对云主机进行调优&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;可以先在物理机上进行跑分，在再相同配置的云主机上进行跑分。结合两个测试结果，分析云主机的性能损耗集中在哪一部分。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;8、自动化测试在项目验证补丁做什么&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;用于验证环境打完补丁后，云主机的基本功能是否正常
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;9、为什么需要NTP，没有会怎么样&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MCloud集群是通过多台服务器协同工作时，日志记录、事务处理、数据同步都需要统一的时间戳。如果时间不一致，可能会出现日志时序
版本冲突等问题。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;10、老版本和新版本NTP有什么区别&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;没有测试过老版本的NTP功能。同步的逻辑应该是相同的，只是老版本无法通过MCloud界面配置NTP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;11、NTP为什么做缓慢同步&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;如果步进同步的话，就会造成时间戳跳变，可能会引起日志时许错乱或数据版本冲突等问题
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;12、ntp时间和本地时间相差较大会怎么样，怎么实现ntp时间差的模拟？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;若只有1个ntp的话，即使与本地时间相差较大，仍会与之同步。通过创建不同的云主机，修改云主机的时间来模拟不同时差的ntp时间源
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;13、ntp时间源之间相差较大时会同步吗？没有重启mcloud服务就能同步？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;若时间源之间相差较大，chrony无法判断时间源的真伪，会放弃同步。mcloud服务与ntp服务没有直接关系，无需重启mcloud服务即可生效
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;14、mcloud和mdbs、ntp怎么配合&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;若在MCloud中已配置ntp服务器，且mdbs被MCloud纳管的情况，mdbs的ntp会以MCloud端为准
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;15、先配置mdbs的ntp再配置MCloud的ntp，chrony.conf怎么变化，有没有看里面的注释&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;没有将MCloud和mdbs进行联合测试过。观察过配置文件中的配置项，但没有仔细查看注释
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;16、时间跨度多大才会采用步进同步？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;默认是大于1秒采用步进同步，由/etc/chrony.conf文件中的makestep参数控制
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;17、怎么模拟时间不一致和ntp服务器失联的情况，ntp同步走什么协议&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;通过创建不同的云主机，修改云主机的时间来模拟不同时差的ntp时间源；通过关机云主机来模拟ntp节点失联。暂未了解过ntp同步采用什么协议
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;18、自动化测试用例痛点，有什么优化的想法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;每次自动化测试时，修改配置文件比较繁琐，且如果一个配置出错，可能导致全部用例失败。可以在执行用例前，增加参数校验的操作。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;19、陕西高速数据库不一致原因，必现步骤&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;只参与验证打补丁后环境的基本功能，未实际了解问题原因。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;20、zstack和MCloud两个产品已关机的云主机当前所在物理机是否显示一致，有没有了解过为什么不一致，有没有进行发散测试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;zstack与MCloud已关机的云主机当前所在物理机显示不一致。zstack显示的是上次启动所在的物理机，MCloud显示为空。只知道最初MC
zstack一致，显示为上次所在物理机，后面改成显示为空。具体原因未了解过。发现这个问题后，尝试检查了云资源-物理机中的云主机子页签
显示，查看是否会因为当前所在物理机为空而不显示已关机云主机。但经检查没有问题
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;21、NTP时间源之前偏差多少算大？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;偏差大小由/etc/chrony.conf文件中的maxdistance参数控制，默认为0.05秒即50ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;22、两个时间源接近但和本地相差较大，算正常吗？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;正常，即使时间源与本地相差较大，但两个时间源接近仍会与它们两个的平均值进行同步
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;23、时间源最多选几个？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;在MCloud界面上最多支持3个时间源，底层chrony无数量限制
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;24、3个都不一致，向谁同步？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3个时间源都不一致时，chrony无法裁决时间源的真伪，会放弃同步
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>SAN存储知识</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/san%E5%AD%98%E5%82%A8%E7%9F%A5%E8%AF%86%E7%AC%94%E8%AE%B0/san%E5%AD%98%E5%82%A8/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/san%E5%AD%98%E5%82%A8%E7%9F%A5%E8%AF%86%E7%AC%94%E8%AE%B0/san%E5%AD%98%E5%82%A8/</guid><description>杭州宏杉学习笔记-SAN存储知识笔记</description><pubDate>Tue, 22 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;SAN存储相关知识&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;1.DAS,NAS,SAN:
&lt;ul&gt;
&lt;li&gt;DAS(Direct Attached Storage，直连存储)：存储设备直接连接服务器&lt;/li&gt;
&lt;li&gt;NAS(Network， Attached Storage，网络附加存储)：通过网络连接，提供文件级的共享存储&lt;/li&gt;
&lt;li&gt;SAN(Storage Area Network，存储区域网络)：通过专用的高速网络（如FC，iSCSI等协议）提供块存储&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2.iSCSI协议：SCSI指令（用于块级存储访问）嵌入到TCP/IP数据包中，通过以太网传输指令和数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;MS存储系列&lt;/h2&gt;
&lt;h3&gt;1.MS主机（storage processor unit，spu）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分类：
&lt;ul&gt;
&lt;li&gt;低端：MS10系列，MS25系列&lt;/li&gt;
&lt;li&gt;中端：MS30系列，MS50系列&lt;/li&gt;
&lt;li&gt;中高端：MS55系列，MS7000&lt;/li&gt;
&lt;li&gt;所有产品软件硬件自研，基于ODSP软件架构&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;组成部分：
&lt;ul&gt;
&lt;li&gt;SP：存储处理器，也叫存储控制器，控制数据收发，处理与保护&lt;/li&gt;
&lt;li&gt;电源模块&lt;/li&gt;
&lt;li&gt;风扇模块&lt;/li&gt;
&lt;li&gt;电池模块&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.MS磁盘扩展柜（Disk shelf unit,dsu）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;组成部分：
&lt;ul&gt;
&lt;li&gt;EP（Expander Processor）：拓展处理器，也叫磁盘柜控制器&lt;/li&gt;
&lt;li&gt;电源模块&lt;/li&gt;
&lt;li&gt;风扇模块&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.MS存储交换单元（Storage Switch Unit，ssu）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;常见存储接口&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;IDE（Integrated Drive Electronics）：一种硬盘传输接口（以前很老的机械硬盘使用），也叫做ATA（Advanced Technology Attachment）
&lt;img src=&quot;./images/image-3.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;li&gt;SCSI（Small Computer System Interface）：一种中线型接口，不是专为硬盘设计的，早期硬盘或者光驱接口，多用在服务器电脑上
&lt;img src=&quot;./images/image-4.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;li&gt;SAS（Serial Attached SCSI）：串行连接SCSI，新一代的SCSI技术，可以向下兼容SATA
&lt;img src=&quot;./images/image-5.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;li&gt;SATA（Serial ATA）：串行接口，现在电脑常用的硬盘接口
&lt;img src=&quot;./images/image-6.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;li&gt;SAS与SATA接口的区别主要在于SAS接口的电源接口与数据接口并没有像SATA那样隔开
&lt;img src=&quot;./images/image-7.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;li&gt;NVMe（Non-Volatile Memory Express）：非易失性存储器，使用pcie通道与cpu直连
&lt;img src=&quot;./images/image-8.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;LUN（Logical Unit Number）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;LUN：SAN用于标识和管理存储设备中的逻辑单元，类似于存储地址。在分布式块存储（MDBS）上叫卷，&lt;em&gt;一个磁盘可以划出多个LUN，多个磁盘也能组成一个LUN&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LUN属于存储池（Pool），且不能跨Pool，它通常位于以下层级：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;物理磁盘 
→ RAID组  
  → 存储池  
    → LUN  
      → 文件系统（如NTFS、VMFS、ext4）
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LUN的基本功能：创建、删除、扩容、修改属性、销毁（需要license）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCSI&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./images/image-20250728094830092.png&quot; alt=&quot;image-20250728094830092&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Initiator：发起端，发起命令接收响应。对接存储的服务器&lt;/li&gt;
&lt;li&gt;Target：接收端，接收命令，处理指令，返回响应。存储设备上提供存储的网口&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I_T_L关联：指Initiator可以通过指定Target访问指定LUN.若干磁盘构成存储池（pool），物理机上创建云主机时，使用的云盘会自动从pool上划分LUN出来，将物理机（i）与存储设备（t）与存储的地方（l）关联起来&lt;/p&gt;
&lt;p&gt;在集群环境中，多个服务器可能同时访问同一个LUN，可能会导致数据写坏。需要SCSI Reservation机制来进行SCSI锁的操作。如果有主机向已锁定的磁盘发送读写请求，则会收到&lt;code&gt;reservation conflict&lt;/code&gt;的报错信息&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FC-SAN：使用光纤通道（Fibre Channel）作为网络介质，使用FC协议，传输SCSI报文&lt;/li&gt;
&lt;li&gt;IP-SAN：使用标准以太网，iSCSI协议传输SCSI报文&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>MDBS&amp;MCloud环境搭建笔记</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mcloud%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/mdbsmcloud%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/mcloud%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/mdbsmcloud%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</guid><description>杭州宏杉学习笔记-MDBS&amp;MCloud环境搭建笔记</description><pubDate>Thu, 17 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;硬件组网及系统安装&lt;/h2&gt;
&lt;p&gt;以3台服务器，存储和管理网络使用1台交换机，1台业务网交换机&lt;/p&gt;
&lt;p&gt;组网图：
&lt;img src=&quot;images/image2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;使用生产工具进行PXE装机&lt;/h2&gt;
&lt;h3&gt;0.pxe装机原理&lt;/h3&gt;
&lt;p&gt;服务端-提供pxe服务的主机
客户端-待装机主机&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.客户端开机时会向服务端广播DHCP发现请求，服务端收到后会给客户端分配一个ip，并回复dhcp消息，消息中包含TFTP服务器地址和启动文件的名称&lt;/li&gt;
&lt;li&gt;2.客户端收到返回消息后，根据TFTP地址下载启动文件&lt;/li&gt;
&lt;li&gt;3.客户端执行启动文件后，会从TFTP服务器或其他服务器（HTTP，FTP等）服务器下载内核和初始化镜像&lt;/li&gt;
&lt;li&gt;4.客户端加载内核和初始化镜像后，会根据预先配置的安装脚本（如 Kickstart 脚本）从文件服务器获取操作系统镜像，并开始自动安装操作系统。安装过程中，客户端会根据脚本中的配置信息进行分区、格式化、软件包安装等操作，最终完成系统的安装。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.上传镜像&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;确保生产工具所使用的网口已连接待装机服务器的管理网（确保生产工具与物理机通信）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在镜像管理界面点击“添加”按钮上传系统镜像&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.创建应答文件&lt;/h3&gt;
&lt;p&gt;应答文件：用于自动配置装机模式和分区大小的配置文件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在应答文件管理界面点击“导入”导入应答文件（对能否自己创建不同参数的应答文件存疑）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.添加主机&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在主机管理界面，点击“添加”按钮，通过BMC IP，用户名、密码和厂商添加主机&lt;/li&gt;
&lt;li&gt;添加失败时可以尝试将厂商切换为&lt;code&gt;other&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;BMC IP可以登录物理机使用&lt;code&gt;ipmitool lan print&lt;/code&gt;查看
&lt;img src=&quot;images/image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.添加PXE源&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在PXE源管理界面点击“添加”按钮，输入IP，用户名和密码添加PXE源&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.创建装机任务&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在系统安装任务管理界面，点击&quot;添加装机任务&quot;，根据BMC IP选择目标机器，随后依次选择装机模式，系统镜像和应答文件，最后点击完成开始装机&lt;/li&gt;
&lt;li&gt;注意：&lt;code&gt;other&lt;/code&gt;类型的机器要取消勾选“是否检查BIOS”，而且&lt;code&gt;other&lt;/code&gt;类型机器装机无法从pxe界面看到安装进度&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;存储交换机配置&lt;/h2&gt;
&lt;h3&gt;1.配置vlan&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;存储网必须划vlan&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;进入全局配置模式&lt;code&gt;configure terminal&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建vlan：&lt;code&gt;vlan &amp;lt;id&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;将端口划入vlan：&lt;code&gt;switchport mode access&lt;/code&gt;,&lt;code&gt;switchport access vlan &amp;lt;id&amp;gt;&lt;/code&gt;
(取消vlan：&lt;code&gt;no switchport access vlan&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.添加端口聚合&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;创建端口聚合：
&lt;ul&gt;
&lt;li&gt;进入网口：&lt;code&gt;interface &amp;lt;eth-x-xx&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;添加端口聚合：&lt;code&gt;channel-group &amp;lt;聚合id&amp;gt; mode actice&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;服务器配置&lt;/h2&gt;
&lt;h3&gt;1.检查服务器配置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;检查系统版本：&lt;code&gt;cat /etc/version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;检查系统盘分区和容量：&lt;code&gt;lsblk&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;检查磁盘数量&lt;/li&gt;
&lt;li&gt;检查网口配置&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.网络初始化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用指令&lt;code&gt;mc_first_config_network&lt;/code&gt;初始化网络（过程较慢可三个节点同时进行）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.网络ip配置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;根据指导书指令配置管理，存储，业务，迁移网络的bond和ip&lt;/li&gt;
&lt;li&gt;bond模式为&lt;code&gt;mode=4，miimon=100，xmit_hash_policy=layer3+4&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;补充修改bond模式：编辑&lt;code&gt;/etc/sysconfig/network-scripts/ifcfg-bondX&lt;/code&gt;文件（同修改其他网络配置）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;注意存储网需要配置mtu(最大传输单元)&lt;code&gt;mc_change_nic_and_bond_mtu -i bond2 -m 9000&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.主机名配置（按需）&lt;/h3&gt;
&lt;p&gt;使用指令&lt;code&gt;hostnamectl set-hostname node01-6&lt;/code&gt;修改服务器主机名&lt;/p&gt;
&lt;h3&gt;5.时间配置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;date -R&lt;/code&gt;检查时间时区（+0800）是否正确&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;cp /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime&lt;/code&gt;修改时区&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;date -s &quot;20221216 17:55:00&quot;&lt;/code&gt;,&lt;code&gt;hwclock -w&lt;/code&gt;修改时间&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;MDBS&amp;amp;MCloud部署及初始配置&lt;/h2&gt;
&lt;p&gt;管理网口名称为管理网口组的bond名，如bond0&lt;/p&gt;
&lt;h3&gt;MDBS初始配置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;添加缓存盘时，可绑定的osd数量代表缓存盘的缓存分区数，默认为6，有几个数据盘就绑定几个osd&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注意：缓存盘若为sata或者sas协议，需要将存储池属性中的并发任务数改为8。可使用指令&lt;code&gt;smartctl -i &amp;lt;硬盘路径&amp;gt; 查看硬盘参数&lt;/code&gt;查看硬盘协议&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;混合型：节点上同时存在SSD和HDD，SSD已创建缓存盘&lt;/li&gt;
&lt;li&gt;全闪型：节点上只有SSD&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据冗余保护机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;三副本：指一份数据写完全相同的三份副本，允许同时丢失两份数据；1/3表示只要有1份数据写入成功就返回写入成功，并且1个副本可读即可返回数据；2/3表示只要有2份数据写入成功就返回写入成功，并且2个副本可读才可返回数据&lt;/li&gt;
&lt;li&gt;EC（纠删码）：将数据被分为m个数据块+n个校验块，最多丢失n个块的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;MCloud初始配置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;注意二层网络创建时不能使用管理网网口和存储网网口&lt;/li&gt;
&lt;li&gt;若为单节点部署，不涉及云主机的迁移，迁移网络可填写管理网的CIDR&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;使用LocalStorage添加主存储&lt;/h4&gt;
&lt;p&gt;默认磁盘没有格式化并挂载&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.使用&lt;code&gt;lsblk&lt;/code&gt;指令确认未挂载的设备名&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.使用&lt;code&gt;df -h&lt;/code&gt;指令确定该设备没有挂载目录&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.使用&lt;code&gt;mkfs.xxx [设备路径]&lt;/code&gt;将设备格式化为对应格式的文件系统；若存在多块磁盘，可以使用逻辑卷管理（LVM）将其组成为一个逻辑卷，具体操作如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建物理卷
pvcreate /dev/sdb1 /dev/sdc1

# 创建卷组
vgcreate my_vg /dev/sdb1 /dev/sdc1

# 创建逻辑卷
lvcreate -n my_lv -l 100%FREE my_vg
# -n 指定逻辑卷名
# -l 100%FREE 指定分配的空间

# 格式化
mkfs.ext4 /dev/my_vg/my_lv
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.使用&lt;code&gt;mount [source] [target]&lt;/code&gt;指令将文件系统挂载到目录上&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.在添加主存储页面选择LocalStorage类型，填写目录的URL&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>备份基础</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E5%A4%87%E4%BB%BD%E5%9F%BA%E7%A1%80%E7%AC%94%E8%AE%B0/%E5%A4%87%E4%BB%BD%E5%9F%BA%E7%A1%80%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E5%A4%87%E4%BB%BD%E5%9F%BA%E7%A1%80%E7%AC%94%E8%AE%B0/%E5%A4%87%E4%BB%BD%E5%9F%BA%E7%A1%80%E7%AC%94%E8%AE%B0/</guid><description>杭州宏杉学习笔记-备份基础培训</description><pubDate>Tue, 15 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;备份基础&lt;/h1&gt;
&lt;h2&gt;备份基础概念&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;备份窗口：执行备份的时间段&lt;/li&gt;
&lt;li&gt;RTO（recovery time objective）：用于衡量系统恢复的速度，业务恢复的最长可接受时间&lt;/li&gt;
&lt;li&gt;RPO（recovery point objective）：用于衡量数据丢失的容忍度，允许丢失的数据量&lt;/li&gt;
&lt;li&gt;数据保留周期：备份数据保留的时间&lt;/li&gt;
&lt;li&gt;事件触发：触发备份的事件&lt;/li&gt;
&lt;li&gt;3+2+1备份原则：3份数据，1份原数据，2份备份数据；2种备份介质，硬盘+云存储；1份离线存储&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;备份种类&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;普通文件、系统备份&lt;/li&gt;
&lt;li&gt;数据库备份&lt;/li&gt;
&lt;li&gt;虚拟平台高效保护&lt;/li&gt;
&lt;li&gt;操作系统保护&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;备份类型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;完全备份：每次备份都完全复制数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;增量备份：只备份&lt;strong&gt;上次备份&lt;/strong&gt;后新增的或修改的数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;差异备份：备份自&lt;strong&gt;上次完全备份后&lt;/strong&gt;所有变化的数据
&lt;img src=&quot;images/image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;物理备份：直接复制数据块&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;逻辑备份：导出数据操作步骤（如数据库导出表和sql语句）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;合成备份：将完全备份和后续的增量或差异备份合并得到一个新的完全备份&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;备份方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;LAN：部署一台备份服务器，应用服务器通过网络将存储服务器的数据上传到备份服务器(会占用应用服务器的带宽)
&lt;img src=&quot;images/image2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;li&gt;LAN-Free&lt;/li&gt;
&lt;li&gt;Server-Free&lt;/li&gt;
&lt;li&gt;NDMP&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;存储&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;存储连接的几种方式
&lt;ul&gt;
&lt;li&gt;直接存储DAS：通过物理线缆直接连接存储，块级存储（涉及SCSI协议，IP协议，FC协议）&lt;/li&gt;
&lt;li&gt;网络附加存储NAS：存储设备通过网络实现文件夹共享，文件级存储（涉及TCP/IP协议）&lt;/li&gt;
&lt;li&gt;IP-SAN存储：以太网版的的SAN（涉及iSCSI协议），块级存储&lt;/li&gt;
&lt;li&gt;存储区域网络SAN：通过专用高速网络（光纤等）连接存储阵列，块级存储（涉及FC-SAN协议）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;容灾&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;容灾：利用特定的技术和方法，提前建立系统化的数据应急方式，以应对灾难的发生&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;容灾分裂：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从距离分类：1.同城异地容灾，建立两个数据中心（相距&amp;lt;=200KM）,一个用作日常使用，另一个做备份中心；2.异地容灾，两个数据中心相距较远（&amp;gt;200KM）,一般采用异步方式实现，会有少量数据丢失&lt;/li&gt;
&lt;li&gt;从业务上分类：1.数据级容灾，只做数据的远程备份，确保数据不会丢失或者被破坏；2.应用级容灾，建立一套完整的与本地生产系统相当的备用系统，在本地业务出现灾难时能够接管本地业务，并且能在本地业务正常后完全恢复数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;备份容灾常用技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定时备份&lt;/li&gt;
&lt;li&gt;数据复制&lt;/li&gt;
&lt;li&gt;CDP（Continuous Data Protection）持续数据保护&lt;/li&gt;
&lt;li&gt;CDM（Copy Data Management）副本数据管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>VMware ESXi和MCloud学习笔记</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/vmware-esxi%E5%92%8Cmcloud%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/vmware-esxi%E5%92%8Cmcloud%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/vmware-esxi%E5%92%8Cmcloud%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/vmware-esxi%E5%92%8Cmcloud%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>杭州宏杉学习笔记-VMware ESXi和MCloud学习笔记</description><pubDate>Fri, 11 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;新建虚拟机&lt;/h1&gt;
&lt;h2&gt;自定义选项&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;CPU:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU、每个插槽内核数，插槽数：ESXi是采用的全虚拟化，没有超线程的概念，也就是说没有物理核心对应逻辑核心，直接虚拟的就是逻辑核心。CPU设置的数量对应线程数，比如我想要4核8线程，直接CPU选8就行了。每个插槽内核数是针对多路CPU的服务器，假设为双路CPU，需要8核，每个插槽内核数如果选择4，此时插槽数就会显示为2，那么就表示这8核会分到两个CPU上。（针对Win的Server系统，最多指支持两路CPU）&lt;/li&gt;
&lt;li&gt;硬件虚拟化：是否启用硬件辅助虚拟化，如英特尔的Intel Vt-x和AMD的AMD-V&lt;/li&gt;
&lt;li&gt;IOMMU（Input-Output Memory Management Unit）:也是一种硬件辅助虚拟化的技术，勾选这个后虚拟机能设备直通，也就是虚拟机直接访问物理设备&lt;/li&gt;
&lt;li&gt;性能计数器：用于监控虚拟机的资源使用情况&lt;/li&gt;
&lt;li&gt;调度关联性：将虚拟机的虚拟核心绑定到物理核心上，可以避免多台虚拟机只占用同一个核心的情况&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;硬盘：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;硬盘置备：控制硬盘空间的分配方式，它有三种方式
&lt;ul&gt;
&lt;li&gt;精简置备：按需分配，仅在使用时分配空间，需要多少分配多少；好处是节省空间，坏处是降低性能，而且可能会导致碎片化&lt;/li&gt;
&lt;li&gt;厚置备置零：先将空间给你分配出来并且立即给你清空，也就是创建时分配空间并初始化，性能最高&lt;/li&gt;
&lt;li&gt;厚置备延迟置零：先给你分配空间但不清空数据，只有在需要写入数据时才清空&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;份额：资源分配的优先级（也就是权重），当多个虚拟机需要同种资源时，它会根据份额决定将资源优先分给谁&lt;/li&gt;
&lt;li&gt;限制-I/OPs：限制硬盘的读写速率&lt;/li&gt;
&lt;li&gt;控制器位置：控制虚拟磁盘连接的虚拟控制器的类型&lt;/li&gt;
&lt;li&gt;磁盘模式：控制硬盘的写入行为，有3中模式
&lt;ul&gt;
&lt;li&gt;独立-持久：直接写入硬盘，永久保存&lt;/li&gt;
&lt;li&gt;独立-非持久：关机或者重启后丢失所有的更改（网吧机子）&lt;/li&gt;
&lt;li&gt;丛属：永久保存，并且有快照&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;共享：字面意思，可以允许多个虚拟机读写该硬盘，但是只能是厚置备指令模式的硬盘&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;引导选项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BIOS：传统引导模式（legacy），最大支持2TB硬盘，支持所有系统&lt;/li&gt;
&lt;li&gt;UEFI：现代引导模式，可选安全启动，启动速度更快&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;虚拟机参数查询：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cpu信息：使用&lt;code&gt;lscpu&lt;/code&gt;可显示CPU的相关信息，包括指令集，字节序（大端，小端），核数，槽数，虚拟化平台（KVM），虚拟化方式（全虚拟化），缓存大小等，也可以使用&lt;code&gt;cat /proc/cpuinfo&lt;/code&gt;查看更为详细的信息&lt;/li&gt;
&lt;li&gt;内存信息：&lt;code&gt;free -h&lt;/code&gt;可粗略的查看总内存，可用内存以及交换区的大小，使用&lt;code&gt;cat /proc/meminfo&lt;/code&gt;查看详细的内存信息，包括基础内存信息，缓存与缓冲区，交换区等等；&lt;code&gt;dmidecode&lt;/code&gt; 查看物理信息（厂商，大小等）
&lt;img src=&quot;images/image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;li&gt;硬盘：&lt;code&gt;df -h&lt;/code&gt;查看文件系统信息，容量情况以及它们的挂载点；&lt;code&gt;lsblk&lt;/code&gt;显示系统的块存储（包括磁盘，光驱等）；或者&lt;code&gt;lshw&lt;/code&gt;在所有硬件数据的disk一项
以下是一个硬盘容量为20G的ubuntu服务器块存储信息&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;  hycer@ubuntu:~$ lsblk
NAME                      MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0                       7:0    0  49.3M  1 loop /snap/snapd/24792 // snap包创建的只读虚拟设备
loop1                       7:1    0  73.9M  1 loop /snap/core22/2010
loop2                       7:2    0 140.6M  1 loop /snap/docker/3265
sda                         8:0    0    20G  0 disk                   // 分配的20G硬盘
├─sda1                      8:1    0   953M  0 part /boot/efi         // 引导分区，存储uefi引导加载程序
├─sda2                      8:2    0   1.8G  0 part /boot             // 内核分区，存储系统启动相关的程序
└─sda3                      8:3    0  17.3G  0 part                   // 剩下17.3G为物理卷用于其他存储分配，被LVM管理 
  └─ubuntu--vg-ubuntu--lv 252:0    0    10G  0 lvm  /                 // 逻辑卷（好比win分出来的CDEF盘），10G用于挂载根目录，另外7.3G未分配，可用LVM管理分配
sr0                        11:0    1     3G  0 rom                    // sr表示光驱，此处挂载的是系统的安装镜像
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;网络：使用&lt;code&gt;ifconfig&lt;/code&gt;(需要安装net-tools包)或者ip addr查看网络信息，包括网卡名，ipV4/v6，子网掩码，广播地址，MAC，上传数据量，下载数据量等&lt;/li&gt;
&lt;li&gt;PCIe设备：&lt;code&gt;lspci&lt;/code&gt;,对于虚拟机来说更多的是虚拟化设备（由Red Hat/QEMU提供），比如虚拟显卡，虚拟网卡，虚拟SCSI控制器等等&lt;/li&gt;
&lt;li&gt;系统及内核：&lt;code&gt;cat /proc/version&lt;/code&gt;, 只看内核可以使用&lt;code&gt;uname -a&lt;/code&gt;，&lt;code&gt;lsb_release -a&lt;/code&gt;可以显示Linux发行版信息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>MCloud对接MDBS开局指导书笔记</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E4%BA%91%E5%8F%8A%E8%B6%85%E8%9E%8D%E5%90%88%E4%BA%A7%E5%93%81%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97/mcloud%E5%AF%B9%E6%8E%A5mdbs%E5%BC%80%E5%B1%80%E6%8C%87%E5%AF%BC%E4%B9%A6%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E4%BA%91%E5%8F%8A%E8%B6%85%E8%9E%8D%E5%90%88%E4%BA%A7%E5%93%81%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97/mcloud%E5%AF%B9%E6%8E%A5mdbs%E5%BC%80%E5%B1%80%E6%8C%87%E5%AF%BC%E4%B9%A6%E7%AC%94%E8%AE%B0/</guid><description>杭州宏杉学习笔记-MCloud对接MDBS开局指导书V1.12</description><pubDate>Fri, 11 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;硬件组网&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.MCloud3012G2服务器配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU：双路&lt;/li&gt;
&lt;li&gt;内存：至少128G&lt;/li&gt;
&lt;li&gt;IO插卡：至少配置2张2x25GE或者2张2x10GE或者1张4x10GE&lt;/li&gt;
&lt;li&gt;系统盘：2*480GB SSD 组RAID1&lt;/li&gt;
&lt;li&gt;缓存盘：NVMe SSD至少1.92TB&lt;/li&gt;
&lt;li&gt;数据盘：&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.交换机配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;管理网络（用于系统管理和维护）：1台千兆交换机且接口数大于2N（N为服务器节点）&lt;/li&gt;
&lt;li&gt;存储网络（用于存储节点间数据通信）：2台10GE/25GE交换机&lt;/li&gt;
&lt;li&gt;业务网络（用于客户业务通信）：1/2台10GE/25GE交换机&lt;/li&gt;
&lt;li&gt;BMC网络（用于服务器硬件管理和维护）：1台千兆交换机且接口数大于N&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.硬件组网：
&lt;img src=&quot;images/image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.网络规划：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BMC网络：每台服务器一个IP&lt;/li&gt;
&lt;li&gt;管理网络：每个节点2个千兆网口做bond，每个节点一个IP&lt;/li&gt;
&lt;li&gt;管理VIP：与管理网络同网段，每套环境一个IP&lt;/li&gt;
&lt;li&gt;存储网络：必须与外网隔离，每个节点2个10GE/25GE网口做bond，bond模式为&lt;code&gt;mode=4，miimon=100，xmit_hash_policy=layer3+4&lt;/code&gt;（动态链路聚合，实现负载均衡和故障切换；设置每100毫秒检测一次链路状态，流量分配策略基于 源/目的IP（Layer 3）和端口（Layer 4）的哈希值，确保同一数据流的流量走同一物理网卡），网口设置为access模式，最大传输字节（MTU）为9000字节。&lt;/li&gt;
&lt;li&gt;业务网络：网口为trunk模式，不需要配置IP（用户创建云主机是才会配置网络），不能使用存储和管理网络&lt;/li&gt;
&lt;li&gt;迁移网络：每台服务器一个IP，可复用业务网络但不能使用存储和管理网络&lt;/li&gt;
&lt;li&gt;iSCSI网络（用于MCloud和MDBS数据读写通信）：可复用存储网络，不可使用业务网络&lt;/li&gt;
&lt;li&gt;补充bond模式：
&lt;ul&gt;
&lt;li&gt;mode0：balance-rr，通过轮询的方式将数据包发送到各个接口（不是每个接口都发一遍，通过轮询将数据包分配给各个接口），能实现负载均衡&lt;/li&gt;
&lt;li&gt;mode1：active-backup，只有一个接口活跃，其余的接口用作备用，仅在活动接口故障时接管，提供了故障切换和冗余性&lt;/li&gt;
&lt;li&gt;mode2：balance-xor，根据数据包的源MAC和目的MAC地址以及传输层协议和端口号的哈希值来固定选择某个接口，也是通过轮询的方式，但不像mode1那样简单的轮询，它会轮询并匹配哈希值，这就使得同一个连接的数据包始终走同一个接口，避免TCP乱序的问题，能提供负载均衡&lt;/li&gt;
&lt;li&gt;mode3：broadcast，将所有数据包发送给所有接口，广播传输&lt;/li&gt;
&lt;li&gt;mode4：802.3ad，也称为“LACP”（Link Aggregation Control Protocol）模式。交换机和服务器之间能通过LACP协议动态协商，根据接口的流量分配接口，一个故障会切换到另一条，实现负载均衡&lt;/li&gt;
&lt;li&gt;mode5：balance-tlb，发送数据包（上传）根据算法（TLB）分配给不同的接口，但接收数据包（下载）只使用一个接口，在接收方向实现负载均衡，适用于上传流量大的情况（直播推流，视频监控）&lt;/li&gt;
&lt;li&gt;mode6：balance-alb，发送和接收都会根据算法分配给不同的接口，比mode5更均衡&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;补充网口模式：
&lt;ul&gt;
&lt;li&gt;access模式：只能传输一个VLAN，用于连接终端设备（电脑，打印机）&lt;/li&gt;
&lt;li&gt;trunk模式：可以传输多个VLAN，允许通过的VLAN才能进入，用于连接其他交换机或路由器&lt;/li&gt;
&lt;li&gt;Hybrid模式：可以传输多个VLAN，不对承载的数据打标签&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.交换机配置
&lt;img src=&quot;images/image2.png&quot; alt=&quot;alt text&quot; /&gt;
将两台交换机hostname修改成不同的主机名，如SwitchA、SwitchB&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Switch# configure terminal
Enter configuration commands, one per line.  End with CNTL/Z.
Switch(config)# hostname SwitchA                                                         #修改主机名为SwitchA
SwitchA(config)# do wr
Building configuration...
[OK]
SwitchA(config)#
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置管理IP&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SwitchA# configure terminal
SwitchA(config)# management ip address 172.22.99.98/16
SwitchA(config)# management route add gateway 172.22.0.1
#设置交换机管理IP、子网掩码、网关，实际以配置为准
SwitchA(config)# do wr
Building configuration...
[OK]
SwitchA(config)# exit
SwitchA# show management ip address
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置端口速率，配置MLAG（两个交换机逻辑上合二为一）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.服务器配置：
使用&lt;code&gt;cat /etc/version&lt;/code&gt;查询系统版本
使用&lt;code&gt;lsblk&lt;/code&gt;查看系统盘分区大小，确保boot分区有1024M，boot/efi分区有500M，core分区100G，/var/log分区20G
使用&lt;code&gt;swapon -s&lt;/code&gt;检查确保没有swap分区&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;资源预留：&lt;/p&gt;
&lt;p&gt;$$
集群可用CPU资源=（CPU总颗数&lt;em&gt;单颗CPU超线程数-(M+ 12&lt;/em&gt;n)*1）*超分率 *0.8
$$&lt;/p&gt;
&lt;p&gt;M为集群所有数据盘数，n为计算存储节点数&lt;/p&gt;
&lt;p&gt;$$
单节点可用内存 = 总内存 - 26GB  - 缓存盘占用内存 – 数据盘占用内存
$$&lt;/p&gt;
&lt;p&gt;26GB为操作系统8GB+10GMDBS基础服务+8GMCloud云平台管理，缓存盘每个缓存分区占用2GB，数据盘占用=数据盘数*N（数据盘容量&amp;lt;=8TB,N=4G，否则N=5G）&lt;/p&gt;
&lt;p&gt;$$
分布式块冗余可用容量 = 单节点裸容量 * 节点数 * 冗余利用率 * 0.8
$$&lt;/p&gt;
&lt;p&gt;仅支持3副本，冗余利用率为33.33%&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>云计算与虚拟化学习笔记</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E4%BA%91%E8%AE%A1%E7%AE%97%E4%B8%8E%E8%99%9A%E6%8B%9F%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E4%BA%91%E8%AE%A1%E7%AE%97%E4%B8%8E%E8%99%9A%E6%8B%9F%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E4%BA%91%E8%AE%A1%E7%AE%97%E4%B8%8E%E8%99%9A%E6%8B%9F%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E4%BA%91%E8%AE%A1%E7%AE%97%E4%B8%8E%E8%99%9A%E6%8B%9F%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>杭州宏杉学习笔记-云计算与虚拟化</description><pubDate>Thu, 10 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;云计算与虚拟化&lt;/h1&gt;
&lt;h2&gt;云计算&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;什么是云计算：通俗来讲云计算就是把计算机资源变成了像水电一样的基础资源，让任何人都能在需要时取用计算机资源（按需使用），而无需自建服务器机房（只需投入很少的管理工作）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;云计算的&lt;strong&gt;特性&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;按需自助&lt;/strong&gt;：类似自助餐，用户可以直接在云平台上自己开通计算，存储等服务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;广阔的网络访问&lt;/strong&gt;：云服务通常有公网，可用任何设备随时随地访问&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源池化&lt;/strong&gt;：云厂商把计算、存储等资源集中成“池子”，所有用户共享，但互相隔离（你的云服务器和别人的在同一台物理机上，但彼此是独立的）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;快速弹性伸缩&lt;/strong&gt;：能根据业务需求秒级扩容或缩容（网络带宽，硬盘，内存等）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可计量服务&lt;/strong&gt;：用多少算多少钱&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;云计算的&lt;strong&gt;服务类型&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SaaS(Software as a Service)：软件即服务，如企业微信，钉钉等在线办公以及云仓库平台，无需自建服务器即可使用的软件工具&lt;/li&gt;
&lt;li&gt;PaaS(Platform as a Service)：平台即服务，如函数计算，Vercel等静态网页托管网站，上传代码自动运行，无需管理服务器&lt;/li&gt;
&lt;li&gt;IaaS(Infrastructure as a Service)：基础设施即服务，如阿里云ECS云服务器等需要完全控制OS或运行自定义软件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;云计算的&lt;strong&gt;部署模式&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;私有云：企业利用自有或租用的设施自建的云&lt;/li&gt;
&lt;li&gt;社区云/行业云：为特定社区或行业所构建的共享基础设施的云&lt;/li&gt;
&lt;li&gt;共有云：出租给公众的大型基础设施的云&lt;/li&gt;
&lt;li&gt;混合云：由两种或两种以上部署模式组成的云&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;云计算关键技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计算架构：采用Scale Out模式（横向拓展，堆叠多个计算资源）而非Scale Up模式（纵向拓展，升级单个计算资源）&lt;/li&gt;
&lt;li&gt;云计算硬件：服务器，交换机，存储&lt;/li&gt;
&lt;li&gt;云计算软件：并行计算技术，分布式存储，分布式文件管理，虚拟化技术，智能化云计算系统管理技术等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;集群：多台主机提供相同的服务的一组相互独立的计算机就叫&lt;strong&gt;集群&lt;/strong&gt;（cluster），单个的计算机系统就是集群的&lt;strong&gt;节点&lt;/strong&gt;（node）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;虚拟化：将资源抽象成共享资源池，上层操作系统与硬件解耦，操作系统从资源池中分配资源，用户可以将单个物理硬件系统为基础创建多个模拟环境或专用资源，从而将一个系统划分为不同的、单独安全环境，也就是虚拟机&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;虚拟化的本质：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分区&lt;/strong&gt;：在单一物理服务器上同时运行多个虚拟机&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隔离&lt;/strong&gt;：多个虚拟机之间相互隔离&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;封装&lt;/strong&gt;：整个虚拟机执行环境封装在独立文件中，可以通过移动文件的方式来迁移该虚拟机&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;相对于硬件独立&lt;/strong&gt;：虚拟机无需修改，即可在任何服务器上运行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;虚拟化中的重要概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Host Machine：物理机，宿主机&lt;/li&gt;
&lt;li&gt;Host Os：物理机的OS&lt;/li&gt;
&lt;li&gt;Hypervisor：虚拟化软件层/虚拟机监控机（Virtual Machine Monitor，VMM）&lt;/li&gt;
&lt;li&gt;Guest Mechine：虚拟机&lt;/li&gt;
&lt;li&gt;Guest OS：虚拟机的OS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;主流的虚拟化技术：Hyper-v, VMware ESXi, Huawei FusionSphere, KVM, Xen&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;计算&lt;strong&gt;虚拟化技术的分类&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虚拟化对象：CPU虚拟化，内存虚拟化，I/O虚拟化&lt;/li&gt;
&lt;li&gt;虚拟化过程：全虚拟化，半虚拟化，硬件辅助虚拟化&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CPU虚拟化：让一个物理CPU假装成多个虚拟CPU供多个虚拟机使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全虚拟化：通过一个虚拟化层完全模拟CPU指令，让虚拟机不知道自己运行在虚拟环境中。但是性能损耗较大，如VMware ESXi&lt;/li&gt;
&lt;li&gt;半虚拟化：让虚拟机知道自己被虚拟化，并主动配合Hypervisor优化指令执行。但是需要修改系统内核（Linux可以，win不信）&lt;/li&gt;
&lt;li&gt;硬件辅助虚拟化：CPU厂商直接在硬件层面支持虚拟化，性能接近原生，兼容性好。如KVM，Hyper-v&lt;/li&gt;
&lt;li&gt;容器化（轻量级虚拟化）：不虚拟化整个CPU，而是让多个容器共享一个操作系统内核，通过隔离技术分配资源。如Docker，Kuberbetes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I/O虚拟化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全虚拟化：Hypervisor完全模拟一个标准的硬件设备，但是性能差&lt;/li&gt;
&lt;li&gt;半虚拟化：Hypervisor提供优化过的虚拟设备驱动（如virtio）VM需要安装特定驱动&lt;/li&gt;
&lt;li&gt;硬件辅助虚拟化：物理设备原生支持虚拟化&lt;/li&gt;
&lt;li&gt;设备直通：将整个设备直接独占分配给一个VM&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;虚拟化技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虚拟化平台：通过虚拟化平台实现虚拟机资源的灵活调度、池化&lt;/li&gt;
&lt;li&gt;高可用：同一集群内某台计算机节点故障，其上承载的虚拟机能够在其他节点自动重新启动&lt;/li&gt;
&lt;li&gt;迁移：能够将某计算节点上承载的虚拟机迁移到其他计算节点&lt;/li&gt;
&lt;li&gt;虚拟机快照：保存虚拟机在某一时刻的数据状态，当发生故障后可进行回滚&lt;/li&gt;
&lt;li&gt;虚拟机热升级：在不需要停机的情况下，添加CPU，内存，磁盘等资源，减少业务系统的停机时间。&lt;/li&gt;
&lt;li&gt;分布式资源调度（Distributed Resource Scheduler）：通过热迁移技术和智能算法，动态均衡集群内资源负载&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SAN存储（Storage Area Network）：是一种专为高速数据访问设计的网络存储系统，它将硬盘等存储设备集中管理，并通过专用网络（如光纤）分配给服务器，让服务器像使用本地硬盘一样远程访问存储。SAN的两种主流协议：光纤通道（FC SAN），iSCSI SAN（以太网传输）&lt;/li&gt;
&lt;li&gt;分布式存储应用架构：将海量数据分散存储在多台服务器上的软件系统，通过协同管理实现高可靠、高扩展和高性能，比如HDFS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;云平台管理功能&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;弹性计算&lt;/li&gt;
&lt;li&gt;裸金属服务：一种既具备物理机性能、又有云服务弹性的计算服务，能让用户直接租用云厂商的物理服务器（可以理解为包整机）&lt;/li&gt;
&lt;li&gt;V2V迁移服务（Virtual to Virtual）：指将VM从一台虚拟化平台搬到另一台虚拟化平台（好比我要把测试环境的软件搬到生产环境去）&lt;/li&gt;
&lt;li&gt;资源编排：通过鼠标拖拉拽等方式快速构建业务资源环境&lt;/li&gt;
&lt;li&gt;多区域管理：总部统一管理所有区域资源&lt;/li&gt;
&lt;li&gt;灾备服务&lt;/li&gt;
&lt;li&gt;VMware接管功能：对接VMware vCenter，支持数据中心、集群和主机发现&lt;/li&gt;
&lt;li&gt;全局监控&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;超融合：用软件定义的方式，将计算、存储、网络整合到标准服务器中（侠义地理解为一体机，将服务器，存储阵列，网络交换机等整合为一体）；可以通过资源池化的方式融合，也可以通过标准化的硬件平台&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MCloud(MacroSAN Cloud)部署方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本地部署&lt;/li&gt;
&lt;li&gt;超融合部署&lt;/li&gt;
&lt;li&gt;分布式分离部署&lt;/li&gt;
&lt;li&gt;SAN分离部署&lt;/li&gt;
&lt;li&gt;SAN+分布式块融合部署&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MCloud高可用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;硬件高可用&lt;/li&gt;
&lt;li&gt;管理节点高可用：双节点高可用，节点间采用master-slave方式&lt;/li&gt;
&lt;li&gt;云主机高可用：宿主机故障云主机自动迁移&lt;/li&gt;
&lt;li&gt;网络高可用：VPC网络，VPC双机&lt;/li&gt;
&lt;li&gt;分布式存储高可用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Linux学习笔记</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/linux%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/linux%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/linux%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/linux%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>杭州宏杉Linux学习笔记</description><pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Linux学习笔记&lt;/h1&gt;
&lt;h2&gt;第三章 安装CentOS7.x&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Linux中镜像文件的写入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dd if=centos7.iso of=/dev/sdc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;dd为一个用于文件复制与转换的指令，if为输入文件（input file）of为输出文件（output file）
这条指令用于将centos7的镜像覆写到sdc设备中，覆写会抹除设备中原内容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;虚拟机的网络连接模式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NAT（网络地址转换）：虚拟机通过宿主机的IP访问外网，宿主机充当路由器给虚拟机分配一个私有IP&lt;/li&gt;
&lt;li&gt;Bridged（桥接模式）：虚拟机直接连接到物理网络，虚拟机会从路由器获得一个独立的IP&lt;/li&gt;
&lt;li&gt;Host-Only(仅主机模式)：虚拟机只能与宿主机进行通信，不能访问外网，如果有多个虚拟机，虚拟机之间可以相互通信&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Linux的显示服务器：显示服务器负责管理图形界面的底层渲染和输入输出，主要有两种&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;X.Org Server (X11): 传统，性能较老，但兼容性好，大多数发行版默认使用&lt;/li&gt;
&lt;li&gt;Wayland：现代替代方案，更轻量，低延迟，主流发行版正逐步迁移至Wayland&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;常见的Linux的桌面环境&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GNOME：现代化，简洁。显示服务器默认Wayland（可切换X11），资源占用较高&lt;/li&gt;
&lt;li&gt;KDE Plasma：高度可定制，功能丰富。显示服务器默认X11，正逐步完善Wayland。资源占用中等&lt;/li&gt;
&lt;li&gt;Xfce：轻量级，界面传统。显示服务器为X11。资源占用低&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Swap分区：它是Linux系统中的一种虚拟内存机制，它通过使用硬盘空间来扩展内存（RAM）一般为实体内存的1.5-2倍，类似于安卓系统中的内存扩展。当有数据被存放在实体内存里面，但是这些数据又不是常被CPU所取用时，那么这些不常被使用的程序将会被丢到硬盘的swap交换空间当中，而将速度较快的实体内存空间释放出来给真正需要的程序使用。 Swap分区可以防止因内存不足造成的系统崩溃，可以用于休眠功能的支持，当设备休眠时，系统会将RAM中的数据写入Swap分区，以便下次开机时恢复。如果没有Swap，休眠功能也就无法使用。使用 &lt;code&gt;free -h&lt;/code&gt;可以查询当前的内存和Swap分区使用情况，其中的&lt;code&gt;-h&lt;/code&gt;参数为将内存数据转化为易读的格式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LVM（Logical Volume Manager）逻辑卷管理器：是Linux系统的一种高级磁盘管理技术，它允许将多个物理磁盘或者分区组合成逻辑卷&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PV（Physical Volume）：物理卷，实际的磁盘或分区。好比于砖块&lt;/li&gt;
&lt;li&gt;VG（Volume Group）：卷组，由多个PV组成，形成一个存储池，用于分配空间。好比于砖块砌成的墙&lt;/li&gt;
&lt;li&gt;LV（Logical Volume）：逻辑卷，从VG中划分出来的逻辑分区，可直接格式化和挂载使用。好比于用墙围成的房间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第四章 首次登录与线上求助&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Linux默认的情况下会提供六个Terminal来让使用者登陆，切换的方式为使用：[Ctrl]+[Alt]+[F1]~[F6]的组合按钮。六个终端接口系统会分别命名为tty1-tty6&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sync指令：在Linux系统中，为了加快数据的读取速度，所以在默认的情况中，某些已经载入内存中的数据将不会直接被写回硬盘，而是先暂存在内存当中。但是万一系统因某些特殊情况造成不正常的关机，就会造成数据的更新不正常。输入sync，那么在内存中尚未被更新的数据，就会被写入硬盘中。一般用户使用sync只会保存自己的数据，而root用户可以保存所有数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;man page说明后面的数字中，1代表一般帐号可用指令，8代表系统管理员常用指令，5
代表系统配置文件格式；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第五章 Linux的文件权限与目录配置&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ls&lt;/code&gt;只会列出非隐藏文件名， &lt;code&gt;ls -al&lt;/code&gt; 可以显示所有文件（包括&lt;code&gt;.&lt;/code&gt;开头的隐藏文件）的详细信息，&lt;code&gt;ls -l&lt;/code&gt;则会列出非隐藏文件的详细信息（等于&lt;code&gt;ll&lt;/code&gt;），比如：&lt;code&gt;-rwxr-xr-- 1 root root 5 jul 9 16:58 test.c&lt;/code&gt;第一栏的首位 &lt;code&gt;-&lt;/code&gt; 代表它为文件，&lt;code&gt;d&lt;/code&gt; 为目录，&lt;code&gt;l&lt;/code&gt; 为链接文件 &lt;code&gt;b&lt;/code&gt; 为设备文件中的可存储设备（如U盘），&lt;code&gt;c&lt;/code&gt; 为设备文件中的外设（如键盘鼠标）
后9位以每3位未一组，分表代表&lt;strong&gt;拥有者&lt;/strong&gt;，&lt;strong&gt;群组&lt;/strong&gt;，&lt;strong&gt;其他&lt;/strong&gt;的读(r)写(w)执行(x)权限
第二栏的数字代表有多少文件名硬链接到该节点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于普通文件，新建文件时默认为1，因为只有该文件名指向该文件；每创建一个硬链接该数字就会加一&lt;/li&gt;
&lt;li&gt;对于目录,这个数字&lt;strong&gt;至少为2&lt;/strong&gt;（目录本身+目录内的&lt;code&gt;.&lt;/code&gt;条目（使用 &lt;code&gt;ls -al&lt;/code&gt; 就能看到）），每有一个子目录，该数字就会加一（子目录中的&lt;code&gt;..&lt;/code&gt;条目指向父目录）&lt;/li&gt;
&lt;li&gt;补充硬链接和软链接：&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;硬链接&lt;/th&gt;
&lt;th&gt;软链接&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;本质&lt;/td&gt;
&lt;td&gt;同一个文件的另一个文件名&lt;/td&gt;
&lt;td&gt;一个特殊文件，存储目标文件的路径（可以理解为win的快捷方式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;指向目录&lt;/td&gt;
&lt;td&gt;通常不支持&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;跨文件系统&lt;/td&gt;
&lt;td&gt;不能跨不同磁盘或分区&lt;/td&gt;
&lt;td&gt;可以跨磁盘或分区&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;删除原文件&lt;/td&gt;
&lt;td&gt;硬链接仍然有效（直到所有硬链接被删除，可以理解为指针，修改指针的数据==修改原数据，数据没有指向它的指针时就会被回收）&lt;/td&gt;
&lt;td&gt;软链接失效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;修改影响&lt;/td&gt;
&lt;td&gt;修改任一硬链接，所有硬链接&lt;strong&gt;同步变化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;修改软链接本身&lt;strong&gt;不影响原文件&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;命令&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ln 源文件 硬链接名&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ln -s 源文件 软链接名&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;使用场景&lt;/td&gt;
&lt;td&gt;需要多个路径访问同一文件，且不希望原文件删除后失效的情况&lt;/td&gt;
&lt;td&gt;需要跨文件系统链接文件或目录、动态引用文件（版本切换，配置文件变更）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;第三栏和第四栏的&lt;code&gt;root&lt;/code&gt;代表该文件的拥有者以及所属群组
第五栏为文件大小，单位为字节（Bytes）
第六栏为文件最后修改的日期
最后为文件名&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;修改文件权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod [-R] xyz 文件或目录 // 使用数字更改权限
chmod [-R] u=rwx,go=rx 文件或目录 // 使用符号更改权限（u, g, o, a）
chmod [-R] a-x 文件或目录 // 使用+,-更改权限，这条指令表示所有人去掉执行权限
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件权限说明：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;元件&lt;/th&gt;
&lt;th&gt;r&lt;/th&gt;
&lt;th&gt;w&lt;/th&gt;
&lt;th&gt;x&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;文件&lt;/td&gt;
&lt;td&gt;读取文件内容&lt;/td&gt;
&lt;td&gt;修改文件内容&lt;/td&gt;
&lt;td&gt;执行文件内容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;目录&lt;/td&gt;
&lt;td&gt;读取该目录下的文件名数据（可使用ls）&lt;/td&gt;
&lt;td&gt;修改文件夹下的内容&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;进入该目录的权限&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;Linux文件结构&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/bin        二进制文件，系统常规命令
/boot       系统启动分区，系统启动时读取的文件
/dev        设备文件
/etc        大多数配置文件
/home       普通用户的主目录
/lib        32位函数库
/lib64      64位库
/media      光盘、DVD等挂载点
/mnt        临时文件系统挂载点(U盘等)
/opt        第三方软件安装位置
/proc       进程信息及硬件信息
/root       超级用户主目录
/sbin       系统管理命令
/srv        网络服务需要取用的数据目录
/var        存放经常变化的文件(如日志)
/sys        内核相关信息
/tmp        临时文件
/usr        存放用户应用程序和文件(unix software resource)

/usr/bin    所有一般用户能够使用的指令
/usr/lib    与lib功能相同
/usr/local  系统管理员在本机自行安装和下载的软件
/usr/sbin   非系统正常运行所需要的系统指令
/usr/share  只读架构的数据文件和共享文件

/var/cache  应用产生的缓存
/var/lib    程序执行过程中产生的数据文件
/var/lock   程序产生的锁文件
/var/log    登录文件的放置目录
/var/mail   放置个人电子邮件的目录
/var/run    某些程序或服务启动后，会将他们的PID放置在这个目录下
/var/spool  放置排队等待其他程序使用的数据
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;images/image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;第六章 Linux文件与目录管理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;特殊目录符号：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.     // 代表当前目录
..    // 代表上层目录（父级目录）
-     // 代表上一个目录
~     // 代表用户目录(cd ~ 与 cd 效果相同)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ls&lt;/code&gt;常用参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-a    // 列出全部文件，连同隐藏文件
-A    // 列出全部文件，但不包括 . 和 ..
-d    // 只列出文件夹
-h    // 将文件大小以人类较易读的方式列出
-l    // 列出文件属性和权限等数据（ll）
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cp&lt;/code&gt;常用参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-a    // 等同-dr,（复制会改变文件的创建时间和拥有属性，加上该参数则会完全复制）
-d    // 若源文件为链接文件，则复制链接文件属性而非文件本身
-i    // 若目标文件已经存在，则在覆盖时会询问
-r    // 递归复制，用于复制目录
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;basename&lt;/code&gt; 和 &lt;code&gt;dirname&lt;/code&gt;：&lt;code&gt;basename&lt;/code&gt; 可以显示文件名称，&lt;code&gt;dirname&lt;/code&gt; 可以显示文件的路径&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hycer@ubuntu:~/dir1$ basename /home/hycer/dir1/file1.txt
file1.txt
hycer@ubuntu:~/dir1$ dirname /home/hycer/dir1/file1.txt
/home/hycer/dir1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件内容查阅：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cat：从第一行开始显示&lt;/li&gt;
&lt;li&gt;tac：从最后一行开始显示&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;nl&lt;/strong&gt;：显示顺带输出行号&lt;/li&gt;
&lt;li&gt;more：一页一页的显示文件内容&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;less&lt;/strong&gt;：与more相似但是它可以往前翻&lt;/li&gt;
&lt;li&gt;head：只看前面几行（-n参数指定具体行数）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tail&lt;/strong&gt;：只看最后几行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件的时间参数&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;modification time（&lt;strong&gt;mtime&lt;/strong&gt;）：文件内容变更时的时间（&lt;em&gt;ls默认显示出来的时间&lt;/em&gt;）&lt;/li&gt;
&lt;li&gt;status time（&lt;strong&gt;ctime&lt;/strong&gt;）：文件状态变更时的时间（权限，属性）&lt;/li&gt;
&lt;li&gt;access time（atime）：文件被读取的时间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;touch修改文件时间&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;touch -m [filename] // 修改文件的mtime为当前时间
touch -t 202507141153 // 修改文件的mtime为2025/7/14/11：53

touch -a [filename] // 修改文件的mtime为当前时间
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件默认权限：&lt;code&gt;umask&lt;/code&gt;
使用指令&lt;code&gt;umask&lt;/code&gt;可查看新建文件或目录的默认权限分数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hycer@ubuntu:~/dir1$ umask
0002
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;注意：首位为&lt;strong&gt;特殊权限&lt;/strong&gt;。权限分数为减掉的权限。如一个txt文件（没有执行权限）创建时的它的权限就为&lt;code&gt;666 - 002 = 664&lt;/code&gt; 也就是其他人没有写入的权限，目录则为&lt;code&gt;777 - 002 = 775&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;也可以使用 &lt;code&gt;-S&lt;/code&gt; 参数用符号的方式展示权限&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hycer@ubuntu:~$ umask -S
u=rwx,g=rwx,o=rx
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件隐藏属性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;chattr&lt;/code&gt;设置隐藏属性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chattr [+-=] [属性]

+：加上属性
-：减掉属性
=：设置属性

A：读取时间 atime 不会被修改
S：文件的修改会同步写入磁盘（一般文件是非同步写入磁盘，参考上面的sync指令）
a：只能增加数据，不嫩修改和删除，只有root能设置这个属性
c：会将文件数据压缩后再写入磁盘（用于大文件且不频繁访问）
d：不会被`dump`程序备份（dump是一个文件系统备份工具，支持全量和增量备份）
i：不能被删除，改名，无法写入或新增数据，只有root能设置在属性
s：被删除时无法恢复（永久删除）
u：与s相反，删除时不会从磁盘中移除，可以恢复

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lsattr&lt;/code&gt;查看隐藏属性&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;观察文件类型：使用&lt;code&gt;file&lt;/code&gt;能显示某个文件的基本数据是ASCII，data还是二进制&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hycer@ubuntu:~/dir1$ file file1.txt 
file1.txt: ASCII text
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查找：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用&lt;code&gt;which&lt;/code&gt;能显示命令存放的路径&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用&lt;code&gt;whereis&lt;/code&gt;可以查找命令的二进制，源码，手册的位置&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用&lt;code&gt;find&lt;/code&gt;能全盘搜索任意文件,支持名称，类型，时间，权限等参数（速度最慢）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find [查找路径] -name [目标名称]

hycer@ubuntu:~$ sudo find / -name .bashrc
[sudo] password for hycer: 
/etc/skel/.bashrc
/snap/core22/2045/etc/skel/.bashrc
/snap/core22/2045/root/.bashrc
/snap/core22/2010/etc/skel/.bashrc
/snap/core22/2010/root/.bashrc
/root/.bashrc
/home/hycer/.bashrc
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第七章 Linux的磁盘和文件系统管理&lt;/h2&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件系统特性：一个超级区块（superblock）记录整个文件系统的整体信息，数据的信息（如权限，属性）会被放在inode种，实际的数据会被存放在datablock里面,这种也叫索引式文件系统，inode和block初始就会完成分配；可以使用指令&lt;code&gt;df&lt;/code&gt;查看文件系统的整体磁盘使用量&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;superblock：记录文件系统的的整体信息，包括inode和block的总量，使用量，余量等。一般来说一个文件系统只会含有一个superblock，但会存在备份的情况&lt;/li&gt;
&lt;li&gt;inode：记录文件属性和记录文件数据的block，一个文件使用一个inode&lt;/li&gt;
&lt;li&gt;block：实际记录文件的内容，一个文件可能会使用多个block（使用ll 查看文件列表时，第一行会显示&lt;code&gt;total&lt;/code&gt;的字样，其实就是该目录下所有数据占用的block数量）
&lt;img src=&quot;images/image2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;区块对照表block bitmap：记录新增数据所需要的空block，删除文件后会释放block号码。inode bitmap与之同理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;目录：创建一个目录时，文件系统会分配一个inode和至少一个block给这个目录（inode bitmap和block bitmap会找到没有使用的inode和block），inode用于记录该目录的相关权限和属性和它分配给它的block号码，而block则记录在这个目录下的&lt;strong&gt;文件名&lt;/strong&gt;和该文件名占用的inode号码（所以inode不记录文件名而是在block中记录，这也是为什么在目录下新增，删除或修改文件名得有该目录的w权限），再将inode和block数据同步更新给inode bitmap 和block bitmap，再更新superblock&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件：创建一个文件时，文件系统会分配一个inode和对应大小的block数量给它（假如创建一个82k的文件，每个block大小为4k，则会给它分配21个block，但是&lt;strong&gt;一个inode最多12个直接指向&lt;/strong&gt;，因此还要多一个inode存储block号码）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件系统的运行：当系统载入一个文件到内存后，若这个文件没有被编辑，则这段数据会被置为干净的（clean），当用户编辑这个文件时，系统会将它置为脏数据，用户的改动全在内存中并不会写入磁盘，系统会不定时将数据写入磁盘（也可以用上面提到的sync指令手动写入磁盘）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;硬链接和软链接：硬链接的实质就是新增一个文件名指向源文件的inode号码；软链接的实质是新增一个文件（新增一个inode），这个文件指向源文件的文件名（所以源文件重命名，删除后就无法它的软链接了）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用&lt;code&gt;parted [设备路径] print&lt;/code&gt;可以列出磁盘的分区表类型和分区信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hycer@ubuntu:~/dir1$ sudo parted /dev/sda print
Model: QEMU QEMU HARDDISK (scsi)
Disk /dev/sda: 21.5GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags: 

Number  Start   End     Size    File system  Name  Flags
1      1049kB  1000MB  999MB   fat32              boot, esp
2      1000MB  2879MB  1879MB  ext4
3      2879MB  21.5GB  18.6GB
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;磁盘分区&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.使用&lt;code&gt;lsblk&lt;/code&gt;查看磁盘名&lt;/li&gt;
&lt;li&gt;2.用&lt;code&gt;parted&lt;/code&gt;查看分区类型&lt;/li&gt;
&lt;li&gt;3.使用&lt;code&gt;gdisk&lt;/code&gt;(gpt分区)或&lt;code&gt;fdisk&lt;/code&gt;(mbr分区)来进行分区操作&lt;/li&gt;
&lt;li&gt;4.保存分区后使用&lt;code&gt;partprobe&lt;/code&gt;更行分区表信息，或者直接重启&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建swap分区&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.使用磁盘分区工具将swap分区划分出来（注意分区的类型要选择Linux swap，也就是GUID=8200）&lt;/li&gt;
&lt;li&gt;2.使用&lt;code&gt;mkswap [分区路径]&lt;/code&gt;创建swap分区&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;磁盘格式化：&lt;code&gt;mkfs&lt;/code&gt;指令，输入mkfs后接tab会列出支持创建的文件系统&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;挂载文件系统&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最好将文件系统挂载到一个空的目录下，因为挂载后会&lt;strong&gt;隐藏&lt;/strong&gt;原目录的内容（并不会覆盖）&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;mount [设备路径] [挂载点]&lt;/code&gt;挂载文件系统&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;umount [设备路径/挂载点]&lt;/code&gt;解除挂载&lt;/li&gt;
&lt;li&gt;/etc/rc.d/rc.local文件负责开机运行的软件或命名，也能用于挂载文件系统&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;mount UUID=xxx /data
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;/etc/fstab文件负责保存开机自动挂载文件系统的配置（该文件配置错误可能导致无法启动，修改完成后需使用&lt;code&gt;mount -a&lt;/code&gt;验证该文件是否配置正确）
&lt;code&gt;/dev/vda  /data ext4 defaults 0 0&lt;/code&gt;
依次为目标设备路径（也可以为UUID=xxx)，挂载路径，文件系统格式，挂载选项（mount指令），备份设置（0为不备份），是否开机检查（0为开机不检查）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;可以使用&lt;code&gt;blkid /dev/sda&lt;/code&gt;查看设备的UUID&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>服务器硬件基本概念</title><link>https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E7%A1%AC%E4%BB%B6%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E7%A1%AC%E4%BB%B6%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%9D%AD%E5%B7%9E%E5%AE%8F%E6%9D%89/%E7%A1%AC%E4%BB%B6%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E7%A1%AC%E4%BB%B6%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>杭州宏杉硬件学习笔记-服务器硬件基本概念</description><pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;服务器硬件基本概念&lt;/h1&gt;
&lt;h2&gt;服务器概念&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;服务器分类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按指令集分：&lt;strong&gt;CISC复杂指令集&lt;/strong&gt;服务器（X86架构）、&lt;strong&gt;RISC精简指令集&lt;/strong&gt;服务器（非X86架构）&lt;/li&gt;
&lt;li&gt;按外形分：塔式（形似家用主机）、机架式、刀片式、高密
&lt;img src=&quot;images/image1.png&quot; alt=&quot;按外形分类&quot; /&gt;&lt;/li&gt;
&lt;li&gt;按处理器数量分：单路服务器（1颗CPU）、双路服务器（2颗CPU）、多路服务器（4颗及以上CPU）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BIOS的功能：&lt;strong&gt;计算机启动时加载的第一个软件&lt;/strong&gt;，主要用于POST自检，检测输入输出设备和可启动设备，包括内存初始化、硬件扫描和寻找启动设备、启动系统，高级电源管理，配置RAID，查看系统信息。在Linux系统中，可以使用指令&lt;code&gt;sudo dmidecode -t bios&lt;/code&gt;查看bios的相关信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UEFI和Legacy模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主流服务器BIOS默认为UEFI模式&lt;/strong&gt;，相较于传统的Legacy，可以支持从大于2.2T的GPT格式硬盘引导，支持网络PXE引导，提供UEFI Shell环境&lt;/li&gt;
&lt;li&gt;没有特殊需求一般为UEFI模式，&lt;strong&gt;备份则为Legacy模式&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BMC（基板管理控制器）：用于监控和管理服务器的专用控制器，它是一个&lt;strong&gt;独立的系统&lt;/strong&gt;，只要上电BMC软件就能快速运转起来，主要功能包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;设备信息管理&lt;/strong&gt;：记录服务器型号，制造商，日期，主板信息等，BMC可以通过Web界面方式查看系统信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务器状态监控管理&lt;/strong&gt;：对服务器的CPU、内存、硬盘、风扇、机框等部件的温度、电压等健康状态进行管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务器远程控制&lt;/strong&gt;：服务器开关机、重启、维护、固件更新、系统安装等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;维护管理&lt;/strong&gt;：日志管理，用户管理，BIOS管理，告警管理等
&lt;img src=&quot;images/image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;IPMI：是一组交互标准管理规范，其实就是BMC系统，可独立运行的板上部件，主要用于服务器系统集群自治，监视服务器的物理健康状态，同时还负责记录各种硬件的信息和日志记录&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;redfish：是IPMI规范的新一代标准，它基于&lt;strong&gt;HTTPS&lt;/strong&gt;服务的管理标准，利用restful接口实现设备管理。与IPMI相比更简单，更安全（基于HTTPS）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CPU缓存：为CPU和内存之间的临时存储器，目前的CPU拥有三级缓存。缓存具有极快的存取速度，它是硬盘与外部接口之间的缓冲器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CPU的频率：&lt;strong&gt;主频 = 外频 * 倍频&lt;/strong&gt; &lt;strong&gt;主频为CPU的额定工作频率&lt;/strong&gt;，主频越高，CPU性能越好&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;外频（Base Clock）为CPU的&lt;strong&gt;基准时钟频率&lt;/strong&gt;，单位为&lt;code&gt;MHz&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;倍频是指CPU主频与外频之间的&lt;strong&gt;相对比例&lt;/strong&gt;关系，是CPU内部的一个放大系数&lt;/li&gt;
&lt;li&gt;在Linux中可以使用&lt;code&gt;lscpu&lt;/code&gt;指令查看cpu的相关参数或查看&lt;code&gt;/proc/cpuinfo&lt;/code&gt;文件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;超线程：一颗CPU同时执行多个程序且共同分享一颗CPU内的资源。当两个线程都同时需要某一资源时，其中一个要暂时停止，并让出资源，知道资源闲置后才能继续，因此超线程的性能并等于两颗CPU的性能&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CPU相关概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;物理CPU：即主板上插了几块CPU，查看指令：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;cat /proc/cpuinfo | grep &quot;physical id&quot; // 查看所有的id，有多少个id就有多少个CPU
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;CPU物理核心（core）：一颗物理CPU中包含的核心数量（&lt;strong&gt;6核&lt;/strong&gt;12线程），查看指令：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;cat /proc/cpuinfo | grep &quot;cpu cores&quot; // 直接查看cpu cores的值，也就是核心数量
cat /proc/cpuinfo | grep &quot;core id&quot; // 查看核心id，有多少个id就有多少个核心
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;CPU逻辑核数（线程数）：通过超线程技术，将一个物理核心分成多个逻辑核心（6核&lt;strong&gt;12线程&lt;/strong&gt;），查看指令：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;cat /proc/cpuinfo | grep &quot;processor&quot; | sort -u | wc -l // sort为对文本进行排序 -u 参数为对结果去重（unique），wc（words count）为文本统计 -l 参数则是统计行数（line）
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;CPU中断：系统发生了非寻常的急需处理事件，CPU短暂中断当前执行的程序转而去执行相应的事件处理程序，处理完毕后返回原来被中断处继续执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;内存条物理结构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DDR1 = 大片（颗粒）+ 圆口（金手指缺口）&lt;/li&gt;
&lt;li&gt;DDR2 = 小片 + 圆口&lt;/li&gt;
&lt;li&gt;DDR3 = 小片 + 方口&lt;/li&gt;
&lt;li&gt;DDR4 = 小片 + 方口（金手指中间稍突出，边缘收矮，方便插入）
&lt;img src=&quot;images/image2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;内存保护技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ECC（Error Checking and Correcting）：能发现数据位上的错误并纠正错误（所以比普通内存贵，但也因为需要计算校验码，延迟稍高，性能比普通内存略低）&lt;/li&gt;
&lt;li&gt;ChipKill：一种IBM研发的高阶内存容错技术，可以理解为“ECC内存的超级加强版”。能修复整个内存芯片（Chip）损坏导致的错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;内存条规格识别：
&lt;img src=&quot;images/image3.png&quot; alt=&quot;alt text&quot; /&gt;
RANK: 内存列，CPU与内存的接口位宽一般为64bit，单个内存颗粒仅有4、8bit，因此必须把多个颗粒并联起来，组成一个宽为64bit的数据集合，64bit集合称为一个RANK.&lt;code&gt;2Rx4&lt;/code&gt;则代表该内存有两个RANK，每个RANK由4个颗粒并联组成&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;内存配置原则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有内存通道配置一样的内存（包括速率，容量，RANK等）&lt;/li&gt;
&lt;li&gt;多颗CPU时，首先保持各个CPU的内存配置一样&lt;/li&gt;
&lt;li&gt;只有一条内存时，必须插在给定通道的slot0槽位（离CPU最远的位置）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;内存带宽计算：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
满配最大内存带宽 = 内存标称频率 * 内存总线位数 * 通道数 * CPU个数
$$&lt;/p&gt;
&lt;p&gt;$$
实际使用的内存带宽 = 内存标称频率 * 内存总线位数 * 实际使用的通道数
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;硬盘类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;介质： 机械硬盘（HDD）、固态硬盘（SSD）&lt;/li&gt;
&lt;li&gt;盘径： 5.25、3.5、2.5、1.8（英寸）&lt;/li&gt;
&lt;li&gt;接口： ATA/IDE、SATA/NL SAS、SCSI、SAS、FC&lt;/li&gt;
&lt;li&gt;功能： 桌面级、企业级&lt;/li&gt;
&lt;li&gt;业务类型：
&lt;ul&gt;
&lt;li&gt;HDD：企业级Performance类、企业级Capacity类、企业级云盘类、桌面级硬盘类&lt;/li&gt;
&lt;li&gt;SSD：读密集型、均衡型、写密集型&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;硬盘颗粒（NAND Flash）类型：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;全称&lt;/th&gt;
&lt;th&gt;每单元比特数&lt;/th&gt;
&lt;th&gt;寿命（每写入擦除循环）&lt;/th&gt;
&lt;th&gt;速度&lt;/th&gt;
&lt;th&gt;价格&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SLC&lt;/td&gt;
&lt;td&gt;Signle-Level Cell&lt;/td&gt;
&lt;td&gt;1bit&lt;/td&gt;
&lt;td&gt;5w~10w&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MLC&lt;/td&gt;
&lt;td&gt;Multi-Level Cell&lt;/td&gt;
&lt;td&gt;2bit&lt;/td&gt;
&lt;td&gt;3k~1w&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLC&lt;/td&gt;
&lt;td&gt;Triple-Level Cell&lt;/td&gt;
&lt;td&gt;3bit&lt;/td&gt;
&lt;td&gt;500~3000&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QLC&lt;/td&gt;
&lt;td&gt;Quad-Level Cell&lt;/td&gt;
&lt;td&gt;4bit&lt;/td&gt;
&lt;td&gt;100~1000&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;td&gt;⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;系统盘：安装操作系统的硬盘为系统盘，服务器环境下要求做RAID1.在linux系统可通过&lt;code&gt;lsblk&lt;/code&gt;命令识别系统盘（挂载点为&lt;code&gt;/boot&lt;/code&gt;的硬盘）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RAID（Redundant Array of Independent Disks）：独立硬盘冗余阵列，简称硬盘阵列。对硬盘的使用策略，基本思想就是把多个相对便宜的硬盘组合起来，成为一个硬盘阵列组，对计算机来说就像一个单独的硬盘或逻辑存储单元。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;常见的RAID类型：RAID-0，RAID-1，RAID-5，RAID-6，RAID-10：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RAID-0（条带化）：至少需要两块硬盘。数据被分割成多个“条带”，然后并发地、均匀地写入到所有硬盘中。写数据时将数据分为N份，以独立的方式实现N块磁盘的读写，将N份数据并发写入磁盘中，性能非常高，没有冗余，任何一块硬盘发生故障，所有数据都会损坏且无法恢复&lt;/li&gt;
&lt;li&gt;RAID-1：至少需要两块硬盘。将同一份数据无差别的写两份到磁盘（将磁盘分为两份，一份工作，一份镜像），有镜像冗余&lt;/li&gt;
&lt;li&gt;RAID-10：至少需要4块硬盘。首先基于RAID-1模式将磁盘分为两份，随后将这两份RAID-1以RAID-0模式组合起来，既保证了效率又保证了安全，有镜像冗余
&lt;img src=&quot;images/image4.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;li&gt;RAID-5：至少需要3块硬盘，数据分布存储（类似RAID-0）+ 奇偶校验信息（用于恢复数据）轮流存储在不同的硬盘上，单盘冗余&lt;/li&gt;
&lt;li&gt;RAID-6：类似与RAID-6，但是至少需要4块硬盘，双硬盘冗余&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JBOD（Just Bundle of Disks）：简单磁盘捆绑，将两块硬盘捆绑为一个大硬盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RAID卡： 磁盘阵列卡，用于统一管理服务器的磁盘，支持RAID和JBOD两种模式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SAS卡： 不只用来连接SAS硬盘，&lt;strong&gt;还是一种低端RAID卡，一般只能做RAID-0，RAID-1&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HBA（Host Bus Adapter）卡：主机总线适配器，在服务器与存储设备之间提供IO，可以将主机的PCIe协议转化为存储设备支持的协议（如SAS、FC等）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Riser卡：就是用于扩展PCIe插槽的卡&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PCIe通道标准：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;X1&lt;/th&gt;
&lt;th&gt;X2&lt;/th&gt;
&lt;th&gt;X4&lt;/th&gt;
&lt;th&gt;X8&lt;/th&gt;
&lt;th&gt;X16&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PCIe1.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;250MB/s&lt;/td&gt;
&lt;td&gt;500MB/s&lt;/td&gt;
&lt;td&gt;1GB/s&lt;/td&gt;
&lt;td&gt;2GB/s&lt;/td&gt;
&lt;td&gt;4GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PCIe2.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500MB/s&lt;/td&gt;
&lt;td&gt;1GB/s&lt;/td&gt;
&lt;td&gt;2GB/s&lt;/td&gt;
&lt;td&gt;4GB/s&lt;/td&gt;
&lt;td&gt;8GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PCIe3.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1GB/s&lt;/td&gt;
&lt;td&gt;2GB/s&lt;/td&gt;
&lt;td&gt;4GB/s&lt;/td&gt;
&lt;td&gt;8GB/s&lt;/td&gt;
&lt;td&gt;16GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PCIe4.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2GB/s&lt;/td&gt;
&lt;td&gt;4GB/s&lt;/td&gt;
&lt;td&gt;8GB/s&lt;/td&gt;
&lt;td&gt;16GB/s&lt;/td&gt;
&lt;td&gt;32GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>ARP攻击与防护实验</title><link>https://fuwari.vercel.app/posts/arp%E6%94%BB%E5%87%BB%E4%B8%8E%E9%98%B2%E6%8A%A4%E5%AE%9E%E9%AA%8C/arp%E6%94%BB%E5%87%BB%E4%B8%8E%E9%98%B2%E6%8A%A4%E5%AE%9E%E9%AA%8C/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/arp%E6%94%BB%E5%87%BB%E4%B8%8E%E9%98%B2%E6%8A%A4%E5%AE%9E%E9%AA%8C/arp%E6%94%BB%E5%87%BB%E4%B8%8E%E9%98%B2%E6%8A%A4%E5%AE%9E%E9%AA%8C/</guid><description>使用华为eNSP模拟器搭建网络环境，使用Kali Linux虚拟机进行ARP攻击与防护实验。</description><pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;ARP攻击与防护实验&lt;/h1&gt;
&lt;h2&gt;一.环境搭建&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;华为eNSP模拟器&lt;/strong&gt; - 用于构建网络拓扑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VMware Workstation&lt;/strong&gt; - 用于运行Kali Linux虚拟机&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1.网络拓扑搭建&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在eNSP中创建以下设备：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1台交换机(S5700)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一台Client和一台Server(模拟客户端和服务器)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1台Cloud(用于连接VMware虚拟机)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;配置网络：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;所有设备在同一子网(192.168.10.0/24)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分别为Client和Server配置IP(192.168.10.20, 192.168.10.30)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在Server上启用FTP服务&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;连接VMware虚拟机：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在eNSP云设备上配置桥接到VMware虚拟网络&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;确保Kali虚拟机与eNSP网络在同一子网&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410135702590.png&quot; alt=&quot;image-20250410135702590&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.设备配置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;交换机&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;登录交换机&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Huawei&amp;gt; system-view  # 进入系统视图
[Huawei] sysname SW1  # 重命名交换机
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;接口配置（初始无VLAN）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[SW1] interface GigabitEthernet 0/0/1
[SW1-GigabitEthernet0/0/1] port link-type access  # 设置接口模式
[SW1-GigabitEthernet0/0/1] port default vlan 1    # 默认VLAN 1
[SW1-GigabitEthernet0/0/1] undo shutdown         # 启用接口
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;连接云设备的接口配置&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[SW1] interface GigabitEthernet 0/0/24  # 连接eNSP云设备的接口
[SW1-GigabitEthernet0/0/24] port link-type access
[SW1-GigabitEthernet0/0/24] port default vlan 1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VMware网络配置&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;虚拟网络编辑器设置&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;选择VMnet8(NAT模式)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410140031742.png&quot; alt=&quot;image-20250410140031742&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kali虚拟机配置&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 网络适配器选择自定义-VMnet8
sudo nano /etc/network/interfaces
# 添加以下内容：
auto eth0
iface eth0 inet static
 address 192.168.10.100
 netmask 255.255.255.0
 gateway 192.168.10.1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Cloud配置&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410140214353.png&quot; alt=&quot;image-20250410140214353&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.连通性测试&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;从Kali测试&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ping 192.168.10.20  # Client
ping 192.168.10.30  # Server

ftp 192.168.10.30 # ftp 登录测试
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410140517885.png&quot; alt=&quot;image-20250410140517885&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二.ARP欺骗攻击&lt;/h2&gt;
&lt;h3&gt;1.使用Ettercap扫描网络主机&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410140730387.png&quot; alt=&quot;image-20250410140730387&quot; /&gt;&lt;/p&gt;
&lt;p&gt;单击Ettercap上方的“三点”菜单 -&amp;gt; Hosts -&amp;gt; Scan for hosts，开始扫描网络中的所有主机&lt;/p&gt;
&lt;p&gt;扫描完成后，单击“三点”菜单 -&amp;gt; Hosts -&amp;gt; Hosts list，可以确认主机列表&lt;/p&gt;
&lt;h3&gt;2.启用ARP欺骗&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410140954968.png&quot; alt=&quot;image-20250410140954968&quot; /&gt;&lt;/p&gt;
&lt;p&gt;将Server和Client主机作为Ettercap嗅探的两个主要对象&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410141059757.png&quot; alt=&quot;image-20250410141059757&quot; /&gt;&lt;/p&gt;
&lt;p&gt;单击Ettercap上方的小地球图标 -&amp;gt; ARP poisoning&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410141153953.png&quot; alt=&quot;image-20250410141153953&quot; /&gt;&lt;/p&gt;
&lt;p&gt;弹出小窗口中选择“OK”，然后就开始基于ARP欺骗方式的网络嗅探了&lt;/p&gt;
&lt;h3&gt;3.验证结果&lt;/h3&gt;
&lt;p&gt;从Client1访问Server1的FTP服务&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410141418262.png&quot; alt=&quot;image-20250410141418262&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410141447152.png&quot; alt=&quot;image-20250410141447152&quot; /&gt;&lt;/p&gt;
&lt;p&gt;嗅探成功后，能直接截获的用户名以及密码&lt;/p&gt;
&lt;p&gt;抓包数据&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20250410142012356.png&quot; alt=&quot;image-20250410142012356&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4.解析:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MAC地址冲突&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在抓包数据中多次出现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;192.168.10.30 is at 00:06:29:cc:3c:98 (duplicate use of 192.168.10.30)
192.168.10.20 is at 00:06:29:cc:3c:98 (duplicate use of 192.168.10.30)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;异常点&lt;/strong&gt;：不同IP（.20和.30）被声明为&lt;strong&gt;同一个MAC地址&lt;/strong&gt;（Kali的MAC：&lt;code&gt;00:06:29:cc:3c:98&lt;/code&gt;）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;伪造ARP响应模式&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;正常情况：ARP响应应由真实设备发出&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当前情况：所有ARP响应均来自Kali（&lt;code&gt;VMware_ec:3c:98&lt;/code&gt;）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;具体攻击过程还原&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;攻击者行为&lt;/strong&gt;（Kali）：
&lt;ul&gt;
&lt;li&gt;向交换机发送伪造的ARP响应：
&lt;ul&gt;
&lt;li&gt;&quot;192.168.10.20的MAC是00:06:29:cc:3c:98&quot;（实际应为Client1的真实MAC）&lt;/li&gt;
&lt;li&gt;&quot;192.168.10.30的MAC是00:06:29:cc:3c:98&quot;（实际应为Server1的真实MAC）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络流量劫持&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;当Client1访问Server1时，数据包被错误地发往Kali&lt;/li&gt;
&lt;li&gt;Kali作为中间人可嗅探/篡改流量&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;正常ARP表应显示&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;IP地址&lt;/th&gt;
&lt;th&gt;MAC地址&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;192.168.10.20&lt;/td&gt;
&lt;td&gt;(Client1的真实MAC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.10.30&lt;/td&gt;
&lt;td&gt;(Server1的真实MAC)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;被污染后的ARP表&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;IP地址&lt;/th&gt;
&lt;th&gt;MAC地址（均为Kali）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;192.168.10.20&lt;/td&gt;
&lt;td&gt;00:06:29:cc:3c:98&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.10.30&lt;/td&gt;
&lt;td&gt;00:06:29:cc:3c:98&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;三.vlan隔离防护&lt;/h2&gt;
&lt;h3&gt;1.交换机配置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;SW1&amp;gt; system-view
[SW1] vlan batch 10 20 # 创建vlan 10 20

# 将Client1划入VLAN10
[SW1] interface GigabitEthernet 0/0/2  
[SW1-GigabitEthernet0/0/2] port link-type access  
[SW1-GigabitEthernet0/0/2] port default vlan 10  

# 将Server1划入VLAN10
[SW1] interface GigabitEthernet 0/0/3 
[SW1-GigabitEthernet0/0/3] port link-type access  
[SW1-GigabitEthernet0/0/3] port default vlan 10  

# 将cloud(kali)划入VLAN20
[SW1] interface GigabitEthernet 0/0/1
[SW1-GigabitEthernet0/0/1] port link-type access  
[SW1-GigabitEthernet0/0/1] port default vlan 20  
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;2、验证防护效果&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;1). 检查Kali的ARP欺骗尝试&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;在Kali启动Ettercap攻击后：
&lt;ul&gt;
&lt;li&gt;无法捕获跨VLAN的ARP请求/响应&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;2). 查看交换机MAC表验证隔离&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;SW1&amp;gt; display mac-address
# 应显示不同VLAN的MAC地址完全隔离
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>DNS劫持实验</title><link>https://fuwari.vercel.app/posts/dns%E5%8A%AB%E6%8C%81%E5%AE%9E%E9%AA%8C/dns%E5%8A%AB%E6%8C%81%E5%AE%9E%E9%AA%8C/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/dns%E5%8A%AB%E6%8C%81%E5%AE%9E%E9%AA%8C/dns%E5%8A%AB%E6%8C%81%E5%AE%9E%E9%AA%8C/</guid><description>本次实验旨在通过配置和使用 Kali Linux 中的 dnsmasq 服务，演示 DNS 劫持攻击的过程，并观察其对被攻击主机的影响</description><pubDate>Wed, 02 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;DNS劫持实验&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;注意:&lt;/strong&gt; 实验中的ip依自己的实验环境为准,注意替换&lt;/p&gt;
&lt;p&gt;​		本文中攻击者kali主机的IP为&lt;code&gt;192.168.183.137&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​					被攻击者ubuntu主机的IP为&lt;code&gt;192.168.183.136&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​					网关为&lt;code&gt;192.168.183.2&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;1.配置dnsmasq服务&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;安装dnsmasq&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install dnsmasq
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402153451250.png&quot; alt=&quot;image-20250402153451250&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;配置dnsmasq&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/dnsmasq.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在文件末尾添加配置:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resolv-file=/etc/resolv.dnsmasq
strict-order
listen-address=192.168.183.137  # Kali IP
address=/lanjinxin.com/192.168.183.137  # 将域名指向Kali自身
server=8.8.8.8  # 上游DNS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402163616376.png&quot; alt=&quot;image-20250402163616376&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;本地启用dnsmasq解析&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改dns服务器为kali ip&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402164008315.png&quot; alt=&quot;image-20250402164008315&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在hosts文件中添加解析&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;192.168.183.137.10 lanjinxin.com&quot; &amp;gt;&amp;gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;启动 dnsmasq 服务并设置开机自启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart dnsmasq &amp;amp;&amp;amp; systemctl enable dnsmasq
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.配置被攻击主机(Ubuntu24.04)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;修改主机hosts文件&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402164347315.png&quot; alt=&quot;image-20250402164347315&quot; /&gt;&lt;/p&gt;
&lt;p&gt;将nameserver 设置为kali ip &lt;code&gt;192.168.183.137&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;3.使用 arpspoof 进行 ARP 欺骗&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在 Kali Linux 上，使用 &lt;code&gt;arpspoof&lt;/code&gt; 进行 ARP 欺骗，使得被攻击主机的流量通过攻击者主机：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;arpspoof -i eth0 -t [被攻击ip] -r [网关ip]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402164940250.png&quot; alt=&quot;image-20250402164940250&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​	&lt;em&gt;如果不知道网关ip可以使用以下指令查询:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;route -n | grep &apos;UG&apos; | awk &apos;{print $2}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.观察被攻击主机访问被劫持域名时的重定向情况&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在kali主机上使用wireshark验证dns劫持重定向&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402165508341.png&quot; alt=&quot;image-20250402165508341&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​	可见原向网关[192.168.183.2]查询dns的ubuntu主机[192.168.183.136]被kali劫持&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在kali上搭建http服务器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -m http.server 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402165950014.png&quot; alt=&quot;image-20250402165950014&quot; /&gt;&lt;/p&gt;
&lt;p&gt;kali主机访问测试&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402170242885.png&quot; alt=&quot;image-20250402170242885&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在被攻击主机上访问域名&lt;code&gt;lanjinxin.com&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402170154024.png&quot; alt=&quot;image-20250402170154024&quot; /&gt;&lt;/p&gt;
&lt;p&gt;被攻击主机访问 &lt;code&gt;lanjinxin.com&lt;/code&gt; 时显示的是 Kali Linux 上的 HTTP 服务器页面，说明 DNS 劫持成功。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 Wireshark 中，可以观察到 DNS 请求和响应&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-20250402170805657.png&quot; alt=&quot;image-20250402170805657&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可见ubuntu主机[192.168.183.136]通过dns查询&lt;code&gt;lanjinxin.com&lt;/code&gt;的ip被kali主机[192.168.183.137]劫持并将自己的ip作为目标ip返回给被攻击主机&lt;/p&gt;
</content:encoded></item><item><title>软件测试基础知识</title><link>https://fuwari.vercel.app/posts/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</guid><description>软件测试基础知识整理</description><pubDate>Wed, 19 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;软件测试基础知识&lt;/h1&gt;
&lt;h2&gt;1、B/S架构和C/S架构区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;B/S 只需要有操作系统和浏览器就行，可以实现跨平台，客户端零维护，维护成本低，但是个性化能力低，响应速度较慢&lt;/li&gt;
&lt;li&gt;C/S响应速度快，安全性强，一般应用于局域网中，因为要针对不同的操作系统,需要针对性的开发,并且维护成本高&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2、HTTP协议&lt;/h2&gt;
&lt;p&gt;http协议又叫做超文本传输协议，在做网络请求的时候，我们基本上是使用http协议。
http协议包括请求和响应。
请求中包括:请求地址，请求方式，请求方式包括get请求和post请求，get和post的区别是get请求是在地址栏后边跟随请求参数，但是请求参数大小是有限制，不同浏览器是不同的。一般是4KB。post请求主要用于向服务器提交请求参数。post请求的参数是放到请求实体内容中的，相对get请求较为安全一些。另外，请求中会有各种请求头信息，比如支持的数据类型，请求的来源位置，以及Cookie头等相关头信息。&lt;/p&gt;
&lt;p&gt;响应，主要包含响应的状态码，像200，304，307，404，500
还有各种响应头信息，比如设置缓存的响应头，Content-Type内容类型，设置cookie头信息。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;200：（成功）服务器已成功处理了请求&lt;/li&gt;
&lt;li&gt;304：（未修改）客户端发出了条件式请求，但服务器上的资源未曾发生改变，则通过响应此响应状态&lt;/li&gt;
&lt;li&gt;307：（重定向）浏览器内部重定向&lt;/li&gt;
&lt;li&gt;404：（未找到）服务器无法找到客户端请求的资源；Not Found&lt;/li&gt;
&lt;li&gt;500：（服务器内部错误）无法完成请求；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3、POST与GET区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Get请求一般是去取获取数据（其实也可以提交，但常见的是获取数据）；post请求一般是去提交数据。&lt;/li&gt;
&lt;li&gt;Get是不安全的，因为在传输过程，数据被放在请求的URL中；Post的所有操作对用户来说都是不可见的，请求数据是放在body体中（最常用场景，用户登录密码提交，一定是使用post请求的）&lt;/li&gt;
&lt;li&gt;Get传送的数据量较小，这主要是因为受url长度限制；Post传送的数据量较大，一般被默认为不受限制。&lt;/li&gt;
&lt;li&gt;Get请求可以被缓存，Post请求不会被缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4、Cookie和Session的区别与联系&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cookie和Session都是会话技术，Cookie是运行在客户端，Session是运行在服务器端。&lt;/li&gt;
&lt;li&gt;Cookie有大小限制以及浏览器在存cookie的个数也有限制，Session是没有大小限制和服务器的内存大小有关。&lt;/li&gt;
&lt;li&gt;Cookie有安全隐患，通过拦截或本地文件找得到你的cookie后可以进行攻击。&lt;/li&gt;
&lt;li&gt;Session是保存在服务器端上会存在一段时间才会消失，如果session过多会增加服务器的压力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5、测试的目的&lt;/h2&gt;
&lt;p&gt;简单地说，就是替用户受过，&lt;strong&gt;测试的最终目的是确保最终交给用户的产品的功能符合用户的需求&lt;/strong&gt;，把尽可能多的问题在产品交给用户之前发现并改正&lt;/p&gt;
&lt;h2&gt;6、软件测试分为哪几个阶段？&lt;/h2&gt;
&lt;p&gt;单元测试、集成测试、系统测试、验收测试。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;单元测试:是在软件开发过程中要进行的最低级别的测试活动，在单元测试活动中，软件的独立单元将在与程序的其他部分相隔离的情况下进行测试，测试重点是系统的模块，包括子程序的正确性验证等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;集成测试:也叫组装测试或联合测试。在单元测试的基础上，将所有模块按照设计要求，组装成为子系统或系统，进行集成测试。实践表明，一些模块虽然能够单独地工作，但并不能保证连接起来也能正常的工作。程序在某些局部反映不出来的问题，在全局上很可能暴露出来，影响功能的实现。测试重点是模块间的衔接以及参数的传递等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;系统测试:是将经过测试的子系统装配成一个完整系统来测试。它是检验系统是否确实能提供系统方案说明书中指定功能的有效方法。测试重点是整个系统的运行以及与其他软件的兼容性。&lt;/p&gt;
&lt;p&gt;系统测试范围 功能测试、ui测试、性能测试、容错测试、可用性测试、异常问题测试、稳定性测试、系统稳定性测试、兼容性测试、接口测试、安全性测试、登录权限测试&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;验收测试:是软件产品检验的最后一个环节。按照项目任务书或合同、供需双方约定的验收依据文档进行的对整个系统的测试与评审，决定是否接收或拒收系统。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;回归测试:是指对软件的新版本测试时，重复执行之前某一个重要版本的所有测试用例目的：
1.验证之前版本产生的所有缺陷已全部被修复；
2.确认修复这些缺陷没有引发新的缺陷&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;冒烟测试:是指在对一个新版本进行系统大规模的测试之前，先验证一下软件的基本功能是否实现，是否具备可测性。也叫可测性测试。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7、a测试与ß测试的区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;а测试 是由一个用户在开发环境下进行的测试，也可以是公司内部的用户在模拟实际操作环境下进行的测试。&lt;/li&gt;
&lt;li&gt;ß测试 是一种验收测试。beta测试是指在一个或多个用户的场所进行的测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;8、验收测试怎么做？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1、界面测试；指软件产品所有的页面浏览时功能按钮或者界面是否能正常显示。&lt;/li&gt;
&lt;li&gt;2、功能测试；产品的功能是否都能正常实现。&lt;/li&gt;
&lt;li&gt;3、性能测试；发现性能瓶颈的过程，包括对CPU、内存、网络环境、版本等多项测试内容。&lt;/li&gt;
&lt;li&gt;4、安全测试；产品的信息保密，密码保护等功能的测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;9、白盒、黑盒和灰盒测试区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;黑盒测试主要是在程序界面进行测试的，它通过设定某种场景来检验程序在这个场景下是否给出正确的反应，从而验证程序是否正确满足需求规格说明书中的要求。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;白盒测试主要针对程序内部结构，对程序代码进行代码走查等等，但是白盒测试的成本会比较高，当程序有多个路径时，可能会出现更多的遗漏。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;灰盒测试是白盒测试和黑盒测试的结合。测试人员对系统的内部结构有一定的了解，但不需要像白盒测试那样深入，测试时既关注功能也关注部分内部逻辑。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;黑盒和灰盒的区别： 如果某软件包含多个模块，当使用黑盒测试时，你只要关心整个软件系统的边界，无需关心软件系统内部各个模块之间如何协作。而如果使用灰盒测试，则需要关心模块与模块之间的交互。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;白盒和灰盒的区别： 在灰盒测试中，你无需关心模块内部的实现细节，对于软件系统的内部模块，灰盒测试依然把它当成一个黑盒来看待。而白盒测试还需要再深入地了解内部模块的实现细节和各个分支。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;10、冒烟测试的目的&lt;/h2&gt;
&lt;p&gt;为正式测试前，验证是否产品或系统的&lt;strong&gt;主要需求&lt;/strong&gt;或&lt;strong&gt;预置条件&lt;/strong&gt;是否存在bug。&lt;/p&gt;
&lt;h2&gt;11、回归测试怎么做？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.在测试策略制定阶段，制定回归测试策略&lt;/li&gt;
&lt;li&gt;2.确定需要回归测试的版本&lt;/li&gt;
&lt;li&gt;3.回归测试版本发布,按照回归测试策略执行回归测试&lt;/li&gt;
&lt;li&gt;4.回归测试通过，关闭缺陷跟踪单（问题单）&lt;/li&gt;
&lt;li&gt;5.回归测试不通过，缺陷跟踪单返回开发人员，开发人员重新修改问题，再次提交测试人员回归测试&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;12、需求分析的目的&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;第一、把用户需求转化为功能需求：
1）对测试范围进度量
2）对处理分支进行度量
3）对需求业务的场景进行度量
4）明确其功能对应的输入、处理和输出
5）把隐式需求转变为明确。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第二、明确测试活动的五个要素：测试需求是什么、决定怎么测试、明确测试时间、确定测试人员、确定测试环境：测试中需要的技能，工具以及相应的背景知识，测试过程中可能遇到的风险等等。测试需求需要做到尽可能的详细明确，以避免测试遗漏和误解。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;13、测试计划的目的&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.尽早地明确测试工作内容（范围）、测试工作的方法以及测试工作所需要的各种资源。&lt;/li&gt;
&lt;li&gt;2.所有涉及到测试工作的人员，尽快将下一步测试工作需要考虑的问题和准备的条件落实。&lt;/li&gt;
&lt;li&gt;3.测试计划工作的重点在于：对当前工作任务的准备和规划以及信息的交流。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;14、什么时候开始写测试计划&lt;/h2&gt;
&lt;p&gt;测试计划是在需求整理完成，和&lt;strong&gt;开发计划一起制定&lt;/strong&gt;的一份计划书，它属于项目计划中其中的一个计划&lt;/p&gt;
&lt;h2&gt;15、由谁来编写测试计划&lt;/h2&gt;
&lt;p&gt;项目经理（从项目角度考虑）
测试经理（从测试组角度考虑）
测试人员（编写具体测试计划）&lt;/p&gt;
&lt;h2&gt;16、测试计划的内容&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;测试目标：对测试目标进行简要的描述。&lt;/li&gt;
&lt;li&gt;测试概要：摘要说明所需测试的软件、名词解释、以及提及所参考的相关文档。&lt;/li&gt;
&lt;li&gt;测试范围：测试计划所包含的测试软件需测试的范围和优先级，哪些需要重点测试、哪些无需测试或无法测试或推迟测试。&lt;/li&gt;
&lt;li&gt;重点事项：列出需要测试的软件的所有的主要功能和测试重点，这部分应该能和测试案例设计相对应和互相检查。&lt;/li&gt;
&lt;li&gt;质量目标：制定测试软件的产品质量目标和软件测试目标。&lt;/li&gt;
&lt;li&gt;资源需求：进行测试所需要的软硬件、测试工具、必要的技术资源、培训、文档等。&lt;/li&gt;
&lt;li&gt;人员组织：需要多少人进行测试，各自的角色和责任，他们是否需要进行相关的学习和培训，什么时候他们需要开始，并将持续多长时间。&lt;/li&gt;
&lt;li&gt;测试策略：制定测试整体策略、所使用的测试技术和方法。&lt;/li&gt;
&lt;li&gt;发布提交：在按照测试计划进行测试发布后需要交付的软件产品、测试案例、测试数据及相关文档。&lt;/li&gt;
&lt;li&gt;测试进度和任务人员安排：将测试的计划合理的分配到不同的测试人员，并注意先后顺序.如果开发的Release不确定，可以给出测试的时间段.对于长期大型的测试计划，可以使用里程碑来表示进度的变化。&lt;/li&gt;
&lt;li&gt;测试开始/完成/延迟/继续的标准：制定测试开始和完成的标准；某些时候，测试计划会因某种原因(过多阻塞性的Bug)而导致延迟，问题解决后测试继续。&lt;/li&gt;
&lt;li&gt;风险分析：需要考虑测试计划中可能的风险和解决方法。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;17、结束条件(项目上线的条件)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.软件经过充分的测试
开发人员测试—〉交叉测试—〉测试人员测试—〉用户的业务专家测试—〉一定数量的用户业务专家集中测试—〉上线前试运行----〉上线。&lt;/li&gt;
&lt;li&gt;2.用户培训
管理员，一定厂或地区必须有一个人经过了培训。&lt;/li&gt;
&lt;li&gt;3.基础数据导入完成
用户、组织机构、业务数据等基础必须数据导入完成。&lt;/li&gt;
&lt;li&gt;4.授权必须完成&lt;/li&gt;
&lt;li&gt;5.新老系统的切换必须提前演练过，各种老数据的导入工作完成。&lt;/li&gt;
&lt;li&gt;6.应急方案必须有。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;18、常见的测试风险&lt;/h2&gt;
&lt;p&gt;1.需求风险、2.测试用例风险、3.缺陷风险、4.代码质量风险、5.测试环境风险、6.测试技术风险、7.回归测试风险、8.沟通协调风险、9.研发流程风险、10.其他不可预计风险&lt;/p&gt;
&lt;h2&gt;19、测试用例的要素&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.用例编号&lt;/li&gt;
&lt;li&gt;2.测试模块&lt;/li&gt;
&lt;li&gt;3.用例的标题&lt;/li&gt;
&lt;li&gt;4.测试级别&lt;/li&gt;
&lt;li&gt;5.测试目的和条件&lt;/li&gt;
&lt;li&gt;6.测试输入&lt;/li&gt;
&lt;li&gt;7.操作步骤&lt;/li&gt;
&lt;li&gt;8.预期结果&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;20、测试用例级别的划分&lt;/h2&gt;
&lt;p&gt;测试用例优先级的目的：测试用例优先级可以用来方便地基于测试策略来筛选用例。
比如某块功能改动小，就只用测高或中高优先级的用例。
比如冒烟测试的时候我们只需要筛选优先级最高的用例执行即可。
根据我们测试用例优先级目的：那么优先级越高的测试用例覆盖的测试点应该是用户最关心的， 比如一个注册功能， 能够注册成功这个用例的优先级就是最高的（但是不是所有的注册成功的case都是优先级最高，只需要挑选一个即可）， 其他各种异常校验都是次要优先级的， 还有一些场景覆盖的测试点很难出现，或者叫就算有问题影响也不大， 可以放到低优先级&lt;/p&gt;
&lt;h2&gt;21、怎样保证覆盖用户需求？&lt;/h2&gt;
&lt;p&gt;1.确认需求、2.梳理需求，确认测试范围、3.制定测试计划、4.根据测试计划编写测试用例、5.执行测试步骤&lt;/p&gt;
&lt;h2&gt;22、写好测试用例的关键 /写好用例要关注的维度&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;测试前：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.对测试目的有一个清晰的认知&lt;/li&gt;
&lt;li&gt;2.熟悉产品的功能测试点&lt;/li&gt;
&lt;li&gt;3.熟悉模块原理，避免“互相影响”问题的产生&lt;/li&gt;
&lt;li&gt;4.关注用户场景问题和上网问题&lt;/li&gt;
&lt;li&gt;5.不可马虎的关键测试用例设计&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编写测试用例时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.构建测试用例框架&lt;/li&gt;
&lt;li&gt;2.测试步骤的设计（一是如果步骤过于粗糙很容易出现漏测问题；二是写的过于细致，可能耗时耗力，并且会限制执行人员的思维。）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;23、常见的测试用例设计方法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;等价类划分&lt;/li&gt;
&lt;li&gt;边界值分析&lt;/li&gt;
&lt;li&gt;错误推断法&lt;/li&gt;
&lt;li&gt;判定法表&lt;/li&gt;
&lt;li&gt;正交实验法&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;24、什么时候用到场景法&lt;/h2&gt;
&lt;p&gt;场景法适用于&lt;strong&gt;解决业务流程清晰&lt;/strong&gt;和&lt;strong&gt;业务比较复杂&lt;/strong&gt;的系统或功能，场景法是一种基于软件业务的测试方法。
使用场景法，目的是用业务流把各个孤立的功能点串起来，为测试人员建立整体业务感觉，从而避免陷入功能细节忽视业务流程要点的错误倾向。例：语音通话典型业务流程就把语音通话、同振顺振、语音留言、呼叫保持、呼叫转移这些功能都串到一起来。
基本上每个软件都会用到场景法，因为每个软件背后都有业务的支撑。
&lt;strong&gt;场景法主要用来测试软件的业务逻辑和业务流程&lt;/strong&gt;。当拿到一个测试任务时，我们并不是先关注某个控件的细节测试（等价类+边界值+判定表等），而是要先关注主要业务流程和主要功能是否正确实现，这就需要使用场景法。当业务流程和主要功能没有问题，我们再从等价类、边界值、判定表等方面对控件细节进行测试（先整体后细节）。
Note：场景法测试用例设计的重点是&lt;strong&gt;测试业务流程是否正确&lt;/strong&gt;，测试时需要注意的是，业务流程测试没有问题并不代表系统的功能都正确，还必须对单个功能进行详细的测试，这样才能保证测试的充分性。&lt;/p&gt;
&lt;h2&gt;25、偶然性问题的处理&lt;/h2&gt;
&lt;p&gt;一定要提交bug&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1． 记得有这么个缺陷，以后再遇到的时候可能就会了解发生的原因。&lt;/li&gt;
&lt;li&gt;2． 尽力去查找出错的原因，比如有什么特别的操作，或者一些操作环境等。&lt;/li&gt;
&lt;li&gt;3． 程序员对程序比测试人员熟悉的多，也许你提交了，即使无法重现，程序员也会了解问题所在。&lt;/li&gt;
&lt;li&gt;4． 无法重现的问题再次出现后，可以直接叫程序员来看看问题。&lt;/li&gt;
&lt;li&gt;5． 记录bug要尽量描述清楚发生问题时的测试步骤、测试环境、测试数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;尽量重现bug&lt;/p&gt;
&lt;h2&gt;26、当我们认为某个地方是bug，但开发认为不是bug，怎么处理？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.告知开发bug的判断依据，同时明确开发说不是bug的理由。&lt;/li&gt;
&lt;li&gt;2.对开发的理由进行校验，校验依据1.参照需求文档，2.跟产品经理进行沟通确认。&lt;/li&gt;
&lt;li&gt;3.校验结果不是bug，关闭bug，如果是bug提交给开发进行处理，确保产品质量&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;27、产品在上线后用户发现bug，这时测试人员应做哪些工作？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1、测试人员复现问题后，提交问题单进行跟踪；&lt;/li&gt;
&lt;li&gt;2、评估该问题的严重程度，以及修复问题时的影响范围，回归测试需要测试哪些功能；&lt;/li&gt;
&lt;li&gt;3、问题修复后，先在测试环境上回归，通过后再在生产环境上打补丁，然后再进行回归测试；&lt;/li&gt;
&lt;li&gt;4、总结经验，分析问题发生的原因，避免下次出现同样问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;28、二八定理&lt;/h2&gt;
&lt;p&gt;缺陷往往喜欢扎堆，一个模块已经发现的缺陷比别的模块多，通常不是代表这个模块已经把缺陷暴露完了，而是意味着这个模块还存在有同样多的缺陷尚未被发现。
这就是著名的二八定理：80%的缺陷出现在 20%的模块。&lt;/p&gt;
&lt;h2&gt;29、如何跟踪缺陷&lt;/h2&gt;
&lt;p&gt;当发现缺陷后，我们要在禅道上提交问题单给开发，并每隔一段时间(间隔一个小时，或两个小时都可以)去检查缺陷是否被处理，如果没及时处理，就要提示开发，让开发及时修复问题，问题修复后，要及时进行回归测试。&lt;/p&gt;
&lt;h2&gt;30、缺陷的状态&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1、 初始化：缺陷的初始状态；&lt;/li&gt;
&lt;li&gt;2、 待分配：缺陷等待分配给相关开发人员处理；&lt;/li&gt;
&lt;li&gt;3、 待修正：缺陷等待开发人员修正；&lt;/li&gt;
&lt;li&gt;4、 待验证：开发人员已完成修正，等待测试人员验证；&lt;/li&gt;
&lt;li&gt;5、 待评审：开发人员拒绝修改缺陷，需要评审委员会评审；&lt;/li&gt;
&lt;li&gt;6、 关闭：缺陷已被处理完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;31、缺陷的等级&lt;/h2&gt;
&lt;p&gt;轻微缺陷、一般缺陷、严重缺陷、致命缺陷&lt;/p&gt;
&lt;h2&gt;32、缺陷单应该包含这些要素&lt;/h2&gt;
&lt;p&gt;缺陷标题、缺陷类型、重现步骤、预期结果、实际结果、缺陷优先级、附件备注&lt;/p&gt;
&lt;h2&gt;33、测试报告的主要内容&lt;/h2&gt;
&lt;p&gt;测试报告包括哪些内容: 测试模块（每个模块里需要记录测试的开始时间、结束时间、设计多少用例、通过多少、失败多少、有多少BUG、遗留多少BUG、解决多少BUG、追后对这个模块总结一下）
BUG的统计，根据时间轴来统计BUG的数量，例如：XXXX年X月X日，发现BUG多少，关闭BUG多少，剩余BUG多少，高级的BUG有多少，中级的BUG有多少，低级和建议的BUG有多少，一直罗列到项目完结
项目总结，汇报一下测试的大致结果。
遗留和风险，该软件还有什么遗留问题，还有什么风险，都要一一说明
最后评判该软件是否符合上线标准，日期，签字，加盖章等&lt;/p&gt;
&lt;h2&gt;34、如何定位bug&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1、发现bug，首先要查看bug的详细信息，根据描述初步分析是哪个模块哪段代码的问题&lt;/li&gt;
&lt;li&gt;2、检查引发bug的测试环境、测试代码段和测试数据，排除测试人员的误操作导致的程序异常&lt;/li&gt;
&lt;li&gt;3、确认测试代码、测试环境和数据都正确后，再进一步分析bug根源。这里就需要看具体的测试业务了，可借助相关的工具进行分析&lt;/li&gt;
&lt;li&gt;4、如果产品或业务有相关的日志记录，可通过分析日志来确认bug&lt;/li&gt;
&lt;li&gt;5、当测试人员经过一系列的分析，可以基本确认bug产生的原因后，就可以直接找开发提bug了（注意沟通技巧）&lt;/li&gt;
&lt;li&gt;6、如果各方面都分析完还不能确认bug的原因，可以找开发一起定位（注意保留bug现场或者可以复现bug场景）&lt;/li&gt;
&lt;li&gt;7、确认bug后，提单给开发进行bug跟踪。
问题单上要描述清楚以下信息：
具体的测试时间、测试环境、测试场景、测试的具体业务和功能、使用的测试代码和测试数据、测试执行步骤、测试结果、bug现象（最好截图）、日志记录、预期结果、bug确认相关人员等&lt;/li&gt;
&lt;li&gt;8、跟踪bug，等开发人员修复bug后进行回归测试。（关注bug是否完全修复、有没有对其他功能造成影响、有没有引入新的问题）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;35、开发没时间修复，如何推进bug的修复&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1、 针对路径较深的bug，测试在推动开发修复bug时，需要注意以下几点：&lt;/p&gt;
&lt;p&gt;a) 从用户的角度分析问题的严重性，分析用户的遇到此问题的概率，引导开发站在用户角度去思考，从而使开发意识到问题的严重性
b) 可以和开发人员列举一个之前的类似问题，为开发提供参考
c) 产品是负责这个软件的人员，当测试与开发意见无法达成一致时，不要因为无法推动开发修改而放弃，一定要找产品确认，最终的决定权交给产品人员。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2、 上线时间紧张，开发来不及修改了，这个时候测试应该分析问题的严重性，和产品人员商议是否需要修改&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3、 修改bug改动较大，影响范围广，没有最优的解决方案等情况在项目即将上线的节点比较忌讳这种事情的发生。面对这种情况，建议开发人员做调研工作，请教其他的同事，或者组织一个临时会议，集众人之力研究好的修改方案&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4、 第三方应用问题，开发无法修改。确认原因之后需要找相关的工作人员，例如产品，联系第三方的工作人员，反馈问题，尽量推动应用解决问题&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;36、软件测试流程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;了解用户需求&lt;/li&gt;
&lt;li&gt;参考需求规格说明书&lt;/li&gt;
&lt;li&gt;测试计划（人力物力时间进度的安排）&lt;/li&gt;
&lt;li&gt;编写测试用例&lt;/li&gt;
&lt;li&gt;评审用例&lt;/li&gt;
&lt;li&gt;搭建环境&lt;/li&gt;
&lt;li&gt;测试包安排预测（冒烟测试&lt;/li&gt;
&lt;li&gt;正式测试-bug-测试结束出报告&lt;/li&gt;
&lt;li&gt;版本上线&lt;/li&gt;
&lt;li&gt;面向用户&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;37、对一支圆珠笔进行测试，要从哪些方面进行测试？三角形测试用例设计&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;性能测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;能否正常书写 是否有笔油泄露 笔帽是否能正常按下弹起来&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;功能测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一支笔可以用多长时间、写出的字是否褪色&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;易用性测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;笔的长短粗细是否顺手 一根笔芯用尽是否方便更换&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;界面测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;外形是否美观 时尚有趣&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安全性测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;笔油是否含有有害物质 笔尖是否容易伤到人 笔油或者墨水保质期多长 过期是否产生有害物质、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;兼容性测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在不同的温度、气压、和重力下能否正常使用 在不同的纸质和力度下书写结果如何&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;三角形测试用例设计&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、当三边不可能构成三角形时提示错误，可构成三角形时计算三角形周长。&lt;/li&gt;
&lt;li&gt;2、若是等腰三角形打印“等腰三角形”， 若两个等腰的平方和等于第三边平方和，则打印“等腰直角三角形”。&lt;/li&gt;
&lt;li&gt;3、若是等边三角形，则打印：“等边三角形”。&lt;/li&gt;
&lt;li&gt;4、画出程序流程图并设计一个测试用例。
因果图、等价类划分（推荐测试方法）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;38、在项目中发现哪些经典bug？什么原因导致的？&lt;/h2&gt;
&lt;p&gt;编码的问题
接口限流防刷的时候，通过计数器限流，如果超过某个阈值，向前端返回一个codeMsg对象用于显示的时候，显示的是String是乱码的问题，之前由于一直返回都是json 格式，都是封装好在data里。
这次返回是直接通过输出流直接写到response直接返回字节数组的，而不是spring controller 返回数据（springboot 默认utf-8），出现乱码问题，用utf-8编码，解决。
比较难解决的bug无非就两种，一种就是程序的逻辑出现问题，导致得不到正确的结果，第二种就是一些中间件，开发环境的问题。
（1）如果是逻辑出现了问题，你项目比较大的话，那只能是通过单步调试，或者用System.out.println()打印出来想要得到的数据看看是哪步出了问题。
（2）如果是开发环境或者是中间件的问题，那只能是通过网上查阅资料来解决问题。如果你英语阅读能力还可以的话，我推荐使用Stack Overflow来查阅资料。&lt;/p&gt;
&lt;h2&gt;39、在需求文档不太详细的情况下，如何开展测试？&lt;/h2&gt;
&lt;p&gt;1.主动了解做这个功能的背景，意图，要去解决一个什么样的问题， 这个可以找产品或者开发要，或者谁要求做这个功能的人要，知道这些后，测试的时候才心中有数，知道功能实现对不对
2.尽量让熟悉这块的业务的人去测试，这样功能的一些业务问题就可以测试出来
3.因为没有需求说明书，测试这边只有在功能做好了，转测试了，才知道功能是什么样子，所以这个时候写测试用例来不及，就采取这样思路操作 ，测试的时候边测试边记录测试的点，然后组内把这些测试点评审下，看看是否还有遗漏的地方，&lt;/p&gt;
&lt;h2&gt;40、如何尽快找到软件中的bug?&lt;/h2&gt;
&lt;p&gt;优先解决可重现的bug、对于某些没有头绪的bug，可以找有经验的同事商讨、bug放大、二分定位法、模拟现场、等&lt;/p&gt;
&lt;h2&gt;41、ATM机吞卡的吞卡现象是不是BUG?&lt;/h2&gt;
&lt;p&gt;不是，是特意设置的安全措施，防止有人马虎，操作后忘记取走银行卡而被人冒领卡中的钱款，所以特意设置了倒计时，限时内没有取走银行卡就会吞卡&lt;/p&gt;
&lt;h2&gt;42、如何减少非问题单的提交？&lt;/h2&gt;
&lt;p&gt;1、 缺陷的概要描述要清晰准确，要使相关开发负责人员能够一目了然问题是什么。
2、 一个完整的缺陷报告单，必须包含其必要的元素信息，例如：概要描述，缺陷发现人，测试环境，浏览器，缺陷重现步骤，严重等级，指派人，所属功能模块等等，必要元素信息必须描述全面清楚。
3、 缺陷的重现步奏必须描写清晰明了，使相关开发负责人能够根据重现步骤准确的重现所提交的缺陷，使其定位缺陷的原因所在。
4、 指派给人一定要明确，如知道缺陷是所属具体的某一个开发人员时，应该直接指派给对应的负责人，这样就能减少中间分配环节的时间。
5、 测试数据，测试的数据作为重现缺陷的一个重要元素信息，一定要将测试时所使用的信息给描写清楚准确。让开发人员根据测试所提供的测试数据准确重现缺陷。
6、 附件截图信息，附件或截图信息能让开发人员能够一目了然的清楚问题的所在，所以必要的时候提供附件或者截图信息也非常的重要。
7、描述缺陷内容的语气，在进行缺陷单书写时，尽量使用专业术语（体现测试的专业性），其次注意书写缺陷报告单时不要带有个人客观的语气内容，以免影响开发和测试人员之间的关系。&lt;/p&gt;
&lt;h2&gt;43、有个程序，在windows上运行很慢，怎么判断是程序存在问题，还是软硬件系统存在问题？&lt;/h2&gt;
&lt;p&gt;1、检查系统是否有中度的特征，如：浏览器窗口连续打开，系统中文件图标改成统一图标，CPU使用率保存90%以上等
2、检查软件/硬件的配置是否符合软件的推荐标准
3、确认当前的系统是否是独立，即没有对外提供什么消耗CPU资源的服务，如：虚拟机运行
4、如果是C/S或者B/S结构的软件，需要检查是不是因为与服务器的连接有问题，或者访问有问题造成的；
5、在系统没有任何负载的情况下，查看性能监视器，确认应用程序对CPU/内存的访问情况，&lt;/p&gt;
&lt;h2&gt;44、搜索功能怎么测试&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;功能方面的测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;搜索单个字，词语，句子，检索到的内容是否准确，链接是否准确
长度：例如输入框支持100字符， 那需要测试100字符、101字符，最大长度的显示是否正常；
哪些是支持的字符类型：数字、字母、汉字、字符！@！#、特殊字符；(需求而定）
字符串前后中带空格，前后的空格是否过滤， 中间的空格是否保留；(需求而定）
全角半角的字母、数字(需求而定）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能方面的测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;点击搜索按钮后，搜索结果多长时间能够显示
进入搜索页面需要多久&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安全性方面的测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;能否防止SQL注入攻击,否防止XSS攻击&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户体验测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;页面布局是否合理，输入框和按钮是否对齐
输入框的大小和按钮的长度，高度是否合理
快捷键：能不能全选，部分选择，复制剪切粘贴是否可用，粘贴超过最大长度的字符串怎么显示&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;兼容性测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;BS架构：不同浏览器测试，比如：IE，火狐，谷歌，360这些。
APP：在主流的不同类型，不同分辨率，不同操作系统的手机上测试，华为，vivo，oppo等&lt;/p&gt;
&lt;h2&gt;45、登录功能怎么测试&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;功能方面的测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;输入正确的用户名和密码，点击提交按钮，登录成功，跳转到正确的页面
输入正确的的用户名, 错误的密码，登录失败，并且提示相应的错误信息
输入错误的用户名，正确的密码, 登录失败，并且提示相应的错误信息
用户名为空, 验证登录失败，并且提示相应的错误信息
密码为空, 验证登录失败，并且提示相应的错误信息
用户名和密码都为空，点击登陆
用户名和密码前后有空格，，中间空格是否处理（需求而定）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能方面的测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;打开登录页面，需要多长时间
输入正确的用户名和密码后，登录成功跳转到新页面，需要多长时间&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安全性方面的测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;密码是否在前端加密，在网络传输的过程中是否加密
用户名和密码的输入框，能否防止SQL注入攻击
用户名和密码的输入框，能否防止XSS攻击
错误登陆的次数限制（防止暴力破解）
是否支持多用户在同一机器上登录
一个用户在不同终端上登陆
用户异地登陆&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户体验测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;页面布局是否合理，输入框和按钮是否对齐
输入框的大小和按钮的长度，高度是否合理
是否可以全用键盘操作，是否有快捷键
输入用户名，密码后按回车，是否可以登陆
牵扯到验证码的，还要考虑文字是否扭曲过度导致辨认难度大，考虑颜色（色盲使用者），刷新或换一个按钮是否好用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;兼容性测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;BS架构：不同浏览器测试，比如：IE，火狐，谷歌，360这些。
APP：在主流的不同机型，不同分辨率，不同操作系统的手机上测试，华为，vivo，oppo、苹果等&lt;/p&gt;
&lt;h2&gt;46、如果需要你来测试淘宝的购物车，你会如何设计测试用例，需要从哪些方面来考虑。&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.打开淘宝页面后，页面的布局是否是完整的&lt;/li&gt;
&lt;li&gt;2.页面的功能按钮是否可以正常显示&lt;/li&gt;
&lt;li&gt;3.在商品页面是否会显示加入购物车&lt;/li&gt;
&lt;li&gt;4.选中的商品是否能加入购物车&lt;/li&gt;
&lt;li&gt;5.加入购物车后是否可以显示商品的所有信息&lt;/li&gt;
&lt;li&gt;6.添加到购物车的商品是否可以进行删除&lt;/li&gt;
&lt;li&gt;7.如果在网络不佳或无网络时是否可以成功的加入购物车&lt;/li&gt;
&lt;li&gt;8.添加购物车后，点击加号的时候数量是否会增长&lt;/li&gt;
&lt;li&gt;9.添加购物车后，点击减号的时候数量是否会减少&lt;/li&gt;
&lt;li&gt;10.如果点击减号减到一定程度时，是否会提示不能再减少了&lt;/li&gt;
&lt;li&gt;11.如果淘宝用户未登录时，如果添加到购物车时是否会提示请先登录&lt;/li&gt;
&lt;li&gt;12.如果没有选择任何商品，点击结算，是否会提示用户“请添加要结算的商品”&lt;/li&gt;
&lt;li&gt;13.勾选商品后已选商品的总价是否会显示&lt;/li&gt;
&lt;li&gt;14.勾选商品显示总价后，总价计算是否正确&lt;/li&gt;
&lt;li&gt;15.勾选商品，点击结算按钮后，是否会进入确认订单信息的页面&lt;/li&gt;
&lt;li&gt;16.进入确认订单信息页面的总价是否正确&lt;/li&gt;
&lt;li&gt;17.总价是否会出现精度不准的情况，比如：正确总价是18.99，结果显示的确实18.999999999999&lt;/li&gt;
&lt;li&gt;18.是否有回到顶部功能&lt;/li&gt;
&lt;li&gt;19.是否可以编辑商品属性&lt;/li&gt;
&lt;li&gt;20.能否移入到收藏中&lt;/li&gt;
&lt;li&gt;21.店铺名称是否显示&lt;/li&gt;
&lt;li&gt;22.能否选择全部商品&lt;/li&gt;
&lt;li&gt;23.能否取消选择全部商品&lt;/li&gt;
&lt;li&gt;24.是否可以在购物车中修改商品的规格&lt;/li&gt;
&lt;li&gt;25.添加购物的数量超过库存数量是否进行限制&lt;/li&gt;
&lt;li&gt;26.是否可以进行清空购物车&lt;/li&gt;
&lt;li&gt;27.结算金额是否会随着商品数量的增加减少进行变化&lt;/li&gt;
&lt;li&gt;28.如果刷新的次数过多，是否会出现闪退的现象&lt;/li&gt;
&lt;li&gt;29.当手机来电话时淘宝页面是会还会运行&lt;/li&gt;
&lt;li&gt;30.当手机内存不够时，淘宝运行起来是否会出现卡顿的现象&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原文链接：https://blog.csdn.net/huace3852/article/details/142593745&lt;/p&gt;
</content:encoded></item><item><title>Ubuntu虚拟机安装VMWare Tools</title><link>https://fuwari.vercel.app/posts/ubuntu%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%AE%89%E8%A3%85vmwaretools/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/ubuntu%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%AE%89%E8%A3%85vmwaretools/</guid><description>Ubuntu虚拟机安装VMWare Tools</description><pubDate>Fri, 31 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Ubuntu虚拟机安装VMWare Tools&lt;/h1&gt;
&lt;p&gt;在大多数情况下VMWare会自动给Ubuntu虚拟机安装VMWare tools,但难免会存在系统版本太新,没有网络等其他导致VMWare tools没法安装的情况&lt;/p&gt;
&lt;p&gt;没有VMWare tools,可能会导致显示的屏幕大小无法适配,无法在宿主机和虚拟机之间复制粘贴文件,文本等&lt;/p&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;h3&gt;一.更新软件包&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;二.卸载旧版本的VMWare tools&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo apt autoremove open-vm-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;三.安装新版本的VMWare tools&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install open-vm-tools-desktop
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;四.重启系统&lt;/h3&gt;
&lt;p&gt;参考文章: https://blog.csdn.net/ylong52/article/details/140824569&lt;/p&gt;
</content:encoded></item><item><title>VMWare Ubuntu24虚拟机开机黑屏,卡死解决方案</title><link>https://fuwari.vercel.app/posts/vmwareubuntu24%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%BC%80%E6%9C%BA%E9%BB%91%E5%B1%8F%E5%8D%A1%E6%AD%BB%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/vmware-ubuntu24%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%BC%80%E6%9C%BA%E9%BB%91%E5%B1%8F%E5%8D%A1%E6%AD%BB%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/vmwareubuntu24%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%BC%80%E6%9C%BA%E9%BB%91%E5%B1%8F%E5%8D%A1%E6%AD%BB%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/vmware-ubuntu24%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%BC%80%E6%9C%BA%E9%BB%91%E5%B1%8F%E5%8D%A1%E6%AD%BB%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</guid><description>解决安装ubuntu24虚拟机登录后黑屏卡死的问题</description><pubDate>Fri, 31 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;VMWare Ubuntu24虚拟机开机黑屏,卡死解决方案&lt;/h1&gt;
&lt;h2&gt;可能出现的异常情况&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;执行安装程序时屏幕花屏,闪烁&lt;/li&gt;
&lt;li&gt;安装后启动黑屏&lt;/li&gt;
&lt;li&gt;启动后能进入登录页面,但登陆后卡死,黑屏,ubuntu无法关机,vmware无响应&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;出现问题的原因&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ubuntu24内部的图形加速驱动异常&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;h3&gt;1.安装旧版Ubuntu系统&lt;/h3&gt;
&lt;p&gt;目前仅发现在24版本会出现上诉问题,可以安装22及以下的系统&lt;/p&gt;
&lt;h3&gt;2.关闭虚拟机加速3D图形功能&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;a.打开虚拟机设置&lt;/li&gt;
&lt;li&gt;b.点击&lt;code&gt;显示器&lt;/code&gt;选项&lt;/li&gt;
&lt;li&gt;c.将&lt;code&gt;加速3D图形&lt;/code&gt;取消勾选&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;img.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.更新系统图形驱动&lt;/h3&gt;
&lt;p&gt;先执行解决方案2的操作并重启进入系统,在终端中执行下面的操作&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a.添加更新源&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo add-apt-repository ppa:oibaf/graphics-drivers
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;b.更新软件包&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后重启系统&lt;/p&gt;
&lt;p&gt;参考文章: https://blog.csdn.net/qq_41830158/article/details/140569963&lt;/p&gt;
</content:encoded></item><item><title>GCC编译器基本用法</title><link>https://fuwari.vercel.app/posts/gcc%E7%BC%96%E8%AF%91%E5%99%A8%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/gcc%E7%BC%96%E8%AF%91%E5%99%A8%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95/</guid><description>介绍几种常用的gcc命令编译C语言程序的使用方法</description><pubDate>Tue, 14 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;GCC编译器基本用法&lt;/h1&gt;
&lt;h2&gt;1.将源文件生成可执行文件&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gcc -o outfile infile
# 或者
gcc infile -o outfile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中&lt;code&gt;infile&lt;/code&gt;是源文件,&lt;code&gt;outfile&lt;/code&gt;是即将生成的可执行文件.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-o&lt;/code&gt; 与 &lt;code&gt;outfile&lt;/code&gt; 一定是相邻的&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;编译多个文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gcc -o outfile infile1 infile2 infile3
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.将源文件生成目标文件(.o)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gcc -c infile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中&lt;code&gt;infile&lt;/code&gt;是源文件,生成的目标文件名为源文件名后加后缀&lt;code&gt;.o&lt;/code&gt;.
生成的目标文件也可用于静态库或共享库的创建&lt;/p&gt;
&lt;h2&gt;3.生成带有调试信息的可执行文件&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gcc -g infile -o outfile
# 或者
gcc -g -o outfile infile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;只有带调试信息的可执行文件才能供GDB调试器进行调试&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;4.生成汇编文件&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gcc -S infile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;汇编阶段&lt;/p&gt;
&lt;h2&gt;5.生成&lt;code&gt;.i&lt;/code&gt;文件&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gcc -E infile -o infile.i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;预处理阶段&lt;/p&gt;
</content:encoded></item><item><title>Go语言中new()和make()的区别</title><link>https://fuwari.vercel.app/posts/go%E8%AF%AD%E8%A8%80%E4%B8%ADnew%E5%92%8Cmake%E7%9A%84%E5%8C%BA%E5%88%AB/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/go%E8%AF%AD%E8%A8%80%E4%B8%ADnew%E5%92%8Cmake%E7%9A%84%E5%8C%BA%E5%88%AB/</guid><description>Go语言中new()和make()的区别</description><pubDate>Sat, 11 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Go语言中new()和make()的区别&lt;/h1&gt;
&lt;h2&gt;new()&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;new()用于创建引用类型以外的数据类型，比如：&lt;strong&gt;基础数据类型&lt;/strong&gt;(int, float, string, bool...)、&lt;strong&gt;结构体&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;new()函数会为数据类型分配内存空间，并返回一个指向已分配内存的指针。&lt;/li&gt;
&lt;li&gt;new()创建的变量是指定类型的零值.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;package new_make

import (
	&quot;fmt&quot;
	&quot;testing&quot;
)

type Person struct {
	Name string
	Age  int
}

func TestNewAndMake(t *testing.T) {
    
    // 创建一个int类型的指针
	a := new(int)
	fmt.Println(*a) //  输出 0
    
	// 创建一个Person类型的指针
	p := new(Person)
	p.Age = 1
	p.Name = &quot;test&quot;
	fmt.Println(p) //   输出 &amp;amp;{test 1}
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;make()&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;make()函数只用于&lt;strong&gt;创建slice、map和channel&lt;/strong&gt;，并且返回一个有初始值(非零值)的&lt;strong&gt;引用类型&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;make()适用于创建切片、映射(map)和通道等引用类型的变量。&lt;/li&gt;
&lt;li&gt;make(T, size, cap)
&lt;ul&gt;
&lt;li&gt;T: 返回值的类型&lt;/li&gt;
&lt;li&gt;size: 返回值的初始长度&lt;/li&gt;
&lt;li&gt;cap: 返回值的容量&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;make()在创建切片时可以省略size参数，此时size默认为0。&lt;/li&gt;
&lt;li&gt;make()在创建映射时可以省略size和cap参数，此时size和cap参数默认为0。&lt;/li&gt;
&lt;li&gt;make()在创建通道时无size,cap参数,只有&lt;strong&gt;缓冲容量&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;package new_make

import (
	&quot;fmt&quot;
	&quot;testing&quot;
)

func TestNewAndMake(t *testing.T){
    // 创建一个长度为3，容量为10的切片
    arr := make([]int, 3, 10)
	fmt.Println(arr) //   输出 [0 0 0]
	arr = append(arr, 1)
	fmt.Println(arr) //    输出 [0 0 0 1]
	
    // 创建容量为3的映射
	m := make(map[int]string, 3)
	fmt.Println(m) //   输出 map[]
	m[1] = &quot;test&quot;
	fmt.Println(m) //   输出 map[1:test]
	
    // 创建一个缓冲大小为3的通道
	c := make(chan int, 3)
	fmt.Println(c) //   输出 0xc00004020
	c &amp;lt;- 1
	fmt.Println(&amp;lt;-c) //   输出 1
		    				    	
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;new()和make()的区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;new() 用于创建任意类型的变量，而 make() 仅用于创建引用类型的变量。&lt;/li&gt;
&lt;li&gt;new() 返回的是&lt;strong&gt;指针&lt;/strong&gt;，而 make() 返回的是初始化后的&lt;strong&gt;值&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;new() 创建的变量是零值，make() 创建的变量是根据类型进行初始化。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Go泛型介绍</title><link>https://fuwari.vercel.app/posts/go%E6%B3%9B%E5%9E%8B%E4%BB%8B%E7%BB%8D/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/go%E6%B3%9B%E5%9E%8B%E4%BB%8B%E7%BB%8D/</guid><description>Go泛型介绍</description><pubDate>Sat, 11 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Go泛型介绍&lt;/h1&gt;
&lt;h2&gt;go语言泛型&lt;/h2&gt;
&lt;p&gt;自 &lt;code&gt;1.18&lt;/code&gt; 版本开始，Go 语言正式支持泛型，为开发者带来了更强大、更灵活的编程能力.
泛型是一种编程语言的特性，它允许我们编写能够处理多种类型的代码，而不是只针对特定类型编写的代码。
泛型主要基于类型参数实现，通过在函数、接口或数据结构定义中添加类型参数，我们可以实现通用的代码逻辑，使其适用于各种数据类型。&lt;/p&gt;
&lt;h2&gt;函数泛型&lt;/h2&gt;
&lt;p&gt;例如我们可以利用泛型编写一个通用的交换函数&lt;code&gt;swap&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func Swap[T any](a, b *T) {
	*a, *b = *b, *a
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;函数名后 &lt;code&gt;T&lt;/code&gt; 是一个类型参数，&lt;code&gt;any&lt;/code&gt; 表示 T 可以是任何类型
我们也可以约束它的类型参数,比如我只接收&lt;code&gt;int&lt;/code&gt;和&lt;code&gt;string&lt;/code&gt;类型的参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func Swap2[T int | string](a, b *T) {
	*a, *b = *b, *a
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样如果传入的参数非&lt;code&gt;int&lt;/code&gt;和&lt;code&gt;string&lt;/code&gt;类型就会报错:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Cannot use [] int as the type interface{ int | string } Type does not implement constraint interface{ int | string } because type is not included in type set (int, string)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;假如我们要约束的类型很多,我们还可以使用&lt;strong&gt;接口&lt;/strong&gt;将这些类型单独封装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Swapable interface {
	~int | string
}

func Swap2[T Swapable | string](a, b *T) {
	*a, *b = *b, *a
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中&lt;code&gt;~int&lt;/code&gt;的意思&lt;strong&gt;也包括基于&lt;code&gt;int&lt;/code&gt;类型的其他派生类型&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;结构体泛型&lt;/h2&gt;
&lt;p&gt;可以利用泛型创建通用的列表、映射、堆栈等数据结构，它们可以存储任意类型的值。例如，我们可以定义一个通用的栈结构体：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package generic

import &quot;fmt&quot;

type Stack[T any] struct {
	data []T
}

func NewStack[T any]() *Stack[T] {
	return &amp;amp;Stack[T]{}
}

func (s *Stack[T]) Push(item T) {
	s.data = append(s.data, item)
}

func (s *Stack[T]) Pop() T {
	if len(s.data) == 0 {
		panic(&quot;stack is empty&quot;)
	}
	item := s.data[len(s.data)-1]
	s.data = s.data[:len(s.data)-1]
	return item
}
func (s *Stack[T]) String() string {
	return fmt.Sprintf(&quot;%v&quot;, s.data)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package generic

import &quot;testing&quot;

func TestStack(t *testing.T) {
	s := NewStack[int]()
	s.Push(1)
	s.Push(2)
	t.Log(s.String())
	t.Log(s.Pop())
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Go语言管道详解</title><link>https://fuwari.vercel.app/posts/go%E8%AF%AD%E8%A8%80%E7%AE%A1%E9%81%93%E8%AF%A6%E8%A7%A3/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/go%E8%AF%AD%E8%A8%80%E7%AE%A1%E9%81%93%E8%AF%A6%E8%A7%A3/</guid><description>Go语言管道详解</description><pubDate>Sat, 11 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Go语言管道详解&lt;/h1&gt;
&lt;p&gt;管道(channel)是go语言中的一种核心引用类型,如同字面意思管道,通过它&lt;strong&gt;并发核心单元&lt;/strong&gt;就可以发送或者接收数据进行通讯.&lt;/p&gt;
&lt;p&gt;同映射和切片一样,管道需要先创建才能使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ch := make(chan int)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用make初始化Channel时可以设置容量(缓存):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ch := make(chan int, 100)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;容量(capacity)代表Channel容纳的最多的元素的数量，代表Channel的缓存的大小。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果没有设置容量，或者容量设置为0, 说明Channel没有缓存，只有sender和receiver都准备好了后它们的通讯才会发生。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果设置了缓存，就有可能不发生阻塞， 只有缓存满了后 send才会阻塞， 而只有缓存空了后receive才会阻塞。一个nil channel不会通信。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以通过内建的close方法可以关闭Channel。&lt;/p&gt;
&lt;p&gt;你可以在多个goroutine从/往 一个channel 中 receive/send 数据, 不必考虑额外的同步措施。&lt;/p&gt;
&lt;p&gt;Channel可以作为一个先入先出(FIFO)的队列，接收的数据和发送的数据的顺序是一致的。&lt;/p&gt;
&lt;h2&gt;channel的类型&lt;/h2&gt;
&lt;p&gt;channel分为3种类型&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ch1 := make(chan int)       
ch2 := make(chan&amp;lt;- int)     
ch3 := make(&amp;lt;-chan int)     
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;ch1:普通的双向管道,可以发送和接收类型T的数据&lt;/li&gt;
&lt;li&gt;ch2:只发送管道,只能向管道发送类型T的数据,不能从管道读取数据&lt;/li&gt;
&lt;li&gt;ch3:只接收(读取)管道,只能从管道读取类型T的数据,不能向管道发送数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;channel操作符 &lt;code&gt;&amp;lt;-&lt;/code&gt;&lt;/h2&gt;
&lt;h3&gt;向channel中发送数据&lt;/h3&gt;
&lt;p&gt;通信操作符 &lt;code&gt;&amp;lt;-&lt;/code&gt; 的箭头指示数据流向，箭头指向哪里，数据就流向哪里&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ch := make(chan int, 3)
ch &amp;lt;- 1 // 向ch管道中添加数据1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;从channel中读取(接收)数据&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;ch := make(chan int, 3)
ch &amp;lt;- 1
ch &amp;lt;- 2
data := &amp;lt;- ch
&amp;lt;- ch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述操作中,我们创建了一个缓存容量为3的管道并向其发送了数据&lt;code&gt;1&lt;/code&gt;和&lt;code&gt;2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后创建变量&lt;code&gt;data&lt;/code&gt;接收了管道中的数据&lt;code&gt;1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果读取数据时没有拿变量接收,管道则会直接丢弃该数据&lt;/p&gt;
&lt;h2&gt;channel各种状态对应的操作结果&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;正常的 channel，可读、可写&lt;/li&gt;
&lt;li&gt;nil 的 channel，表示未初始化的状态，只进行了声明，未向其发送数据,或者手动赋值为 nil&lt;/li&gt;
&lt;li&gt;已经 closed 的 channel，表示已经 close 关闭了，千万不要误认为关闭 channel 后，channel 的值是 nil&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;正常channel&lt;/th&gt;
&lt;th&gt;nil channel&lt;/th&gt;
&lt;th&gt;closed channel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ch &amp;lt;-&lt;/td&gt;
&lt;td&gt;成功或阻塞&lt;/td&gt;
&lt;td&gt;panic&lt;/td&gt;
&lt;td&gt;panic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;-ch&lt;/td&gt;
&lt;td&gt;成功或阻塞&lt;/td&gt;
&lt;td&gt;panic&lt;/td&gt;
&lt;td&gt;读取零值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;close(ch)&lt;/td&gt;
&lt;td&gt;成功&lt;/td&gt;
&lt;td&gt;panic&lt;/td&gt;
&lt;td&gt;panic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;应用示例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func outputFromCh(ch chan int) {
	for {
		time.Sleep(time.Second * 2)
		fmt.Println(&quot;output: &quot;, &amp;lt;-ch)
	}
}

func main() {
	ch := make(chan int)
	go outputFromCh(ch)
	for {
		var data int
		fmt.Scan(&amp;amp;data)
		ch &amp;lt;- data
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段Go程序实现了一个简单的从控制台接收整数输入并通过通道发送给另一个goroutine进行处理的流程。下面是详细的程序流程解释：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.创建通道：&lt;/strong&gt;
使用 &lt;code&gt;make(chan int)&lt;/code&gt; 创建一个可以传递整数的通道，并将其存储在变量 &lt;code&gt;ch&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.启动 &lt;code&gt;outputFromCh&lt;/code&gt; goroutine：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;go outputFromCh(ch)&lt;/code&gt; 启动一个新的goroutine来执行 &lt;code&gt;outputFromCh&lt;/code&gt; 函数，并传入通道 &lt;code&gt;ch&lt;/code&gt; 作为参数。这意味着 &lt;code&gt;outputFromCh&lt;/code&gt; 函数将在后台并发执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 &lt;code&gt;outputFromCh&lt;/code&gt; 函数中，程序进入一个无限循环：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每次循环开始前，先调用 &lt;code&gt;time.Sleep(time.Second * 2)&lt;/code&gt; 使goroutine暂停2秒钟。&lt;/li&gt;
&lt;li&gt;然后执行 &lt;code&gt;&amp;lt;-ch&lt;/code&gt; 从通道 &lt;code&gt;ch&lt;/code&gt; 中接收一个整数。由于通道是阻塞类型的，如果通道中没有数据，goroutine会在这里阻塞&lt;strong&gt;直到数据可读&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;接收到整数后，打印出该整数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3.主函数中的无限循环：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同时，在 &lt;code&gt;main&lt;/code&gt; 函数中，程序也进入了一个无限循环：&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;fmt.Scan(&amp;amp;data)&lt;/code&gt; 从标准输入读取一个整数。用户输入的整数将被存储在变量 &lt;code&gt;data&lt;/code&gt; 中。&lt;/li&gt;
&lt;li&gt;将读入的整数 &lt;code&gt;data&lt;/code&gt; 发送到通道 &lt;code&gt;ch&lt;/code&gt; 中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;程序会无限期地运行下去。&lt;code&gt;main&lt;/code&gt; 函数中的循环会不断地从用户那里接收输入，并将输入发送到通道 &lt;code&gt;ch&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;outputFromCh&lt;/code&gt; 函数中的循环会每隔2秒从通道 &lt;code&gt;ch&lt;/code&gt; 读取一个整数并打印它。&lt;/p&gt;
</content:encoded></item><item><title>算法-二分查找</title><link>https://fuwari.vercel.app/posts/%E7%AE%97%E6%B3%95-%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E7%AE%97%E6%B3%95-%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE/</guid><description>基于go语言的二分查找实现</description><pubDate>Sat, 11 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;算法-二分查找&lt;/h1&gt;
&lt;p&gt;二分查找（binary search）是一种基于分治策略的高效搜索算法。它利用数据的&lt;strong&gt;有序性&lt;/strong&gt;，每轮缩小一半搜索范围，直至找到目标元素或搜索区间为空为止。&lt;/p&gt;
&lt;h2&gt;二分查找实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// BinarySearch
//
//	@Description: 二分查找
//	@param nums 有序数组
//	@param target 目标值
//	@return int 目标值索引,若不存在返回-1
func BinarySearch(nums []int, target int) int {
	if len(nums) == 0 {
		return -1
	}
	// 初始化左右指针
	left := 0
	right := len(nums) - 1
	// 当右指针小于左指针时，循环结束
	for left &amp;lt;= right {
		// 计算中间元素的索引
		mid := (right-left)/2 + left
		if nums[mid] == target {
			return mid
		}
		// 当中间元素小于目标值时，移动左指针,反之移动右指针
		if nums[mid] &amp;lt; target {
			left = mid + 1
		} else {
			right = mid - 1
		}
	}
	return -1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二分查找插入点实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// BinarySearchInsertion
//
//	@Description: 二分查找插入位置
//	@param nums 有序数组
//	@param target 目标值
//	@return int 插入位置下标
func BinarySearchInsertion(nums []int, target int) int {
	if len(nums) == 0 {
		return 0
	}
	// 初始化左右指针
	left := 0
	right := len(nums) - 1
	// 当右指针小于左指针时，循环结束
	// 此时左指针指向的元素为第一个大于目标值的元素,即插入位置
	for left &amp;lt;= right {
		// 计算中间元素的索引
		mid := (right-left)/2 + left
		// 当中间元素等于目标值时, 考虑到重复元素,我们规定将重复元素插入到其最左侧
		if nums[mid] == target {
			right = mid - 1
		} else if nums[mid] &amp;lt; target {
			left = mid + 1
		} else {
			right = mid - 1
		}
	}
	return left
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>乐观锁和悲观锁</title><link>https://fuwari.vercel.app/posts/%E4%B9%90%E8%A7%82%E9%94%81%E5%92%8C%E6%82%B2%E8%A7%82%E9%94%81/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E4%B9%90%E8%A7%82%E9%94%81%E5%92%8C%E6%82%B2%E8%A7%82%E9%94%81/</guid><description>乐观锁和悲观锁</description><pubDate>Thu, 05 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;乐观锁和悲观锁&lt;/h1&gt;
&lt;h2&gt;乐观锁&lt;/h2&gt;
&lt;p&gt;顾名思义,乐观锁是一种处于乐观思想的锁,它假设数据都不会发生冲突,所以不会显式的给数据上锁.
取而代之的是在每次&lt;strong&gt;提交更新前&lt;/strong&gt;去检查别人是否有更新这个数据,如果有就会&lt;strong&gt;重试更新&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;乐观锁的实现方式&lt;/h3&gt;
&lt;h4&gt;版本号机制&lt;/h4&gt;
&lt;p&gt;给数据添加一个版本号的字段,在提交更新前检查版本号是否改变:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若没有改变则更新版本号,提交更新&lt;/li&gt;
&lt;li&gt;若发生改变则说明数据在此之前已被更新,则需要重试更新&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;go语言的简单实现&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;数据定义&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// GetVal 获取值
func (d *Data) GetVal() int {
	return d.Val
}

// GetVersion 获取版本号
func (d *Data) GetVersion() int {
	return d.Version
}
// WriteVal 写入值，如果写入失败则返回false
func (d *Data) WriteVal(val, version int) bool {
	// 判断版本号是否改变
	if d.Version == version {
		d.Val = val
		d.Version++
		return true
	}
	return false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;读写操作&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Reader 读取值
func Reader(data *Data, id int, msg chan string) {
	time.Sleep(time.Second)
	msg &amp;lt;- fmt.Sprintf(&quot;Reader %d, val: %d, ver: %d&quot;, id, data.GetVal(), data.GetVersion())
}

// Writer 写入值
func Writer(data *Data, id int, msg chan string) {
	attempts := 0
	// 最多尝试5次
	for attempts &amp;lt; 5 {
		ver := data.GetVersion()
		v := data.GetVal()
		// 随机休眠
		time.Sleep(time.Duration(rand.Intn(10) * 100))
		if data.WriteVal(v+1, ver) {
			msg &amp;lt;- fmt.Sprintf(&quot;Writer %d update success; val: %d, ver: %d&quot;, id, data.GetVal(), data.GetVersion())
			break
		} else {
			attempts++
			msg &amp;lt;- fmt.Sprintf(&quot;Writer %d update No%d failed; val: %d, ver: %d&quot;, id, attempts, data.GetVal(), data.GetVersion())
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Reader&lt;/code&gt; 函数模拟读取操作，读取数据并输出信息。读取操作前会休眠一秒钟。
&lt;code&gt;Writer&lt;/code&gt; 函数模拟写入操作，尝试更新数据值。每次尝试前会随机休眠一段时间，最多尝试5次。如果更新成功，则输出成功信息，否则输出失败信息。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主函数&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func main() {
	data := &amp;amp;Data{Val: 0, Version: 0}
	msg := make(chan string)
	defer close(msg)
	for i := 0; i &amp;lt; 10; i++ {
		go Reader(data, i, msg)
		go Writer(data, i, msg)
	}
	for {
		select {
		case m := &amp;lt;-msg:
			fmt.Println(m)
		default:
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;输出&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Writer 3 update No1 failed; val: 1, ver: 1
Writer 6 update No1 failed; val: 1, ver: 1
Writer 1 update No1 failed; val: 1, ver: 1
Writer 7 update No1 failed; val: 1, ver: 1
Writer 5 update No1 failed; val: 1, ver: 1
Writer 4 update success; val: 1, ver: 1
Writer 0 update No1 failed; val: 1, ver: 1
Writer 2 update No1 failed; val: 1, ver: 1
Writer 9 update No1 failed; val: 1, ver: 1
Writer 8 update No1 failed; val: 1, ver: 1
Writer 7 update success; val: 2, ver: 2
Writer 3 update No2 failed; val: 2, ver: 2
Writer 5 update No2 failed; val: 2, ver: 2
Writer 1 update No2 failed; val: 2, ver: 2
Writer 9 update success; val: 3, ver: 3
Writer 6 update No2 failed; val: 3, ver: 3
Writer 0 update No2 failed; val: 3, ver: 3
Writer 8 update No2 failed; val: 3, ver: 3
Writer 2 update No2 failed; val: 3, ver: 3
Writer 8 update success; val: 4, ver: 4
Writer 0 update No3 failed; val: 4, ver: 4
Writer 2 update success; val: 5, ver: 5
Writer 1 update No3 failed; val: 5, ver: 5
Writer 6 update No3 failed; val: 5, ver: 5
Writer 3 update No3 failed; val: 5, ver: 5
Writer 5 update No3 failed; val: 5, ver: 5
Writer 1 update success; val: 6, ver: 6
Writer 0 update No4 failed; val: 6, ver: 6
Writer 6 update success; val: 7, ver: 7
Writer 3 update No4 failed; val: 7, ver: 7
Writer 5 update No4 failed; val: 7, ver: 7
Writer 5 update success; val: 8, ver: 8
Writer 0 update No5 failed; val: 8, ver: 8
Writer 3 update No5 failed; val: 8, ver: 8
Reader 1, val: 8, ver: 8
Reader 0, val: 8, ver: 8
Reader 9, val: 8, ver: 8
Reader 6, val: 8, ver: 8
Reader 7, val: 8, ver: 8
Reader 3, val: 8, ver: 8
Reader 5, val: 8, ver: 8
Reader 2, val: 8, ver: 8
Reader 8, val: 8, ver: 8
Reader 4, val: 8, ver: 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以发现当有&lt;code&gt;writer&lt;/code&gt;写入成功后,在这之前尝试更新的&lt;code&gt;writer&lt;/code&gt;都更新失败了
但这也让我们发现乐观锁的缺点:&lt;strong&gt;在写入比较频繁的数据中容易造成大量的更新失败和重试,从而浪费资源&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;CAS算法&lt;/h4&gt;
&lt;p&gt;算法CAS 的全称是 Compare And Swap（比较与交换） ，用于实现乐观锁，被广泛应用于各大框架中。
CAS 的思想很简单，就是用一个预期值和要更新的变量值进行比较，两值相等才会进行更新。
CAS 是一个原子操作，底层依赖于一条 CPU 的原子指令。原子操作 即最小不可拆分的操作，也就是说操作一旦开始，就不能被打断，直到操作完成。
CAS 涉及到三个操作数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;V&lt;/code&gt;：要更新的变量值(Var)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;E&lt;/code&gt;：预期值(Expected)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;N&lt;/code&gt;：拟写入的新值(New)
当且仅当 V 的值等于 E 时，CAS 通过原子方式用新值 N 来更新 V 的值。如果不等，说明已经有其它线程更新了 V，则当前线程放弃更新。
举一个简单的例子：线程 A 要修改变量 i 的值为 6，i 原值为 1（V = 1，E=1，N=6，假设不存在 ABA 问题）。
i 与 1 进行比较，如果相等， 则说明没被其他线程修改，可以被设置为 6 。
i 与 1 进行比较，如果不相等，则说明被其他线程修改，当前线程放弃更新，CAS 操作失败。
当多个线程同时使用 CAS 操作一个变量时，只有一个会胜出，并成功更新，其余均会失败，但失败的线程并不会被挂起，仅是被告知失败，并且允许再次尝试，当然也允许失败的线程放弃操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;悲观锁&lt;/h2&gt;
&lt;p&gt;顾名思义,悲观锁是一种处于悲观思想的锁,它假设数据在更新时总会发生冲突,所以在每次获取资源时都会给资源上锁,其他线程想要访问该资源必须&lt;strong&gt;等待&lt;/strong&gt;解锁,从而实现&lt;strong&gt;资源独享&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;互斥锁&lt;/h3&gt;
&lt;p&gt;共享资源的使用是互斥的，即一个线程获得资源的使用权后就会将该资源加锁，使用完后会将其解锁，
如果在使用过程中有其他线程想要获取该资源的锁，那么它就会被阻塞陷入睡眠状态-空等待(sleep-waiting)，直到该资源被解锁才会被唤醒，
如果被阻塞的资源不止一个，那么它们都会被唤醒，但是获得资源使用权的是第一个被唤醒的线程，其它线程又陷入沉睡.&lt;/p&gt;
&lt;h4&gt;go语言的简单实现&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;数据定义&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Data 带互斥锁的数据
type Data struct {
	Val int
	sync.Mutex
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;读写操作&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Reader 读取数据
func Reader(data *Data, id int, msg chan string) {
	// 加锁
	data.Mutex.Lock()
	msg &amp;lt;- fmt.Sprintf(&quot;Reader %d locked&quot;, id)
	// 读取完成后解锁
	defer func() {
		data.Mutex.Unlock()
		msg &amp;lt;- fmt.Sprintf(&quot;Reader %d unlocked&quot;, id)

	}()
	msg &amp;lt;- fmt.Sprintf(&quot;Reader %d read %d&quot;, id, data.Val)

}

// Writer 写入数据
func Writer(data *Data, id int, msg chan string) {
	// 加锁
	data.Mutex.Lock()
	msg &amp;lt;- fmt.Sprintf(&quot;Writer %d locked&quot;, id)
	// 写入完成后解锁
	defer func() {
		data.Mutex.Unlock()
		msg &amp;lt;- fmt.Sprintf(&quot;Writer %d unlocked&quot;, id)

	}()
	data.Val++
	msg &amp;lt;- fmt.Sprintf(&quot;Writer %d wrote %d&quot;, id, data.Val)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;主函数&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func main() {
	var wg sync.WaitGroup
	data := &amp;amp;Data{Val: 0}
	msg := make(chan string)
	defer close(msg)
	for i := 0; i &amp;lt; 10; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			Reader(data, id, msg)
		}(i)

		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			Writer(data, id, msg)
		}(i)
	}

	for i := 0; i &amp;lt; 60; i++ {
		fmt.Println(&amp;lt;-msg)
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;输出&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Reader 0 locked
Reader 0 read 0
Reader 0 unlocked
Writer 1 locked
Writer 1 wrote 1
Writer 1 unlocked
Reader 3 locked
Reader 3 read 1
Reader 1 locked
Reader 1 read 1
Reader 3 unlocked
Reader 1 unlocked
Writer 5 locked
Writer 5 wrote 2
Writer 5 unlocked
Writer 3 locked
Writer 3 wrote 3
Writer 3 unlocked
Reader 4 locked
Reader 4 read 3
Reader 4 unlocked
Writer 4 locked
Writer 4 wrote 4
Writer 4 unlocked
Reader 5 locked
Reader 5 read 4
Reader 5 unlocked
Writer 6 locked
Writer 6 wrote 5
Writer 6 unlocked
Reader 6 locked
Reader 6 read 5
Reader 6 unlocked
Reader 7 locked
Reader 7 read 5
Reader 7 unlocked
Reader 2 locked
Reader 2 read 5
Reader 2 unlocked
Writer 2 locked
Writer 2 wrote 6
Writer 2 unlocked
Writer 7 locked
Writer 7 wrote 7
Writer 7 unlocked
Reader 8 locked
Reader 8 read 7
Reader 8 unlocked
Writer 8 locked
Writer 8 wrote 8
Writer 8 unlocked
Reader 9 locked
Reader 9 read 8
Reader 9 unlocked
Writer 9 locked
Writer 9 wrote 9
Writer 9 unlocked
Writer 0 locked
Writer 0 wrote 10
Writer 0 unlocked
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以发现每一次读写操作都是原子性的既每次读写完成前都不会发生另外的读写操作&lt;/p&gt;
&lt;h3&gt;读写锁&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Mutex&lt;/code&gt;在大量并发的情况下，会造成锁等待，对性能的影响比较大。
如果某个读操作的协程加了锁，其他的协程没必要处于等待状态，可以并发地访问共享变量，这样能让读操作并行，提高读性能。
&lt;code&gt;RWLock&lt;/code&gt;就是用来干这个的，这种锁在某一时刻能由&lt;strong&gt;多个&lt;/strong&gt;&lt;code&gt;reader&lt;/code&gt;持有，或者被&lt;strong&gt;一个&lt;/strong&gt;&lt;code&gt;writer&lt;/code&gt;持有&lt;/p&gt;
&lt;p&gt;主要遵循以下规则 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读写锁的读锁可以重入，在已经有读锁的情况下，可以任意加读锁。&lt;/li&gt;
&lt;li&gt;在读锁没有全部解锁的情况下，写操作会阻塞直到所有读锁解锁。&lt;/li&gt;
&lt;li&gt;写锁定的情况下，其他协程的读写都会被阻塞，直到写锁解锁。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;go语言的简单实现&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;数据定义&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Data 带读写锁的数据
type Data struct {
	Val int
	mu  sync.RWMutex
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;读写操作&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Reader 读取数据
func Reader(data *Data, id int, msg chan string) {
	// 加读锁
	data.mu.RLock()
	msg &amp;lt;- fmt.Sprintf(&quot;Reader %d locked&quot;, id)

	// 读取完成后解锁
	defer func() {
		data.mu.RUnlock()
		msg &amp;lt;- fmt.Sprintf(&quot;Reader %d unlocked&quot;, id)

	}()
	msg &amp;lt;- fmt.Sprintf(&quot;Reader %d read %d&quot;, id, data.Val)

}

// Writer 写入数据
func Writer(data *Data, id int, msg chan string) {
	// 加写锁
	data.mu.Lock()
	msg &amp;lt;- fmt.Sprintf(&quot;Writer %d locked&quot;, id)
	// 写入完成后解锁
	defer func() {
		data.mu.Unlock()
		msg &amp;lt;- fmt.Sprintf(&quot;Writer %d unlocked&quot;, id)

	}()
	data.Val++
	time.Sleep(time.Second * 2)
	msg &amp;lt;- fmt.Sprintf(&quot;Writer %d wrote %d&quot;, id, data.Val)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;主函数&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func main() {
	var wg sync.WaitGroup
	data := &amp;amp;Data{Val: 0}
	msg := make(chan string)
	defer close(msg)
	for i := 0; i &amp;lt; 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			Reader(data, id, msg)
		}(i)

		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			Writer(data, id, msg)
		}(i)
	}

	for i := 0; i &amp;lt; 30; i++ {
		fmt.Println(&amp;lt;-msg)
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;输出&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Reader 0 locked
Reader 0 read 0
Reader 0 unlocked
Writer 0 locked
Writer 0 wrote 1
Writer 0 unlocked
Reader 1 locked
Reader 3 locked
Reader 4 locked
Reader 2 locked
Reader 2 read 1
Reader 2 unlocked
Reader 4 read 1
Reader 4 unlocked
Reader 1 read 1
Reader 1 unlocked
Reader 3 read 1
Reader 3 unlocked
Writer 2 locked
Writer 2 wrote 2
Writer 2 unlocked
Writer 3 locked
Writer 3 wrote 3
Writer 3 unlocked
Writer 4 locked
Writer 4 wrote 4
Writer 4 unlocked
Writer 1 locked
Writer 1 wrote 5
Writer 1 unlocked
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以发现数据可以由多个&lt;code&gt;Reader&lt;/code&gt;读取,但是同时只能由1个&lt;code&gt;Writer&lt;/code&gt;写入&lt;/p&gt;
&lt;h2&gt;乐观锁和悲观锁的应用场景&lt;/h2&gt;
&lt;h3&gt;悲观锁的特点：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.悲观锁适用于并发写操作较多的场景，因为写操作涉及到数据的修改，需要保证数据的一致性。&lt;/li&gt;
&lt;li&gt;2.悲观锁在加锁期间，其他线程无法访问被锁定的资源，从而保证了数据的完整性。&lt;/li&gt;
&lt;li&gt;3.悲观锁需要频繁地进行加锁和解锁操作，开销较大。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;悲观锁的应用场景：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.银行账户转账：在进行转账操作时，需要保证同时只有一个线程能够修改账户余额，避免出现数据不一致的情况。&lt;/li&gt;
&lt;li&gt;2.数据库行锁：在数据库中，使用悲观锁可以在读取数据之前对数据进行加锁，避免其他事务对数据的并发修改。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;乐观锁的特点：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.乐观锁适用于并发读操作较多的场景，因为读操作不涉及到数据的修改，不需要加锁。&lt;/li&gt;
&lt;li&gt;2.乐观锁在更新数据时，只有在提交更新操作时才对数据进行版本检查，减少了加锁和解锁的开销。&lt;/li&gt;
&lt;li&gt;3.乐观锁可能需要进行重试，以处理并发修改引起的冲突。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;乐观锁的应用场景：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.数据库乐观锁：在数据库中，可以使用版本号或时间戳来实现乐观锁，用于避免并发修改引起的数据冲突。&lt;/li&gt;
&lt;li&gt;2.缓存更新：在缓存中，可以使用版本号或时间戳来实现乐观锁，用于保证缓存数据的一致性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;悲观锁适用于并发写操作较多的场景，需要频繁地进行加锁和解锁操作，保证数据的一致性；
而乐观锁适用于并发读操作较多的场景，通过版本检查来处理并发修改引起的冲突，减少了加锁和解锁的开销。&lt;/p&gt;
</content:encoded></item><item><title>Golang基础知识</title><link>https://fuwari.vercel.app/posts/golang%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/golang%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</guid><description>Golang基础知识整理</description><pubDate>Sun, 17 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Golang基础知识&lt;/h1&gt;
&lt;h2&gt;进程,线程,协程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;进程: &lt;strong&gt;进程&lt;/strong&gt;是&lt;strong&gt;系统资源&lt;/strong&gt;(地址空间,文件句柄...)分配的基本单位.&lt;/li&gt;
&lt;li&gt;线程: &lt;strong&gt;线程&lt;/strong&gt;是&lt;strong&gt;CPU调度&lt;/strong&gt;的基本单位.
&lt;ul&gt;
&lt;li&gt;所有线程共享相同的虚拟空间地址,可以访问相同的代码段,数据段和堆栈段.&lt;/li&gt;
&lt;li&gt;所有线程可以访问所属进程的全局变量和静态变量.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;协程: &lt;strong&gt;用户态&lt;/strong&gt;的线程,由用户程序创建,删除,协程切换&lt;strong&gt;不需要&lt;/strong&gt;切换内核态.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;线程和协程的区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.线程是操作系统的概念，而协程是程序级的概念。线程由&lt;strong&gt;操作系统调度&lt;/strong&gt;执行，每个线程都有自己的&lt;strong&gt;执行上下文&lt;/strong&gt;，包
括程序计数器、寄存器等。而协程由&lt;strong&gt;程序自身控制&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;2.多个线程之间通过&lt;strong&gt;切换执行&lt;/strong&gt;的方式实现&lt;strong&gt;并发&lt;/strong&gt;。线程切换时需要保存和恢复上下文，涉及到上下文切换的开销。而协
程切换时不需要操作系统的介入，只需要保存和恢复自身的上下文，切换开销较小。&lt;/li&gt;
&lt;li&gt;3.&lt;strong&gt;线程是抢占式的并发&lt;/strong&gt;，即操作系统可以随时剥夺一个线程的执行权。而协程是合作式的并发，&lt;em&gt;协程的执行权由程序
自身决定&lt;/em&gt;，只有当协程主动让出执行权时，其他协程才会得到执行机会。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;线程之间的通信方式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.匿名管道&lt;/li&gt;
&lt;li&gt;2.命名管道&lt;/li&gt;
&lt;li&gt;3.消息队列&lt;/li&gt;
&lt;li&gt;4.共享内存&lt;/li&gt;
&lt;li&gt;5.信号&lt;/li&gt;
&lt;li&gt;6.信号量&lt;/li&gt;
&lt;li&gt;7.socket&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;同步和异步&lt;/h2&gt;
&lt;p&gt;同步和异步是描述计算机程序中任务执行方式的两个重要概念，它们主要的区别在于任务执行的顺序和时间点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;同步（Synchronous）&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;同步操作指在发起任务后，调用方会等待这个任务完成，然后再继续执行后续操作。执行方式是顺序的，一个任务完成后才进行下一个任务。&lt;/li&gt;
&lt;li&gt;特点是代码执行简单直观，容易理解和管理，但可能会导致性能瓶颈和响应延迟，尤其在等待I/O操作或网络请求等耗时任务时。&lt;/li&gt;
&lt;li&gt;在前端开发中，同步操作可能会阻塞用户界面，影响用户体验。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;异步（Asynchronous）&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;异步操作指在发起任务后，调用方不会等待这个任务完成，而是继续执行后续操作。任务的完成会在将来的某个时间点通过回调函数、事件、Promise等机制来处理。&lt;/li&gt;
&lt;li&gt;特点是不阻塞当前执行流程，可以提高效率和响应速度，尤其在I/O密集型或高并发的应用场景中。但也可能导致代码复杂度增加，需要更多的控制逻辑来处理异步流程和状态。&lt;/li&gt;
&lt;li&gt;在前端开发中，异步操作允许页面保持响应，提升用户体验，例如使用&lt;code&gt;setTimeout&lt;/code&gt;、&lt;code&gt;setInterval&lt;/code&gt;和&lt;code&gt;ajax&lt;/code&gt;等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总结来说就是，&lt;strong&gt;同步是按顺序执行任务&lt;/strong&gt;，简单但可能效率低；&lt;strong&gt;异步是同时执行多个任务&lt;/strong&gt;，复杂但效率高。根据不同的应用场景和性能要求来决定使用同步还是异步方式。&lt;/p&gt;
&lt;h2&gt;GMP调度和CSP模型&lt;/h2&gt;
&lt;h3&gt;CSP模型&lt;/h3&gt;
&lt;p&gt;CSP 是 Communicating Sequential Process 的简称，中文可以叫做通信顺序进程，是一种&lt;strong&gt;并发编程模型&lt;/strong&gt;，是一个很强大的并发数据模型，是上个世纪七十年代提出的，用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。
相对于Actor模型，CSP中channel是第一类对象，它不关注发送消息的实体，而关注与发送消息时使用的channel。&lt;/p&gt;
&lt;p&gt;严格来说，CSP 是一门形式语言（类似于 ℷ calculus），用于描述并发系统中的互动模式，也因此成为一众面向并发的编程语言的理论源头，并衍生出了 Occam/Limbo/Golang…&lt;/p&gt;
&lt;p&gt;而具体到编程语言，如 Golang，其实只用到了 CSP 的很小一部分，即理论中的 Process/Channel（对应到语言中的 goroutine/channel）：这两个并发原语之间没有从属关系， Process 可以订阅任意个 Channel，Channel 也并不关心是哪个 Process 在利用它进行通信；Process 围绕 Channel 进行读写，形成一套有序阻塞和可预测的并发模型。&lt;/p&gt;
&lt;h3&gt;什么是GMP&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;G: goroutine, go的协程,每个&lt;code&gt;go&lt;/code&gt;关键字都会创建一个协程.&lt;/li&gt;
&lt;li&gt;P: process, 包含运行Go代码所需要的必要资源，用来&lt;strong&gt;调度G和M之间的关联关系&lt;/strong&gt;，其数量可以通过&lt;code&gt;GOMAXPROCS&lt;/code&gt;来设置，默认为核心数&lt;/li&gt;
&lt;li&gt;M: machine, 工作线程, 数量对应真是的CPU数.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;线程想运行任务就得获取 P，从 P 的本地队列获取 G，当 P 的本地队列为空时，M 也会尝试从全局队列或其他 P 的本地队列获取 G。M 运行 G，G 执行之后，M 会从 P 获取下一个 G，不断重复下去。&lt;/p&gt;
&lt;h3&gt;Goroutine调度策略&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.队列轮转：P会周期性的将G调度到M中执行，执行一段时间后，保存上下文，将G放到队列尾部，然后从队列中再取出一个G进行调度，P还会周期性的查看全局队列是否有G等待调度到M中执行&lt;/li&gt;
&lt;li&gt;2.系统调用：当G0即将进入系统调用时，M0将释放P，进而某个空闲的M1获取P，继续执行P队列中剩下的G。M1的来源有可能是M的缓存池，也可能是新建的。&lt;/li&gt;
&lt;li&gt;3.当G0系统调用结束后，如果有空闲的P，则获取一个P，继续执行G0。如果没有，则将G0放入全局队列，等待被其他的P调度。然后M0将进入缓存池睡眠。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Goroutine的切换时机&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.select阻塞&lt;/li&gt;
&lt;li&gt;2.io阻塞&lt;/li&gt;
&lt;li&gt;3.channel阻塞&lt;/li&gt;
&lt;li&gt;4.等待锁&lt;/li&gt;
&lt;li&gt;5.程序调用&lt;/li&gt;
&lt;li&gt;6.程序员显示编码操作??&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;context&lt;/h2&gt;
&lt;h3&gt;Context结构原理&lt;/h3&gt;
&lt;p&gt;Context（上下文）是Golang应用开发常用的并发控制技术 ，它可以控制一组呈树状结构的goroutine，每个goroutine拥有相同的上下文。Context 是&lt;strong&gt;并发安全&lt;/strong&gt;的，主要是用于控制多个协程之间的协作、取消操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Context interface {
  Deadline() (deadline time.Time, ok bool)
  Done() &amp;lt;-chan struct{}
  Err() error
  Value(key interface{}) interface{}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;「Deadline」 方法：可以获取设置的截止时间，返回值 deadline 是截止时间，到了这个时间，Context 会自动发起取消请求，返回值 ok 表示是否设置了截止时间。
「Done」 方法：返回一个只读的 channel ，类型为 struct{}。如果这个 chan 可以读取，说明已经发出了取消信号，可以做清理操作，然后退出协程，释放资源。
「Err」 方法：返回Context 被取消的原因。
「Value」 方法：获取 Context 上绑定的值，是一个键值对，通过 key 来获取对应的值。&lt;/p&gt;
&lt;p&gt;几个实现context接口的对象：&lt;/p&gt;
&lt;p&gt;context.Background()和context.TODO()相似，返回的context一般作为根对象存在，其不可以退出，也不能携带值。要具体地使用context的功能，需要派生出新的context。
context.WithCancel()函数返回一个子context并且有cancel退出方法。子context在两种情况下会退出，一种情况是调用cancel，另一种情况是当参数中的父context退出时，该context及其关联的context都退出。
context.WithTimeout函数指定超时时间，当超时发生后，子context将退出。因此子context的退出有3种时机，一种是父context退出；一种是超时退出；一种是主动调用cancel函数退出。
context.WithDeadline()与context.WithTimeout()返回的函数类似，不过其参数指定的是最后到期的时间。
context.WithValue()函数返回待key-value的子context&lt;/p&gt;
&lt;h3&gt;Context原理&lt;/h3&gt;
&lt;p&gt;context在很大程度上利用了通道在close时会通知所有监听它的协程这一特性来实现。每一个派生出的子协程都会创建一个新的退出通道，组织好context之间的关系即可实现继承链上退出的传递。&lt;/p&gt;
&lt;p&gt;context使用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.RPC调用&lt;/li&gt;
&lt;li&gt;2.PipeLine&lt;/li&gt;
&lt;li&gt;3.超时请求&lt;/li&gt;
&lt;li&gt;4.HTTP服务器的request互相传递数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;竞态、内存逃逸&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.资源竞争，就是在程序中，同一块内存同时被多个 goroutine 访问。我们使用 go build、go run、go test 命令时，添加 -race 标识可以检查代码中是否存在资源竞争。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解决这个问题，我们可以给资源进行加锁，让其在同一时刻只能被一个协程来操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sync.Mutex
sync.RWMutex
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.一般来说，局部变量会在函数返回后被销毁，因此被返回的引用就成为了&quot;无所指&quot;的引用，程序会进入未知状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但这在Go中是安全的，Go编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数，则不会将内存分配在栈上，而是分配在&lt;strong&gt;堆&lt;/strong&gt;上，因为他们不在栈区，即使释放函数，其内容也不会受影响。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func add(x, y int) *int {
  res := x + y
  return &amp;amp;res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个例子中，函数&lt;code&gt;add&lt;/code&gt;局部变量&lt;code&gt;res&lt;/code&gt;发生了逃逸。&lt;code&gt;res&lt;/code&gt;作为返回值，在&lt;code&gt;main&lt;/code&gt;函数中继续使用，因此&lt;code&gt;res&lt;/code&gt;指向的内存不能够分配在栈上，随着函数结束而回收，只能分配在堆上。&lt;/p&gt;
&lt;p&gt;编译时可以借助选项 &lt;code&gt;-gcflags=-m&lt;/code&gt;，查看变量逃逸的情况&lt;/p&gt;
&lt;h2&gt;new和make的区别&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;var&lt;/code&gt;声明值类型的变量时，系统会默认为他分配内存空间，并赋该类型的&lt;strong&gt;零值&lt;/strong&gt;
如果是&lt;strong&gt;指针类型&lt;/strong&gt;或者&lt;strong&gt;引用类型&lt;/strong&gt;的变量，系统不会为它分配内存，默认是&lt;code&gt;nil&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.&lt;code&gt;make&lt;/code&gt; 仅用来分配及初始化类型为 &lt;code&gt;slice&lt;/code&gt;、&lt;code&gt;map&lt;/code&gt;、&lt;code&gt;chan&lt;/code&gt; 的数据。&lt;/li&gt;
&lt;li&gt;2.&lt;code&gt;new&lt;/code&gt; 可分配任意类型的数据，根据传入的类型申请一块内存，返回指向这块内存的指针，即类型 &lt;code&gt;*Type&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;3.&lt;code&gt;make&lt;/code&gt; 返回&lt;strong&gt;引用&lt;/strong&gt;，即 &lt;code&gt;Type&lt;/code&gt;，&lt;code&gt;new&lt;/code&gt; 分配的空间被清零， &lt;code&gt;make&lt;/code&gt; 分配空间后，会进行&lt;strong&gt;初始化&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;4.&lt;code&gt;make&lt;/code&gt;函数返回的是&lt;code&gt;slice&lt;/code&gt;、&lt;code&gt;map&lt;/code&gt;、&lt;code&gt;chan&lt;/code&gt;类型本身&lt;/li&gt;
&lt;li&gt;5.&lt;code&gt;new&lt;/code&gt;函数返回一个指向该类型内存地址的指针&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;slice的实现原理&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;slice&lt;/code&gt;不是线程安全的&lt;/em&gt;
切片是基于&lt;strong&gt;数组&lt;/strong&gt;实现的，底层是数组，可以理解为对底层数组的抽象&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type slice struct{
  array unsafe.Pointer
  len int
  cap int
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;slice&lt;/code&gt;占24个字节
&lt;code&gt;array&lt;/code&gt;：指向底层数组的指针，占用8个字节
&lt;code&gt;len&lt;/code&gt;:切片的长度，占用8个字节
&lt;code&gt;cap&lt;/code&gt;：切片的容量，cap总是大于等于len，占用8个字节&lt;/p&gt;
&lt;p&gt;初始化&lt;code&gt;slice&lt;/code&gt;调用的是&lt;code&gt;runtime.makeslice&lt;/code&gt;，&lt;code&gt;makeslice&lt;/code&gt;函数的工作主要就是计算&lt;code&gt;slice&lt;/code&gt;所需内存大小，然后调用&lt;code&gt;mallocgc&lt;/code&gt;进行内存的分配&lt;/p&gt;
&lt;p&gt;&lt;em&gt;所需内存的大小=切片中元素大小*切片的容量&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;slice和array的区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.长度不同&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数组的初始化必须指定长度且长度是固定的&lt;/li&gt;
&lt;li&gt;切片的长度是不固定的,可以追加元素且追加元素时可能触发扩容&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.函数传参不同&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数组是值类型,在作为函数参数时会将该数组的值&lt;strong&gt;拷贝&lt;/strong&gt;给另一个数组,传递的是一份深拷贝,在函数中对数组进行操作不会影响原数组&lt;/li&gt;
&lt;li&gt;切片是引用类型,在作为函数参数时(或一个切片赋值给另一个切片)会只会将切片的&lt;code&gt;len&lt;/code&gt;,&lt;code&gt;cap&lt;/code&gt;拷贝出去,底层共用一个数组,不会占用额外空间,所以函数对数组的操作会影响到原数组&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.计算长度的方式不同&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数组计算长度需要遍历,时间复杂度为&lt;code&gt;O(n)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;切片包含字段&lt;code&gt;len&lt;/code&gt;,可直接获得切片长度,时间复杂度为&lt;code&gt;O(1)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;map的实现原理&lt;/h2&gt;
&lt;p&gt;Go中的&lt;code&gt;map&lt;/code&gt;是一个指针，占用8个字节，指向&lt;code&gt;hmap&lt;/code&gt;结构体，&lt;code&gt;map&lt;/code&gt;底层是基于 &lt;strong&gt;哈希表+链地址法(链表结构作为桶)&lt;/strong&gt; 存储的。&lt;/p&gt;
&lt;h3&gt;map的特点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.键不能重复&lt;/li&gt;
&lt;li&gt;2.键必须可哈希,如&lt;code&gt;int/bool/string/float/array&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;3.无序&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;map底层结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// A header for a Go map.
type hmap struct {
  count int //代表哈希表中的元素个数，调用len(map)时，返回的就是该字段值。
  flags uint8 //状态标志是否处于正在写入的状态等
  B uint8 //buckets(桶)的对数 如果B=5，则buckets数组的长度=2^B=32，意味着有32个桶
  noverflow uint16 //溢出桶的数量
  hash0 uint32  //生成hash的随机数种子
  buckets unsafe.Pointer  //指向buckets数组的指针，数组大小为2^B，如果元素个数为0，它为nil.
  oldbuckets unsafe.Pointer //如果发生扩容，oldbuckets是指向老的buckets数组的指针，老的buckets数组大小是新的buckets的1/2;非扩容状态下，它为ni1.
  nevacuate uintptr //表示扩容进度。小于此地址的buckets代表已搬迁完成。
  extra *mapextra //存储溢出桶，这个字段是为了优化GC扫描面设计的
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;源码包中&lt;code&gt;src/runtime/map.go&lt;/code&gt;定义了&lt;code&gt;hmap&lt;/code&gt;的数据结构
&lt;code&gt;hmap&lt;/code&gt;包含若干个结构为&lt;code&gt;bmap&lt;/code&gt;的数组，每个&lt;code&gt;bmap&lt;/code&gt;底层都采用链表结构，&lt;code&gt;bmap&lt;/code&gt;通常叫其&lt;code&gt;bucket&lt;/code&gt;,也就是我们常说的&lt;strong&gt;桶&lt;/strong&gt;,一个桶最多装8个&lt;strong&gt;key&lt;/strong&gt;
这些&lt;code&gt;key&lt;/code&gt;之所以会落入同一个桶，是因为它们经过哈希计算后，哈希结果的低&lt;code&gt;B&lt;/code&gt;位是相同的&lt;/p&gt;
&lt;h3&gt;map的初始化过程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.创建一个&lt;code&gt;hmap&lt;/code&gt;结构体对象&lt;/li&gt;
&lt;li&gt;2.生成一个哈希因子hash0并赋值到&lt;code&gt;hmap&lt;/code&gt;对象中（用于后续为key创建哈希值）&lt;/li&gt;
&lt;li&gt;3.根据&lt;code&gt;hint=10&lt;/code&gt;，并根据算法规则来创建&lt;code&gt;B&lt;/code&gt;，此时的&lt;code&gt;B&lt;/code&gt;为1&lt;/li&gt;
&lt;li&gt;4.根据&lt;code&gt;B&lt;/code&gt;去创建桶(&lt;code&gt;bmap&lt;/code&gt;对象)并存放在&lt;code&gt;bucket&lt;/code&gt;数组中。当前的&lt;code&gt;bmap&lt;/code&gt;的数量为2
&lt;ul&gt;
&lt;li&gt;B&amp;lt;4时，根据B创建桶的个数的规则为：&lt;code&gt;2^B&lt;/code&gt;（标准桶）&lt;/li&gt;
&lt;li&gt;B&amp;gt;=4时，根据B创建桶的个数的规则为：&lt;code&gt;2^B+2^(B-4)&lt;/code&gt; （标准桶+溢出桶）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Go的map为什么是无序的&lt;/h3&gt;
&lt;p&gt;使用&lt;code&gt;range&lt;/code&gt;多次遍历map时输出的&lt;code&gt;key&lt;/code&gt;和&lt;code&gt;value&lt;/code&gt;的顺序可能不同。这是Go语言的设计者们有意为之，旨在提示开发者们，Go底层实现并不保证map遍历顺序稳定，请大家不要依赖range遍历结果顺序&lt;/p&gt;
&lt;p&gt;主要原因有2点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.&lt;code&gt;map&lt;/code&gt;在遍历时，并不是从固定的0号&lt;code&gt;bucket&lt;/code&gt;开始遍历的，每次遍历，都会从一个随机值序号的&lt;code&gt;bucket&lt;/code&gt;， 再从其中随机的&lt;code&gt;cell&lt;/code&gt;开始遍历&lt;/li&gt;
&lt;li&gt;2.&lt;code&gt;map&lt;/code&gt;遍历时，是按序遍历&lt;code&gt;bucket&lt;/code&gt;，同时按需遍历&lt;code&gt;bucket&lt;/code&gt;中和其&lt;code&gt;overflow bucket&lt;/code&gt;中的cell。但是&lt;code&gt;map&lt;/code&gt;在扩容后，会发生&lt;code&gt;key&lt;/code&gt;的搬迁，这造成原来落在一个&lt;code&gt;bucket&lt;/code&gt;中的&lt;code&gt;Key&lt;/code&gt;,搬迁后，有可能会落到其他&lt;code&gt;bucket&lt;/code&gt;中了，从这个角度看，遍历&lt;code&gt;map&lt;/code&gt;的结果就不可能是按照原来的顺序了&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;map是如何查找的&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.写保护检测:先检查&lt;code&gt;map&lt;/code&gt;的&lt;code&gt;flags&lt;/code&gt;标志位是否为1, 如果是则表明有其他协程正在写入该&lt;code&gt;map&lt;/code&gt;继而导致&lt;code&gt;panic&lt;/code&gt;,这也说明&lt;code&gt;map&lt;/code&gt;&lt;strong&gt;不是线程安全的&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;2.计算hash值:将&lt;code&gt;key&lt;/code&gt;经过hash函数得到哈希值,不同类型的&lt;code&gt;key&lt;/code&gt;有不同的hash函数&lt;/li&gt;
&lt;li&gt;3.找到hash值对应的&lt;code&gt;bucket&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;bucket定位：哈希值的低B个bit位，用来定位&lt;code&gt;key&lt;/code&gt;所存放的&lt;code&gt;bucket&lt;/code&gt;
如果当前正在扩容中，并且定位到的旧&lt;code&gt;bucket&lt;/code&gt;数据还未完成迁移，则用旧的&lt;code&gt;bucket&lt;/code&gt;（扩容前的&lt;code&gt;bucket&lt;/code&gt;）&lt;pre&gt;&lt;code&gt;hash:=t.hasher(key, uintprt(h.hash0))
m=bucketMask(h.B)
b:=(*bmap)(add(h.buckets,(hash&amp;amp;m)*uintptr(t.bucketsize))
if c:=h.oldbucket;c!=nil{
  if !h.sameSizeGrow(){
    m&amp;gt;&amp;gt;=1
  }
  oldb:=(*bmap)(add(c,(hash&amp;amp;m)*uintptr(t.bucketsize)))
  if !evacuated(oldb){
    b=oldb
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4.遍历&lt;code&gt;bucket&lt;/code&gt;查找&lt;/li&gt;
&lt;li&gt;5.返回&lt;code&gt;key&lt;/code&gt;对应的指针&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;负载因子&lt;/h3&gt;
&lt;p&gt;负载因子(load factor)，用于衡量当前哈希表中空间占用率的核心指标，也就是每个bucket桶存储的平均元素个数。
&lt;em&gt;负载因子=哈希表存储的元素个数/桶个数&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Go官方发现:
装载因子越大，填入的元素越多，空间利用率就越高，但发生哈希冲突的几率就变大。反之，装载因子越小，填入的元素越少，冲突发生的几率减小，但空间浪费也会变得更多，而且还会提高扩容操作的次数
根据这份测试结果和讨论，Go官方取了一个相对适中的值，把Go中的 map的负载因子硬编码为6.5，这就是6.5的选择缘由。
这意味着在Go语言中，当map存储的元素个数大于或等于6.5*桶个数时，就会触发扩容行为。&lt;/p&gt;
&lt;h3&gt;map扩容&lt;/h3&gt;
&lt;p&gt;扩容条件:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;条件1：超过负载 map元素个数&amp;gt;6.5*桶个数&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func overLoadFactor(count int, B uint8) bool{
  return count &amp;gt; bucketCnt &amp;amp;&amp;amp; uintptr(count)&amp;gt;loadFactor*bucketShift(B)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bucketCnt=8&lt;/code&gt;，一个桶可以装的最大元素个数
&lt;code&gt;loadFactor=6.5&lt;/code&gt;，负载因子，平均每个桶的元素个数
&lt;code&gt;bucketShift(8)&lt;/code&gt;, 桶的个数&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;条件2：溢出桶太多(桶中存放的元素超出了最大的存储数量,则需要将超出的元素存放进另一个桶中,则这个桶就叫做溢出桶)
当桶总数&amp;lt;2^15时，如果溢出桶总数&amp;gt;=桶总数，则认为溢出桶过多
当桶总数&amp;gt;=2^15时，直接与2^15比较，当溢出桶总数&amp;gt;=2^15时，即认为溢出桶太多了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;扩容机制:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.双倍扩容：针对条件1，新建一个&lt;code&gt;buckets&lt;/code&gt;数组，新的&lt;code&gt;buckets&lt;/code&gt;大小是原来的&lt;code&gt;2&lt;/code&gt;倍，然后旧的&lt;code&gt;buckets&lt;/code&gt;数据 搬迁到新的&lt;code&gt;buckets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2.等量扩容：针对条件2，并不扩大容量，&lt;code&gt;buckets&lt;/code&gt;数量维持不变，重新做一遍类似双倍扩容的搬迁操作， 把松散的键值对重新排列一次，使得同一个&lt;code&gt;bucket&lt;/code&gt;中的&lt;code&gt;key&lt;/code&gt;排列地更紧密，节省空间，提高&lt;code&gt;buckets&lt;/code&gt;利用 率，进而保证更快的存取。该方法我们称之为等量扩容。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Golang中对nil的Slice和空Slice的处理是一致的吗?&lt;/h2&gt;
&lt;p&gt;首先Go的JSON 标准库对 nil slice 和 空 slice 的处理是不一致的。
1.&lt;code&gt;slice := make([]int,0)&lt;/code&gt;：slice不为nil，但是slice没有值(即为&lt;code&gt;[]&lt;/code&gt;)，slice的底层的空间是空的。
2.&lt;code&gt;var slice []int&lt;/code&gt; ：slice的值是nil，可用于需要返回slice的函数，当函数出现异常的时候，保证函数依然会有nil的返回值。&lt;/p&gt;
&lt;h2&gt;chanel实现原理&lt;/h2&gt;
&lt;p&gt;Go中的&lt;code&gt;channel&lt;/code&gt;是一个循环队列，遵循&lt;strong&gt;先进先出&lt;/strong&gt;的原则，负责&lt;strong&gt;协程&lt;/strong&gt;之间的&lt;strong&gt;通信&lt;/strong&gt;(Go语言提倡不要通过共享内存
来通信，而要通过通信来实现内存共享，CSP(CommunicatingSequential Process)并发模型，就是通过
goroutine和channel来实现的)&lt;/p&gt;
&lt;h3&gt;使用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.停止信号监听&lt;/li&gt;
&lt;li&gt;2.定时任务&lt;/li&gt;
&lt;li&gt;3.生产方和消费方解耦&lt;/li&gt;
&lt;li&gt;4.控制并发数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;channel是并发安全的&lt;/h3&gt;
&lt;p&gt;通道的发送和接收操作是原子的，即一个完整的发送或接收操作是一个原子操作，不会被其他&lt;code&gt;goroutine&lt;/code&gt;中断。&lt;/p&gt;
&lt;p&gt;当一个&lt;code&gt;goroutine&lt;/code&gt;向&lt;code&gt;channel&lt;/code&gt;发送数据时，如果&lt;code&gt;channel&lt;/code&gt;已满，则发送操作会被&lt;strong&gt;阻塞&lt;/strong&gt;，直到有其他&lt;code&gt;goroutine&lt;/code&gt;从该&lt;code&gt;channel&lt;/code&gt;中接收数据后释放
空间，发送操作才能继续执行。在这种情况下，&lt;code&gt;channel&lt;/code&gt;内部会获取一个锁，保证只有一个&lt;code&gt;goroutine&lt;/code&gt;能够往其中写入数据。&lt;/p&gt;
&lt;p&gt;同样地，当一个&lt;code&gt;goroutine&lt;/code&gt;从&lt;code&gt;channel&lt;/code&gt;中接收数据时，如果&lt;code&gt;channel&lt;/code&gt;为空，则接收操作会被&lt;strong&gt;阻塞&lt;/strong&gt;，直到有其他&lt;code&gt;goroutine&lt;/code&gt;向该&lt;code&gt;channel&lt;/code&gt;中发送
数据后才能继续执行。在这种情况下，&lt;code&gt;channel&lt;/code&gt;内部也会获取一个锁，保证只有一个&lt;code&gt;goroutine&lt;/code&gt;能够从其中读取数据。&lt;/p&gt;
&lt;p&gt;因此可以通过&lt;code&gt;channel&lt;/code&gt;的阻塞实现主线程等待协程的效果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	result := make(chan int)
	params := make(chan int)
	go inputParams(params)
	go add(params, result)
	fmt.Println(&amp;lt;-result) // 未计算出结果之前主线程将一直阻塞在这
}

func inputParams(params chan int) {
	fmt.Println(&quot;等待输入&quot;)
	var a, b int
	fmt.Scanln(&amp;amp;a, &amp;amp;b)
	params &amp;lt;- a
	params &amp;lt;- b

}

func add(params chan int, result chan int) {
	a := &amp;lt;-params
	b := &amp;lt;-params
	// 等待计算
	fmt.Println(&quot;等待计算&quot;)
	time.Sleep(time.Second * 2)
	result &amp;lt;- a + b
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Channel是同步的还是异步的？&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;channel&lt;/code&gt;是异步进行的, &lt;code&gt;channel&lt;/code&gt;存在3种状态：&lt;/p&gt;
&lt;p&gt;1.&lt;code&gt;nil&lt;/code&gt;，未初始化的状态，只进行了声明，或者手动赋值为&lt;code&gt;nil&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送: 阻塞&lt;/li&gt;
&lt;li&gt;接收: 阻塞&lt;/li&gt;
&lt;li&gt;关闭: panic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2.&lt;code&gt;active&lt;/code&gt;，正常的&lt;code&gt;channel&lt;/code&gt;，可读或者可写&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送: 依是否有值以及是否有缓存而发送成功或阻塞&lt;/li&gt;
&lt;li&gt;接收: 依是否有值以及是否有缓存而接受成功或阻塞&lt;/li&gt;
&lt;li&gt;关闭: 成功&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;3.&lt;code&gt;closed&lt;/code&gt;，已关闭，千万不要误认为关闭&lt;code&gt;channel&lt;/code&gt;后，&lt;code&gt;channel&lt;/code&gt;的值是&lt;code&gt;nil&lt;/code&gt;，对已关闭&lt;code&gt;channel&lt;/code&gt;读写都会&lt;code&gt;panic&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Channel死锁场景&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.非缓存&lt;code&gt;channel&lt;/code&gt;只写不读&lt;/li&gt;
&lt;li&gt;2.非缓存&lt;code&gt;channel&lt;/code&gt;读在写后面&lt;/li&gt;
&lt;li&gt;3.缓存&lt;code&gt;channel&lt;/code&gt;写入超过缓冲区数量&lt;/li&gt;
&lt;li&gt;4.空读&lt;/li&gt;
&lt;li&gt;5.多个协程相互等待&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;悲观锁和乐观锁&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;悲观锁：它假设最坏的情况，认为数据冲突很可能发生。因此，&lt;em&gt;在数据被读取时悲观锁就会对数据进行加锁&lt;/em&gt;，以防止其他事务对数据的修改，直到事务完成。这适用于写操作频繁、冲突多的场景，典型实现是使用数据库锁机制。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;乐观锁：与悲观锁相反，它假设冲突很少发生。在数据读取时不会加锁，而是在提交更新时检查在读取和更新之间数据是否被改变，通常使用版本号或时间戳来检测。这适用于读操作多、写操作少的场景，能够减少锁的使用，提高系统吞吐量。实现乐观锁常见的方法是 CAS（比较并替换）操作。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;原子操作&lt;/h2&gt;
&lt;p&gt;原子操作是计算机科学中的一个重要概念，指的是在执行过程中&lt;strong&gt;不可分割&lt;/strong&gt;的操作。具体来说，&lt;em&gt;原子操作在执行完毕之前不会被任何其他操作打断&lt;/em&gt;，具有以下特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不可分割性&lt;/strong&gt;：原子操作执行过程中不能被中断，确保了操作的&lt;strong&gt;完整性&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程安全&lt;/strong&gt;：原子操作避免了多个线程同时对同一数据进行操作时可能产生的数据竞争和不一致性问题。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;原子操作在多线程编程中非常有用，用于保证变量操作的安全性。例如，在多线程环境中，递增一个计数器通常需要通过原子操作来确保其线程安全。&lt;/p&gt;
&lt;p&gt;实现原子操作的方式包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;锁机制&lt;/strong&gt;：通过锁保护临界区代码来保证操作的原子性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特定CPU指令&lt;/strong&gt;：如x86架构的CMPXCHG指令，可以在硬件层面实现原子性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存屏障&lt;/strong&gt;：如在ARM架构中通过内存屏障指令实现原子操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;原子操作和锁的区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.原子操作由底层硬件支持，而锁是基于原子操作+信号量完成的。若实现相同的功能，前者通常会更有效率&lt;/li&gt;
&lt;li&gt;2.原子操作是单个指令的互斥操作；互斥锁/读写锁是一种数据结构，可以完成临界区（多个指令）的互斥操作，扩大原子操作的范围&lt;/li&gt;
&lt;li&gt;3.原子操作是无锁操作，属于乐观锁；说起锁的时候，一般属于悲观锁&lt;/li&gt;
&lt;li&gt;4.原子操作存在于各个指令/语言层级，比如*机器指令层级的原子操作&quot;，““汇编指令层级的原子操作”，“Go语言层级的原子操作”等。&lt;/li&gt;
&lt;li&gt;5.锁也存在于各个指令/语言层级中，比如“机器指令层级的锁”，“汇编指令层级的锁“Go语言层级的锁“等*&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Goroutine的实现原理&lt;/h2&gt;
&lt;p&gt;Goroutine可以理解为一种Go语言的协程 &lt;strong&gt;（轻量级线程）&lt;/strong&gt;，是Go支持高并发的基础，属于&lt;strong&gt;用户态&lt;/strong&gt;的线程，由&lt;code&gt;Goruntime&lt;/code&gt;管理而不是操作系统。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type g struct {
  goid int64 //唯一的goroutine的ID
  sched gobuf //goroutine切换时，用于保存g的上下文
  stack stack //栈
  gopc //pc of go statement that created this goroutine
  startpc uintptr  //pc of goroutine function
  ...
}
type gobuf struct {
   sp uintptr //栈指针位置
   pc uintptr //运行到的程序位置
   g guintptr //指向goroutine
   ret uintptr //保存系统调用的返回值
   ...
}
type stack struct {
  lo uintptr //栈的下界内存地址
  hi uintptr //栈的上界内存地址
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.&lt;strong&gt;创建&lt;/strong&gt;：&lt;code&gt;go&lt;/code&gt;关键字会调用底层函数&lt;code&gt;runtime.newproc()&lt;/code&gt;创建一个&lt;code&gt;goroutine&lt;/code&gt;，调用该函数之后，&lt;code&gt;goroutine&lt;/code&gt;会被设置成&lt;code&gt;runnable&lt;/code&gt;状态&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建好的这个&lt;code&gt;goroutine&lt;/code&gt;会新建一个自己的栈空间，同时在&lt;code&gt;G&lt;/code&gt;的&lt;code&gt;sched&lt;/code&gt;中维护栈地址与程序计数器这些信息。&lt;/li&gt;
&lt;li&gt;每个&lt;code&gt;G&lt;/code&gt;在被创建之后，都会被优先放入到本地队列中，如果本地队列已经满了，就会被放入到全局队列中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.&lt;strong&gt;运行&lt;/strong&gt;：&lt;code&gt;goroutine&lt;/code&gt;本身只是一个数据结构，真正让&lt;code&gt;goroutine&lt;/code&gt;运行起来的是调度器。Go实现了一个用户态的调度器（GMP模型），这个调度器充分利用现代计算机的多核特性，同时让多个&lt;code&gt;goroutine&lt;/code&gt;运行，同时&lt;code&gt;goroutine&lt;/code&gt;设计的很轻量级，调度和上下文切换的代价都比较小。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.&lt;strong&gt;阻塞&lt;/strong&gt;：&lt;code&gt;channel&lt;/code&gt;的读写操作、等待锁、等待网络数据、系统调用等都有可能发生阻塞，会调用底层函数&lt;code&gt;runtime. gopark()&lt;/code&gt;，会让出CPU时间片，让调度器安排其它等待的任务运行，并在下次某个时候从该位置恢复执行。
当调用该函数之后，&lt;code&gt;goroutine&lt;/code&gt;会被设置成&lt;code&gt;waiting&lt;/code&gt;状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.&lt;strong&gt;唤醒&lt;/strong&gt;：处于&lt;code&gt;waiting&lt;/code&gt;状态的&lt;code&gt;goroutine&lt;/code&gt;，在调用&lt;code&gt;runtime.goready()&lt;/code&gt;函数之后会被唤醒，唤醒的&lt;code&gt;goroutine&lt;/code&gt;会被重新放到&lt;strong&gt;M&lt;/strong&gt;对应的上下文&lt;strong&gt;P&lt;/strong&gt;对应的&lt;code&gt;runqueue&lt;/code&gt;中，等待被调度。
当调用该函数之后，&lt;code&gt;goroutine&lt;/code&gt;会被设置成&lt;code&gt;runnable&lt;/code&gt;状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.&lt;strong&gt;退出&lt;/strong&gt;：当&lt;code&gt;goroutine&lt;/code&gt;执行完成后，会调用底层函数&lt;code&gt;runtime.Goexit()&lt;/code&gt;，当调用该函数之后，&lt;code&gt;goroutine&lt;/code&gt;会被设置成 dead 状态&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Goroutine的泄露&lt;/h3&gt;
&lt;p&gt;泄露原因:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Goroutine&lt;/code&gt;内进行&lt;code&gt;channel&lt;/code&gt;/&lt;code&gt;mutex&lt;/code&gt;等读写操作被一直阻塞。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Goroutine&lt;/code&gt;内的业务逻辑进入死循环，资源一直无法释放。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Goroutine&lt;/code&gt;内的业务逻辑进入长时间等待，有不断新增的&lt;code&gt;Goroutine&lt;/code&gt;进入等待&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;怎么查看Goroutine的数量？怎么限制Goroutine的数量？&lt;/h3&gt;
&lt;p&gt;在开发过程中，如果不对&lt;code&gt;goroutine&lt;/code&gt;加以控制而进行滥用的话，可能会导致服务整体崩溃。比如耗尽系统资源导致程序崩溃,或者CPU使用率过高导致系统忙不过来。&lt;/p&gt;
&lt;p&gt;1.在&lt;code&gt;Golang&lt;/code&gt;中,&lt;code&gt;GOMAXPROCS&lt;/code&gt;中控制的是未被阻塞的所有&lt;code&gt;Goroutine&lt;/code&gt;,可以被 &lt;code&gt;Multiplex&lt;/code&gt; 到多少个线程上运行,通过&lt;code&gt;GOMAXPROCS&lt;/code&gt;可以查看&lt;code&gt;Goroutine&lt;/code&gt;的数量。
2.使用&lt;strong&gt;通道&lt;/strong&gt;。每次执行的&lt;code&gt;go&lt;/code&gt;之前向通道写入值，直到通道满的时候就阻塞了&lt;/p&gt;
&lt;h3&gt;Goroutine和线程的区别？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.一个线程可以有多个协程&lt;/li&gt;
&lt;li&gt;2.线程、进程都是同步机制，而协程是异步&lt;/li&gt;
&lt;li&gt;3.协程可以保留上一次调用时的状态，当过程重入时，相当于进入了上一次的调用状态&lt;/li&gt;
&lt;li&gt;4.协程是需要线程来承载运行的，所以协程并不能取代线程，「线程是被分割的CPU资源，协程是组织好的代码流程」&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Go中两个Nil可能不相等吗？&lt;/h2&gt;
&lt;p&gt;Go中两个Nil可能不相等。&lt;/p&gt;
&lt;p&gt;接口(interface) 是对非接口值(例如指针，struct等)的封装，内部实现包含 2 个字段，类型 T 和 值 V。一个接口等于 nil，当且仅当 T 和 V 处于 unset 状态（T=nil，V is unset）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两个接口值&lt;/strong&gt;比较时，会先比较 T，再比较 V。接口值与非接口值比较时，会先将非接口值尝试转换为接口值，再比较&lt;/p&gt;
&lt;h2&gt;go 打印时 %v %+v %#v 的区别？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%v&lt;/code&gt; 只输出所有的值；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%+v&lt;/code&gt; 先输出字段名字，再输出该字段的值；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%#v&lt;/code&gt; 先输出结构体名字值，再输出结构体（字段名字+字段的值）；&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;package main
import &quot;fmt&quot;

type student struct {
  id   int32
  name string
}

func main() {
  a := &amp;amp;student{id: 1, name: &quot;Hycer&quot;}
  
  fmt.Printf(&quot;a=%v \n&quot;, a) // a=&amp;amp;{1 Hycer}
  fmt.Printf(&quot;a=%+v \n&quot;, a) // a=&amp;amp;{id:1 name:Hycer}
  fmt.Printf(&quot;a=%#v \n&quot;, a) // a=&amp;amp;main.student{id:1, name:&quot;Hycer&quot;}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;什么是 rune 类型？&lt;/h2&gt;
&lt;p&gt;Go语言的字符有以下两种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.&lt;code&gt;uint8&lt;/code&gt; 类型，或者叫 &lt;code&gt;byte&lt;/code&gt; 型，代表了 &lt;code&gt;ASCII&lt;/code&gt; 码的一个字符。&lt;/li&gt;
&lt;li&gt;2.&lt;code&gt;rune&lt;/code&gt; 类型，代表一个 &lt;code&gt;UTF-8&lt;/code&gt; 字符，当需要处理中文、日文或者其他复合字符时，则需要用到 &lt;code&gt;rune&lt;/code&gt; 类型。&lt;strong&gt;&lt;code&gt;rune&lt;/code&gt; 类型等价于 &lt;code&gt;int32&lt;/code&gt; 类型&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;golang值接收者和指针接收者的区别&lt;/h2&gt;
&lt;p&gt;golang函数与方法的区别是，方法有一个接收者。&lt;/p&gt;
&lt;p&gt;如果方法的接收者是&lt;strong&gt;指针类型&lt;/strong&gt;，无论调用者是对象还是对象指针，修改的都是对象本身，会影响调用者&lt;/p&gt;
&lt;p&gt;如果方法的接收者是&lt;strong&gt;值类型&lt;/strong&gt;，无论调用者是对象还是对象指针，修改的都是对象的副本，不影响调用者&lt;/p&gt;
&lt;h2&gt;recover&lt;/h2&gt;
&lt;p&gt;在Go语言中，&lt;code&gt;recover&lt;/code&gt; 是一个内建函数，用于从panic中恢复并返回一个错误值。其主要作用和特点如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;恢复panic状态&lt;/strong&gt;：&lt;code&gt;recover&lt;/code&gt; 可以捕获并恢复由 &lt;code&gt;panic&lt;/code&gt; 引起的程序异常状态，防止程序崩溃退出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只能在defer函数中使用&lt;/strong&gt;：&lt;code&gt;recover&lt;/code&gt; 只能在延迟函数（由 &lt;code&gt;defer&lt;/code&gt; 修饰的函数）中调用，否则编译将不通过。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;返回错误值&lt;/strong&gt;：如果程序发生panic，&lt;code&gt;recover&lt;/code&gt; 可以从panic中恢复，并返回panic值；如果没有发生panic或者&lt;code&gt;recover&lt;/code&gt; 不在defer函数中调用，它会返回 &lt;code&gt;nil&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阻止程序终止&lt;/strong&gt;：通过适当使用 &lt;code&gt;recover&lt;/code&gt;，可以阻止panic导致的程序终止，使程序能够继续运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源清理和善后处理&lt;/strong&gt;：使用 &lt;code&gt;recover&lt;/code&gt; 可以在程序崩溃前做一些资源清理和善后处理工作，例如关闭数据库连接、释放资源等。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总之，&lt;code&gt;recover&lt;/code&gt; 提供了一种机制在程序发生panic时进行优雅地恢复，避免程序崩溃退出，对于处理运行时错误和异常情况非常重要。正确使用 &lt;code&gt;defer&lt;/code&gt;、&lt;code&gt;panic&lt;/code&gt; 和 &lt;code&gt;recover&lt;/code&gt; 可以让程序更加健壮和稳定。&lt;/p&gt;
&lt;h2&gt;select&lt;/h2&gt;
&lt;p&gt;Go语言中的 &lt;code&gt;select&lt;/code&gt; 语句是一种控制结构，它用于处理多个&lt;code&gt;channel&lt;/code&gt;（通道）操作。&lt;code&gt;select&lt;/code&gt; 语句允许在多个发送或接收操作上等待，并且当其中一个操作准备就绪时执行对应的代码块，类似于一个多路复用的开关。&lt;/p&gt;
&lt;p&gt;以下是 &lt;code&gt;select&lt;/code&gt; 语句的基本用法和特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;多路复用机制&lt;/strong&gt;：&lt;code&gt;select&lt;/code&gt; 允许通过一个协程同时处理多个IO请求（Channel 读写事件）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：只能用于channel操作，即只能包含发送或接收channel的case。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基本语法&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select {
case &amp;lt;-channel1:
    // 当channel1可以接收数据时执行这里的代码
case channel2 &amp;lt;- x:
    // 当channel2可以发送数据时执行这里的代码
default:
    // 如果没有channel操作可以执行，则执行default分支的代码（如果没有default分支，则select会阻塞）
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;非阻塞行为&lt;/strong&gt;：如果没有channel操作可以立即执行，&lt;code&gt;select&lt;/code&gt;将阻塞，直到有操作可以执行，或者有超时（如果用在for循环中）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;随机性&lt;/strong&gt;：如果有多个case都准备就绪，&lt;code&gt;select&lt;/code&gt;会随机选择一个case来执行，而不是顺序执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;效率&lt;/strong&gt;：相比于简单地使用for循环遍历通道，使用 &lt;code&gt;select&lt;/code&gt; 语句可以更加高效地管理多个通道。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;控制并发&lt;/strong&gt;：&lt;code&gt;select&lt;/code&gt; 语句是实现高效并发控制的一种机制，常用于Go协程间的消息传递和同步。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;String和[]byte的区别&lt;/h2&gt;
&lt;p&gt;string类型本质也是一个结构体，定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type stringStruct struct {
  str unsafe.Pointer
  len int
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;string&lt;/code&gt;类型底层是一个&lt;code&gt;byte&lt;/code&gt;类型的数组，&lt;code&gt;stringStruct&lt;/code&gt;和&lt;code&gt;slice&lt;/code&gt;还是很相似的，&lt;code&gt;str&lt;/code&gt;指针指向的是&lt;code&gt;byte&lt;/code&gt;数组的首地址，&lt;code&gt;len&lt;/code&gt;代表的就是数组长度。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;string&lt;/code&gt;和&lt;code&gt;byte&lt;/code&gt;的区别：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;string&lt;/code&gt;类型为什么还要在数组的基础上再进行一次封装呢？
这是因为在Go语言中&lt;code&gt;string&lt;/code&gt;类型被设计为不可变的，不仅是在Go语言，其他语言中&lt;code&gt;string&lt;/code&gt;类型也是被设计为不可变的，这样的好处就是：在并发场景下，我们可以在不加锁的控制下，多次使用同一字符串，在保证高效共享的情况下而不用担心安全问题。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;string&lt;/code&gt;类型虽然是不能更改的，但是可以被替换，因为&lt;code&gt;stringStruct&lt;/code&gt;中的&lt;code&gt;str&lt;/code&gt;指针是可以改变的，只是指针指向的内容是不可以改变的。&lt;/p&gt;
&lt;h2&gt;HTTP和RPC对比&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;RPC（Remote Produce Call）：远程过程调用，&lt;/li&gt;
&lt;li&gt;HTTP：网络传输协议&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;相同点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.都是基于TCP协议的应用层协议&lt;/li&gt;
&lt;li&gt;2.都可以实现远程调用，服务调用服务&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;不同点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.RPC主要用于在不同的进程或计算机之间进行函数调用和数据交换。HTTP主要用于数据传输和通信。&lt;/li&gt;
&lt;li&gt;2.RPC协议通常采用二进制协议和高效的序列化方式，而HTTP通常采用文本协议和基于ASCII码的编码方式，数据传输效率较低&lt;/li&gt;
&lt;li&gt;3.RPC通常需要使用专门的IDL文件来定义服务和消息类型，生成服务端和客户端的代码。而HTTP没有这个限制，可以使用套接字进行通信&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;golang中指针的作用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.传递大对象&lt;/li&gt;
&lt;li&gt;2.修改函数外部变量&lt;/li&gt;
&lt;li&gt;3.动态分配内存&lt;/li&gt;
&lt;li&gt;4.函数返回指针&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>zsh+oh-my-zsh+powerlevel10的linuxshell美化</title><link>https://fuwari.vercel.app/posts/zshoh-my-zshpowerlevel10%E7%9A%84linuxshell%E7%BE%8E%E5%8C%96/zshoh-my-zshpowerlevel10%E7%9A%84linux-shell%E7%BE%8E%E5%8C%96/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/zshoh-my-zshpowerlevel10%E7%9A%84linuxshell%E7%BE%8E%E5%8C%96/zshoh-my-zshpowerlevel10%E7%9A%84linux-shell%E7%BE%8E%E5%8C%96/</guid><description>使用zsh+oh-my-zsh+powerlevel10获得一个好看实用的linux终端</description><pubDate>Sun, 17 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;zsh+oh-my-zsh+powerlevel10的linux shell美化&lt;/h1&gt;
&lt;h2&gt;zsh&lt;/h2&gt;
&lt;h3&gt;安装zsh&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;将zsh设置为默认shell&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;chsh -s /bin/zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改完成后需要退出当前用户登录(注销),随后再次打开终端即可进入zsh&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;img.png&quot; alt=&quot;zsh初始界面&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;oh-my-zsh&lt;/h2&gt;
&lt;h3&gt;安装oh-my-zsh&lt;/h3&gt;
&lt;p&gt;使用搜索引擎进入oh-my-zsh的&lt;a href=&quot;https://ohmyz.sh&quot;&gt;官网&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;根据官网的命令安装oh-my-zsh&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sh -c &quot;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无法访问github的可以使用gitee的镜像安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sh -c &quot;$(curl -fsSL https://gitee.com/mirrors/oh-my-zsh/raw/master/tools/install.sh \
    | sed &apos;s|^REPO=.*|REPO=${REPO:-mirrors/oh-my-zsh}|g&apos; \
    | sed &apos;s|^REMOTE=.*|REMOTE=${REMOTE:-https://gitee.com/${REPO}.git}|g&apos;)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;img_1.png&quot; alt=&quot;安装成功&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;安装插件&lt;/h3&gt;
&lt;p&gt;oh-my-zsh提供了很多实用的插件,通过&lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh/wiki/Plugins&quot;&gt;wiki&lt;/a&gt;查找需要的插件&lt;/p&gt;
&lt;p&gt;推荐几个实用的插件:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zsh-autosuggestions&lt;/code&gt; 自动建议补全&lt;pre&gt;&lt;code&gt;git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
&lt;/code&gt;&lt;/pre&gt;
使用方向键右键&amp;gt;可以自动补全命令&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zsh-syntax-highlighting&lt;/code&gt; 命令语法高亮&lt;pre&gt;&lt;code&gt;git clone https://github.com/zsh-users/zsh-syntax-highlighting ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker&lt;/code&gt; oh-my-zsh自带的插件,可以支持docker命令语法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker-compose&lt;/code&gt; oh-my-zsh自带的插件,可以支持docker-compose命令语法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ufw&lt;/code&gt; oh-my-zsh自带的插件,可以支持ufw命令语法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo&lt;/code&gt; oh-my-zsh自带的插件,双击&lt;code&gt;esc&lt;/code&gt;给当前命令加上&lt;code&gt;sudo&lt;/code&gt;前缀&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;加载插件&lt;/h3&gt;
&lt;p&gt;编辑zsh的配置文件加载插件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;img_2.png&quot; alt=&quot;img_2.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;将需要加载的插件填入&lt;code&gt;plugins&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;plugins=(
         git
         docker
         docker-compose
         sudo
         ufw
         zsh-autosuggestions
         zsh-syntax-highlighting                                             
 )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存,并使用&lt;code&gt;source ~/.zshrc&lt;/code&gt;重新加载配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;powerlevel10k主题安装&lt;/h2&gt;
&lt;h3&gt;安装主题&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;加载主题&lt;/h3&gt;
&lt;p&gt;编辑zsh的配置文件加载主题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;img_3.png&quot; alt=&quot;img_3.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;修改为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ZSH_THEME=&quot;powerlevel10k/powerlevel10k&quot;  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存,并使用&lt;code&gt;source ~/.zshrc&lt;/code&gt;重新加载配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后会进入pl10k主题的配置页面,跟随引导设置主题即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;img_4.png&quot; alt=&quot;主题配置完成&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果配置不满意可以使用&lt;code&gt;p10k configure&lt;/code&gt;重新进入配置引导&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;另外因为更换了shell所以原来位于&lt;code&gt;.bashrc&lt;/code&gt;的环境变量需要转移到&lt;code&gt;.zshrc中&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>详解TCP的三握四挥</title><link>https://fuwari.vercel.app/posts/%E8%AF%A6%E8%A7%A3tcp%E7%9A%84%E4%B8%89%E6%8F%A1%E5%9B%9B%E6%8C%A5/%E8%AF%A6%E8%A7%A3tcp%E7%9A%84%E4%B8%89%E6%8F%A1%E5%9B%9B%E6%8C%A5/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E8%AF%A6%E8%A7%A3tcp%E7%9A%84%E4%B8%89%E6%8F%A1%E5%9B%9B%E6%8C%A5/%E8%AF%A6%E8%A7%A3tcp%E7%9A%84%E4%B8%89%E6%8F%A1%E5%9B%9B%E6%8C%A5/</guid><description>详细解释TCP的三次握手和四次挥手</description><pubDate>Tue, 25 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;详解TCP的三握四挥&lt;/h1&gt;
&lt;h2&gt;一.TCP报文段简介&lt;/h2&gt;
&lt;p&gt;TCP报文结构如图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-ttcp.webp&quot; alt=&quot;image-ttcp&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其中TCP的头部包含一下内容:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;端口号(Source Port and Destination Port): 包含源端口号和目的端口号,标识发送端和接收段的应用进程.配合IP报头部的源IP地址和目的IP地址能够确定唯一一个TCP连接.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;序号&lt;/strong&gt;(Sequence Number): 通常简写为&lt;code&gt;seq&lt;/code&gt;.这个字段的主要作用是用于将失序的数据重新排列.TCP 会隐式地对字节流中的每个字节进行编号，而 TCP 报文段的序号被设置为其数据部分的第一个字节的编号.序号是 32 bit 的无符号数，取值范围是0到 232 - 1.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;确认序号&lt;/strong&gt;(Acknowledgment Number): 通常简写为&lt;code&gt;ack&lt;/code&gt;.接收方在接收到数据后,会回复确认报文,其中就包含有确认序号,作用是为了告诉发送方自己接收到了哪些数据,下一次的数据从哪里开始发送,因此,ack序号应当是上次已成功收到的数据字节序号加1.只用ACK标志为1时确认序号字段才有效.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;首部长度 (Header Length)：首部中的选项部分的长度是可变的，因此首部的长度也是可变的，所以需要这个字段来明确表示首部的长度，这个字段占 4 bit，4 位的二进制数最大可以表示 15，而首部长度是以 4 个字节为一个单位的，因此首部最大长度是 15 * 4 = 60 字节。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;保留字段 (Reserved)：占 6 位，未来可能有具体用途，目前默认值为0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;控制位 (Control Bits)：在三次握手和四次挥手中会经常看到 SYN、ACK 和 FIN 的身影，一共有 6 个标志位，它们表示的意义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URG (Urgent Bit)：值为 1 时，紧急指针生效&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACK&lt;/strong&gt; (Acknowledgment Bit)：值为 1 时，确认序号生效&lt;/li&gt;
&lt;li&gt;PSH (Push Bit)：接收方应尽快将这个报文段交给应用层&lt;/li&gt;
&lt;li&gt;RST (Reset Bit)：发送端遇到问题，想要重建连接&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SYN&lt;/strong&gt; (Synchronize Bit)：同步序号，用于发起一个连接&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIN&lt;/strong&gt; (Finish Bit)：发送端要求关闭连接&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;窗口大小 (Window)： TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数，起始于确认序号字段指明的值，这个值是接收端正期望接收的字节。窗口大小是一个 16 bit 字段，单位是字节， 因而窗口大小最大为 65535 字节。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;检验和 (Checksum)：功能类似于数字签名，用于验证数据完整性，也就是确保数据未被修改。检验和覆盖了整个 TCP 报文段，包括 TCP 首部和 TCP 数据，发送端根据特定算法对整个报文段计算出一个检验和，接收端会进行计算并验证。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;紧急指针 (Urgent Pointer)：当 URG 控制位值为 1 时，此字段生效，紧急指针是一个正的偏移量，和序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;选项 (Options)：这一部分是可选字段，也就是非必须字段，最常见的可选字段是“最长报文大小 (MSS，Maximum Segment Size)”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;有效数据部分 (Data)：这部分也不是必须的，比如在建立和关闭 TCP 连接的阶段，双方交换的报文段就只包含 TCP 首部。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二.TCP的连接控制&lt;/h2&gt;
&lt;h3&gt;2.1建立连接&lt;/h3&gt;
&lt;h4&gt;2.1.1三次握手&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;image-20240810160954877.png&quot; alt=&quot;image-20240810160954877&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;第一次握手：客户端向服务端发送报文段1，其中的 SYN 标志位 (前文已经介绍过各种标志位的作用)的值为 1，表示这是一个用于请求发起连接的报文段，其中的序号字段 (Sequence Number，图中简写为seq)被设置为初始序号x (Initial Sequence Number，ISN)，TCP 连接双方均可随机选择初始序号。发送完报文段1之后，客户端进入 SYN-SENT 状态，等待服务端的确认。&lt;/li&gt;
&lt;li&gt;第二次握手：服务端在收到客户端的连接请求后，向客户端发送报文段2作为应答，其中 ACK 标志位设置为 1，表示对客户端做出应答，其确认序号字段 (Acknowledgment Number，图中简写为小写 ack) 生效，该字段值为 x + 1，也就是从客户端收到的报文段的序号加一，&lt;strong&gt;代表服务端期望下次收到客户端的数据的序号&lt;/strong&gt;。此外，报文段2的 SYN 标志位也设置为1，代表这同时也是一个用于发起连接的报文段，序号 seq 设置为服务端初始序号y。发送完报文段2后，服务端进入 SYN-RECEIVED 状态。&lt;/li&gt;
&lt;li&gt;第三次握手：客户端在收到报文段2后，向服务端发送报文段3，其 ACK 标志位为1，代表对服务端做出应答，确认序号字段 ack 为 y + 1，序号字段 seq 为 x + 1(对应报文段2的ack)。此报文段发送完毕后，双方都进入 ESTABLISHED 状态，表示连接已建立。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h4&gt;2.1.2常见面试题&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;常见面试题 1：&lt;/strong&gt; TCP 建立连接为什么要三次握手而不是两次？&lt;/p&gt;
&lt;p&gt;答：网上大多数资料对这个问题的回答只有简单的一句：防止已过期的连接请求报文突然又传送到服务端，因而产生错误，这既不够全面也不够具体。下面给出比较详细而全面的回答：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;防止已过期的连接请求报文突然又传送到服务端，因而产生错误&lt;/p&gt;
&lt;p&gt;在双方两次握手即可建立连接的情况下，假设客户端发送 A 报文段请求建立连接，由于网络原因造成 A 暂时无法到达服务端，服务端接收不到请求报文段就不会返回确认报文段，客户端在长时间得不到应答的情况下重新发送请求报文段 B，这次 B 顺利到达服务端，服务端随即返回确认报文并进入 ESTABLISHED 状态，客户端在收到 确认报文后也进入 ESTABLISHED 状态，双方建立连接并传输数据，之后正常断开连接。此时姗姗来迟的 A 报文段才到达服务端，服务端随即返回确认报文并进入 ESTABLISHED 状态，但是已经进入 CLOSED 状态的客户端无法再接受确认报文段，更无法进入 ESTABLISHED 状态，这将导致服务端长时间单方面等待，造成资源浪费。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;三次握手才能让双方均确认自己和对方的发送和接收能力都正常&lt;/p&gt;
&lt;p&gt;第一次握手：客户端只是发送处请求报文段，什么都无法确认，而服务端可以确认自己的接收能力和对方的发送能力正常；&lt;/p&gt;
&lt;p&gt;第二次握手：客户端可以确认自己发送能力和接收能力正常，对方发送能力和接收能力正常；&lt;/p&gt;
&lt;p&gt;第三次握手：服务端可以确认自己发送能力和接收能力正常，对方发送能力和接收能力正常；&lt;/p&gt;
&lt;p&gt;可见三次握手才能让双方都确认自己和对方的发送和接收能力全部正常，这样就可以愉快地进行通信了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;告知对方自己的初始序号值，并确认收到对方的初始序号值&lt;/p&gt;
&lt;p&gt;TCP 实现了可靠的数据传输，原因之一就是 TCP 报文段中维护了序号字段和确认序号字段，也就是图中的 seq 和 ack，通过这两个字段双方都可以知道在自己发出的数据中，哪些是已经被对方确认接收的。这两个字段的值会在初始序号值得基础递增，如果是两次握手，只有发起方的初始序号可以得到确认，而另一方的初始序号则得不到确认。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;常见面试题2：&lt;/strong&gt; TCP 建立连接为什么要三次握手而不是四次？&lt;/p&gt;
&lt;p&gt;答：相比上个问题而言，这个问题就简单多了。因为三次握手已经可以确认双方的发送接收能力正常，双方都知道彼此已经准备好，而且也可以完成对双方初始序号值得确认，也就无需再第四次握手了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常见面试题3：&lt;/strong&gt; 有一种网络攻击是利用了 TCP 建立连接机制的漏洞，你了解吗？这个问题怎么解决？&lt;/p&gt;
&lt;p&gt;答：在三次握手过程中，服务端在收到了客户端的 SYN 报文段后，会分配并初始化连接变量和缓存，并向客户端发送 SYN + ACK 报文段，这相当于是打开了一个“半开连接 (half-open connection)”，会消耗服务端资源。如果客户端正常返回了 ACK 报文段，那么双方可以正常建立连接，否则，服务端在等待一分钟后会终止这个“半开连接”并回收资源。这样的机制为 SYN洪泛攻击 (SYN flood attack)提供了机会，这是一种经典的 DoS攻击 (Denial of Service，拒绝服务攻击)，所谓的拒绝服务攻击就是通过进行攻击，使受害主机或网络不能提供良好的服务，从而间接达到攻击的目的。在 SYN 洪泛攻击中，攻击者发送大量的 SYN 报文段到服务端请求建立连接，但是却不进行第三次握手，这会导致服务端打开大量的半开连接，消耗大量的资源，最终无法进行正常的服务。&lt;/p&gt;
&lt;p&gt;解决方法：SYN Cookies，现在大多数主流操作系统都有这种防御系统。SYN Cookies 是对 TCP 服务端端的三次握手做一些修改，专门用来防范 SYN 洪泛攻击的一种手段。它的原理是，在服务端接收到 SYN 报文段并返回 SYN + ACK 报文段时，不再打开一个半开连接，也不分配资源，而是根据这个 SYN 报文段的重要信息 (包括源和目的 IP 地址，端口号可一个秘密数)，利用特定散列函数计算出一个 cookie 值。这个 cookie 作为将要返回的SYN + ACK 报文段的初始序列号(ISN)。当客户端返回一个 ACK 报文段时，服务端根据首部字段信息计算 cookie，与返回的确认序号(初始序列号 + 1)进行对比，如果相同，则是一个正常连接，然后分配资源并建立连接，否则拒绝建立连接。&lt;/p&gt;
&lt;h3&gt;2.2关闭连接&lt;/h3&gt;
&lt;h4&gt;2.2.1四次挥手&lt;/h4&gt;
&lt;p&gt;建立一个连接需要三次握手，而终止一个连接要经过 4次握手。这由 TCP 的半关闭( half-close) 造成的。既然一个 TCP 连接是&lt;strong&gt;全双工&lt;/strong&gt; (即数据在两个方向上能同时传递)， 因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向连接。当一端收到一个 FIN，它必须通知应用层另一端已经终止了数据传送。理论上客户端和服务端都可以发起主动关闭，但是更多的情况下是客户端主动发起。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20240810164451480.png&quot; alt=&quot;image-20240810164451480&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端发送关闭连接的报文段，FIN 标志位1，请求关闭连接，并停止发送数据。序号字段 seq = x &lt;em&gt;(等于之前发送的所有数据的最后一个字节的序号加一)&lt;/em&gt;，然后客户端会进入 FIN-WAIT-1 状态，等待来自服务端的确认报文。&lt;/li&gt;
&lt;li&gt;服务端收到 FIN 报文后，发回确认报文，ACK = 1， ack = x + 1，并带上自己的序号 seq = y，然后服务端就进入 CLOSE-WAIT 状态。服务端还会通知上层的应用程序对方已经释放连接，此时 TCP 处于半关闭状态，也就是说客户端已经没有数据要发送了，但是服务端还可以发送数据，客户端也还能够接收。&lt;/li&gt;
&lt;li&gt;客户端收到服务端的 ACK 报文段后随即进入 FIN-WAIT-2 状态，此时还能收到来自服务端的数据，直到收到 FIN 报文段。&lt;/li&gt;
&lt;li&gt;服务端发送完所有数据后，会向客户端发送 FIN 报文段，各字段值如图所示，随后服务端进入 LAST-ACK 状态，等待来自客户端的确认报文段。&lt;/li&gt;
&lt;li&gt;客户端收到来自服务端的 FIN 报文段后，向服务端发送 ACK 报文，随后进入 TIME-WAIT 状态，等待 &lt;strong&gt;2MSL&lt;/strong&gt;(2 * Maximum Segment Lifetime，两倍的报文段最大存活时间) ，这是任何报文段在被丢弃前能在网络中存在的最长时间，常用值有30秒、1分钟和2分钟。如无特殊情况，客户端会进入 CLOSED 状态。&lt;/li&gt;
&lt;li&gt;服务端在接收到客户端的 ACK 报文后会随即进入 CLOSED 状态，由于没有等待时间，一般而言，服务端比客户端更早进入 CLOSED 状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h4&gt;2.2.2常见面试题&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;常见面试题1：&lt;/strong&gt; 为什么 TCP 关闭连接要四次而不是三次？&lt;/p&gt;
&lt;p&gt;答：服务器在收到客户端的 FIN 报文段后，可能还有一些数据要传输，所以不能马上关闭连接，但是会做出应答，返回 ACK 报文段，接下来可能会继续发送数据，在数据发送完后，服务器会向客户单发送 FIN 报文，表示数据已经发送完毕，请求关闭连接，然后客户端再做出应答，因此一共需要四次挥手。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常见面试题2：&lt;/strong&gt; 客户端为什么需要在 TIME-WAIT 状态等待 2MSL 时间才能进入 CLOSED 状态？&lt;/p&gt;
&lt;p&gt;答：按照常理，在网络正常的情况下，四个报文段发送完后，双方就可以关闭连接进入 CLOSED 状态了，但是网络并不总是可靠的，如果客户端发送的 ACK 报文段丢失，服务器在接收不到 ACK 的情况下会一直重发 FIN 报文段，这显然不是我们想要的。因此客户端为了确保服务器收到了 ACK，会设置一个定时器，并在 TIME-WAIT 状态等待 2MSL 的时间，如果在此期间又收到了来自服务器的 FIN 报文段，那么客户端会重新设置计时器并再次等待 2MSL 的时间，如果在这段时间内没有收到来自服务器的 FIN 报文，那就说明服务器已经成功收到了 ACK 报文，此时客户端就可以进入 CLOSED 状态了。&lt;/p&gt;
</content:encoded></item><item><title>数据结构-哈希表</title><link>https://fuwari.vercel.app/posts/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E5%93%88%E5%B8%8C%E8%A1%A8/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E5%93%88%E5%B8%8C%E8%A1%A8/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E5%93%88%E5%B8%8C%E8%A1%A8/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E5%93%88%E5%B8%8C%E8%A1%A8/</guid><description>基于go语言的哈希表讲解</description><pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;数据结构-哈希表&lt;/h1&gt;
&lt;h2&gt;哈希表的定义&lt;/h2&gt;
&lt;p&gt;哈希表（Hash table，也叫哈希映射），是根据键（Key）而直接访问在内存存储位置的数据结构。也就是说，它通过计算一个关于键值的函数，将所需查询的数据映射到表中一个位置来访问记录，这加快了查找速度。这个映射函数称做哈希函数，存放记录的数组称做哈希表。&lt;/p&gt;
&lt;h2&gt;哈希表的数组实现&lt;/h2&gt;
&lt;p&gt;我们先仅用一个数组来模拟哈希表.在哈希表中,我们将数组中的每个元素位置称为&lt;strong&gt;桶(bucket&lt;/strong&gt;),每个桶可以存放一个&lt;strong&gt;键值对&lt;/strong&gt;.查询就是找到key对应的桶,然后返回对应的value.&lt;/p&gt;
&lt;p&gt;如何基于key定位桶呢?这就是 &lt;strong&gt;哈希函数(hash function)&lt;/strong&gt; 要做的事.哈希函数将key转换为数组下标,然后将key-value对存放在对应的桶中.换句话说，输入一个 key ，我们可以通过哈希函数得到该 key 对应的键值对在数组中的存储位置。&lt;/p&gt;
&lt;p&gt;通常,输入一个key,哈希函数的计算过程如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.通过某种哈希算法 &lt;code&gt;hash()&lt;/code&gt; (常见的哈希算法有:&lt;code&gt;MD5,SHA-1,SHA-2,SHA-3&lt;/code&gt;)计算得出哈希值.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.将哈希值对桶的数量(数组的长度) &lt;code&gt;capacity&lt;/code&gt; 取模,得到哈希值对应的数组下标 &lt;code&gt;index&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;即:
&lt;code&gt;index = hash(key) % capacity&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;假如数组的长度 &lt;code&gt;capacity&lt;/code&gt; 为100, 哈希算法&lt;code&gt;hash(key) = key&lt;/code&gt;, 那么它的哈希函数就为 &lt;code&gt;index = key % 100&lt;/code&gt;,下图展示了哈希函数的工作原理&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-iidf.png&quot; alt=&quot;hash_function.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;哈希表的结构实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// pair 将键值对封装为pair结构体
type pair struct {
	key   int
	value any
}

// ArrayHashMap 使用数组实现哈希表
type ArrayHashMap struct {
	buckets []*pair
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;哈希表的初始化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// NewArrayHashMap 初始化哈希表
func NewArrayHashMap() *ArrayHashMap {
	// 初始化桶, 默认100个桶
	buckets := make([]*pair, 100)
	return &amp;amp;ArrayHashMap{buckets: buckets}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;哈希函数实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// hashFunc 实现哈希函数
func (h *ArrayHashMap) hashFunc(key int) int {
	return key % 100
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;哈希表的常规操作&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// put 向哈希表中插入键值对
func (h *ArrayHashMap) put(key int, value any) {
	// 1. 计算哈希值 得出桶的索引
	index := h.hashFunc(key)
	// 2. 将键值对插入到索引对应的桶中
	h.buckets[index] = &amp;amp;pair{key: key, value: value}
}

// get 从哈希表中获取键对应的值
func (h *ArrayHashMap) get(key int) any {
	// 1. 计算哈希值 得出桶的索引
	index := h.hashFunc(key)
	// 2. 从索引对应的桶中获取值
	// 如果桶为空，则返回nil
	if h.buckets[index] != nil {
		return h.buckets[index].value
	}
	return nil
}

// remove 从哈希表中删除键值对
func (h *ArrayHashMap) remove(key int) {
	// 1. 计算哈希值 得出桶的索引
	index := h.hashFunc(key)
	// 2. 将索引对应的桶置为nil
	h.buckets[index] = nil
}

// keys 获取所有的键
func (h *ArrayHashMap) keys() []int {
	var keys []int
	// 遍历所有的桶
	for _, bucket := range h.buckets {
		// 如果桶不为空，则将键添加到keys中
		if bucket != nil {
			keys = append(keys, bucket.key)
		}
	}
	return keys
}

// values 获取所有的值
func (h *ArrayHashMap) values() []any {
	var values []any
	// 遍历所有的桶
	for _, bucket := range h.buckets {
		// 如果桶不为空，则将值添加到values中
		if bucket != nil {
			values = append(values, bucket.value)
		}
	}
	return values
}

// pairs 获取所有的键值对
func (h *ArrayHashMap) pairs() []*pair {
	var pairs []*pair
	// 遍历所有的桶
	for _, bucket := range h.buckets {
		// 如果桶不为空，则将键值对添加到pairs中
		if bucket != nil {
			pairs = append(pairs, bucket)
		}
	}
	return pairs
}

// print 打印哈希表
func (h *ArrayHashMap) print() {
	for _, bucket := range h.buckets {
		if bucket != nil {
			fmt.Printf(&quot;key: %d, value: %v\n&quot;, bucket.key, bucket.value)
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;测试函数示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package hashmap

import &quot;testing&quot;

func TestHashMap(t *testing.T) {
	hashMap := NewArrayHashMap()
	hashMap.put(10, &quot;hello&quot;)
	hashMap.put(20, &quot;world&quot;)
	hashMap.put(30, &quot;golang&quot;)

	hashMap.print()

	t.Log(&quot;key-20: &quot;, hashMap.get(20))

	t.Log(&quot;keys: &quot;, hashMap.keys())
	t.Log(&quot;values: &quot;, hashMap.values())

	hashMap.remove(20)
	t.Log(&quot;after remove key-20: &quot;, hashMap.get(20))

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;测试结果示例&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;image-polj.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;哈希冲突与扩容&lt;/h2&gt;
&lt;h3&gt;哈希冲突&lt;/h3&gt;
&lt;p&gt;从本质上看，哈希函数的作用是将所有 key 构成的输入空间映射到数组所有索引构成的输出空间，而输入空间往往远大于输出空间。因此，&lt;strong&gt;理论上一定存在“多个输入对应相同输出”的情况&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在上述示例中,假如插入的key为&lt;code&gt;120&lt;/code&gt;时，经过哈希函数计算后，得到的索引为&lt;code&gt;20&lt;/code&gt;，而数组中索引为&lt;code&gt;20&lt;/code&gt;的位置已经被&lt;strong&gt;占用&lt;/strong&gt;了。此时，我们称&lt;code&gt;120&lt;/code&gt;和&lt;code&gt;20&lt;/code&gt;发生了&lt;strong&gt;哈希冲突（hash collision）&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;扩容&lt;/h3&gt;
&lt;p&gt;容易想到，哈希表容量越大，多个 key 被分配到同一个桶中的概率就越低，冲突就越少。因此，我们可以通过&lt;strong&gt;扩容&lt;/strong&gt;哈希表来减少哈希冲突。&lt;/p&gt;
&lt;p&gt;例如将上述哈希表扩容为原来的两倍,即&lt;code&gt;capacity=200&lt;/code&gt;, 哈希函数为&lt;code&gt;index = key % 200&lt;/code&gt;, 至此,上述的哈希冲突问题得到解决。&lt;/p&gt;
&lt;p&gt;类似于数组扩容，哈希表扩容需将所有键值对从原哈希表迁移至新哈希表，非常耗时；并且由于哈希表容量 capacity 改变，我们需要通过哈希函数来重新计算所有键值对的存储位置，这进一步增加了扩容过程的计算开销。为此，编程语言通常会预留足够大的哈希表容量，防止频繁扩容。&lt;/p&gt;
&lt;h3&gt;负载因子&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;负载因子（load factor）&lt;strong&gt;是哈希表的一个重要概念，其定义为哈希表的元素数量除以桶数量，用于衡量哈希冲突的严重程度，也常作为哈希表扩容的触发条件。例如在 Java 中，当负载因子超过
时，系统会将哈希表扩容至原先的&lt;/strong&gt;2&lt;/strong&gt;倍。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;上面我们提到,通常情况下哈希函数的输入空间远大于输出空间，因此理论上哈希冲突是不可避免的。使用扩容解决哈希冲突虽然简单粗暴有效,但同时也带来了性能和空间上的开销。&lt;/p&gt;
&lt;p&gt;为了提升效率，我们可以采用以下策略。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.改良哈希表数据结构，使得哈希表可以在出现哈希冲突时正常工作。&lt;/li&gt;
&lt;li&gt;2.仅在必要时，即当哈希冲突比较严重时，才执行扩容操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此哈希表的结构改良方法主要包括 &lt;strong&gt;“链式地址”&lt;/strong&gt; 和 &lt;strong&gt;“开放寻址”&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;哈希表的链式地址实现&lt;/h2&gt;
&lt;p&gt;在原始哈希表中，每个桶仅能存储一个键值对。链式地址（separate chaining）将单个元素转换为&lt;strong&gt;链表&lt;/strong&gt;，将键值对作为链表节点，将所有发生冲突的键值对都存储在同一链表中。下图展示了一个链式地址哈希表的例子。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-qwsa.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;基于链式地址实现的哈希表的操作方法发生了以下变化。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查询元素：输入 key ，经过哈希函数得到&lt;strong&gt;桶索引&lt;/strong&gt;，即可访问链表头节点，然后&lt;strong&gt;遍历&lt;/strong&gt;链表并对比 key 以查找目标键值对。&lt;/li&gt;
&lt;li&gt;添加元素：首先通过哈希函数访问链表头节点，然后将节点（键值对）添加到链表中。&lt;/li&gt;
&lt;li&gt;删除元素：根据哈希函数的结果访问链表头部，接着遍历链表以查找目标节点并将其删除。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;链式地址存在以下局限性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;占用空间增大：链表包含节点指针，它相比数组更加耗费内存空间。&lt;/li&gt;
&lt;li&gt;查询效率降低：因为需要线性遍历链表来查找对应元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;哈希表的结构实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// pair 键值对
type pair struct {
	key int
	value any
}

type ChainingHashMap struct {
	buckets       []*list.List // 桶
	size          int          // 当前哈希表中的元素个数
	capacity      int          // 哈希表容量
	loadThreshold float64      // 负载因子
	extendRatio   int          // 扩容因子(扩容时桶的个数变为原先的extendRatio倍)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;哈希表的初始化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// NewChainingHashMap 创建一个链式地址哈希表
func NewChainingHashMap() *ChainingHashMap {
	// 初始一个默认桶大小为9, 负载因子为2/3, 扩容因子为2的哈希表
	return &amp;amp;ChainingHashMap{
		buckets:       make([]*list.List, 9),
		size:          0,
		capacity:      9,
		loadThreshold: 2.0 / 3.0,
		extendRatio:   2,
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;哈希函数实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// hashFunc 哈希函数
func (h *ChainingHashMap) hashFunc(key int) int {
	return key % h.capacity
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;计算负载因子&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// loadFactor 计算负载因子
func (h *ChainingHashMap) loadFactor() float64 {
	return float64(h.size) / float64(h.capacity)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;扩容实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// extend 扩容
func (h *ChainingHashMap) extend() {
	// 1.计算扩容后的容量
	h.capacity *= h.extendRatio
	// 2.创建新的桶列表
	newBuckets := make([]*list.List, h.capacity)
	// 3.将旧桶中的元素迁移到新桶中
	for _, bucket := range h.buckets {
		if bucket != nil {
			for e := bucket.Front(); e != nil; e = e.Next() {
				// 遍历每个桶中的键值对,重新计算新的索引
				newIndex := h.hashFunc(e.Value.(*pair).key)
				if newBuckets[newIndex] == nil {
					newList := list.New()
					newList.PushBack(e.Value)
					newBuckets[newIndex] = newList
				} else {
					newBuckets[newIndex].PushBack(e.Value)
				}
			}
		}
	}
	// 4.更新桶列表
	h.buckets = newBuckets
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;哈希表的常规操作&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// put 插入元素
func (h *ChainingHashMap) put(key int, value any) {
	// 负载因子超过阈值，进行扩容
	if h.loadFactor() &amp;gt; h.loadThreshold {
		fmt.Println(&quot;HashMap Extend&quot;)
		h.extend()
	}
	index := h.hashFunc(key)

	if h.buckets[index] == nil {
		// 如果桶为空，则新建一个桶
		newList := list.New()
		newList.PushBack(&amp;amp;pair{
			key:   key,
			value: value,
		})
		h.buckets[index] = newList
		// 更新长度
		h.size++
	} else {
		// 如果桶不为空，则遍历桶中的元素，如果存在相同的key，则更新value，否则插入新元素
		for e := h.buckets[index].Front(); e != nil; e = e.Next() {
			if e.Value.(*pair).key == key { // 找到相同的key，更新value
				e.Value.(*pair).value = value
				return

			}
		}
		// 没有找到相同的key，插入新元素
		h.buckets[index].PushBack(&amp;amp;pair{
			key:   key,
			value: value,
		})
		h.size++
	}
}

// get 获取元素
func (h *ChainingHashMap) get(key int) any {
	// 1.计算索引
	index := h.hashFunc(key)
	// 2.遍历桶中的元素，找到key对应的value
	if h.buckets[index] != nil {
		for e := h.buckets[index].Front(); e != nil; e = e.Next() {
			if e.Value.(*pair).key == key {
				return e.Value.(*pair).value
			}
		}
	}
	return nil
}

// remove 删除元素
func (h *ChainingHashMap) remove(key int) {
	// 1.计算索引
	index := h.hashFunc(key)
	// 2.遍历桶中的元素，找到key对应的value，并删除
	if h.buckets[index] != nil {
		for e := h.buckets[index].Front(); e != nil; e = e.Next() {
			if e.Value.(*pair).key == key {
				h.buckets[index].Remove(e)
				h.size--
				return
			}
		}
	}
}

// print 打印哈希表
func (h *ChainingHashMap) print() {
	for _, bucket := range h.buckets {
		if bucket != nil {
			for e := bucket.Front(); e != nil; e = e.Next() {
				fmt.Printf(&quot;key: %d, value: %v\n&quot;, e.Value.(*pair).key, e.Value.(*pair).value)
			}
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;测试函数示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package hashmap

import (
	&quot;fmt&quot;
	&quot;testing&quot;
)

func TestChainingHashMap(t *testing.T) {
	hashMap := NewChainingHashMap()
	hashMap.put(10, &quot;hello&quot;)
	hashMap.put(20, &quot;world&quot;)
	hashMap.put(220, &quot;world2&quot;)
	hashMap.put(30, &quot;golang&quot;)
	hashMap.put(40, &quot;hashmap&quot;)
	hashMap.put(50, &quot;test&quot;)
	hashMap.put(60, &quot;hashmap2&quot;)
	hashMap.print()
	fmt.Println(&quot;---------------------------------&quot;)

	fmt.Println(&quot;get key-20 value: &quot;, hashMap.get(20))
	fmt.Println(&quot;get key-220 value: &quot;, hashMap.get(220))
	fmt.Println(&quot;---------------------------------&quot;)

	// 此时负载因子达到7/9, 超过了2/3阈值,触发扩容
	hashMap.put(70, &quot;hashmap&quot;)
	fmt.Println(&quot;after resize: &quot;, hashMap.capacity)
	hashMap.print()
	fmt.Println(&quot;---------------------------------&quot;)

	hashMap.remove(220)
	fmt.Println(&quot;after remove key-220: &quot;, hashMap.get(220))

	hashMap.put(20, &quot;world3&quot;)
	fmt.Println(&quot;after update key-20 value: &quot;, hashMap.get(20))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;测试结果示例&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;image-thgf.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;开放寻址&lt;/h2&gt;
&lt;p&gt;开放寻址(Open Addressing),不引入额外的数据结构,通过&quot;多次探测&quot;来解决冲突.&lt;/p&gt;
&lt;h3&gt;线性探测&lt;/h3&gt;
&lt;p&gt;线性探测采用固定步长的线性搜索来进行探测，其操作方法与普通哈希表有所不同。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;插入元素&lt;/strong&gt;：通过哈希函数计算桶索引，若发现桶内已有元素，则从冲突位置&lt;strong&gt;向后线性遍历&lt;/strong&gt;（步长通常为
），直至找到空桶，将元素插入其中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查找元素&lt;/strong&gt;：若发现哈希冲突，则使用相同步长向后进行线性遍历，直到找到对应元素，返回 value 即可；如果遇到空桶，说明目标元素不在哈希表中，返回 None 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;值得注意的是，我们不能在开放寻址哈希表中直接删除元素。这是因为删除元素会在数组内产生一个空桶 None ，而当查询元素时，线性探测到该空桶就会返回，因此在该空桶之下的元素都无法再被访问到.&lt;/p&gt;
&lt;h3&gt;平方探测&lt;/h3&gt;
&lt;p&gt;平方探测与线性探测类似，都是开放寻址的常见策略之一。当发生冲突时，平方探测不是简单地跳过一个固定的步数，而是跳过“探测次数的平方”的步数，即
&lt;code&gt;1,4,9,...&lt;/code&gt;步。&lt;/p&gt;
&lt;p&gt;平方探测主要具有以下优势。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;平方探测通过跳过探测次数平方的距离，试图缓解线性探测的聚集效应。&lt;/li&gt;
&lt;li&gt;平方探测会跳过更大的距离来寻找空位置，有助于数据分布得更加均匀。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然而，平方探测并不是完美的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;仍然存在聚集现象，即某些位置比其他位置更容易被占用。&lt;/li&gt;
&lt;li&gt;由于平方的增长，平方探测可能不会探测整个哈希表，这意味着即使哈希表中有空桶，平方探测也可能无法访问到它。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;多次哈希&lt;/h3&gt;
&lt;p&gt;顾名思义，多次哈希方法使用多个哈希函数进行探测。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;插入元素：若哈希函数&lt;code&gt;f1(x)&lt;/code&gt;出现冲突，则尝试&lt;code&gt;f2(x)&lt;/code&gt;，以此类推，直到找到空位后插入元素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查找元素：在相同的哈希函数顺序下进行查找，直到找到目标元素时返回；若遇到空位或已尝试所有哈希函数，说明哈希表中不存在该元素，则返回 None 。
与线性探测相比，多次哈希方法不易产生聚集，但多个哈希函数会带来&lt;strong&gt;额外的计算量&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>数据结构-树</title><link>https://fuwari.vercel.app/posts/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%A0%91/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%A0%91/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%A0%91/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%A0%91/</guid><description>基于go语言的二叉树讲解</description><pubDate>Sun, 09 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;数据结构-树&lt;/h1&gt;
&lt;p&gt;二叉树（binary tree）是一种非线性数据结构，代表“祖先”与“后代”之间的派生关系，体现了“一分为二”的分治逻辑.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// TreeNode 二叉树节点结构
type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

// NewTreeNode 创建二叉树节点
func NewTreeNode(val int) *TreeNode {
	return &amp;amp;TreeNode{
		Val: val,
		Left: nil,
		Right: nil,
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个节点都有两个引用（指针），分别指向左子节点（left-child node）和右子节点（right-child node），该节点被称为这两个子节点的父节点（parent node）。当给定一个二叉树的节点时，我们将该节点的左子节点及其以下节点形成的树称为该节点的左子树（left subtree），同理可得右子树（right subtree）。&lt;/p&gt;
&lt;h2&gt;二叉树的专业术语&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;根节点（root node）: 没有父节点的节点,树的顶层节点&lt;/li&gt;
&lt;li&gt;叶子节点（leaf node）: 没有子节点的节点&lt;/li&gt;
&lt;li&gt;边（edge）: 连接两个节点的线段(引用)称为边&lt;/li&gt;
&lt;li&gt;节点所在的层数（level）: 根节点所在的层数是1，以此类推&lt;/li&gt;
&lt;li&gt;节点的度（degree）: 节点的子节点数量, 二叉树的节点的度的取值范围为[0, 1, 2]&lt;/li&gt;
&lt;li&gt;节点的深度（depth）: 根节点到该节点的边的数量&lt;/li&gt;
&lt;li&gt;节点的高度（height）: 该节点到最远叶子节点的边的数量&lt;/li&gt;
&lt;li&gt;二叉树的高度（height）: 根节点的高度,即根节点到最远叶子节点的边的数量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-tree.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如在上图所示的二叉树中,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根节点为&lt;code&gt;节点1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;叶子节点为&lt;code&gt;节点5&lt;/code&gt;,&lt;code&gt;节点6&lt;/code&gt;,&lt;code&gt;节点7&lt;/code&gt;,&lt;code&gt;节点8&lt;/code&gt;,&lt;code&gt;节点9&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;总共有&lt;code&gt;8&lt;/code&gt;条边&lt;/li&gt;
&lt;li&gt;&lt;code&gt;节点2&lt;/code&gt;所在的层数为2&lt;/li&gt;
&lt;li&gt;&lt;code&gt;节点2&lt;/code&gt;的度为2&lt;/li&gt;
&lt;li&gt;&lt;code&gt;节点2&lt;/code&gt;的深度为1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;节点2&lt;/code&gt;的高度为2&lt;/li&gt;
&lt;li&gt;二叉树的高度为3&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二叉树的分类&lt;/h2&gt;
&lt;h3&gt;完美二叉树(满二叉树)&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;img.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;完美二叉树（perfect binary tree）所有层的节点都被完全填满。在完美二叉树中，叶节点的度为&lt;code&gt;0&lt;/code&gt;，其余所有节点的度都为 &lt;code&gt;2&lt;/code&gt;；若树的高度为&lt;code&gt;h&lt;/code&gt;，则节点总数为
$$
2^{(h+1)}-1
$$
，呈现标准的指数级关系&lt;/p&gt;
&lt;h3&gt;完全二叉树&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;image-tree.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;完全二叉树（complete binary tree）是除了最底层节点可能没填满外，其余每层节点都被完全填满，并且最后一层节点都靠左排列.&lt;/p&gt;
&lt;h3&gt;完满二叉树&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;img2.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;完满二叉树（full binary tree）除了叶节点之外，其余所有节点都有两个子节点。&lt;/p&gt;
&lt;h3&gt;平衡二叉树&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;balanced_binary_tree.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;平衡二叉树（balanced binary tree）中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。&lt;/p&gt;
&lt;h2&gt;二叉树的遍历&lt;/h2&gt;
&lt;h3&gt;层序遍历&lt;/h3&gt;
&lt;p&gt;层序遍历（level-order traversal）从&lt;em&gt;顶部到底部逐层遍历二叉树&lt;/em&gt;，并在每一层按照&lt;em&gt;从左到右&lt;/em&gt;的顺序访问节点。&lt;/p&gt;
&lt;p&gt;层序遍历本质上属于广度优先遍历（breadth-first traversal），也称广度优先搜索（breadth-first search, BFS），它体现了一种“一圈一圈向外扩展”的逐层遍历方式。&lt;/p&gt;
&lt;p&gt;广度优先遍历通常借助“&lt;strong&gt;队列&lt;/strong&gt;”来实现。队列遵循“先进先出”的规则，而广度优先遍历则遵循“逐层推进”的规则，两者背后的思想是一致的。实现代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// LevelOrder 层序遍历,通常使用队列实现
func LevelOrder(root *TreeNode) []any {
	// 用来保存遍历结果的切片
	result := make([]any, 0)
	// 初始化一个队列
	queue := list.New()
	// 根节点入队
	queue.PushBack(root)
	// 当队列不为空时
	for queue.Len() &amp;gt; 0 {
		// 出队
		node := queue.Remove(queue.Front()).(*TreeNode)
		// 如果左节点不为空，入队
		if node.Left != nil {
			queue.PushBack(node.Left)
		}
		// 如果右节点不为空，入队
		if node.Right != nil {
			queue.PushBack(node.Right)
		}
		// 将节点值加入结果切片
		result = append(result, node.Val)
	}
	return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;前序, 中序, 后序遍历&lt;/h2&gt;
&lt;p&gt;前序、中序和后序遍历都属于&lt;strong&gt;深度优先遍历&lt;/strong&gt;（depth-first traversal），也称&lt;strong&gt;深度优先搜索&lt;/strong&gt;（depth-first search, DFS），它体现了一种“先走到尽头，再回溯继续”的遍历方式。&lt;/p&gt;
&lt;p&gt;前序遍历是指根节点在前，中序遍历是指根节点在中，后序遍历是指根节点在后。&lt;/p&gt;
&lt;p&gt;深度优先遍历通产基于递归实现.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// PreOrder 先序遍历
func PreOrder(root *TreeNode, result *[]any) {
	if root != nil {
		// 访问优先级：根节点 -&amp;gt; 左子树 -&amp;gt; 右子树
		*result = append(*result, root.Val)
		PreOrder(root.Left, result)
		PreOrder(root.Right, result)
	}
}

// InOrder 中序遍历
func InOrder(root *TreeNode, result *[]any) {
	if root != nil {
		// 访问优先级：左子树 -&amp;gt; 根节点 -&amp;gt; 右子树
		InOrder(root.Left, result)
		*result = append(*result, root.Val)
		InOrder(root.Right, result)
	}
}

// PostOrder 后序遍历
func PostOrder(root *TreeNode, result *[]any) {
	// 访问优先级：左子树 -&amp;gt; 右子树 -&amp;gt; 根节点
	if root != nil {
		PostOrder(root.Left, result)
		PostOrder(root.Right, result)
		*result = append(*result, root.Val)
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二叉搜索树&lt;/h2&gt;
&lt;p&gt;二叉搜索树（Binary Search Tree，简称BST, 也叫二叉排序树）是指一棵空树或者具有下列性质的二叉树：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.对于根节点，左子树中所有节点的值 &lt;code&gt;&amp;lt;&lt;/code&gt; 根节点的值 &lt;code&gt;&amp;lt;&lt;/code&gt; 右子树中所有节点的值。&lt;/li&gt;
&lt;li&gt;2.任意节点的左、右子树也是二叉搜索树，即同样满足条件 1 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;binary_search_tree.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;二叉搜索树的操作&lt;/h3&gt;
&lt;p&gt;我们将二叉搜索树封装为一个结构体 &lt;code&gt;BinarySearchTree&lt;/code&gt; ，并声明一个成员变量 root ，指向树的根节点。&lt;/p&gt;
&lt;h3&gt;查找节点&lt;/h3&gt;
&lt;p&gt;给定目标节点值 num ，可以根据二叉搜索树的性质来查找。我们声明一个节点 cur ，从二叉树的根节点 root 出发，循环比较节点值 cur.val 和 num 之间的大小关系。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 cur.val &amp;lt; num ，说明目标节点在 cur 的右子树中，因此执行 cur = cur.right 。&lt;/li&gt;
&lt;li&gt;若 cur.val &amp;gt; num ，说明目标节点在 cur 的左子树中，因此执行 cur = cur.left 。&lt;/li&gt;
&lt;li&gt;若 cur.val = num ，说明找到目标节点，跳出循环并返回该节点。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;type BinarySearchTree struct {
	root *TreeNode
}

func NewBinarySearchTree(root *TreeNode) *BinarySearchTree {
	return &amp;amp;BinarySearchTree{root: root}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// searchByIteration 迭代实现查找
func (bst *BinarySearchTree) searchByIteration(val int) *TreeNode {
	cur := bst.root
	// 遍历节点
	for cur != nil {
		if cur.Val == val {
			return cur
		}
		// 小于则向左查找
		if cur.Val &amp;lt; val {
			cur = cur.Right
		} else {
			// 大于则向右查找
			cur = cur.Left
		}
	}
	return nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// searchByRecursion 递归实现查找
func (bst *BinarySearchTree) searchByRecursion(val int) *TreeNode {
	if bst.root != nil {
		if bst.root.Val == val {
			return bst.root
		}
		// 小于则将右树放入递归查找
		if bst.root.Val &amp;lt; val {
			return NewBinarySearchTree(bst.root.Right).searchByRecursion(val)
		} else {
			// 大于则将左树放入递归查找
			return NewBinarySearchTree(bst.root.Left).searchByRecursion(val)
		}
	}
	return nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;测试函数示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func TestBST(t *testing.T) {
	// 构造一个二叉搜索树
	n1 := NewTreeNode(1)
	n2 := NewTreeNode(2)
	n3 := NewTreeNode(3)
	n4 := NewTreeNode(4)
	n5 := NewTreeNode(5)
	n6 := NewTreeNode(6)
	n7 := NewTreeNode(7)
	n8 := NewTreeNode(8)
	n9 := NewTreeNode(9)
	n10 := NewTreeNode(10)
	n11 := NewTreeNode(11)
	n12 := NewTreeNode(12)
	n13 := NewTreeNode(13)
	n14 := NewTreeNode(14)
	n15 := NewTreeNode(15)

	n8.Left = n4
	n8.Right = n12
	n4.Left = n2
	n4.Right = n6
	n2.Left = n1
	n2.Right = n3
	n6.Left = n5
	n6.Right = n7
	n12.Left = n10
	n12.Right = n14
	n10.Left = n9
	n10.Right = n11
	n14.Left = n13
	n14.Right = n15

	bst := NewBinarySearchTree(n8)
	inOrderResult := make([]any, 0)
	InOrder(bst.root, &amp;amp;inOrderResult)
	fmt.Println(&quot;中序遍历: &quot;, inOrderResult)

	fmt.Println(&quot;迭代查找节点: &quot;, bst.searchByIteration(10).Val)
	fmt.Println(&quot;递归查找节点: &quot;, bst.searchByRecursion(10).Val)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;中序遍历:  [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
迭代查找节点:  10
递归查找节点:  10
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;插入节点&lt;/h3&gt;
&lt;p&gt;插入节点的流程如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.查找插入位置：与查找操作相似，从根节点出发，根据当前节点值和 num 的大小关系循环向下搜索，直到越过叶节点（遍历至 None ）时跳出循环。&lt;/li&gt;
&lt;li&gt;2.在该位置插入节点：初始化节点 num ，将该节点置于 None 的位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// insert 插入节点
func (bst *BinarySearchTree) insert(val int) {
	cur := bst.root
	// 如果根节点为空，则直接插入
	if cur == nil {
		bst.root = NewTreeNode(val)
		return
	}
	// 记录待插入节点的前置节点
	var pre *TreeNode = nil
	// 按照查询的规则找到插入的位置
	for cur != nil {
		// 节点已存在,直接返回
		if cur.Val == val {
			return
		}
		// 保存前置节点
		pre = cur
		if cur.Val &amp;lt; val {
			// 向右查找
			cur = cur.Right
		} else {
			// 向左查找
			cur = cur.Left
		}
	}
	// 此时cur为nil，pre为待插入节点的前置节点
	newNode := NewTreeNode(val)
	if pre == nil {
		panic(&quot;pre is nil&quot;)
	}
	// 根据val与pre.Val的大小关系，决定插入到pre的左子树还是右子树
	if pre.Val &amp;lt; val {
		pre.Right = newNode
	} else {
		pre.Left = newNode
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;删除节点&lt;/h3&gt;
&lt;p&gt;删除节点的流程如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.查找删除节点：与查找操作相似，从根节点出发，根据当前节点值和 num 的大小关系循环向下搜索，直到越过叶节点（遍历至 None ）时跳出循环。&lt;/li&gt;
&lt;li&gt;2.删除节点：
&lt;ul&gt;
&lt;li&gt;如果待删除节点为叶节点，则直接删除。&lt;/li&gt;
&lt;li&gt;如果待删除节点只有一个子节点，则用子节点代替。&lt;/li&gt;
&lt;li&gt;如果待删除节点有两个子节点，则用后继节点代替。由于要保持二叉搜索树“左子树&amp;lt;根节点&amp;lt;右子树”的性质，因此这个节点可以是&lt;strong&gt;右子树的最小节点&lt;/strong&gt;或&lt;strong&gt;左子树的最大节点&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// remove 删除节点
func (bst *BinarySearchTree) remove(val int) {
	cur := bst.root
	// 若树为空，直接提前返回
	if cur == nil {
		return
	}
	// 待删除节点之前的节点位置
	var pre *TreeNode = nil
	// 循环查找，越过叶节点后跳出
	for cur != nil {
		if cur.Val == val {
			break
		}
		pre = cur
		if cur.Val &amp;lt; val {
			// 待删除节点在右子树中
			cur = cur.Right
		} else {
			// 待删除节点在左子树中
			cur = cur.Left
		}
	}
	// 若找不到删除节点，提前返回
	if cur == nil {
		return
	}
	// 根据节点的子节点数量分为3种情况删除
	// 子节点数为 0 或 1
	if cur.Left == nil || cur.Right == nil {
		var child *TreeNode = nil
		// 取出待删除节点的子节点
		if cur.Left != nil {
			child = cur.Left
		} else {
			child = cur.Right
		}
		// 删除节点 cur
		if cur != bst.root {
			if pre.Left == cur {
				pre.Left = child
			} else {
				pre.Right = child
			}
		} else {
			// 若删除节点为根节点，则重新指定根节点
			bst.root = child
		}
		// 子节点数为 2
	} else {
		// 获取中序遍历中待删除节点 cur 的下一个节点
		tmp := cur.Right
		for tmp.Left != nil {
			tmp = tmp.Left
		}
		// 递归删除节点 tmp
		bst.remove(tmp.Val)
		// 用 tmp 覆盖 cur
		cur.Val = tmp.Val
	}
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>数据结构-链表</title><link>https://fuwari.vercel.app/posts/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E9%93%BE%E8%A1%A8/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E9%93%BE%E8%A1%A8/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E9%93%BE%E8%A1%A8/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E9%93%BE%E8%A1%A8/</guid><description>基于go语言的链表实现</description><pubDate>Sun, 09 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;数据结构-链表&lt;/h1&gt;
&lt;h2&gt;链表的定义&lt;/h2&gt;
&lt;p&gt;链表（linked list）是一种&lt;strong&gt;线性&lt;/strong&gt;数据结构，其中的每个元素都是一个节点对象，各个节点通过&lt;strong&gt;引用&lt;/strong&gt;相连接。引用记录了下一个节点的&lt;strong&gt;内存地址&lt;/strong&gt;，通过它可以从当前节点访问到下一个节点。
链表的设计使得各个节点可以分散存储在内存各处，它们的内存地址无须连续。&lt;/p&gt;
&lt;h2&gt;链表的分类&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;单向链表：即本文重点介绍的普通链表。单向链表的节点包含值和指向下一节点的引用两项数据。首个节点称为头节点，将最后一个节点称为尾节点，尾节点指向空 &lt;code&gt;nil&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;环形链表：令单向链表的尾节点指向头节点（首尾相接），则得到一个环形链表。在环形链表中，任意节点都可以视作头节点。&lt;/li&gt;
&lt;li&gt;双向链表：与单向链表相比，双向链表记录了两个方向的引用。双向链表的节点定义同时包含指向后继节点（下一个节点）和前驱节点（上一个节点）的引用（指针）。相较于单向链表，双向链表更具灵活性，可以朝两个方向遍历链表，但相应地也需要占用更多的内存空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;链表的结构定义&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// ListNode 链表节点结构体
type ListNode struct {
	Val  int       // 节点的值
	Next *ListNode // 指向下一个节点的指针
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;初始化一个链表节点&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// NewListNode 创建一个新节点
// Params:
//   - val: 节点值
// Return:
//   - node: 链表节点
func NewListNode(val int) *ListNode {
	return &amp;amp;ListNode{Val: val, Next: nil}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;初始化一个链表&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// NewLinkedList 创建一个新链表
// Params:
//   - size: 链表长度
// Return:
//   - headNode: 链表头节点
func NewLinkedList(size int) (headNode *ListNode) {
	headNode = nil // 头节点指针
	var currentNode *ListNode = nil // 当前节点指针
	// 前往后创建节点
	for i := 0; i &amp;lt; size; i++ {
		newNode := NewListNode(i) //  创建新节点
		if headNode == nil {
			headNode = newNode // 头节点为空则将头节点指向新节点
		}
		if currentNode == nil {
			currentNode = newNode //  当前节点为空则将当前节点指向新节点
		}
		currentNode.Next = newNode //  当前节点指向新节点
		currentNode = newNode // 
	}
	return headNode
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;链表的常用操作&lt;/h2&gt;
&lt;h3&gt;打印(遍历)链表&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// Print 打印链表
func (l *ListNode) Print() {
	for l != nil { 
		fmt.Print(l.Val, &quot; &quot;)
		l = l.Next // 指针后移
	}
	fmt.Println()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;插入新节点&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// InsertNode 在头节点后插入新节点
// Params:
//   - headNode: 链表头节点
//   - newNode: 新节点
func InsertNode(headNode *ListNode, newNode *ListNode) {
	newNode.Next = headNode.Next // 新节点的Next指向头节点的Next
	headNode.Next = newNode      // 头节点的Next指向新节点
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;删除节点&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// DeleteNode 删除头节点后的节点
// Params:
//   - headNode: 链表头节点
func DeleteNode(headNode *ListNode) {
	headNode.Next = headNode.Next.Next // 头节点的Next指向头节点的Next的Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;获取链表的第index个节点&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// GetNode 获取链表中的第index个节点
// Params:
//   - headNode: 链表头节点
//   - index: 节点索引
//
// Return:
//   - node: 节点
func GetNode(headNode *ListNode, index int) (node *ListNode) {
	currentNode := headNode
	for i := 0; i &amp;lt;= index; i++ {
		if i == index {
			return currentNode
		}
		currentNode = currentNode.Next
	}
	return nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;获取链表中第一个值为val的节点&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// FindNode 获取链表中第一个值为val的节点
// Params:
//   - headNode: 链表头节点
//   - val: 节点值
//
// Return:
//   - node: 节点
func FindNode(headNode *ListNode, val int) (node *ListNode) {
	currentNode := headNode
	for currentNode != nil {
		if currentNode.Val == val {
			return currentNode
		}
		currentNode = currentNode.Next
	}
	return nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;主函数示例代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func main() {
	linkedList := NewLinkedList(5)
	linkedList.Print()

	newNode := NewListNode(100)
	InsertNode(linkedList, newNode)
	fmt.Println(&quot;After insert a new node&quot;)
	linkedList.Print()

	DeleteNode(linkedList)
	fmt.Println(&quot;After delete a node&quot;)
	linkedList.Print()

	fmt.Println(&quot;Get node 2: &quot;, GetNode(linkedList, 2).Val)
	fmt.Println(&quot;Find node value==4: &quot;, FindNode(linkedList, 4).Val)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-ddgr.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;双向链表示例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/* 双向链表节点结构体 */
type DoublyListNode struct {
    Val  int             // 节点值
    Next *DoublyListNode // 指向后继节点的指针
    Prev *DoublyListNode // 指向前驱节点的指针
}

// NewDoublyListNode 初始化
func NewDoublyListNode(val int) *DoublyListNode {
    return &amp;amp;DoublyListNode{
        Val:  val,
        Next: nil,
        Prev: nil,
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他操作大差不差，这里不再赘述。&lt;/p&gt;
</content:encoded></item><item><title>数据结构-栈</title><link>https://fuwari.vercel.app/posts/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%A0%88/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%A0%88/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%A0%88/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%A0%88/</guid><description>基于go语言的栈实现</description><pubDate>Sat, 08 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;数据结构-栈&lt;/h1&gt;
&lt;h2&gt;栈的定义&lt;/h2&gt;
&lt;p&gt;栈（stack）是限定仅在表尾进行插入和删除操作的线性表,一种遵循&lt;strong&gt;先入后出&lt;/strong&gt;逻辑的线性数据结构。&lt;/p&gt;
&lt;p&gt;本文将使用go语言,从切片以及链表两种方式实现栈(推荐使用切片来模拟栈)。&lt;/p&gt;
&lt;h2&gt;栈的结构定义&lt;/h2&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Stack 栈
type Stack []int
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// Stack 栈
type Stack struct {
	*list.List // 务必注意嵌入的是指针类型
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;栈的初始化&lt;/h3&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// NewStack 创建一个栈
// Returns:
// - *Stack: 栈指针
func NewStack() *Stack {
	return &amp;amp;Stack{}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// NewStack 创建一个栈
// Return
// - *Stack 栈指针
func NewStack() *Stack {
	return &amp;amp;Stack{List: list.New()}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;栈的常用操作&lt;/h2&gt;
&lt;h3&gt;入栈&lt;/h3&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// push 入栈
// Params:
// - v: 入栈元素
func (s *Stack) push(v int) {
	*s = append(*s, v) // 将v添加到切片的末尾
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// push 入栈
// Params:
// - v 入栈元素
func (s *Stack) push(v interface{}) {
	s.List.PushBack(v)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;出栈&lt;/h3&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// pop 出栈
// Returns:
// - int: 出栈元素
func (s *Stack) pop() int {
	if len(*s) == 0 {
		return -1
	}
	v := (*s)[len(*s)-1]  // 获取切片的最后一个元素
	*s = (*s)[:len(*s)-1] // 切片截断,删除最后一个元素
	return v
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// pop 出栈
// Return:
// - any 出栈元素
func (s *Stack) pop() any {
	return s.List.Remove(s.Back())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;获取栈顶元素&lt;/h3&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// peek 获取栈顶元素
func (s *Stack) peek() int {
	if len(*s) == 0 {
		return -1
	}
	return (*s)[len(*s)-1]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// peek 查看栈顶元素
// Return:
// - any 栈顶元素
func (s *Stack) peek() any {
	return s.List.Back().Value
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;判断栈是否为空&lt;/h3&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// isEmpty 判断栈是否为空
func (s *Stack) isEmpty() bool {
	return len(*s) == 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// isEmpty 栈是否为空
// Return:
// - bool 是否为空
func (s *Stack) isEmpty() bool {
	return s.List.Len() == 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;获取栈的大小&lt;/h3&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// len 获取栈长度
func (s *Stack) len() int {
	return len(*s)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// len 栈大小
// Return:
// - int 栈大小
func (s *Stack) len() int {
	return s.List.Len()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;测试函数示例代码&lt;/h2&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package slice

import (
	&quot;testing&quot;
)

func TestSliceStack(t *testing.T) {
	stack := NewStack()
	stack.push(1)
	stack.push(2)
	stack.push(3)

	t.Log(&quot;stack:&quot;, stack)

	t.Log(&quot;pop:&quot;, stack.pop())
	t.Log(&quot;stack:&quot;, stack)
	t.Log(&quot;peek:&quot;, stack.peek())
	t.Log(&quot;len:&quot;, stack.len())
	t.Log(&quot;is empty:&quot;, stack.isEmpty())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;package linked_list

import &quot;testing&quot;

func TestLinkedListStack(t *testing.T) {
	stack := NewStack()
	stack.push(1)
	stack.push(2)
	stack.push(3)

	t.Log(&quot;stack:&quot;)
	stack.print()

	t.Log(&quot;pop:&quot;, stack.pop())
	t.Log(&quot;stack:&quot;)
	stack.print()
	t.Log(&quot;peek:&quot;, stack.peek())
	t.Log(&quot;len:&quot;, stack.len())
	t.Log(&quot;is empty:&quot;, stack.isEmpty())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;测试结果&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-lkij.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>算法-排序</title><link>https://fuwari.vercel.app/posts/%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F/%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F/%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F/</guid><description>基于go语言的排序算法实现</description><pubDate>Wed, 05 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;算法-排序&lt;/h1&gt;
&lt;h2&gt;选择排序&lt;/h2&gt;
&lt;p&gt;选择排序（selection sort）的工作原理非常简单：开启一个循环，每轮从未排序区间选择最小的元素，将其放到已排序区间的末尾。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// SelectionSort
//
//	@Description: 选择排序由小到大
//	@param nums 待排序数组
//	@return []int 排序后的数组
func SelectionSort(nums []int) []int {
	n := len(nums)
	// 外层循环,控制未排序区间
	// 只循环到n-2,因为最后一个元素不需要再比较
	for i := 0; i &amp;lt; n-1; i++ {
		minIndex := i
		// 内层循环,找到未排序区间的最小值的索引
		for j := i; j &amp;lt; n; j++ {
			if nums[j] &amp;lt; nums[minIndex] {
				minIndex = j
			}
		}
		// 将最小值放到未排序区间的最前面
		nums[i], nums[minIndex] = nums[minIndex], nums[i]
	}
	return nums
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;插入排序&lt;/h2&gt;
&lt;p&gt;插入排序（insertion sort）是一种简单的排序算法，它的工作原理与手动整理一副牌的过程非常相似。&lt;/p&gt;
&lt;p&gt;具体来说，我们在未排序区间选择一个基准元素，将该元素与其左侧已排序区间的元素逐一比较大小，并将该元素插入到正确的位置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// InsertionSort
//
//	@Description: 插入排序由小到大
//	@param nums 待排序数组
//	@return []int 排序后的数组
func InsertionSort(nums []int) []int {
	// 外循环：已排序区间为 [0, i-1]
	for i := 1; i &amp;lt; len(nums); i++ {
		base := nums[i]
		j := i - 1
		// 内循环：将 base 插入到已排序区间 [0, i-1] 中的正确位置
		for j &amp;gt;= 0 &amp;amp;&amp;amp; nums[j] &amp;gt; base {
			nums[j+1] = nums[j] // 将 nums[j] 向右移动一位
			j--
		}
		nums[j+1] = base // 将 base 赋值到正确位置
	}
	return nums
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;冒泡排序&lt;/h2&gt;
&lt;p&gt;冒泡排序（bubble sort）通过连续地比较与交换相邻元素实现排序。这个过程就像气泡从底部升到顶部一样，因此得名冒泡排序。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// BubbleSort
//
//	@Description: 冒泡排序由小到大
//	@param nums 待排序数组
//	@return []int 排序后的数组
func BubbleSort(nums []int) []int {
	n := len(nums)
	// 外循环,控制比较轮数及未排序区间
	for i := 0; i &amp;lt; n; i++ {
		// 从后往前排序
		for j := 0; j &amp;lt; n-i-1; j++ {
			// 左边元素大于右边元素，则交换
			if nums[j] &amp;gt; nums[j+1] {
				nums[j], nums[j+1] = nums[j+1], nums[j]
			}
			// 一轮内循环后，最大的元素会被冒泡到未排序区间的末尾
		}
	}
	return nums
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;快速排序&lt;/h2&gt;
&lt;p&gt;快速排序（quick sort）是一种基于分治策略的排序算法，运行高效，应用广泛。&lt;/p&gt;
&lt;p&gt;快速排序的核心操作是“哨兵划分”，其目标是：选择数组中的某个元素作为“基准数”，将所有小于基准数的元素移到其左侧，而大于基准数的元素移到其右侧。具体来说，哨兵划分的流程如图 11-8 所示。&lt;/p&gt;
&lt;p&gt;选取数组最左端元素作为基准数，初始化两个指针 i 和 j 分别指向数组的两端。
设置一个循环，在每轮中使用 i（j）分别寻找第一个比基准数大（小）的元素，然后交换这两个元素。
循环执行步骤 2. ，直到 i 和 j 相遇时停止，最后将基准数交换至两个子数组的分界线。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// partition
//
//	@Description: 快速排序的分区操作(查找基准数索引)
//	@param nums 待排序数组
//	@param left 分区左边界
//	@param right 分区右边界
//	@return int 基准数索引
func partition(nums []int, left, right int) int {
	// 以 nums[left] 为基准数
	i, j := left, right
	for i &amp;lt; j {
		for i &amp;lt; j &amp;amp;&amp;amp; nums[j] &amp;gt;= nums[left] {
			j-- // 从右向左找首个小于基准数的元素
		}
		for i &amp;lt; j &amp;amp;&amp;amp; nums[i] &amp;lt;= nums[left] {
			i++ // 从左向右找首个大于基准数的元素
		}
		// 元素交换
		nums[i], nums[j] = nums[j], nums[i]
	}
	// 将基准数交换至两子数组的分界线
	nums[i], nums[left] = nums[left], nums[i]
	return i // 返回基准数的索引
}

// QuickSort
//
//	@Description: 快速排序
//	@param nums 待排序数组
//	@return []int 排序后的数组
func QuickSort(nums []int) []int {
	// 递归终止条件
	if len(nums) &amp;lt;= 1 {
		return nums
	}
	// 分区操作，获取基准数的索引
	pivot := partition(nums, 0, len(nums)-1)
	// 递归地对基准数左右两边的子数组进行快速排序
	QuickSort(nums[:pivot])
	QuickSort(nums[pivot+1:])
	return nums
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;归并排序&lt;/h2&gt;
&lt;p&gt;归并排序（merge sort）是一种基于分治策略的排序算法，包含“划分”和“合并”阶段。&lt;/p&gt;
&lt;p&gt;1.划分阶段：通过递归不断地将数组从中点处分开，将长数组的排序问题转换为短数组的排序问题。&lt;/p&gt;
&lt;p&gt;2.合并阶段：当子数组长度为 1 时终止划分，开始合并，持续地将左右两个较短的有序数组合并为一个较长的有序数组，直至结束。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;img.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 合并左子数组和右子数组 */
func merge(nums []int, left, mid, right int) {
    // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right]
    // 创建一个临时数组 tmp ，用于存放合并后的结果
    tmp := make([]int, right-left+1)
    // 初始化左子数组和右子数组的起始索引
    i, j, k := left, mid+1, 0
    // 当左右子数组都还有元素时，进行比较并将较小的元素复制到临时数组中
    for i &amp;lt;= mid &amp;amp;&amp;amp; j &amp;lt;= right {
        if nums[i] &amp;lt;= nums[j] {
            tmp[k] = nums[i]
            i++
        } else {
            tmp[k] = nums[j]
            j++
        }
        k++
    }
    // 将左子数组和右子数组的剩余元素复制到临时数组中
    for i &amp;lt;= mid {
        tmp[k] = nums[i]
        i++
        k++
    }
    for j &amp;lt;= right {
        tmp[k] = nums[j]
        j++
        k++
    }
    // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间
    for k := 0; k &amp;lt; len(tmp); k++ {
        nums[left+k] = tmp[k]
    }
}

/* 归并排序 */
func mergeSort(nums []int, left, right int) {
    // 终止条件
    if left &amp;gt;= right {
        return
    }
    // 划分阶段
    mid := left + (right - left) / 2
    mergeSort(nums, left, mid)
    mergeSort(nums, mid+1, right)
    // 合并阶段
    merge(nums, left, mid, right)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;堆排序&lt;/h2&gt;
&lt;p&gt;堆排序（heap sort）是一种基于堆数据结构实现的高效排序算法。我们可以利用已经学过的“建堆操作”和“元素出堆操作”实现堆排序。&lt;/p&gt;
&lt;p&gt;1.输入数组并建立小顶堆，此时最小元素位于堆顶。&lt;/p&gt;
&lt;p&gt;2.不断执行出堆操作，依次记录出堆元素，即可得到从小到大排序的序列。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 堆的长度为 n ，从节点 i 开始，从顶至底堆化 */
func siftDown(nums *[]int, n, i int) {
    for true {
        // 判断节点 i, l, r 中值最大的节点，记为 ma
        l := 2*i + 1
        r := 2*i + 2
        ma := i
        if l &amp;lt; n &amp;amp;&amp;amp; (*nums)[l] &amp;gt; (*nums)[ma] {
            ma = l
        }
        if r &amp;lt; n &amp;amp;&amp;amp; (*nums)[r] &amp;gt; (*nums)[ma] {
            ma = r
        }
        // 若节点 i 最大或索引 l, r 越界，则无须继续堆化，跳出
        if ma == i {
            break
        }
        // 交换两节点
        (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i]
        // 循环向下堆化
        i = ma
    }
}

/* 堆排序 */
func heapSort(nums *[]int) {
    // 建堆操作：堆化除叶节点以外的其他所有节点
    for i := len(*nums)/2 - 1; i &amp;gt;= 0; i-- {
        siftDown(nums, len(*nums), i)
    }
    // 从堆中提取最大元素，循环 n-1 轮
    for i := len(*nums) - 1; i &amp;gt; 0; i-- {
        // 交换根节点与最右叶节点（交换首元素与尾元素）
        (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0]
        // 以根节点为起点，从顶至底进行堆化
        siftDown(nums, i, 0)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;桶排序&lt;/h2&gt;
&lt;p&gt;桶排序（bucket sort）是分治策略的一个典型应用。它通过设置一些具有大小顺序的桶，每个桶对应一个数据范围，将数据平均分配到各个桶中；然后，在每个桶内部分别执行排序；最终按照桶的顺序将所有数据合并。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;img2.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 桶排序 */
func bucketSort(nums []float64) {
    // 初始化 k = n/2 个桶，预期向每个桶分配 2 个元素
    k := len(nums) / 2
    buckets := make([][]float64, k)
    for i := 0; i &amp;lt; k; i++ {
        buckets[i] = make([]float64, 0)
    }
    // 1. 将数组元素分配到各个桶中
    for _, num := range nums {
        // 输入数据范围为 [0, 1)，使用 num * k 映射到索引范围 [0, k-1]
        i := int(num * float64(k))
        // 将 num 添加进桶 i
        buckets[i] = append(buckets[i], num)
    }
    // 2. 对各个桶执行排序
    for i := 0; i &amp;lt; k; i++ {
        // 使用内置切片排序函数，也可以替换成其他排序算法
        sort.Float64s(buckets[i])
    }
    // 3. 遍历桶合并结果
    i := 0
    for _, bucket := range buckets {
        for _, num := range bucket {
            nums[i] = num
            i++
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;计数排序&lt;/h2&gt;
&lt;p&gt;计数排序（counting sort）通过统计元素数量来实现排序，通常应用于&lt;strong&gt;整数数组&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;1.遍历数组，找出其中的最大数字，记为 &lt;code&gt;m&lt;/code&gt;，然后创建一个长度为 &lt;code&gt;m+1&lt;/code&gt; 的辅助数组 &lt;code&gt;counter&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;2.借助 &lt;code&gt;counter&lt;/code&gt; 统计 &lt;code&gt;nums&lt;/code&gt; 中各数字的出现次数，其中 &lt;code&gt;counter[num]&lt;/code&gt; 对应数字 &lt;code&gt;num&lt;/code&gt; 的出现次数。统计方法很简单，只需遍历 &lt;code&gt;nums&lt;/code&gt;（设当前数字为 num），每轮将 &lt;code&gt;counter[num]&lt;/code&gt; 增加 &lt;code&gt;1&lt;/code&gt;即可。&lt;/p&gt;
&lt;p&gt;3.由于 &lt;code&gt;counter&lt;/code&gt; 的各个索引天然有序，因此相当于所有数字已经排序好了。接下来，我们遍历 &lt;code&gt;counter&lt;/code&gt; ，根据各数字出现次数从小到大的顺序填入 &lt;code&gt;nums&lt;/code&gt; 即可。&lt;/p&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 计数排序 */
// 简单实现，无法用于排序对象
func countingSortNaive(nums []int) {
    // 1. 统计数组最大元素 m
    m := 0
    for _, num := range nums {
        if num &amp;gt; m {
            m = num
        }
    }
    // 2. 统计各数字的出现次数
    // counter[num] 代表 num 的出现次数
    counter := make([]int, m+1)
    for _, num := range nums {
        counter[num]++
    }
    // 3. 遍历 counter ，将各元素填入原数组 nums
    for i, num := 0, 0; num &amp;lt; m+1; num++ {
        for j := 0; j &amp;lt; counter[num]; j++ {
            nums[i] = num
            i++
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/* 计数排序 */
// 完整实现，可排序对象，并且是稳定排序
func countingSort(nums []int) {
    // 1. 统计数组最大元素 m
    m := 0
    for _, num := range nums {
        if num &amp;gt; m {
            m = num
        }
    }
    // 2. 统计各数字的出现次数
    // counter[num] 代表 num 的出现次数
    counter := make([]int, m+1)
    for _, num := range nums {
        counter[num]++
    }
    // 3. 求 counter 的前缀和，将“出现次数”转换为“尾索引”
    // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引
    for i := 0; i &amp;lt; m; i++ {
        counter[i+1] += counter[i]
    }
    // 4. 倒序遍历 nums ，将各元素填入结果数组 res
    // 初始化数组 res 用于记录结果
    n := len(nums)
    res := make([]int, n)
    for i := n - 1; i &amp;gt;= 0; i-- {
        num := nums[i]
        // 将 num 放置到对应索引处
        res[counter[num]-1] = num
        // 令前缀和自减 1 ，得到下次放置 num 的索引
        counter[num]--
    }
    // 使用结果数组 res 覆盖原数组 nums
    copy(nums, res)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;基数排序&lt;/h2&gt;
&lt;p&gt;基数排序（radix sort）的核心思想与计数排序一致，也通过统计个数来实现排序。在此基础上，基数排序利用数字各位之间的递进关系，依次对每一位进行排序，从而得到最终的排序结果。&lt;/p&gt;
&lt;p&gt;以学号数据为例，假设数字的最低位是第 &lt;code&gt;1&lt;/code&gt; 位，最高位是第 &lt;code&gt;8&lt;/code&gt; 位，基数排序的流程如下图所示。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.初始化位数 &lt;code&gt;k = 1&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;2.对学号的第 &lt;code&gt;k&lt;/code&gt; 位执行“计数排序”。完成后，数据会根据第 &lt;code&gt;k&lt;/code&gt; 位从小到大排序。&lt;/li&gt;
&lt;li&gt;3.将 &lt;code&gt;k&lt;/code&gt; 增加 &lt;code&gt;1&lt;/code&gt; ，然后返回步骤 &lt;code&gt;2.&lt;/code&gt; 继续迭代，直到所有位都排序完成后结束。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;img3.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 获取元素 num 的第 k 位，其中 exp = 10^(k-1) */
func digit(num, exp int) int {
    // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算
    return (num / exp) % 10
}

/* 计数排序（根据 nums 第 k 位排序） */
func countingSortDigit(nums []int, exp int) {
    // 十进制的位范围为 0~9 ，因此需要长度为 10 的桶数组
    counter := make([]int, 10)
    n := len(nums)
    // 统计 0~9 各数字的出现次数
    for i := 0; i &amp;lt; n; i++ {
        d := digit(nums[i], exp) // 获取 nums[i] 第 k 位，记为 d
        counter[d]++             // 统计数字 d 的出现次数
    }
    // 求前缀和，将“出现个数”转换为“数组索引”
    for i := 1; i &amp;lt; 10; i++ {
        counter[i] += counter[i-1]
    }
    // 倒序遍历，根据桶内统计结果，将各元素填入 res
    res := make([]int, n)
    for i := n - 1; i &amp;gt;= 0; i-- {
        d := digit(nums[i], exp)
        j := counter[d] - 1 // 获取 d 在数组中的索引 j
        res[j] = nums[i]    // 将当前元素填入索引 j
        counter[d]--        // 将 d 的数量减 1
    }
    // 使用结果覆盖原数组 nums
    for i := 0; i &amp;lt; n; i++ {
        nums[i] = res[i]
    }
}

/* 基数排序 */
func radixSort(nums []int) {
    // 获取数组的最大元素，用于判断最大位数
    max := math.MinInt
    for _, num := range nums {
        if num &amp;gt; max {
            max = num
        }
    }
    // 按照从低位到高位的顺序遍历
    for exp := 1; max &amp;gt;= exp; exp *= 10 {
        // 对数组元素的第 k 位执行计数排序
        // k = 1 -&amp;gt; exp = 1
        // k = 2 -&amp;gt; exp = 10
        // 即 exp = 10^(k-1)
        countingSortDigit(nums, exp)
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>版本号定义规范</title><link>https://fuwari.vercel.app/posts/%E7%89%88%E6%9C%AC%E5%8F%B7%E5%AE%9A%E4%B9%89%E8%A7%84%E8%8C%83/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E7%89%88%E6%9C%AC%E5%8F%B7%E5%AE%9A%E4%B9%89%E8%A7%84%E8%8C%83/</guid><description>软件项目、产品版本号定义规范</description><pubDate>Tue, 04 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;版本号定义规范&lt;/h1&gt;
&lt;h3&gt;软件项目、产品版本号般都是这样定义的：&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;V1.0.0.20211028_base&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一位：&lt;/strong&gt; 版本前缀（&lt;em&gt;&lt;strong&gt;V&lt;/strong&gt;&lt;/em&gt;1.0.0.20211028_base）&lt;/p&gt;
&lt;p&gt;V （version）英文版本的缩写&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二位：&lt;/strong&gt; 主版本号（V&lt;em&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/em&gt;.0.0.20211028_base）&lt;/p&gt;
&lt;p&gt;当功能模块有较大的变动，比如增加模块或是整体架构发生变化。此版本号由项目经理决定是否修改。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三位:&lt;/strong&gt; 副版本号（V1.&lt;em&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/em&gt;.0.20211028_base）&lt;/p&gt;
&lt;p&gt;当功能有一定的增加或变化，比如增加了对权限控制、增加自定义视图等功能。此版本号由项目经理决定是否修改。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第四位:&lt;/strong&gt;  修订版本号（V1.0.&lt;em&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/em&gt;.20211028_base）&lt;/p&gt;
&lt;p&gt;一般是 Bug 修复或是一些小的变动，要经常发布修订版，时间间隔不限，修复一个严重的bug即可发布一个修订版。此版本号由项目经理决定是否修改。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第五位：&lt;/strong&gt; 日期版本号（V1.0.0.&lt;em&gt;&lt;strong&gt;20211028&lt;/strong&gt;&lt;/em&gt;_base）&lt;/p&gt;
&lt;p&gt;用于记录修改项目的当前日期，每天对项目的修改都需要更改日期版本号。此版本号由开发人员决定是否修改。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第六位：&lt;/strong&gt; 希腊字母版本号（V1.0.0.20211028_&lt;em&gt;&lt;strong&gt;base&lt;/strong&gt;&lt;/em&gt;）&lt;/p&gt;
&lt;p&gt;希腊字母版本号共有五种，分别为&lt;code&gt;base、alpha、beta 、RC 、 release&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Base：&lt;/strong&gt; 此版本表示该软件仅仅是一个假页面链接，通常包括所有的功能和页面布局，但是页面中的功能都没有做完整的实现，只是做为整体网站的一个基础架构。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alpha：&lt;/strong&gt; 软件的初级版本，表示该软件在此阶段以实现软件功能为主，通常只在软件开发者 内部交流，一般而言，该版本软件的Bug较多，需要继续修改，是测试版本。测试人员提交Bug经开发人员修改确认之后，发布到测试网址让测试人员测试，此时可将软件版本标注为alpha版。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beta：&lt;/strong&gt; 该版本相对于Alpha 版已经有了很大的进步，消除了严重错误，但还需要经过多次测试来进一步消除，此版本主要的修改对象是软件的UI。修改的的Bug 经测试人员测试确认后可发布到外网上，此时可将软件版本标注为 beta版。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RC：&lt;/strong&gt; 该版本已经相当成熟了，基本上不存在导致错误的Bug，与即将发行的正式版本相差无几。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Release：&lt;/strong&gt; 该版本意味“最终版本”，在前面版本的一系列测试版之后，终归会有一个正式的版本，是最终交付用户使用的一个版本。该版本有时也称标准版。&lt;/p&gt;
&lt;p&gt;摘自：https://blog.csdn.net/qq_29974981/article/details/121008029&lt;/p&gt;
</content:encoded></item><item><title>前,中,后缀表达式</title><link>https://fuwari.vercel.app/posts/%E5%89%8D%E4%B8%AD%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F/%E5%89%8D%E4%B8%AD%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E5%89%8D%E4%B8%AD%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F/%E5%89%8D%E4%B8%AD%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F/</guid><description>前缀表达式、中缀表达式、后缀表达式都是四则运算的表达方式</description><pubDate>Tue, 04 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;前,中,后缀表达式&lt;/h1&gt;
&lt;h2&gt;中缀表达式&lt;/h2&gt;
&lt;p&gt;中缀表达式就是我们常用的数学表达方式,按照&lt;strong&gt;操作符&lt;/strong&gt;在&lt;strong&gt;操作数****中间&lt;/strong&gt;的方式排列数学表达式&lt;/p&gt;
&lt;p&gt;例如:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a + b * ( c - d / e )
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;前缀表达式&lt;/h2&gt;
&lt;p&gt;前缀表达式又称&lt;strong&gt;波兰式&lt;/strong&gt;，前缀表达式的运算符位于操作数&lt;strong&gt;之前&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如中缀表达式&lt;code&gt;a+b*(c-d/e)&lt;/code&gt;的前缀表达式为:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ a b * c - d / e
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;中缀表达式转为前缀表达式&lt;/h3&gt;
&lt;p&gt;转换步骤如下:&lt;/p&gt;
&lt;p&gt;（1）初始化两个栈:运算符栈 s1，储存中间结果的栈 s2&lt;/p&gt;
&lt;p&gt;（2）从右至左扫描中缀表达式&lt;/p&gt;
&lt;p&gt;（3）遇到操作数时，将其压入 s2&lt;/p&gt;
&lt;p&gt;（4）遇到运算符时，比较其与 s1 栈顶运算符的优先级&lt;/p&gt;
&lt;p&gt;a：如果 s1 为空，或栈顶运算符为右括号 “ ) ”，则直接将此运算符入栈
b：否则，若优先级比栈顶运算符的较高或相等，也将运算符压入 s1
c：否则，将 s1 栈顶的运算符弹出并压入到 s2 中，再次转到 ( 4 - 1 ) 与 s1 中新的栈顶运算符相比
（5）遇到括号时&lt;/p&gt;
&lt;p&gt;a：如果是右括号“)”，则直接压入 s1
b：如果是左括号“(”，则依次弹出 s1 栈顶的运算符，并压入 s2 ，直到遇到右括号为止，此时将这一对括号丢弃
（6）重复步骤（2）至（5），直到表达式的最左边&lt;/p&gt;
&lt;p&gt;（7）将 s1 中剩余的运算符依次弹出并压入 s2&lt;/p&gt;
&lt;p&gt;（8）依次弹出 s2 中的元素并输出，结果即为中缀表达式对应的前缀表达式。&lt;/p&gt;
&lt;h2&gt;后缀表达式&lt;/h2&gt;
&lt;p&gt;后缀表达式又称&lt;strong&gt;逆波兰式&lt;/strong&gt;，后缀表达式的运算符位于操作数&lt;strong&gt;之后&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如中缀表达式&lt;code&gt;a+b*(c-d/e)&lt;/code&gt;的后缀表达式为:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a b c d e / - * +
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;中缀表达式转后缀表达式&lt;/h3&gt;
&lt;p&gt;转换步骤如下:&lt;/p&gt;
&lt;p&gt;（1）从左到右扫描中缀表达式。&lt;/p&gt;
&lt;p&gt;（2）若遇到操作数，则将其输出。&lt;/p&gt;
&lt;p&gt;（3）若遇到操作符 op：&lt;/p&gt;
&lt;p&gt;a:若操作符 op 的优先级高于栈顶操作符，或栈为空，则将 op 压入栈中。
b:否则，弹出栈顶操作符，并输出，直到遇到优先级小于或等于 op 的操作符，然后将 op 压入栈中。&lt;/p&gt;
&lt;p&gt;（4）若遇到左括号 (，则将其压入栈中。&lt;/p&gt;
&lt;p&gt;（5）若遇到右括号 )，则依次弹出栈顶操作符并输出，直到遇到左括号 ( 为止。弹出左括号但不出，仅为保持括号平衡。&lt;/p&gt;
&lt;h2&gt;根据二叉树转换表达式&lt;/h2&gt;
&lt;p&gt;中缀表达式&lt;code&gt;a+b*(c-d/e)&lt;/code&gt; 转换为二叉树为:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;img.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;前,中,后缀表达式分别对应二叉树的前,中,后序遍历的结果&lt;/p&gt;
</content:encoded></item><item><title>关于vue项目打包无法使用浏览器直接访问的问题</title><link>https://fuwari.vercel.app/posts/%E5%85%B3%E4%BA%8Evue%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85%E6%97%A0%E6%B3%95%E4%BD%BF%E7%94%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9B%B4%E6%8E%A5%E8%AE%BF%E9%97%AE%E7%9A%84%E9%97%AE%E9%A2%98/%E5%85%B3%E4%BA%8Evue%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85%E6%97%A0%E6%B3%95%E4%BD%BF%E7%94%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9B%B4%E6%8E%A5%E8%AE%BF%E9%97%AE%E7%9A%84%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E5%85%B3%E4%BA%8Evue%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85%E6%97%A0%E6%B3%95%E4%BD%BF%E7%94%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9B%B4%E6%8E%A5%E8%AE%BF%E9%97%AE%E7%9A%84%E9%97%AE%E9%A2%98/%E5%85%B3%E4%BA%8Evue%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85%E6%97%A0%E6%B3%95%E4%BD%BF%E7%94%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9B%B4%E6%8E%A5%E8%AE%BF%E9%97%AE%E7%9A%84%E9%97%AE%E9%A2%98/</guid><description>在使用GitHub Pages或其他静态网站托管部署vue项目时我发现不能访问具体表现为404报错，静态资源无法访问</description><pubDate>Fri, 31 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;关于vue项目打包无法使用浏览器直接访问的问题&lt;/h1&gt;
&lt;p&gt;在使用GitHub Pages或其他静态网站托管部署vue项目时我发现不能访问&lt;/p&gt;
&lt;p&gt;具体表现为404报错，静态资源无法访问&lt;/p&gt;
&lt;p&gt;对于vite构建的项目，可通过修改vite.config.js中的默认静态资源路径解决此问题（下图的base字段）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-thhv.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;另一个问题表现为无报错，但是显示空白，可能是路由的问题，需要将路由的history属性由createWebHistory函数改为createWebHashHistory函数（如下图）&lt;/p&gt;
&lt;p&gt;:::code-group&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const router = createRouter({
    history: createWebHashHistory(),
    routes,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;const router = createRouter({
    history: createWebHistory(),
    routes,    
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
</content:encoded></item><item><title>基于GitHub Action与Docker watchtower的CI/CD</title><link>https://fuwari.vercel.app/posts/%E5%9F%BA%E4%BA%8Egithub-action%E4%B8%8Edocker-watchtower%E7%9A%84cicd/%E5%9F%BA%E4%BA%8Egithub-action%E4%B8%8Edocker-watchtower%E7%9A%84cicd/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E5%9F%BA%E4%BA%8Egithub-action%E4%B8%8Edocker-watchtower%E7%9A%84cicd/%E5%9F%BA%E4%BA%8Egithub-action%E4%B8%8Edocker-watchtower%E7%9A%84cicd/</guid><description>持续集成和持续交付/持续部署的实践能够提高开发效率、减少错误，并使团队能够更快地交付高质量的软件产品</description><pubDate>Wed, 10 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;基于GitHub Action与Docker watchtower的CI/CD&lt;/h1&gt;
&lt;p&gt;需求来于近期开发的前端需要后端运行进行调试，所以想把后端服务部署在服务器上，但又因为后端开发迭代很快，所以就需要一种持续部署方式来解决此问题。&lt;/p&gt;
&lt;p&gt;首先先理解几个概念。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;项目CI/CD&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在互联网开发中，CI 和 CD 是两个常用的缩写词，分别代表持续集成（Continuous Integration）和持续交付/持续部署（Continuous Delivery/Continuous Deployment）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;持续集成（Continuous Integration，CI）：&lt;/strong&gt;
持续集成是一种开发实践，旨在通过频繁地将代码合并到主干分支，并进行自动化构建、测试和部署等操作，以确保团队成员的工作能够快速且无冲突地集成到共享代码库中。CI 的目标是尽早发现和解决代码集成问题，并提供可靠的构建和测试过程，以减少集成带来的风险。&lt;/p&gt;
&lt;p&gt;在 CI 中，开发者在完成某项工作后，会将代码变更提交到版本控制系统（如 Git），触发自动化构建和测试流程。这些流程通常包括编译代码、运行单元测试、进行静态代码分析等。如果构建和测试通过，代码变更就可以合并到共享代码库，否则需要开发者修复问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;持续交付/持续部署（Continuous Delivery/Continuous Deployment，CD）：&lt;/strong&gt;
持续交付和持续部署是指在 CI 的基础上，通过自动化流程将经过验证的代码变更交付给生产环境。&lt;/p&gt;
&lt;p&gt;**持续交付（Continuous Delivery）：**持续交付意味着代码变更通过了构建和测试，并已经准备好进行部署，但是部署的时机由人工决定。在持续交付中，开发团队可以随时选择将已验证的代码发布到生产环境，以快速响应用户需求或满足业务的要求。&lt;/p&gt;
&lt;p&gt;**持续部署（Continuous Deployment）：**持续部署是在持续交付的基础上更进一步，自动化地将经过验证的代码变更直接部署到生产环境，无需人工干预。在持续部署中，如果代码通过了构建和测试，它将立即被部署到生产环境，从而实现快速的软件发布。&lt;/p&gt;
&lt;p&gt;总结来说，&lt;strong&gt;CI&lt;/strong&gt; 是一种开发实践，旨在帮助团队更好地集成和验证代码，而 &lt;strong&gt;CD&lt;/strong&gt; 则进一步强调了对代码变更的自动化交付和部署。持续集成和持续交付/持续部署的实践能够提高开发效率、减少错误，并使团队能够更快地交付高质量的软件产品。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Github Actions&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt; 是一个持续集成（Continuous integration）和持续交付（Continuous delivery）的平台，它可以做到自动化构建、测试、部署。你可以创建工作流，构建和测试每一个 &lt;strong&gt;pull request&lt;/strong&gt; 或者部署合并后的代码到生产环境。GitHub Actions 可以在你的代码仓库发生某个事件时运行一个工作流。举个例子，当有人给你的项目提出issue时，可以自动通知团队成员等。GitHub 提供了 Linux、Windows、和 macOS 虚拟机运行你的工作流，当然你也可以自定义运行环境。&lt;/p&gt;
&lt;p&gt;在本文中主要用于使用Github Actions来进行项目CI，当有代码提交到指定分支时，会自动打包容器镜像并推送到docker hub，用于更新镜像仓库的镜像。&lt;/p&gt;
&lt;h2&gt;实现流程&lt;/h2&gt;
&lt;p&gt;采用docker部署，利用GitHub Action 在每次提交特定消息时进行镜像构建，再推送至docker hub；&lt;/p&gt;
&lt;p&gt;watchtower定时检查容器镜像是否更新，实现容器自动迭代升级。&lt;/p&gt;
&lt;h2&gt;GitHub Action&lt;/h2&gt;
&lt;p&gt;GitHub Actions 是一种持续集成和持续交付 (CI/CD) 平台，可用于自动执行生成、测试和部署管道。 你可以创建工作流程来构建和测试存储库的每个拉取请求，或将合并的拉取请求部署到生产环境。GitHub 提供 Linux、Windows 和 macOS 虚拟机来运行工作流程，或者可以在自己的数据中心或云基础架构中托管自己的自托管运行器。&lt;/p&gt;
&lt;p&gt;这里我以我项目的gin后端为例，首先项目需上传进GitHub仓库，随后点击仓库的Action&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-sprp.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;点击新增工作流&lt;code&gt;New Wrokflow&lt;/code&gt;,再找到&lt;strong&gt;&lt;em&gt;build and push docker container*&lt;strong&gt;这个action并点击&lt;/strong&gt;&lt;/em&gt;configure*&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;img src=&quot;image-blfn.png&quot; alt=&quot;img&quot; /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;它会在你仓库根目录创建.github/workflows文件夹来保存action&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-rsrc.png&quot; alt=&quot;img&quot; /&gt;你也可以直接在项目路径下新建action的yml文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-ngql.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我的需求是在commit中含有#docker-push字段时触发action，action自动将我commit 的后端代码打包成镜像并上传至docker hub。具体action代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#workflow名称
name: Build Test Environment Image
# 触发条件 在backend-dev分支上push时触发
on:
  push:
    branches:
      - backend-dev
#变量配置      
env:
  #dockerHub仓库名称
  DOCKER_REGISTRY: lancehe/go-simplewms-test

jobs:
  build-image:
    #运行的环境  
    runs-on: ubuntu-latest
    env:
      TZ: Asia/Shanghai
    steps:
      # 拉取代码
      - uses: actions/checkout@v3
      # 检查提交信息 当提交信息中包含 #docker-push 时，才构建并推送镜像
      - name: Check commit message
        id: check_message
        run: |
            if echo &quot;${{ github.event.head_commit.message }}&quot; | grep -q &quot;#docker-push&quot;; then
              echo &quot;::set-output name=RUN_TESTS::true&quot;
            else
              echo &quot;::set-output name=RUN_TESTS::false&quot;
            fi

      # 登录镜像仓库
      - name: Login Docker Hub
        if: steps.check_message.outputs.RUN_TESTS == &apos;true&apos;
        uses: docker/login-action@v2
        with:
          #这里引用仓库的secrets登录dockerhub
          username: ${{ secrets.DOCKER_USERNAME }} # dockerhub 用户名
          password: ${{ secrets.DOCKER_PASSWORD }} # dockerhub 密码
      # 打包构建并推送
      - name: Build and push
        if: steps.check_message.outputs.RUN_TESTS == &apos;true&apos;
        uses: docker/build-push-action@v4
        with:
          context: ./go-server # dockerfile位于go-server文件夹下
          dockerfile: Dockerfile
          platforms: |
            linux/amd64
          #推送到镜像仓库  
          push: true
          # 镜像标签
          tags: |
            ${{ env.DOCKER_REGISTRY }}:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;注：构建镜像的dockerfile需提前写好放在项目目录下&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;随后将写好的action推送至github仓库&lt;/p&gt;
&lt;p&gt;action在执行过程中会登录我的docker hub账号用于推送镜像，为了隐私安全GitHub提供了仓库secrets(类似于环境变量)用于保存我们的信息，并能让action读取。&lt;/p&gt;
&lt;p&gt;在仓库的setting中，点击&lt;em&gt;secrets and variables&lt;/em&gt;中的action来添加我们dokcer hub的用户名和密码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-zowe.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-zwha.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;至此，我们已经实现了当提交内容含有#docker-push触发action自动构建镜像并推送至docker hub的工作。&lt;/p&gt;
&lt;h2&gt;watchtower&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Watchtower&lt;/strong&gt; 是一款自由开源的容器应用，用来监控运行中的 Docker 容器，并且当它发现基础镜像被更改后，可以自动的更新容器。&lt;/p&gt;
&lt;p&gt;若 Watchtower 发现一个运行中的容器需要更新，它会以发送 SIGTERM 信号的方式，优雅的结束运行中容器的运行。&lt;/p&gt;
&lt;p&gt;它会下载新镜像，然后以最初部署时使用的方式，重启容器。所有文件会在后台自动下载，因此不需要用户的介入。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-boro.png&quot; alt=&quot;img&quot; /&gt;这里我已经将action构建的镜像部署在服务器，并命名为go-server，随后我们需要拉取watchtower镜像并配置它&lt;/p&gt;
&lt;p&gt;我们使用docker compose来部署，并让它只检查go-server这个容器，当更新完成时发送邮件提醒我们&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3&apos;
services:
  watchtower:
    image: containrrr/watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: go-server --interval 10 --cleanup # 只检查go-server容器，检查间隔10s， 删除旧镜像
    environment:
      - WATCHTOWER_NOTIFICATIONS=email # 提醒方式为发送邮件
      - WATCHTOWER_NOTIFICATION_EMAIL_FROM=2765543491@qq.com # 发送邮件使用的邮箱
      - WATCHTOWER_NOTIFICATION_EMAIL_TO=hycerlance@gmail.com # 接收邮件的邮箱
      - WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.qq.com # 发送邮件的smtp服务器
      - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587 # 服务器端口，默认为587
      - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=2765543491@qq.com # 发送邮箱登录账号
      - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=owxfbbuxrsoofdfsif # 邮箱授权码（不是邮箱密码需到官网设置）
      - WATCHTOWER_NOTIFICATION_EMAIL_DELAY=2 # 提醒延迟
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;docker-compose.yml文件配置完成后使用&lt;code&gt;docker-compose up -d&lt;/code&gt;即可运行容器&lt;/p&gt;
&lt;p&gt;我们可通过日志发现它在每隔10s检查容器的镜像是否更新&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-yqjr.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;至此基于GitHub Action与Docker watchtower的CI/CD就部署完成了&lt;/p&gt;
</content:encoded></item><item><title>利用Nginx实现端口转发</title><link>https://fuwari.vercel.app/posts/%E5%88%A9%E7%94%A8nginx%E5%AE%9E%E7%8E%B0%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/%E5%88%A9%E7%94%A8nginx%E5%AE%9E%E7%8E%B0%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E5%88%A9%E7%94%A8nginx%E5%AE%9E%E7%8E%B0%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/%E5%88%A9%E7%94%A8nginx%E5%AE%9E%E7%8E%B0%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/</guid><description>Nginx的端口转发来实现ip+端口与域名绑定</description><pubDate>Fri, 01 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;利用Nginx实现端口转发&lt;/h1&gt;
&lt;p&gt;起初需求源于：我有两台服务器，一台是我部署在家的无外网服务器，另一台为购买的阿里云vps。我的业务都运行在家庭服务器上，我通过frp内网穿透实现了从外网访问内网服务器的业务。但是，每次都需要通过ip+端口号来访问，非常的不方便。这时就需要Nginx的端口转发来实现ip+端口与域名绑定了。&lt;/p&gt;
&lt;h2&gt;Nginx的安装&lt;/h2&gt;
&lt;p&gt;nginx服务需安装在有公网的服务器上&lt;/p&gt;
&lt;p&gt;以Linux的Ubuntu系统为例，使用如下指令安装nginx服务&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后使用如下指令启动nginx服务&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl start nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后你可以通过一下命令查看nginx是否正常运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你看到&quot;&lt;code&gt;active (running)&lt;/code&gt;&quot;的状态，那么Nginx就已经成功启动了。&lt;/p&gt;
&lt;h2&gt;配置端口转发&lt;/h2&gt;
&lt;p&gt;Nginx的配置文件通常位于&lt;code&gt;/etc/nginx/&lt;/code&gt;目录下，主配置文件是&lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt;，但是通常我们会在&lt;code&gt;/etc/nginx/sites-available/&lt;/code&gt;目录下创建单独的配置文件，然后创建一个链接到&lt;code&gt;/etc/nginx/sites-enabled/&lt;/code&gt;目录，这样做的好处是可以方便地启用和禁用某个站点的配置。&lt;/p&gt;
&lt;p&gt;以下是一个基本的Nginx配置文件的例子，它将所有到你的域名的请求转发到本机的7000端口（你需要将这个端口改成你的frp内网穿透的端口）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 80;
    server_name your-domain.com; # 将这里改成你的域名

    location / {
        proxy_pass http://localhost:7000; # 将这里的7000改成你的frp内网穿透的端口
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你可以将这个配置文件保存为&lt;code&gt;/etc/nginx/sites-available/my-site&lt;/code&gt;，然后创建一个链接到&lt;code&gt;/etc/nginx/sites-enabled/&lt;/code&gt;目录：&lt;/p&gt;
&lt;p&gt;(这样是为了分离配置文件,易于维护)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/my-site /etc/nginx/sites-enabled/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，你需要重启Nginx来应用新的配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后将你的域名解析到nignx服务器上&lt;/p&gt;
&lt;p&gt;现在，你应该能够通过你的域名来访问你的服务了。&lt;/p&gt;
&lt;h2&gt;附言 安装SSL证书实现https访问&lt;/h2&gt;
&lt;p&gt;以上操作只能通过http访问对应的服务，如果想使用https又该怎么办呢？&lt;/p&gt;
&lt;h3&gt;通过certbot安装SSL证书&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://certbot.eff.org/&quot;&gt;certbot &lt;/a&gt;一个能免费安装SSL证书的网站&lt;/p&gt;
&lt;p&gt;在主页选择要安装的服务类型（nginx apache等等）和系统&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image1.png&quot; alt=&quot;img&quot; /&gt;随后网页会给出对应的安装方法，依次执行即可&lt;/p&gt;
</content:encoded></item><item><title>Linux系统常用命令</title><link>https://fuwari.vercel.app/posts/linux%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4/linux%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/linux%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4/linux%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</guid><description>记录常用Linux系统命令</description><pubDate>Mon, 12 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Linux系统常用指令&lt;/h1&gt;
&lt;h2&gt;Linux文件结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/bin        二进制文件，系统常规命令
/boot       系统启动分区，系统启动时读取的文件
/dev        设备文件
/etc        大多数配置文件
/home       普通用户的主目录
/lib        32位函数库
/lib64      64位库
/media      可移动设备(U盘)挂载点
/mnt        临时文件系统挂载点(光驱)
/opt        第三方软件安装位置
/proc       进程信息及硬件信息
/root       超级用户主目录
/sbin       系统管理命令
/srv        数据
/var        存放经常变化的文件(如日志)
/sys        内核相关信息
/tmp        临时文件
/usr        存放用户应用程序和文件
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;电源操作&lt;/h2&gt;
&lt;h3&gt;立刻关机&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;shutdown -h now
# 或者
poweroff
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;定时关机&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 两分钟后关机
shutdown -h 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;立刻重启&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;shutdown -r now
# 或者
reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;定时重启&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 两分钟后重启
shutdown -r 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;命令帮助&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;command --help 	# 查看command命令的用法

man command 	# 打开command命令的说明
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;用户和用户组操作&lt;/h2&gt;
&lt;h3&gt;用户切换&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;su hycer 		# 切换为hycer用户
exit 			# 退出当前用户

sudo -i 		# 切换为root用户
# 或者
su root
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;密码修改&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;passwd # root用户后面可跟用户名修改对应用户的密码，普通用户只能修改自己的密码
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;用户管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;useradd hycer   # 新增名为hycer的用户

userdel -r hycer # 删除名为hycer的用户账号以及主目录
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;用户组管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;groupadd hycer   # 新增名为hycer的用户组

groupdel hycer # 删除名为hycer的用户组
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;目录操作&lt;/h2&gt;
&lt;h3&gt;切换目录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cd 路径 	# 切换到路径
cd / 		# 切换到根目录
cd ~ 		# 切换到home目录
cd .. 		# 切换到父级目录
cd - 		# 切换到上次访问的目录
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看目录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  ls                   # 查看当前目录下的所有目录和文件（不包括隐藏的文件）
  ls -a                # 查看当前目录下的所有目录和文件（包括隐藏的文件）
  ls -l                # 列表查看当前目录下的所有目录和文件（列表查看，显示更多信息），与命令&quot;ll&quot;效果一样
  ls /bin              # 查看指定目录下的所有目录和文件 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;创建目录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  mkdir tools          # 在当前目录下创建一个名为tools的目录
  mkdir /bin/tools     # 在指定目录下创建一个名为tools的目录
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;删除目录或文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  rm 文件名              # 删除当前目录下的文件
  rm -f 文件名           # 删除当前目录的的文件（不询问）
  rm -r 文件夹名         # 递归删除当前目录下此名的目录
  rm -rf 文件夹名        # 递归删除当前目录下此名的目录（不询问）
  rm -rf *               # 将当前目录下的所有目录和文件全部删除

  rm -rf /*              # 将根目录下的所有文件全部删除【慎用！相当于格式化系统】
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;移动或重命名目录或文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  mv 当前目录名 新目录名      # 修改目录名，同样适用与文件操作
  mv /usr/tmp/tool /opt       # 将/usr/tmp目录下的tool目录剪切到 /opt目录下面
  mv -r /usr/tmp/tool /opt    # 递归剪切目录中所有文件和文件夹
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;拷贝目录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  cp /usr/tmp/tool /opt       # 将/usr/tmp目录下的tool目录复制到 /opt目录下面
  cp -r /usr/tmp/tool /opt    # 递归剪复制目录中所有文件和文件夹
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;搜索目录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  find /bin -name &apos;a*&apos;        # 查找/bin目录下的所有以a开头的文件或者目录
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看当前目录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pwd			# 显示当前位置路径
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;文件操作&lt;/h2&gt;
&lt;h3&gt;新增文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;touch  a.txt    # 在当前目录下创建名为a的txt文件（文件不存在），如果文件存在，将文件时间属性修改为当前系统时间
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;删除文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;rm 文件名              # 删除当前目录下的文件
rm -f 文件名           # 删除当前目录的的文件（不询问）
rm -r 文件夹		   # 递归删除文件夹下所有文件
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;编辑文件（vim）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;vi 文件名              # 打开需要编辑的文件

--进入后，操作界面有三种模式：命令模式（command mode）、插入模式（Insert mode）和底行模式（last line mode）
命令模式
-刚进入文件就是命令模式，通过方向键控制光标位置，
-使用命令&quot;dd&quot;删除当前整行
-使用命令&quot;/字段&quot;进行查找
-按&quot;i&quot;在光标所在字符前开始插入
-按&quot;a&quot;在光标所在字符后开始插入
-按&quot;o&quot;在光标所在行的下面另起一新行插入
-按&quot;：&quot;进入底行模式
插入模式
-此时可以对文件内容进行编辑，左下角会显示 &quot;-- 插入 --&quot;&quot;
-按&quot;ESC&quot;进入底行模式
底行模式
-退出编辑：      :q
-强制退出：      :q!
-保存并退出：    :wq
## 操作步骤示例 ##
1.保存文件：按&quot;ESC&quot; -&amp;gt; 输入&quot;:&quot; -&amp;gt; 输入&quot;wq&quot;,回车     # 保存并退出编辑
2.取消操作：按&quot;ESC&quot; -&amp;gt; 输入&quot;:&quot; -&amp;gt; 输入&quot;q!&quot;,回车     # 撤销本次修改并退出编辑

## 补充 ##
vim +10 filename.txt                   # 打开文件并跳到第10行
vim -R /etc/passwd                     # 以只读模式打开文件
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cat a.txt          # 查看文件最后一屏内容
less a.txt         # PgUp向上翻页，PgDn向下翻页，&quot;q&quot;退出查看
more a.txt         # 显示百分比，回车查看下一行，空格查看上一页，&quot;q&quot;退出查看
tail -100 a.txt    # 查看文件的后100行，&quot;Ctrl+C&quot;退出查看
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;文件权限&lt;/h2&gt;
&lt;h3&gt;权限说明&lt;/h3&gt;
&lt;p&gt;文件权限简介：&apos;r&apos; 代表可读（4），&apos;w&apos; 代表可写（2），&apos;x&apos; 代表执行权限（1），括号内代表&quot;8421法&quot;&lt;/p&gt;
&lt;p&gt;##文件权限信息示例：-rwxrw-r--&lt;/p&gt;
&lt;p&gt;-第一位：&apos;-&apos;就代表是文件，&apos;d&apos;代表是文件夹&lt;/p&gt;
&lt;p&gt;-第一组三位：拥有者的权限&lt;/p&gt;
&lt;p&gt;-第二组三位：拥有者所在的组，组员的权限&lt;/p&gt;
&lt;p&gt;-第三组三位：代表的是其他用户的权限&lt;/p&gt;
&lt;h3&gt;更改权限&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;chmod +x a.txt    
chmod 777 a.txt     # 1+2+4=7，&quot;7&quot;说明授予所有权限
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;更改用户组&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;chgrp 组 文件
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;更改所有者&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;chown 用户 文件
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;打包与解压&lt;/h2&gt;
&lt;h3&gt;拓展名说明&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;.zip、.rar        # windows系统中压缩文件的扩展名
.tar              # Linux中打包文件的扩展名
.gz               # Linux中压缩文件的扩展名
.tar.gz           # Linux中打包并压缩文件的扩展名
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;压缩文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;tar -zcvf 打包压缩后的文件名 要打包的文件
  # 参数说明：z：调用gzip压缩命令进行压缩; c：打包文件; v：显示运行过程; f：指定文件名;
  # 示例：
tar -zcvf a.tar file1 file2,...      # 多个文件压缩打包
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解压文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;tar -zxvf a.tar                      # 解包至当前目录
tar -zxvf a.tar -C /usr------        # 指定解压的位置
unzip test.zip             # 解压*.zip文件 
unzip -l test.zip          # 查看*.zip文件的内容 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;screen&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;screen -ls						# 查看所有screen会话
screen -S screenname			# 创建一个新的screen会话
screen -r screenname			# 回到一个screen会话
screen -S screenname -X quit	# 删除一个screen会话
ctrl + a, d						# 从一个screen会话中退出
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;top
htop							# 显示系统进程及资源占用情况

service ssh status 				# 查看服务状态
service ssh restart				# 重启服务

df -h 							# 显示文件系统的磁盘使用情况

uname -a						# 显示系统信息

fdisk -l						# 列出所有磁盘（包括U盘）的分区情况
mount /dev/sdb1 /mnt			# 将文件系统（U盘）挂载到/mnt路径下
umount /mnt						# 解除挂载

ifconfig						# 查看网络配置
iwconfig						# 查看网络设备(网卡)信息

ps -ef         					# 查看所有正在运行的进程

kill pid       					# 杀死该pid的进程
kill -9 pid    					# 强制杀死该进程   
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>华为RH2288V3服务器风扇转速调节</title><link>https://fuwari.vercel.app/posts/%E5%8D%8E%E4%B8%BArh2288v3%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%A3%8E%E6%89%87%E8%BD%AC%E9%80%9F%E8%B0%83%E8%8A%82/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E5%8D%8E%E4%B8%BArh2288v3%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%A3%8E%E6%89%87%E8%BD%AC%E9%80%9F%E8%B0%83%E8%8A%82/</guid><description>基于IBMC服务器后台调节风扇转速</description><pubDate>Wed, 23 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;华为RH2288V3服务器风扇转速调节&lt;/h1&gt;
&lt;p&gt;本篇将会介绍基于IBMC服务器后台调节风扇转速&lt;/p&gt;
&lt;p&gt;需要：网线一根，电脑一台（笔记本最好）&lt;/p&gt;
&lt;p&gt;注意：全程服务器须保持上电状态，可不开机&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;因博客迁移,导致文章图片缺失!!!&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;1.连接服务器&lt;/h3&gt;
&lt;p&gt;利用网线将服务器与个人电脑直接连接&lt;/p&gt;
&lt;p&gt;网线的服务器端应插在带有&lt;code&gt;Mgmt&lt;/code&gt;字样的接口上&lt;/p&gt;
&lt;h3&gt;2.设置电脑ip，使电脑能够访问服务器&lt;/h3&gt;
&lt;p&gt;打开网络和Internet，进入以太网设置&lt;/p&gt;
&lt;p&gt;编辑IP分配，改为手动分配ip地址&lt;/p&gt;
&lt;p&gt;设置ipv4地址如下图所示并保存&lt;/p&gt;
&lt;h3&gt;3.登录IBMC&lt;/h3&gt;
&lt;p&gt;win+r 打开运行窗口并输入 ‘cmd’ 打开命令行&lt;/p&gt;
&lt;p&gt;使用如下指令登录IBMC&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh root@192.168.2.100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果返回如下提示&lt;/p&gt;
&lt;p&gt;指令则需要添加参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -oKexAlgorithms=+diffie-hellman-group-exchange-sha1 root@192.168.2.100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时它会提示输入密码&lt;/p&gt;
&lt;p&gt;服务器默认密码为：&lt;code&gt;HuaWei#$&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;注意大小写，密码不会显示，输完按下回车即可&lt;/p&gt;
&lt;p&gt;看到&lt;code&gt;IBMC&lt;/code&gt;即代表登录成功&lt;/p&gt;
&lt;h3&gt;4.指令更改转速&lt;/h3&gt;
&lt;p&gt;输入指令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ipmcget -d faninfo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可查看当前风扇运行模式为auto&lt;/p&gt;
&lt;p&gt;输入指令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ipmcset -d fanmode -v 1 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置风扇为手动模式&lt;/p&gt;
&lt;p&gt;最后输入指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ipmcset -d fanlevel -v 20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调节风扇转速为正常的20%（最低20%）&lt;/p&gt;
&lt;p&gt;到此风扇转速调节完毕&lt;/p&gt;
&lt;p&gt;（完成后要将第2步的ip设置改回自动，否则插网线无法上网）&lt;/p&gt;
</content:encoded></item><item><title>使用treer生成文件夹结构</title><link>https://fuwari.vercel.app/posts/%E4%BD%BF%E7%94%A8treer%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E5%A4%B9%E7%BB%93%E6%9E%84/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E4%BD%BF%E7%94%A8treer%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E5%A4%B9%E7%BB%93%E6%9E%84/</guid><description>合理地组织项目文件夹结构对于维护项目的可读性和可扩展性至关重要</description><pubDate>Sun, 28 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;使用treer生成文件夹结构&lt;/h1&gt;
&lt;h2&gt;安装treer&lt;/h2&gt;
&lt;p&gt;要开始使用&lt;code&gt;treer&lt;/code&gt;，首先需要通过&lt;code&gt;npm&lt;/code&gt;（Node.js包管理器）全局安装它。在你的终端或命令提示符中运行以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install treer -g
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;在需要生成结构的文件夹内输入如下指令:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;treer -e ./structure.txt -i node_modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它就会将目录结构在保存在项目目录中&lt;code&gt;structure.txt&lt;/code&gt;中并且忽略&lt;code&gt;node_modules&lt;/code&gt;文件夹&lt;/p&gt;
&lt;h2&gt;效果演示&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Go_SimpleWMS\go-server
├─config.yaml 				
├─go.mod
├─go.sum
├─main.go 					
├─utils						
├─route						
|   ├─route.go 				
|   ├─group					
├─logs						
├─static					
├─handler					
├─database					
|    ├─myDb					
|    ├─model				
├─config					
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Python记账本</title><link>https://fuwari.vercel.app/posts/%E9%99%88%E5%B9%B4%E5%BE%80%E4%BA%8B/python%E8%AE%B0%E8%B4%A6%E6%9C%AC/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E9%99%88%E5%B9%B4%E5%BE%80%E4%BA%8B/python%E8%AE%B0%E8%B4%A6%E6%9C%AC/</guid><description>一个基于PySimpleGUI图形界面开发库写的记账本</description><pubDate>Sat, 14 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Python记账本&lt;/h1&gt;
&lt;p&gt;一个基于PySimpleGUI图形界面开发库写的记账本
使用时会在程序目录下生成一个data.txt文件用于存放数据
能简单的实现账单项目的录入及删除
内置导出Excel文件功能，可自定义导出路径并以导出时间命名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import json
import datetime
import PySimpleGUI as sg
import pandas as pd
import os
import xlwt
# import openpyxl

#读取数据
def read_data():
    try:
    with open(r&quot;data.txt&quot;, &quot;r&quot;) as f:
    json_data=f.read()
    datalist=json.loads(json_data)

        return datalist
    except:
        return []#当文本里面没有数据时返回空列表

#写入数据
def write_data(datalist):
    with open(r&quot;data.txt&quot;, &quot;w&quot;) as f:
    f.write(json.dumps(datalist, ensure_ascii=False))#防止改变中文编码格式

#显示数据
def show_data():
    try:
        data = read_data()
        datalist = []
        for i in data:
            if i[&quot;分类&quot;] == &quot;支出&quot;:
                data_list = [i[&quot;时间&quot;], i[&quot;项目&quot;], i[&quot;金额&quot;] * -1, i[&quot;分类&quot;]]
                datalist.append(data_list)
            else:
                data_list = [i[&quot;时间&quot;], i[&quot;项目&quot;], i[&quot;金额&quot;], i[&quot;分类&quot;]]
                datalist.append(data_list)
                return datalist
            except:
                return []

#总收入支出  1为收入0为支出
def amount_sum(cla):
    try:
        data = read_data()
        sum = 0
        if cla == 1:
            for i in data:
                if i[&quot;分类&quot;] == &quot;收入&quot;:
                    sum += i[&quot;金额&quot;]
        if cla == 0:
            for i in data:
                if i[&quot;分类&quot;] == &quot;支出&quot;:
                    sum += i[&quot;金额&quot;]
        return sum
    except:
        return 0#当文本里面没有数据时返回0

#添加数据
def add_data(project, amount, cla):
    datalist=read_data()
    time=datetime.datetime.now().strftime(&quot;%Y/%m/%d&quot;)
    data={&quot;时间&quot;: time, &quot;项目&quot;: project, &quot;金额&quot;: amount, &quot;分类&quot;: cla}
    datalist.append(data)
    write_data(datalist)
    sg.popup_auto_close(&apos;项目录入成功&apos;)  # 弹窗

#删除数据
def delete_data(index):
    datalist=read_data()
    del datalist[index]
    write_data(datalist)
    sg.popup_auto_close(&apos;删除成功&apos;)

#导出Excel文件
def export(path):
    with open(r&quot;data.txt&quot;, &quot;r&quot;) as f:
        data=f.read()
        f.close()
    with open(r&quot;data.json&quot;, &quot;w&quot;, encoding=&apos;utf-8&apos;) as f:
        f.write(data)
        f.close()
    json_data=pd.read_json(r&quot;data.json&quot;)
    json_data.to_excel(fr&apos;{path}/{datetime.datetime.now().strftime(&quot;%Y%m%d&quot;)}.xls&apos;)
    os.remove(r&quot;data.json&quot;)
    sg.popup(&quot;导出成功&quot;)

if __name__ == &apos;__main__&apos;:
    List=show_data()
    #图形界面布局列表
    sg.theme(&apos;LightBrown&apos;)
    layout=[
        [sg.T(&quot;账目列表：&quot;)]+[sg.B(&quot;导出Excel文件&quot;, key=&quot;-export-&quot;, pad=((390, 0), (0, 0)))],
        [sg.Table(List,
                headings=[&quot;时间&quot;, &quot;项目&quot;, &quot;金额&quot;, &quot;分类&quot;],
                key=&quot;-show_table-&quot;,
                justification=&quot;c&quot;,
                auto_size_columns=False,
                def_col_width=15,)],
        [sg.T(f&quot;总收入{amount_sum(1)}元  总支出{amount_sum(0)}元  结余:{amount_sum(1)-amount_sum(0)}元&quot;, key=&quot;-show_data-&quot;)],
        [sg.T(&quot;账单项目：&quot;), sg.In(key=&quot;-project-&quot;)],
        [sg.T(&quot;账单金额：&quot;), sg.In(key=&quot;-amount-&quot;)],
        [sg.T(&quot;项目分类：&quot;)] + [sg.Radio(i, group_id=1, key=i) for i in [&quot;收入&quot;, &quot;支出&quot;]],#单选框
        [sg.B(&quot;提交项目&quot;, key=&quot;-submit-&quot;)]+[sg.B(&quot;删除项目&quot;, key=&quot;-remove-&quot;, pad=((430, 0), (0, 0)))]
        
        ]

    window=sg.Window(&quot;记账本&quot;, layout, font=&apos;宋体 13&apos;)

    while True:
        event, values =window.read()#获取用户事件
        if event==&quot;-submit-&quot;:#用户触发提交数据

            #判断用户是否输入完整数据
            if values[&quot;-project-&quot;]==&apos;&apos; or values[&quot;-amount-&quot;]==&apos;&apos; or (values[&quot;收入&quot;]==False and values[&quot;支出&quot;]==False):
                sg.popup(&quot;请填写完整的数据&quot;)
                continue
            #判断用户输入金额是否为数字
            if not str.isdigit(values[&quot;-amount-&quot;]):
                sg.popup(&quot;请输入正确的金额&quot;)
                continue
            # 获取输入框中的值
            project=values[&quot;-project-&quot;]
            amount=float(values[&quot;-amount-&quot;])

            for k, v in values.items():
                if v == True:
                    cla=k
                    add_data(project, amount, cla)
                    List=show_data()
                    text=f&quot;总收入{amount_sum(1)}元  总支出{amount_sum(0)}元  结余:{amount_sum(1)-amount_sum(0)}元&quot;
                    window[&quot;-show_table-&quot;].update(values=List)
                    window[&quot;-show_data-&quot;].update(value=text)
                    #清空输入栏
                    window[&quot;-project-&quot;].update(value=&quot;&quot;)
                    window[&quot;-amount-&quot;].update(value=&quot;&quot;)
                    window[&quot;收入&quot;].ResetGroup()

        if event==&quot;-remove-&quot;:#用户触发删除数据
            #判断用户是否选中数据
            if values[&quot;-show_table-&quot;]:
                #确认删除弹窗
                confirm = sg.popup_ok_cancel(&quot;确认删除选中的项目&quot;)
                if confirm == &quot;OK&quot;:
                    delete_data(values[&quot;-show_table-&quot;][0])#删除数据
                    #更新表格
                    List = show_data()
                    text = f&quot;总收入{amount_sum(1)}元  总支出{amount_sum(0)}元  结余:{amount_sum(1) - amount_sum(0)}元&quot;
                    window[&quot;-show_table-&quot;].update(values=List)
                    window[&quot;-show_data-&quot;].update(value=text)
            else:
                sg.popup(&quot;请选中一个项目&quot;)

        if event==&quot;-export-&quot;:#导出Excel文件
            path=sg.popup_get_folder(&quot;保存路径:&quot;)

            if path ==&apos;&apos;:#判断路径是否为空
                sg.popup(&quot;请选择正确的路径&quot;)
                continue
            elif path is None:#防止用户因取消路径选择而关闭程序
                continue
            else:
                export(path)

        if event in (None, &apos;Exit&apos;):
            break
    window.close()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Python B站视频爬虫</title><link>https://fuwari.vercel.app/posts/%E9%99%88%E5%B9%B4%E5%BE%80%E4%BA%8B/python-b%E7%AB%99%E8%A7%86%E9%A2%91%E7%88%AC%E8%99%AB/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E9%99%88%E5%B9%B4%E5%BE%80%E4%BA%8B/python-b%E7%AB%99%E8%A7%86%E9%A2%91%E7%88%AC%E8%99%AB/</guid><description>简易的B站视频爬虫</description><pubDate>Mon, 11 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Python B站视频爬虫&lt;/h1&gt;
&lt;h2&gt;非大会员番剧爬虫&lt;/h2&gt;
&lt;p&gt;基于上一篇的视频爬虫改过来的，不能爬取大会员番剧，需要安装 &lt;strong&gt;&lt;code&gt;ffmpeg&lt;/code&gt;&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;需要填写番剧网址上的番剧编号并选择番剧的编号前缀&lt;/p&gt;
&lt;p&gt;在b站反爬更新之前应该有效&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# -*- coding = utf-8 -*-
# @File:anime_crawler.py
# @Author:Hycer_Lance
# @Time:2022/4/3 20:49
# @Software:PyCharm

import sys
import time
import requests
import re
import json
import subprocess
import os
import PySimpleGUI as sg

def select_mode():
    # 选择下载模式
    try:
        print(&apos;Select download mode&apos;)
        print(&apos;___1___    ___2___&apos;)
        print(&apos;Single     Multiple&apos;)
        download_mode = input(&apos;Select:&amp;gt;&apos;)
        if download_mode == &apos;2&apos;:
            num = int(input(&quot;Enter the number of episodes to download:&quot;))
        elif download_mode == &apos;1&apos;:
            num = 1
        else:
            print(&apos;No such mode&apos;)
            sys.exit()
        return num
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()

# 判断编号前缀
def determine_prefix():
    # 判断编号前缀
    try:
        number_type = input(&quot;Select number type (ep/ss):&quot;)
        if number_type == &apos;ep&apos;:
            flag = &apos;ep&apos;
        elif number_type == &apos;ss&apos;:
            flag = &apos;ss&apos;
        else:
            print(&quot;An unrecognized type&quot;)
            sys.exit()
        return flag
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()

# 获取网页源码
def get_html_res(flag, av):
    try:
        baseurl = &quot;https://www.bilibili.com/bangumi/play/&quot; + flag + av
        print(&quot;The video URL:&quot;, baseurl)
        html_res = requests.get(baseurl, headers=headers).text  # 获取网页源码
        # print(html_res)
        return html_res
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()

# 获取番剧信息
def get_playinfo(html_res):
    try:
        playinfo = re.findall(&quot;&amp;lt;script&amp;gt;window.__playinfo__=(.*?)&amp;lt;/script&amp;gt;&quot;, html_res)[0]  # 正则表达式提取音视频网址
        return playinfo
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()

# 转换成json文件
def conversion_json(playinfo):
    try:
        json_data = json.loads(playinfo)  # 转换为json数据
        return json_data
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()

# 获取番剧名字
def get_title1(html_res):
    try:
        title1 = re.findall(&apos;&amp;lt;title&amp;gt;(.*?)-番剧-全集-高清正版在线观看-bilibili-哔哩哔哩&amp;lt;/title&amp;gt;&amp;lt;meta name=&quot;description&quot;&apos;, html_res)[
            0]  # 提取标题 并取第一个元素（变成字符串）
        print(&quot;The video title:&quot;, title1)
        return title1
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()

# 提取资源地址
def get_video_url(json_data):
    try:
        video_url = json_data[&apos;data&apos;][&apos;dash&apos;][&apos;video&apos;][0][&apos;baseUrl&apos;]  # 提取视频地址
        print(&quot;Get video URL successfully&quot;)
        return video_url
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()

def get_audio_url(json_data):
    try:
        audio_url = json_data[&apos;data&apos;][&apos;dash&apos;][&apos;audio&apos;][0][&apos;baseUrl&apos;]  # 提取音频地址
        print(&quot;Get audio URL successfully&quot;)
        return audio_url
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()


# 下载数据
def download_data(url):
    try:
        get_data = requests.get(url=url, headers=headers).content  # 以二进制获取视频
        return get_data
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()


# 保存数据
def save_data(title2, video_data, audio_data):
    try:
        # 保存音视频
        print(&quot;Start writing video data...&quot;)
        with open(title2 + &apos;_video.mp4&apos;, mode=&apos;wb&apos;) as f:
            f.write(video_data)
        print(&quot;Write video data successfully&quot;)
        time.sleep(1)

        print(&quot;Start writing audio data...&quot;)
        with open(title2 + &apos;_audio.mp3&apos;, mode=&apos;wb&apos;) as f:
            f.write(audio_data)
        print(&quot;Write video data successfully&quot;)
        time.sleep(1)
    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()


# 合成音视频
def synthesize(title2, title1):
    try:
        # 利用子进程调用系统的ffmpeg合成音视频
        print(&quot;Start synthesizing audio and video...&quot;)
        # 用bv号命名合成放在因标题的非法字符导致命令报错
        subprocess.call(
            f&quot;ffmpeg -i {title2}_video.mp4 -i {title2}_audio.mp3 -c:v copy -c:a aac -strict experimental {title2}.mp4&quot;)
        os.rename(f&apos;{title2}.mp4&apos;, f&apos;{title1}.mp4&apos;)  # 给文件重命名
        print(&quot;Synthesize audio and video successfully&quot;)
        print(f&apos;The video {title1} is saved in &apos; + os.getcwd())
        time.sleep(2)

    except Exception as result:
        print(result)
        print(&quot;Please retry&quot;)
        sys.exit()


# 删除单独的音视频
def delete_res(title2):
    try:
        os.remove(os.getcwd() + f&apos;/{title2}_video.mp4&apos;)
        os.remove(os.getcwd() + f&apos;/{title2}_audio.mp3&apos;)  # 删除音视频
    except Exception as result:
        print(result)
        print(&quot;Failed to delete audio and video files, you can delete them manually&quot;)


if __name__ == &apos;__main__&apos;:
    # 选择下载模式
    num = select_mode()
    # 输入番剧编号
    av = input(&quot;Input start anime number:&quot;)
    # 判断编号前缀
    flag = determine_prefix()
    # 循环下载
    for i in range(num):
        # 请求头
        headers = {&quot;user-agent&quot;: &quot;Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) &quot;
                                 &quot;Chrome/100.0.4896.60 Safari/537.36&quot;,
                   &quot;referer&quot;: &quot;https://www.bilibili.com/bangumi&quot;}
        html_res = get_html_res(flag, av)  # 以文本形式获取网页源码

        title1 = get_title1(html_res)  # 正则获取番剧标题
        title2 = flag + av  # 用于给资源命名
        av = str(int(av) + 1)  # 多集下载时确定下一集的编号 一般是递增但有些番剧师乱序的
        playinfo = get_playinfo(html_res)  # 从网页源码中找到番剧信息
        json_data = conversion_json(playinfo)  # 将信息转为json数据格式
        video_url = get_video_url(json_data)  # 从json数据中获取视频地址
        audio_url = get_audio_url(json_data)  # 获取音频地址
        # 下载二进制文件
        print(&quot;Starting download video data...&quot;)
        video_data = download_data(video_url)
        print(&quot;Download video data successfully&quot;)
        print(&apos;--------------------------------&apos;)
        print(&quot;Starting download audio data...&quot;)
        audio_data = download_data(audio_url)
        print(&quot;Download audio data successfully&quot;)

        save_data(title2, video_data, audio_data)  # 保存数据
        synthesize(title2, title1)  # 合成音视频
        delete_res(title2)  # 删除单独的音视频


&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;B站视频爬虫&lt;/h2&gt;
&lt;p&gt;b站上的视频都是音视频分离的
电脑上需要安装&lt;code&gt;ffmpeg&lt;/code&gt;来合成下载下来的音视频
在b站反爬更新之前可以成功爬取视频不能爬番剧&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# -*- coding = utf-8 -*-
# @File:B_crawler.py
# @Author:Hycer_Lance
# @Time:2022/4/2 16:08
# @Software:PyCharm
import sys
import time

import requests
import re#正则表达式
import json
import pprint#格式化输出方便查看数据
import subprocess
import os

try:
    bv=input(&quot;BV:&quot;)
    baseurl=&quot;https://www.bilibili.com/video/&quot;+bv
    print(&quot;The video URL:&quot;, baseurl)
    headers={&quot;user-agent&quot;:&quot;Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36&quot;,
             &quot;referer&quot;: &quot;https://www.bilibili.com&quot;,
             &quot;cookie&quot;: &quot;buvid3=37DE417F-8350-C04A-154A-12B7B50B5ADF50216infoc; i-wanna-go-back=-1; b_ut=7; &quot;
                       &quot;_uuid=61023FFA5-163E-75A5-6B39-1010FD7D85B75463413infoc; &quot;
                       &quot;buvid4=AC529F3A-9AA5-3D76-C31D-2BD5C52F3B9A51558-022040218-OuD1af3VG92lX1kdOOSHzg%3D%3D; &quot;
                       &quot;CURRENT_BLACKGAP=0; blackside_state=1; rpdid=|(k|~JkmRJ~Y0J&apos;uYR))mYRYk; innersign=0; &quot;
                       &quot;b_lsid=8A8CEBAD_17FEF73F50C; fingerprint=b5f6c1bb5af1df68d53c77ffdeedfb8c; &quot;
                       &quot;buvid_fp_plain=undefined; SESSDATA=5f313593%2C1664541835%2C6f7a2%2A41; &quot;
                       &quot;bili_jct=ad3a2931c76e1b09b32821a684b482c7; DedeUserID=259424943; &quot;
                       &quot;DedeUserID__ckMd5=0c3254f433bfda4b; sid=at4dozdd; buvid_fp=b5f6c1bb5af1df68d53c77ffdeedfb8c; &quot;
                       &quot;CURRENT_FNVAL=4048; CURRENT_QUALITY=16; PVID=3&quot;}

    html_res=requests.get(baseurl, headers=headers).text#获取网页源码

    title1=re.findall(&apos;&amp;lt;h1 title=&quot;(.*?)&quot; class=&quot;video-title&quot;&amp;gt;&apos;, html_res)[0]#提取标题 并取第一个元素（变成字符串）
    print(&quot;The video title:&quot;, title1)
    title2=bv
except Exception as result:
    print(result)
    print(&quot;Please retry&quot;)
    sys.exit()

try:
    playinfo=re.findall(&quot;&amp;lt;script&amp;gt;window.__playinfo__=(.*?)&amp;lt;/script&amp;gt;&quot;, html_res)[0]#正则表达式提取音视频网址
    json_data=json.loads(playinfo)#转换为json数据
    # pprint.pprint(json_data)

    video_url=json_data[&apos;data&apos;][&apos;dash&apos;][&apos;video&apos;][0][&apos;baseUrl&apos;]#提取视频地址
    print(&quot;Get video URL successfully&quot;)
    audio_url=json_data[&apos;data&apos;][&apos;dash&apos;][&apos;audio&apos;][0][&apos;baseUrl&apos;]#提取音频地址
    print(&quot;Get audio URL successfully&quot;)
except Exception as result:
    print(result)
    print(&quot;Please retry&quot;)
    sys.exit()

try:
    print(&quot;Start downloading video data...&quot;)
    get_video=requests.get(url=video_url, headers=headers).content#以二进制获取视频
    print(&quot;Download video data successfully&quot;)
    time.sleep(1.5)

    print(&quot;Start downloading audio data...&quot;)
    get_audio=requests.get(url=audio_url, headers=headers).content#以二进制获取音频
    print(&quot;Download video data successfully&quot;)
    time.sleep(1.5)
except Exception as result:
    print(result)
    print(&quot;Please retry&quot;)
    sys.exit()

try:
    #保存音视频
    print(&quot;Start writing video data...&quot;)
    with open(title2+&apos;_video.mp4&apos;, mode=&apos;wb&apos;) as f:
        f.write(get_video)
    print(&quot;Write video data successfully&quot;)
    time.sleep(1)

    print(&quot;Start writing audio data...&quot;)
    with open(title2+&apos;_audio.mp3&apos;, mode=&apos;wb&apos;) as f:
        f.write(get_audio)
    print(&quot;Write video data successfully&quot;)
    time.sleep(1)
except Exception as result:
    print(result)
    print(&quot;Please retry&quot;)
    sys.exit()

try:
    #利用子进程调用系统的ffmpeg合成音视频
    print(&quot;Start synthesizing audio and video...&quot;)
    #用bv号命名合成放在因标题的非法字符导致命令报错
    subprocess.call(f&quot;ffmpeg -i {title2}_video.mp4 -i {title2}_audio.mp3 -c:v copy -c:a aac -strict experimental {title2}.mp4&quot;)
    os.rename(f&apos;{title2}.mp4&apos;, f&apos;{title1}.mp4&apos;)#给文件重命名
    print(&quot;Synthesize audio and video successfully&quot;)
    print(f&apos;The video {title1} is saved in &apos;+os.getcwd())
    time.sleep(2)

except Exception as result:
    print(result)
    print(&quot;Please retry&quot;)
    sys.exit()

try:
    os.remove(os.getcwd() +f&apos;/{title2}_video.mp4&apos;)
    os.remove(os.getcwd() +f&apos;/{title2}_audio.mp3&apos;)# 删除音视频
except Exception as result:
    print(result)
    print(&quot;Failed to delete audio and video files, you can delete them manually&quot;)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Python源码文件打包exe文件</title><link>https://fuwari.vercel.app/posts/%E9%99%88%E5%B9%B4%E5%BE%80%E4%BA%8B/python%E6%BA%90%E7%A0%81%E6%96%87%E4%BB%B6%E6%89%93%E5%8C%85exe%E6%96%87%E4%BB%B6/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E9%99%88%E5%B9%B4%E5%BE%80%E4%BA%8B/python%E6%BA%90%E7%A0%81%E6%96%87%E4%BB%B6%E6%89%93%E5%8C%85exe%E6%96%87%E4%BB%B6/</guid><description>软件项目、产品版本号定义规范</description><pubDate>Wed, 22 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Python源码文件打包exe文件&lt;/h1&gt;
&lt;h2&gt;安装pyinstaller&lt;/h2&gt;
&lt;p&gt;需要用到的是&lt;code&gt;pyinstaller&lt;/code&gt; 默认安装的Python是没有这个模块的，需要手动安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install pyinstaller
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在控制台输入如上指令安装&lt;code&gt;pyinstaller&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;打包&lt;/h2&gt;
&lt;p&gt;随后在控制台中进入你程序源码所在的文件夹&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd 路径
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后执行命令开始打包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyinstaller -F -w -i xx.ico xxx.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-F&lt;/code&gt; 产生单个的可执行文件&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-w&lt;/code&gt; 指定程序运行时不产生命令行窗口&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-i&lt;/code&gt; 更换生成的exe文件图标（如果不想换就可以不加）&lt;/p&gt;
&lt;p&gt;xx.ico 需要更换的图标，需与源码在同一文件夹下或填写完整的图片路径（图片如果是非ico格式需要转换成ico格式才能成功）&lt;/p&gt;
&lt;p&gt;更多的执行参数自行百度&lt;/p&gt;
&lt;p&gt;打包完成后会在源码目录下生成build ， dist文件夹&lt;/p&gt;
&lt;p&gt;另外的pycache 和.spec个人觉得无关紧要（应该不会影响程序的运行）&lt;/p&gt;
&lt;p&gt;dist文件夹下就是打包好的exe程序，build是程序的依赖文件&lt;/p&gt;
</content:encoded></item><item><title>Python爬虫 图片api 批量下载</title><link>https://fuwari.vercel.app/posts/%E9%99%88%E5%B9%B4%E5%BE%80%E4%BA%8B/python%E7%88%AC%E8%99%AB-%E5%9B%BE%E7%89%87api-%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/%E9%99%88%E5%B9%B4%E5%BE%80%E4%BA%8B/python%E7%88%AC%E8%99%AB-%E5%9B%BE%E7%89%87api-%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD/</guid><description>一个通过图片api批量下载图片的爬虫</description><pubDate>Wed, 22 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Python爬虫 图片api 批量下载&lt;/h1&gt;
&lt;p&gt;只能爬图片api！！，较稳定&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import urllib.request
import random
import time
import ssl
ssl._create_default_https_context=ssl._create_unverified_context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;导包，取消验证ssl证书，否则部分网站无法爬取&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;URL=input(&quot;输入爬取图片的网址:&amp;gt;&quot;)
n=input(&quot;输入爬取图片的数量:&amp;gt;&quot;)
n=int(n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用户输入默认为字符串，强制转化为整型数&lt;/p&gt;
&lt;p&gt;定义变量用于下载循环&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i in range (n):
    order =lambda : int (round(time.time()*1000*1000))
    #lambda关键字，声明一个匿名函数，返回的时间数值用于图片命名
    response =urllib.request.urlopen(URL)
    #向目标URL发送请求
    cat_img = response.read()
    #接收返回值（二进制）

    time.sleep(2)
    #等待2s
    print (&quot;Download The Picture Successfully and Named:&quot;+str(order()))
    print (&quot;Completed:&quot;,round(i/n*100),&quot;%&quot;)

    #打印进度
    i +=1
    with open (str(order())+&quot;.jpg&quot;,&quot;wb&quot; ) as f:
        f.write(cat_img)
    #将接受到的内容读取到内存并用jpg格式写入文件夹
    # wb:以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在，创建新文件。

print (&quot;Download Completed&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载循环&lt;/p&gt;
</content:encoded></item></channel></rss>