-
AppImage 是 Linux 系统中一种新型的软件包格式,它与 rpm、deb 这些软件包格式相比最大的不同便是:(1)无需安装,即用即删。(2)只需打包一次,便可到处运行。完美的解决了不同 Linux 发行版(Ubuntu/Debian/Fedora/CentOS)之间软件包不统一的问题。它的工作原理便是将程序运行所需的文件全部打包在一个文件中,待程序运行时再将这些文件提取在 /tmp/.mount_xxxxxxx/ 目录中,然后执行 AppRun 脚本启动程序以进行资源的调用。以下便是一个 AppImage 文件内部包含的目录树结构:AppDir/ ├── AppRun ├── 应用图标.png ├── 程序名.desktop ├── usr/ ├── bin/ ├── lib/ ├── share/它本质上就是一个 squashfs 文件系统 + runtime 执行器。特别注意:(1)要实现跨平台运行,待打包的程序最好是在 CentOS 7 系统上进行编译 ,然后再进行打包。【注:编译 C/C++ 程序所使用的系统库 glibc 在 Linux 系统上几乎肯定存在,而该库有着良好的向后兼容性,因此使用旧版本的 glibc 库编译出来的程序几乎可以完美的运行在新版本的 glibc 系统上。而在 CentOS 7 上的 glibc 版本是 2.17,该版本较旧且兼容性较好,因此在其系统上编译出来的 C 程序通常也可以在大部分的 Linux 发行版系统中使用。】(2)待打包程序依赖的 lib 文件中最好只包含其专属的库文件即可,不要包含类似 glibc 这样的系统库文件。【注:这是因为在 A 系统中的 glibc 文件通常并不可以在 B 系统中使用,因此为了避免 AppImage 程序运行错误,请勿这样去做。再者,glibc 在 Linux 系统中是肯定会存在的,因此也并不需要额外去包含这样的依赖文件。】手动打包 - appimagetool linuxdeploy是由 AppImage 官方制作的打包工具,在使用它进行打包时,必须要先分析待打包程序的动态库依赖情况,然后再完成对 AppDir 目录的装填,最后才能使用 appimagetool 完成对程序的打包。由于分析程序的依赖情况是个很复杂的问题,因此该工具在使用上体验并不太好。接下来,我将演示如何对一个简单的 C 程序完成打包过程:(1)文件准备:hello.c。// 主文件 hello.c#include <stdio.h>int main() { printf("Hello Appimage\n"); return 0;}(2)编译并打包#(1)编译及检验运行gcc -o hello hello.c./hello#(2)制作 AppDir 目录树mkdir -p AppDir/usr/bin/cp ./hello AppDir/usr/bin/wget https://github.com/boolean-world/appimage-resources/blob/master/hello-world-appimage/hello-world-icon.png -O AppDir/hello.png #任意图片文件即可nano AppDir/hello.desktop #文件内容见下方nano AppDir/AppRun #脚本内容见下方#(3)开始制作 AppImage 程序/root/appimagetool-x86_64.AppImage AppDir/附注:hello.desktop 文件如下:[Desktop Entry]Name=helloExec=helloIcon=helloType=ApplicationCategories=Utility;Terminal=trueAppRun 脚本如下:#!/bin/shAPPDIR="$(dirname "$(readlink -f "$0")")"# 添加库目录if [ -d "$APPDIR/lib64" ]; then export LD_LIBRARY_PATH="$APPDIR/lib64:$LD_LIBRARY_PATH"fiif [ -d "$APPDIR/usr/lib" ]; then export LD_LIBRARY_PATH="$APPDIR/usr/lib:$LD_LIBRARY_PATH"fi# 启动主程序 //注意:不同应用主程序路径需要修改exec "$APPDIR/usr/bin/hello" "$@"AppDir 目录树结构如下:AppDir/├── AppRun //启动程序,可以是简单的脚本,也可以是 ELF,只要保证运行该脚本主程序能被启动即可。├── hello.desktop //注意 EXEC 的值,它对应的是/usr/bin/目录中的程序,而 Icon 对应的是当前目录├── hello.png //也支持 svg 格式└── usr └── bin └── hello自动打包 - linuxdeploylinuxdeploy是一个由第三方制作的 AppImage 打包工具,与 appimagetool 不同的是,它可以对待打包程序自动进行依赖分析,并自动将所需的依赖及资源文件按照 AppDir 的目录格式给装填完毕,用户只需将模版化的 desktop 文件和 icon 文件准备好即可,使用起来简直美滋滋。【示例一】:接下来,我将演示如何对一个需要依赖的简单 C 程序完成打包过程:(1)文件准备:mylib.h、mylib.c、main.c、Makefile。// 动态库头文件 mylib.h#ifndef MYLIB_H#define MYLIB_Hint add(int a, int b);void hello();#endif// 动态库源码 mylib.c#include <stdio.h>#include "mylib.h"int add(int a, int b) { return a + b;}void hello() { printf("Hello from my dynamic library!\n");}// 主程序 main.c#include <stdio.h>#include "mylib.h"int main() { hello(); int result = add(3, 5); printf("3 + 5 = %d\n", result); return 0;}# Makefile 文件CC=gccCFLAGS=-fPIC -WallLDFLAGS=-sharedTARGET_LIB=libmylib.soTARGET_MAIN=mainall: $(TARGET_LIB) $(TARGET_MAIN)$(TARGET_LIB): mylib.o $(CC) $(LDFLAGS) -o $(TARGET_LIB) mylib.omylib.o: mylib.c mylib.h $(CC) $(CFLAGS) -c mylib.c$(TARGET_MAIN): main.o $(TARGET_LIB) $(CC) main.o -L. -lmylib -o $(TARGET_MAIN)main.o: main.c mylib.h $(CC) -c main.cclean: rm -f *.o $(TARGET_MAIN) $(TARGET_LIB)(2)编译并打包#(1)编译及检验运行cd myappmakemv libmylib.so /lib64/libmylib.so./main#(2)制作的 main.desktop 文件内容cat main.desktop[Desktop Entry]Name=mainExec=mainIcon=mainType=ApplicationCategories=Utility;Terminal=true#(3)获取一个 Icon 文件wget https://github.com/boolean-world/appimage-resources/blob/master/hello-world-appimage/hello-world-icon.png -O main.png#(4)开始制作 AppImage 程序/root/linuxdeploy-x86_64.AppImage --appdir /root/myapp --output appimage --icon-file main.png --desktop-file main.desktop -e mainls -l main*.AppImage 【示例二】:最后,我再演示如何对一个系统命令 find 完成打包过程:#(1)制作的 find.desktop 文件内容cat find.desktop[Desktop Entry]Name=findExec=findIcon=findType=ApplicationCategories=Utility;Terminal=true#(2)获取一个 Icon 文件wget https://github.com/boolean-world/appimage-resources/blob/master/hello-world-appimage/hello-world-icon.png -O find.png#(3)开始制作 AppImage 程序cd $(dirname $(which find))/root/linuxdeploy-x86_64.AppImage --appdir /root/find --output appimage --icon-file find.png --desktop-file find.desktop -e findls -l find*.AppImage 注意:(1)建议将 icon 和 desktop 文件放置在 find 命令根目录下,这样在打包的时候能够避免很多问题。(2)由于 linuxdeploy 在打包环节调用的是 appimagetool,而 appimagetool 在打包的时候会在 github 上拉取 runtime 文件,因此在使用前建议设置全局代理以确保 github 可访问。(*)全局代理设置export http_proxy=http://192.168.56.1:7890export https_proxy=http://192.168.56.1:7890export no_proxy=192.168.56.1,localhostexport HTTP_PROXY=http://192.168.56.1:7890export HTTPS_PROXY=http://192.168.56.1:7890export NO_PROXY=192.168.56.1,localhost文章来源:https://www.cnblogs.com/kqdssheng/p/19269888
-
在软件开发的演进长河中,我们始终在追求一个理想状态:一次编写,随处运行。从 Java 的“Write Once, Run Anywhere”到 .NET 的跨平台战略,再到容器化与云原生的兴起,这一目标不断被重新定义。而今天,一项名为 WebAssembly(简称 Wasm) 的技术正以惊人的速度重塑软件分发、执行与安全模型——它不仅让高性能应用在浏览器中成为可能,更正在成为操作系统之上的通用轻量级运行时,悄然开启“后容器时代”的序幕。一、什么是 WebAssembly?不只是浏览器的“加速器”WebAssembly 是一种低级的、可移植的字节码格式,设计目标是在现代 Web 浏览器中以接近原生的速度执行代码。它于 2017 年由 W3C 联合 Mozilla、Google、Microsoft 和 Apple 正式标准化,如今已被所有主流浏览器支持。但 WebAssembly 的意义远不止于此:Wasm 不是 JavaScript 的替代品,而是其高性能补充;更重要的是,它正在脱离浏览器,成为通用的沙箱化运行时。核心特性特性说明接近原生性能预编译为紧凑字节码,JIT 编译后执行效率达 C/C++ 的 90%+语言无关支持 Rust、C/C++、Go、Python(实验性)、TypeScript 等编译为 Wasm安全沙箱默认无文件/网络访问权限,能力通过“导入”显式授予快速启动毫秒级冷启动,远优于 JVM 或容器跨平台同一字节码可在 Windows、Linux、macOS、甚至嵌入式设备运行二、从浏览器到全栈:Wasm 的三大演进阶段阶段 1:浏览器中的高性能模块(2017–2020)典型应用:Figma(设计工具)、AutoCAD Web、视频编辑器价值:将计算密集型任务(如图像处理、物理引擎)从 JS 迁移至 Wasm,提升流畅度// Rust 示例:计算斐波那契数列(编译为 Wasm)#[no_mangle]pub extern "C" fn fibonacci(n: u32) -> u32 { if n <= 1 { return n; } fibonacci(n - 1) + fibonacci(n - 2)}阶段 2:服务端 Wasm(2020–2023)随着 WASI(WebAssembly System Interface) 的提出,Wasm 获得了访问文件、网络、时间等系统能力的标准接口,从而走出浏览器。代表项目:Wasmtime(Bytecode Alliance):独立 Wasm 运行时WasmEdge:面向边缘和 Serverless 优化Deno:内置 Wasm 支持的 JS/TS 运行时阶段 3:通用插件与函数运行时(2023–至今)Wasm 正成为安全、轻量、跨语言插件系统的事实标准:场景案例数据库扩展SingleStore、FerretDB 允许用 Wasm 编写 UDFAPI 网关插件Envoy Proxy 支持 Wasm 过滤器Serverless 函数Shopify Functions、Cloudflare Workers区块链智能合约CosmWasm(Cosmos 生态)CLI 工具插件HashiCorp Vault、Terraform 插件实验三、实战:用 Rust 编写一个 Wasm Serverless 函数我们将创建一个简单的 HTTP 处理函数,部署到 WasmEdge 运行时。1. 初始化项目(使用 cargo-wasi)cargo new --lib hello-wasmcd hello-wasm2. 修改 Cargo.toml[package]name = "hello-wasm"version = "0.1.0"edition = "2021"[lib]crate-type = ["cdylib"] # 编译为动态库(Wasm 模块)[dependencies]wasmedge-bindgen = "0.4"wasmedge-macro = "0.4"3. 编写函数逻辑// src/lib.rsuse wasmedge_bindgen::*;use wasmedge_macro::*;#[async_func]pub fn handle_request(request: Vec<u8>) -> Vec<u8> { let name = std::str::from_utf8(&request).unwrap_or("Guest"); format!("Hello, {}! Time: {}", name, chrono::Utc::now()).into_bytes()}4. 编译为 Wasmcargo build --target wasm32-wasi --release# 输出: target/wasm32-wasi/release/hello_wasm.wasm5. 在 WasmEdge 中运行# 安装 WasmEdgecurl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash# 启动 HTTP 服务wasmedge --http-addr=0.0.0.0:3000 hello_wasm.wasm现在访问 http://localhost:3000/Alice,将返回:Hello, Alice! Time: 2025-11-17 08:30:45 UTC优势:冷启动 < 10ms内存占用 < 5MB自动沙箱隔离,无法访问主机文件系统四、Wasm vs 容器:为何它是“后容器时代”的答案?维度Docker 容器WebAssembly启动速度秒级毫秒级内存开销100MB+1–10MB镜像大小100MB–1GB100KB–10MB安全模型基于 Linux Namespace/cgroups基于能力的安全(Capability-based)多语言支持需打包完整运行时共享统一运行时冷启动成本高(Serverless 痛点)极低趋势:在边缘计算、FaaS(Function as a Service)、微服务插件等场景,Wasm 正逐步替代轻量级容器。 结语:Wasm 不是未来,而是现在WebAssembly 已从“浏览器性能补丁”蜕变为新一代软件交付与执行的通用基座。它以极致的轻量、安全与跨平台能力,解决了云原生时代的关键痛点:如何在保障安全的前提下,实现极致的资源效率与启动速度。💡 给开发者的行动建议:前端工程师:尝试用 Wasm 优化图像/音视频处理后端工程师:评估 WasmEdge/Wasmtime 作为 FaaS 运行时系统架构师:在插件系统、边缘节点中引入 Wasm 沙箱区块链开发者:关注 CosmWasm、Substrate Wasm 智能合约正如当年 JavaScript 将浏览器变成应用平台,WebAssembly 正在将整个计算世界变成一个安全、高效、可组合的函数网络。这一次,舞台不再局限于浏览器——而是无处不在。
-
11月技术干货合集分享:1、 Linux使用waitpid回收多个子进程的方法小结 — 转载cid:link_02、 Linux获取子进程退出值和异常终止信号的完整指南 — 转载cid:link_1PostgreSQL 安装部署及配置使用教程 — 转载cid:link_34、 MongoDB分片模式集群部署方案详解 — 转载cid:link_4redis缓存神器之@Cacheable注解详解 — 转载cid:link_55、通过Redisson监听Redis集群的Key过期事件的实现指南 — 转载cid:link_66、Oracle数据库空间回收从诊断到优化实战指南详细教程 — 转载cid:link_77、MySQL EXPLAIN详细解析 — 转载cid:link_88、Linux使用wait函数回收子进程的操作指南 — 转载cid:link_99、Nginx 中的Rewrite 使用示例详解 — 转载cid:link_1010、 Linux系统日志持久化配置的完整指南 — 转载cid:link_1111、 PostgreSQL中pg_surgery的扩展使用 — 转载cid:link_1212、PostgreSQL扩展bloom的具体使用 — 转载cid:link_1313、PostgreSQL扩展UUID-OSSP的使用方法 — 转载cid:link_214、MySQL深度分页优化的常用策略 — 转载cid:link_1415、Linux使用du和sort命令查找最大文件和目录 — 转载https://bbs.huaweicloud.com/forum/thread-0250198135428299001-1-1.html
-
JFR(Java Flight Recorder)是 JDK 内置的高性能诊断工具,以极低开销记录 JVM 和应用运行时的关键事件。然而,不当配置可能导致录制开销升高、文件过大或分析困难。尤其在高并发、长时间运行的生产环境(如鲲鹏 ARM64 服务器)中,合理优化 JFR 的使用策略至关重要。本文将从 录制配置、资源控制、事件筛选、分析效率 四个维度,系统讲解如何优化 JFR 的性能表现。一、核心原则:平衡“数据完整性”与“运行开销”JFR 的默认配置(profile.jfc)已针对通用场景做了权衡,但实际业务需根据目标调整:目标推荐策略长期监控(7×24)仅启用关键事件(GC、线程、CPU 采样),降低采样频率故障复现启用详细事件(方法、异常、I/O),短时间高保真录制性能压测对比使用统一配置,确保数据可比性二、优化录制阶段的性能1. 选择合适的预设模板JDK 提供两个内置模板:default.jfc:基础事件(低开销,适合长期运行)profile.jfc:包含方法采样、锁竞争等(中等开销,适合性能分析)建议:# 长期监控用 default-XX:StartFlightRecording=settings=default,duration=1h,filename=/data/jfr/low.jfr# 深度分析用 profile-XX:StartFlightRecording=settings=profile,duration=5m,filename=/data/jfr/high.jfr 毕昇 JDK 还提供 kunpeng-optimized.jfc(如有),可进一步适配 ARM64。2. 自定义 .jfc 配置文件(推荐)复制并修改模板,关闭非必要事件:<!-- custom-low-overhead.jfc --><configuration ...> <event name="jdk.MethodSampling"> <setting name="enabled">false</setting> <!-- 关闭方法采样 --> </event> <event name="jdk.JavaMonitorEnter"> <setting name="enabled">true</setting> <setting name="threshold">10ms</setting> <!-- 仅记录 >10ms 的锁等待 --> </event> <event name="jdk.GCPhasePause"> <setting name="enabled">true</setting> </event></configuration>启动时指定:-XX:StartFlightRecording=settings=/path/to/custom-low-overhead.jfc,...3. 控制录制时长与文件大小避免无限制录制导致磁盘爆满:# 方式1:固定时长duration=10m# 方式2:循环录制(保留最近数据)maxsize=500MB,maxage=1h# 方式3:条件触发(JDK 17+ 支持)-XX:FlightRecorderOptions:repository=/tmp/jfr-cache生产建议:单文件 ≤ 1GB总录制时长 ≤ 30 分钟(除非明确需要长期趋势)4. 调整采样频率(降低 CPU 开销)关键参数:jdk.ThreadCPULoad:线程 CPU 采样间隔(默认 1s)jdk.ExecutionSample:方法栈采样间隔(默认 10ms)在自定义 .jfc 中调整:<event name="jdk.ExecutionSample"> <setting name="period">100ms</setting> <!-- 从 10ms 放宽到 100ms --></event> 注意:采样间隔越长,热点方法识别精度越低。三、减少 I/O 与内存开销1. 使用高速存储路径将 .jfr 文件写入 SSD 或内存盘:filename=/dev/shm/app.jfr # 写入 tmpfs(内存文件系统) 优势:避免磁盘 I/O 成为瓶颈 风险:重启丢失,需及时备份2. 启用压缩(JDK 17+)-XX:FlightRecorderOptions=compress=true可减少 30%~50% 文件体积。3. 避免多进程同时写同一目录每个 Java 进程应使用独立子目录,防止文件锁竞争。四、优化分析阶段的效率1. 使用命令行快速筛查(避免 GUI 开销)# 查看 GC 暂停总时间jfr print --events GCPhasePause app.jfr | grep "duration"# 统计最耗时的 10 个方法jfr summary app.jfr --category "Code" | head -n 102. 在分析机而非生产机运行 JMC将 .jfr 文件拷贝至开发机或专用分析服务器;避免在生产环境启动图形界面工具。3. 使用脚本自动化分析结合 jfr 命令 + Shell/Python 脚本,实现:自动提取关键指标生成性能报告触发告警(如 GC 暂停 > 100ms)示例脚本片段:MAX_PAUSE=$(jfr print --events GCPhasePause app.jfr | awk '/duration/ {print $2}' | sort -nr | head -1)if [ "$MAX_PAUSE" -gt 100000000 ]; then # 100ms in nanoseconds echo "ALERT: Max GC pause exceeds 100ms!"fi五、鲲鹏 ARM64 环境下的特别建议优先使用毕昇 JDK其 JFR 实现针对鲲鹏处理器的缓存、NUMA 架构优化,事件采集效率更高。关注 ARM64 特有事件如:jdk.CPULoad:ARM64 大小核调度可能影响 CPU 利用率jdk.NativeLibrary:验证 native 库是否为 ARM64 编译对比 x86 基线在相同负载下,分别录制 x86 与鲲鹏的 JFR 数据,使用 JMC 的 Compare 功能 定位架构差异点。
-
一、系统日志存储机制概述1.1 默认存储行为在红帽企业Linux 9中,系统日志默认存储在/run/log目录中。重要特性:易失性存储:系统重启后自动清除内存存储:/run文件系统仅存在于运行时内存性能优先:读写速度快,但不持久1.2 为什么需要持久化日志?默认配置的问题:系统重启后无法查看历史日志故障排查时缺少关键历史信息无法进行安全审计和历史分析二、配置持久化系统日志2.1 核心配置文件持久化配置通过/etc/systemd/journald.conf文件实现,主要修改Storage参数:12# 编辑配置文件sudo vim /etc/systemd/journald.conf2.2 Storage参数详解参数值存储位置持久性说明persistent/var/log/journal✅ 持久系统重启后保留日志volatile/run/log/journal❌ 易失默认值,重启后清除auto自动选择条件持久目录存在则持久,否则易失none无存储❌ 易失丢弃所有日志(仅转发)2.3 配置步骤详解步骤1:创建持久化目录1sudo mkdir /var/log/journal步骤2:编辑配置文件1sudo vim /etc/systemd/journald.conf在[Journal]部分添加或修改:12[Journal]Storage=persistent步骤3:重启服务生效1sudo systemctl restart systemd-journald2.4 验证配置检查目录结构:12345# 查看生成的日志目录ls /var/log/journal/ # 查看具体的日志文件ls /var/log/journal/4ec03abd2f7b40118b1b357f479b3112/预期输出:12system.journal # 系统日志user-1000.journal # 用户日志三、日志文件结构与管理3.1 日志文件特点二进制格式:结构化存储,带索引文件扩展名:.journal目录命名:长十六进制字符串(机器ID)自动分类:系统日志、用户日志分开存储3.2 日志大小管理systemd-journald具有自动的日志轮转和大小限制机制:默认限制规则:每月自动触发日志轮转日志大小不超过文件系统的10%保证文件系统可用空间不低于15%查看当前日志大小:1journalctl | grep -E 'Runtime Journal|System Journal'输出示例:12Mar 15 04:21:14 host systemd-journald[226]: Runtime Journal is 8.0M, max 113.3M, 105.3M free.Mar 15 04:21:19 host systemd-journald[719]: System Journal is 8.0M, max 4.0G, 4.0G free.四、按系统启动查看日志4.1 查看启动列表持久化日志后,可以查看历次系统启动记录:12# 列出所有系统启动事件journalctl --list-boots输出示例:1234567-6 27de... Wed 2022-04-13 20:04:32 EDT-Wed 2022-04-13 21:09:36 EDT-5 6a18... Tue 2022-04-26 08:32:22 EDT-Thu 2022-04-28 16:02:33 EDT-4 e2d7... Thu 2022-04-28 16:02:46 EDT-Fri 2022-05-06 20:59:29 EDT-3 45c3... Sat 2022-05-07 11:19:47 EDT-Sat 2022-05-07 11:53:32 EDT-2 dfae... Sat 2022-05-07 13:11:13 EDT-Sat 2022-05-07 13:27:26 EDT-1 e754... Sat 2022-05-07 13:58:08 EDT-Sat 2022-05-07 14:10:53 EDT 0 ee2c... Mon 2022-05-09 09:56:45 EDT-Mon 2022-05-09 12:57:21 EDT字段说明:左侧数字:启动序号(0=当前,-1=上一次,-2=上上次)中间ID:启动的唯一标识符右侧时间:启动的开始和结束时间4.2 按启动序号查看日志1234567891011# 查看当前启动的日志journalctl -b # 查看上一次启动的日志journalctl -b -1 # 查看上上次启动的日志journalctl -b -2 # 查看特定序号的启动日志journalctl -b 14.3 故障排查应用场景:系统崩溃分析12345678# 查看崩溃前最后一次启动的日志journalctl -b -1 # 结合时间范围筛选journalctl -b -1 --since "14:00" --until "15:00" # 只看错误信息journalctl -b -1 -p err五、高级配置选项5.1 自定义大小限制在/etc/systemd/journald.conf中可以调整大小限制:12345[Journal]SystemMaxUse=1G # 系统日志最大使用量SystemKeepFree=2G # 系统保持空闲空间RuntimeMaxUse=100M # 运行时日志最大使用量RuntimeKeepFree=200M # 运行时保持空闲空间5.2 压缩配置123[Journal]Compress=yes # 启用压缩Seal=yes # 启用密封(安全特性)六、实际应用场景6.1 生产环境配置建议服务器环境:123456[Journal]Storage=persistentSystemMaxUse=2GSystemKeepFree=4GCompress=yesMaxRetentionSec=1month开发测试环境:123[Journal]Storage=autoSystemMaxUse=500M6.2 故障排查流程1234567891011# 1. 确认系统重启情况journalctl --list-boots # 2. 查看问题发生时间段的日志journalctl -b -1 --since "2024-01-15 14:00" --until "2024-01-15 15:00" # 3. 筛选关键错误信息journalctl -b -1 -p err --no-pager # 4. 查看特定服务日志journalctl -b -1 -u nginx.service七、注意事项与最佳实践7.1 权限管理123# 确保日志目录权限正确sudo chown root:systemd-journal /var/log/journalsudo chmod 2755 /var/log/journal7.2 监控日志大小123456# 定期检查日志大小journalctl --disk-usage # 手动清理旧日志sudo journalctl --vacuum-time=30d # 保留30天sudo journalctl --vacuum-size=1G # 保留1GB7.3 备份策略12# 备份重要时间段的日志sudo journalctl --since "2024-01-01" --until "2024-01-31" > /backup/january-2024.log八、总结8.1 配置持久化日志的价值故障诊断:系统崩溃后仍可分析日志安全审计:保留完整的安全事件记录性能分析:长期跟踪系统性能趋势合规要求:满足日志保留的法规要求8.2 关键命令速查命令功能使用场景journalctl --list-boots列出系统启动记录查看重启历史journalctl -b -1查看上一次启动日志分析系统崩溃journalctl --disk-usage查看日志磁盘使用监控存储空间journalctl --vacuum-*清理旧日志释放磁盘空间
-
wait 函数的作用wait 函数的作用是阻塞父进程,直到一个子进程终止。当子进程终止时,wait 函数会返回子进程的退出状态,并回收子进程的资源。具体功能包括:阻塞父进程:父进程在调用 wait 后会进入阻塞状态,直到子进程终止。获取子进程退出状态:通过 wait 函数可以获取子进程的退出状态信息。回收子进程资源:wait 函数会清理子进程的进程控制块(PCB),释放子进程占用的资源【1†source】。创建僵尸进程的示例在深入讲解 wait 函数之前,我们先通过一个示例代码展示僵尸进程的产生。123456789101112131415161718192021222324252627#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h> int main() { pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } if (pid == 0) { // 子进程 printf("Child process (PID: %d) is exiting.\n", getpid()); exit(EXIT_SUCCESS); } else { // 父进程 printf("Parent process (PID: %d) is running. Child PID: %d\n", getpid(), pid); sleep(10); // 父进程继续运行,但不调用 wait() printf("Parent process exiting.\n"); exit(EXIT_SUCCESS); } return 0;}代码解释fork() :创建子进程。pid 为 0 表示子进程,正值表示父进程的 PID。子进程终止:子进程调用 exit(EXIT_SUCCESS) 终止。父进程未回收:父进程未调用 wait,导致子进程成为僵尸进程。运行上述代码后,可以使用以下命令查看僵尸进程:1ps -o pid,ppid,stat,cmd僵尸进程的 STAT 列将显示为 Z【5†source】。使用 wait 函数回收子进程为了避免僵尸进程的产生,父进程需要调用 wait 或 waitpid 函数来回收子进程。以下是使用 wait 函数的示例代码。12345678910111213141516171819202122232425262728293031323334353637#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/wait.h> int main() { pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } if (pid == 0) { // 子进程 printf("Child process (PID: %d) is exiting.\n", getpid()); exit(EXIT_SUCCESS); } else { // 父进程 printf("Parent process (PID: %d) is waiting for child %d.\n", getpid(), pid); // 调用 wait 回收子进程 int status; pid_t child_pid = wait(&status); if (child_pid == -1) { perror("wait"); exit(EXIT_FAILURE); } printf("Child process %d terminated with status %d.\n", child_pid, status); exit(EXIT_SUCCESS); } return 0;}代码解释fork() :创建子进程。子进程终止:子进程调用 exit(EXIT_SUCCESS)。父进程调用 wait :父进程调用 wait(&status) 阻塞,直到子进程终止。获取退出状态:status 包含子进程的退出状态信息。回收子进程资源:wait 函数回收子进程的资源,避免僵尸进程的产生。wait 函数的详细解析函数原型1pid_t wait(int *status);参数说明status :一个指向整数的指针,用于存储子进程的退出状态信息。如果 status 为 NULL,则不返回退出状态信息【6†source】。返回值成功:返回子进程的 PID。失败:返回 -1,并设置 errno 以指示错误原因。注意事项wait 函数会阻塞父进程,直到子进程终止。如果子进程已经终止,wait 函数会立即返回。如果父进程调用 wait 时没有子进程,函数会阻塞,直到有子进程终止【7†source】。处理僵尸进程的方法除了使用 wait 函数,还可以通过以下方法避免僵尸进程的产生:及时调用 wait 或 waitpid :父进程在子进程终止后立即调用 wait 或 waitpid 回收子进程。使用信号处理:设置 SIGCHLD 信号处理函数,在信号处理函数中调用 wait 或 waitpid【8†source】。忽略 SIGCHLD 信号:调用 signal(SIGCHLD, SIG_IGN);,让内核自动回收子进程【9†source】。常见问题解答1. wait 函数会阻塞父进程吗?是的,wait 函数会阻塞父进程,直到子进程终止【1†source】。2. 如何避免僵尸进程的产生?及时调用 wait 或 waitpid 回收子进程,或者设置 SIGCHLD 信号处理函数【8†source】。3. wait 和 waitpid 的区别是什么?wait 函数会阻塞父进程,直到任意一个子进程终止。waitpid 函数可以指定要等待的子进程,并支持非阻塞模式【6†source】。总结wait 函数是 Linux 进程管理中一个重要的工具,用于回收子进程并避免僵尸进程的产生。通过调用 wait 函数,父进程可以获取子进程的退出状态信息,并清理子进程的资源。在实际开发中,及时调用 wait 或 waitpid 函数是确保系统稳定性和高效性的关键。
-
子进程退出状态的基本概念在Linux/Unix系统中,子进程终止时会向父进程发送一个SIGCHLD信号,父进程可以通过特定的系统调用来获取子进程的终止状态。这个状态包含了子进程是正常退出还是被信号异常终止,以及相关的退出码或信号编号。wait()和waitpid()系统调用父进程可以使用wait()或waitpid()系统调用来等待子进程终止并获取其状态。wait()系统调用1234#include <sys/types.h>#include <sys/wait.h> pid_t wait(int *status);wait()会阻塞调用进程,直到其任意一个子进程终止。如果子进程已经终止,wait()会立即返回。终止状态会通过status指针返回给父进程。waitpid()系统调用1234#include <sys/types.h>#include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);waitpid()提供了更灵活的控制:pid参数可以指定要等待的特定子进程options参数可以控制等待行为(如WNOHANG使调用非阻塞)检查子进程退出状态通过status参数返回的状态值,我们可以使用一组宏来解析子进程的终止信息:检查是否正常退出123#include <sys/wait.h> WIFEXITED(status); // 如果子进程正常返回,则为非零如果子进程通过exit()或_exit()正常退出,可以使用WEXITSTATUS(status)获取退出码:1WEXITSTATUS(status); // 返回子进程的退出码(低8位)检查是否被信号终止1WIFSIGNALED(status); // 如果子进程被信号终止,则为非零如果子进程被信号终止,可以使用WTERMSIG(status)获取导致终止的信号编号:1WTERMSIG(status); // 返回导致子进程终止的信号编号完整示例代码下面是一个完整的示例,展示如何创建子进程,并在父进程中获取子进程的退出状态:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/wait.h>#include <signal.h> void child_process() { printf("子进程开始运行,PID: %d\n", getpid()); // 模拟子进程执行某些操作 sleep(2); // 正常退出 exit(42); // 使用退出码42退出} void child_process_with_signal() { printf("子进程开始运行,PID: %d\n", getpid()); // 模拟子进程执行某些操作 sleep(2); // 发送信号给自己 raise(SIGINT); // 发送SIGINT信号} int main() { pid_t pid; int status; // 示例1:正常退出的子进程 pid = fork(); if (pid == 0) { // 子进程 child_process(); } else if (pid > 0) { // 父进程 printf("父进程等待子进程 %d\n", pid); waitpid(pid, &status, 0); if (WIFEXITED(status)) { printf("子进程 %d 正常退出,退出码: %d\n", pid, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("子进程 %d 被信号 %d 终止\n", pid, WTERMSIG(status)); } } else { perror("fork失败"); } printf("\n"); // 示例2:被信号终止的子进程 pid = fork(); if (pid == 0) { // 子进程 child_process_with_signal(); } else if (pid > 0) { // 父进程 printf("父进程等待子进程 %d\n", pid); waitpid(pid, &status, 0); if (WIFEXITED(status)) { printf("子进程 %d 正常退出,退出码: %d\n", pid, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("子进程 %d 被信号 %d 终止\n", pid, WTERMSIG(status)); } } else { perror("fork失败"); } return 0;}高级主题:信号处理和进程状态使用WNOHANG选项waitpid()的options参数可以设置为WNOHANG,使调用非阻塞:12345678pid_t ret = waitpid(pid, &status, WNOHANG);if (ret == 0) { // 子进程仍在运行} else if (ret > 0) { // 子进程已终止,状态在status中} else { // 错误}检查子进程是否被暂停可以使用WIFSTOPPED(status)和WSTOPSIG(status)来检查子进程是否被暂停:123if (WIFSTOPPED(status)) { printf("子进程被信号 %d 暂停\n", WSTOPSIG(status));}检查子进程是否被继续执行可以使用WIFCONTINUED(status)来检查子进程是否从暂停状态继续执行:123if (WIFCONTINUED(status)) { printf("子进程从暂停状态继续执行\n");}常见问题和解决方案问题1:子进程变成僵尸进程如果父进程没有调用wait()或waitpid()来收集子进程的状态,子进程会变成僵尸进程(Zombie Process)。僵尸进程会占用系统资源,因为内核需要保留其退出状态直到父进程读取。解决方案:父进程应该始终调用wait()或waitpid()来收集子进程的状态,或者可以设置对SIGCHLD信号的忽略处理(在Linux上):1signal(SIGCHLD, SIG_IGN); // 忽略SIGCHLD信号,子进程退出时自动清理问题2:如何处理多个子进程当父进程有多个子进程时,可以使用循环和waitpid()的pid参数为-1来等待任意子进程:123while ((pid = waitpid(-1, &status, 0)) > 0) { // 处理已终止的子进程}问题3:获取子进程的core dump状态如果子进程因为信号终止并生成了core dump,可以使用WCOREDUMP(status)来检查:123if (WCOREDUMP(status)) { printf("子进程生成了core dump\n");}实际应用场景场景1:构建并行任务系统在需要并行执行多个任务的系统中,父进程需要监控每个子任务的执行状态:1234567891011121314151617181920212223242526#define MAX_CHILDREN 10 pid_t child_pids[MAX_CHILDREN];int child_status[MAX_CHILDREN]; // 创建子进程for (int i = 0; i < MAX_CHILDREN; i++) { pid_t pid = fork(); if (pid == 0) { // 子进程执行任务 execute_task(i); exit(EXIT_SUCCESS); } else { child_pids[i] = pid; }} // 等待所有子进程完成int completed = 0;while (completed < MAX_CHILDREN) { pid_t pid = waitpid(-1, NULL, 0); if (pid > 0) { completed++; // 可以在这里记录完成的子进程 }}场景2:实现超时机制在某些情况下,可能需要限制子进程的运行时间:123456789101112131415161718pid_t pid = fork();if (pid == 0) { // 子进程执行长时间任务 long_running_task(); exit(EXIT_SUCCESS);} else { // 设置定时器 alarm(10); // 10秒超时 int status; waitpid(pid, &status, 0); if (WIFEXITED(status)) { printf("任务正常完成\n"); } else { printf("任务被超时或信号终止\n"); }}总结在Linux系统编程中,正确获取子进程的退出值和异常终止信号对于编写健壮的程序至关重要。通过wait()和waitpid()系统调用,结合一系列状态检查宏,父进程可以全面了解子进程的执行状态。本文介绍了基本概念、系统调用、状态检查宏、示例代码以及常见问题和解决方案,希望能帮助开发者更好地处理子进程监控任务。在实际应用中,根据具体需求选择合适的等待方式和状态检查方法,可以有效地管理子进程的生命周期,提高程序的可靠性和稳定性。
-
什么是waitpidwaitpid是Unix/Linux系统提供的一个系统调用,用于等待子进程的状态改变并回收其资源。相比于wait函数,waitpid提供了更灵活的控制选项,可以指定等待哪个特定的子进程,以及是否阻塞等待【1†source】。123#include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);参数说明:pid: 要等待的子进程ID。特殊值包括:-1: 等待任意子进程0: 等待与调用进程同组的任意子进程0: 等待指定PID的子进程status: 用于存储子进程退出状态的指针options: 控制选项,最常用的是WNOHANG,表示非阻塞模式回收多个子进程的方法方法一:循环调用waitpid最简单的方法是在父进程中循环调用waitpid,直到所有子进程都被回收:123456789101112131415161718192021222324252627282930313233343536#include <stdio.h>#include <unistd.h>#include <sys/wait.h>#include <stdlib.h> int main() { pid_t pids[5]; int i; // 创建5个子进程 for (i = 0; i < 5; i++) { pids[i] = fork(); if (pids[i] == 0) { // 子进程代码 printf("Child process %d started\n", getpid()); sleep(1 + rand() % 3); // 随机休眠1-3秒 printf("Child process %d exiting\n", getpid()); exit(0); } } // 父进程回收子进程 int status; pid_t pid; for (i = 0; i < 5; i++) { pid = waitpid(pids[i], &status, 0); if (pid == -1) { perror("waitpid"); exit(1); } printf("Parent reaped child %d\n", pid); } return 0;}方法二:非阻塞方式回收子进程使用WNOHANG选项,父进程可以在不阻塞的情况下检查子进程状态:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647#include <stdio.h>#include <unistd.h>#include <sys/wait.h>#include <stdlib.h> int main() { pid_t pids[5]; int i; // 创建5个子进程 for (i = 0; i < 5; i++) { pids[i] = fork(); if (pids[i] == 0) { // 子进程代码 printf("Child process %d started\n", getpid()); sleep(1 + rand() % 3); // 随机休眠1-3秒 printf("Child process %d exiting\n", getpid()); exit(0); } } // 父进程非阻塞方式回收子进程 int status; pid_t pid; int children_left = 5; while (children_left > 0) { pid = waitpid(-1, &status, WNOHANG); if (pid > 0) { // 成功回收一个子进程 printf("Parent reaped child %d\n", pid); children_left--; } else if (pid == 0) { // 有子进程仍在运行 printf("Waiting for children to finish...\n"); sleep(1); } else { // 出错 perror("waitpid"); exit(1); } } printf("All children have been reaped\n"); return 0;}方法三:信号处理方式回收子进程可以通过SIGCHLD信号来通知父进程子进程已经终止,然后在信号处理函数中回收子进程:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849#include <stdio.h>#include <unistd.h>#include <sys/wait.h>#include <signal.h>#include <stdlib.h> void sigchld_handler(int sig) { int status; pid_t pid; // 回收所有已终止的子进程 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { printf("Parent reaped child %d\n", pid); }} int main() { pid_t pids[5]; int i; // 设置SIGCHLD信号处理 struct sigaction sa; sa.sa_handler = sigchld_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(1); } // 创建5个子进程 for (i = 0; i < 5; i++) { pids[i] = fork(); if (pids[i] == 0) { // 子进程代码 printf("Child process %d started\n", getpid()); sleep(1 + rand() % 3); // 随机休眠1-3秒 printf("Child process %d exiting\n", getpid()); exit(0); } } // 父进程继续执行其他任务 printf("Parent doing other work...\n"); sleep(5); printf("Parent finished\n"); return 0;}最佳实践和注意事项及时回收子进程:父进程应该及时回收子进程,避免僵尸进程的产生【2†source】。处理异常情况:在调用waitpid时,应该检查返回值,处理可能的错误情况。信号处理注意事项:使用信号处理方式回收子进程时,需要注意信号处理函数的可重入性和安全性。避免竞争条件:在多线程环境中使用waitpid时,需要注意同步问题,避免竞争条件。使用WNOHANG:对于需要同时处理多个任务的父进程,使用WNOHANG选项可以避免阻塞,提高程序的响应性【3†source】。
-
1. 服务器无法访问排查步骤:检查物理连接:确认服务器的电源、网络连接是否正常。查看显示器(如有)是否有故障信息。SSH 登录失败:使用 `ping` 命令检查服务器是否在网络上。检查是否能够访问网络的其他设备。2. 系统资源耗尽排查步骤:使用 CTRL + ALT + F1 进入控制台:登录后使用 `top` 或 `htop` 查看 CPU、内存使用情况。检查磁盘使用情况:1df -h如果根目录 (`/`) 使用率过高,应清理不必要的文件。检查进程状况:1ps aux --sort=-%mem | head # 查看内存占用最高的进程3. 服务未运行排查步骤:检查服务状态:1systemctl status <service-name>如果服务未运行,可以尝试重启:1systemctl restart <service-name>查看服务日志:1journalctl -u <service-name>4. 内核崩溃(Kernel Panic)排查步骤:重启服务器,检查引导日志:在 `GRUB` 引导菜单中,选择“编辑”引导行,查找是否有错误信息。**检查 `/var/log/kern.log` 或 `/var/log/messages`**: 这些日志文件可以提供有关崩溃的详细信息。5. 网络故障排查步骤:使用 `ping` 命令确认本机到其他IP(如路由器、外部地址)的连通性。检查网络配置:12ip address # 查看IP配置ip route # 查看路由设置检查网络服务状态:1systemctl status NetworkManager6. 文件系统损坏排查步骤:启动进入单用户模式或者使用 Live CD。使用 `fsck` 命令修复文件系统:1fsck /dev/sdXn # 替换为具体的设备7. 应用程序异常排查步骤:查看应用程序日志,通常在 `/var/log` 或应用程序的配置目录下。检查配置文件,确认没有错误的配置导致服务错误。8. 定期健康检查定期监控服务器健康状态的做法:设置监控工具: 使用工具如 Zabbix、Nagios 或 Grafana 监控服务器的 CPU、内存、磁盘和网络使用情况。实施备份方案: 定期备份数据,以便在恶性 事件后快速恢复。9. 记录与文档在每次故障排查和修复后,记录相关信息和操作步骤。
-
1.du命令概述du(Disk Usage)是 Linux 系统中的一个常用命令,用于显示指定文件和目录的磁盘空间使用情况。它可以递归地计算目录及其子目录所占的空间大小,并显示每个文件或子目录的大小。du 是非常适合用于分析磁盘空间使用情况的工具,特别是当我们需要知道哪个目录占用了大量磁盘空间时,它显得尤为重要。du命令常用选项-h:以“人类可读”的格式输出,即显示为带单位的大小(例如 K、M、G)。这对于直观查看文件大小非常有用。-s:仅显示每个目录的总大小,而不是递归列出每个文件。-a:显示每个文件的大小(而不仅仅是目录的大小)。-c:输出总计,显示所有文件和目录的总大小。例如,如果我们要查看 blog 目录下的磁盘使用情况,命令如下:1du -h blog/该命令会列出 blog 目录下所有文件和子目录的大小。2. 使用sort命令排序结果在日常工作中,我们不仅仅关心每个文件或目录的大小,还想要查看哪些文件或目录占用了最多的磁盘空间。此时,结合 sort 命令的使用可以帮助我们快速找到这些“占地最大”的文件或目录。sort 是 Linux 中用于排序文本行的命令。它支持按字母、数字、时间等多种方式进行排序。在我们的需求中,我们关心的是按数字大小来排序。为了达到这一目的,我们需要使用 sort 命令的 -h 和 -r 选项。2.1sort命令常用选项-h:按照“人类可读”的格式(即类似 1K、2M、3G 这样的单位)进行排序。-r:反向排序,即从大到小排序。当我们把这两个选项与 du 命令结合使用时,可以按从大到小的顺序列出 blog 目录下的所有文件和子目录。具体命令如下:1du -h blog/* | sort -hr2.2 命令解析du -h blog/*:这个部分会列出 blog 目录下所有文件和子目录的大小,并以人类可读的格式显示。|(管道符):表示将 du 命令的输出结果传递给 sort 命令进行处理。sort -hr:按照从大到小的顺序排序输出的结果,其中 -h 让排序考虑人类可读格式,-r 则是反向排序,从大到小。3. 示例:如何查看博客目录下最大文件假设我们有一个名为 blog 的目录,其中包含了大量的文件和子目录。如果我们想要查看哪些文件或目录占用了最多的空间,可以执行如下命令:1du -h blog/* | sort -hr3.1 命令输出示例假设命令输出如下:2.3G blog/images1.5G blog/videos512M blog/articles128M blog/styles10M blog/script.js这个输出结果告诉我们,blog 目录下占用最大空间的是 images 子目录(2.3G),其次是 videos(1.5G)。通过这种方式,我们可以清晰地知道每个文件或目录的大小,进而决定是否需要清理一些不再需要的数据。4. 进一步优化命令在某些情况下,blog/* 可能会列出大量的文件和子目录,而我们只关心其中某个子目录的磁盘使用情况。为了更加精准地定位问题,我们可以将 du 命令的路径限定为具体的文件或子目录,而不是整个目录。例如,如果我们只关心 blog/images 目录,可以执行以下命令:1du -h blog/images/* | sort -hr这样我们就可以只查看 images 子目录下的文件大小,并按从大到小的顺序进行排序,快速找到最大的文件。5. 清理不必要的文件通过使用 du 和 sort 命令,我们可以快速找到占用空间最多的文件或目录,从而进行清理。清理不必要的文件不仅能节省磁盘空间,还能提高系统的性能和响应速度。5.1 删除大文件一旦我们找到了占用空间最多的文件,我们可以使用 rm 命令删除它们。例如,如果 blog/images 目录下有一个占用 1GB 空间的文件 large_image.jpg,我们可以使用以下命令删除它:1rm blog/images/large_image.jpg5.2 清理临时文件有时,一些临时文件(如缓存文件)会占用大量磁盘空间。我们可以通过 du 命令找出这些临时文件并删除。例如,许多应用程序会在 /tmp 目录下创建临时文件,我们可以使用如下命令清理它:1du -h /tmp/* | sort -hr然后,删除那些不再需要的临时文件。
-
理解文件⽂件在磁盘⾥,磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的Linux 下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘……)0KB 的空⽂件也是占⽤磁盘空间的⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件 = 属性(元数据)+ 内容)所有的⽂件操作本质是⽂件内容操作和⽂件属性操作对⽂件的操作本质是进程对⽂件的操作, 磁盘的管理者是操作系统⽂件的读写本质不是通过 C 语⾔ / C++ 的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽是通过⽂件相关的系统调⽤接⼝来实现的理解硬盘机械磁盘是计算机中唯⼀的⼀个机械设备磁盘是外设,特点:慢,容量⼤,价格便宜磁盘的物理结构:磁盘的存储结构:扇区是从磁盘读出和写⼊信息的最⼩单位,通常⼤⼩为 512 字节。磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道...,靠近主轴的同⼼圆⽤于停靠磁头,不存储数据柱⾯(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同圆盘(platter)数:就是盘⽚的数量磁盘容量=磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数细节:传动臂上的磁头是共进退的通过柱⾯(cylinder),磁头(head),扇区(sector),就可以定位数据了,这就是数据定位(寻址)⽅式之⼀,CHS寻址⽅式。磁盘的逻辑结构磁带逻辑结构在理解磁盘的逻辑结构之前我们先来了解一下磁带的逻辑结构: 磁带上面存取数据,当我们把磁带拉直就形成了线性结构 虽然磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻辑存储结构我们也可以类似于: 磁盘逻辑结构机械臂上的磁头是共进退的 柱⾯是⼀个逻辑上的概念,其实就是每⼀⾯上,相同半径的磁道逻辑上构成柱⾯。所以,磁盘物理上分了很多⾯,但是在我们看来,逻辑上,磁盘整体是由“柱⾯”卷起来的 这样看来一个柱面就是一个二维数组.整盘 故整盘就是多张二维的扇区数组表 “三维数组” .这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做 LBA.CHS && LBA地址CHS转成LBA:磁头数*每磁道扇区数 = 单个柱⾯的扇区总数LBA = 柱⾯号C*单个柱⾯的扇区总数 + 磁头号H*每磁道扇区数 + 扇区号S - 1即:LBA = 柱⾯号C*(磁头数*每磁道扇区数) + 磁头号H*每磁道扇区数 + 扇区号S - 1LBA转成CHS:柱⾯号C = LBA // (磁头数*每磁道扇区数)【就是单个柱⾯的扇区总数】磁头号H = (LBA % (磁头数*每磁道扇区数)) // 每磁道扇区数扇区号S = (LBA % 每磁道扇区数) + 1在磁盘使⽤者看来,根本就不关⼼CHS地址,⽽是直接使⽤LBA地址,磁盘内部⾃⼰转换。故磁盘是⼀个 元素为扇区 的⼀维数组,数组的下标就是每⼀个扇区的LBA地址。OS使⽤磁盘,就可以⽤⼀个数字访问磁盘扇区了。引入文件系统“块” 的概念操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个”块”(block)硬盘的每个分区是被划分为⼀个个的”块”。⼀个”块”的⼤⼩是由格式化的时候确定的,并且不可以更改,最常⻅的是4KB,即连续⼋个扇区组成⼀个 ”块”。(每个扇区512B)文件系统和存储管理中,“块(Block)” 是磁盘(或其他存储设备)进行数据读写的最小单位,也是文件系统管理存储空间的基础单元。“分区” 的概念其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的⼀种格式化。但是Linux的设备都是以⽂件形式存在,那是怎么分区的呢?柱⾯是分区的最⼩单位,我们可以利⽤参考柱⾯号码的⽅式来进⾏分区,其本质就是设置每个区的起始柱⾯和结束柱⾯号码。 柱⾯⼤⼩⼀致,扇区个位⼀致,那么其实只要知道每个分区的起始和结束柱⾯号,知道每⼀个柱⾯多少个扇区,那么该分区多⼤,其实和解释LBA是多少也就清楚了. inode我们知道 ⽂件=内容+属性 ,我们使⽤ ls -l 的时候看到的除了看到⽂件名,还能看到⽂件元数据(属性)每⾏的7列分别代表:模式硬链接数⽂件所有者组⼤⼩最后修改时间⽂件名这个信息除了通过ls来读取,还有⼀个stat命令能够看到更多信息。 我们知道⽂件数据都储存在”块”中,那么很显然,我们还必须找到⼀个地⽅储存⽂件的元信息(属性信息),⽐如⽂件的创建者、⽂件的创建⽇期、⽂件的⼤⼩等等。这种储存⽂件元信息的区域就叫做inode,中⽂译名为”索引节点”。每⼀个⽂件都有对应的inode,⾥⾯包含了与该⽂件有关的⼀些信息。为了能解释清楚inode,我们需要是深⼊了解⼀下⽂件系统。注意:Linux下⽂件的存储是属性和内容分离存储的Linux下,保存⽂件属性的集合叫做inode,⼀个⽂件,⼀个inode,inode内有⼀个唯⼀的标识符,叫做inode号⽂件名属性并未纳⼊到inode数据结构内部inode的⼤⼩⼀般是128字节或者256任何⽂件的内容⼤⼩可以不同,但是属性⼤⼩⼀定是相同的ext2 ⽂件系统认识文件系统所有的准备⼯作都已经做完,是时候认识下⽂件系统了。我们想要在硬盘上储⽂件,必须先把硬盘格式化为某种格式的⽂件系统,才能存储⽂件。⽂件系统的⽬的就是组织和管理硬盘中的⽂件。在 Linux 系统中,最常⻅的是 ext2 系列的⽂件系统。其早期版本为 ext2,后来⼜发展出 ext3 和 ext4。ext3 和 ext4 虽然对 ext2 进⾏了增强,但是其核⼼设计并没有发⽣变化。ext2⽂件系统将整个分区划分成若⼲个同样⼤⼩的块组 (Block Group),如下图所⽰。只要能管理⼀个分区就能管理所有分区,也就能管理所有磁盘⽂件。上图中启动块(Boot Sector)的⼤⼩是确定的,为1KB,由PC标准规定,⽤来存储磁盘分区信息和启动信息,任何⽂件系统都不能修改启动块。启动块之后才是ext2⽂件系统的开始。Block GroupBlock Group(块组) 是文件系统管理磁盘空间的核心 “模块化单元”,每ext2⽂件系统会根据分区的⼤⼩划分为数个Block Group。⽽每个Block Group都有着相同的结构组成。每个Block Group自主管理资源,以提升效率和容错性。块组内部构成超级块(Super Block)存放⽂件系统本⾝的结构信息,描述整个分区的⽂件系统信息。记录的信息主要有:bolck 和 inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了。GDT(Group Descriptor Table)块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储⼀个块组 的描述信息,如在这个块组中从哪⾥开始是inode Table,从哪⾥开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷⻉。块位图(Block Bitmap)Block Bitmap以位图的形式记录着Data Block中哪个数据块已经被占⽤,哪个数据块没有被占⽤。inode位图(Inode Bitmap)每个bit表⽰⼀个inode是否空闲可⽤。(位值为0表示可用,为1不可用)i节点表(Inode Table)存放⽂件属性 如 ⽂件⼤⼩,所有者,最近修改时间等当前分组所有Inode属性的集合inode编号以分区为单位,整体划分,不可跨分区Data Block数据区:存放⽂件内容,也就是⼀个⼀个的Block。根据不同的⽂件类型有以下⼏种情况:对于普通⽂件,⽂件的数据存储在数据块中。对于⽬录,该⽬录下的所有⽂件名和⽬录名存储在所在⽬录的数据块中,除了⽂件名外,ls -l命令看到的其它信息保存在该⽂件的inode中。Block 号按照分区划分,不可跨分区,只能在自己分区内有效不可跨分区inode和datablock映射 12 个直接块指针(直接翻到章节):直接指向存储文件数据的 “普通数据块”(小文件可通过这些指针直接找到所有数据块,因此小文件存储非常高效)。间接块索引指针一级间接块索引指针(“先查目录,再翻章节”):该指针指向了一级数据块索引表(4KB),该索引表中储存了多个普通数据块的编号(每个编号4字节),则索引表中管理了1024个数据块。则可以在直接块的基础上多管理(1024 * 4KB = 4MB)4MB的文件。二级间接块索引指针(“查目录的目录,再翻章节”):该指针指向了数据块索引表,该数据块索引表又可以指向1024个数据块索引表,每个索引块可以管理1024个数据块。则可以在前面的基础上多管理(1024 * 1024 * 4KB = 4GB)4GB的文件。三级间接块索引指针(“查目录的目录的目录,再翻章节”):以此类推,则可以在前面的基础上多管理(1024 * 1024 * 1024 * 4KB = 4TB)4TB的文件。结论:分区之后的格式化操作,就是对分区进⾏分组,在每个分组中写⼊SB、GDT、Block、Bitmap、Inode Bitmap等管理信息,这些管理信息统称: ⽂件系统只要知道⽂件的inode号,就能在指定分区中确定是哪⼀个分组,进⽽在哪⼀个分组确定是哪⼀个inode拿到inode⽂件属性和内容就全部都有了[root@localhost linux]# touch abc[root@localhost linux]# ls -i abc263466 abc 如上图可知创建⼀个新⽂件主要有以下4个操作:1. 存储属性内核先找到⼀个空闲的i节点(这⾥是263466)。内核把⽂件信息记录到其中。2. 存储数据该⽂件需要存储在三个磁盘块,内核找到了三个空闲块:300 ,500,800。将内核缓冲区的第⼀块数据复制到300,下⼀块复制到500,以此类推。3. 记录分配情况⽂件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。4. 添加⽂件名到⽬录新的⽂件名abc。linux如何在当前的⽬录中记录这个⽂件?内核将⼊⼝(263466,abc)添加到⽬录⽂件。⽂件名和inode之间的对应关系将⽂件名和⽂件的内容及属性连接起来。目录与文件名我们发现平常访问⽂件,⽤的是⽂件名,并没⽤inode号,那它是怎么访问的呢?答:目录是文件,磁盘上是没有目录这一概念的,目录的内容保存的是当前目录下的文件名和inode的映射关系。所以,访问⽂件,必须打开当前⽬录,根据⽂件名,获得对应的inode号,然后进⾏⽂件访问。所以,访问⽂件必须要知道当前⼯作⽬录,本质是必须能打开当前⼯作⽬录⽂件,查看⽬录⽂件的内容!路径解析我们知道访问文件时需要访问目录文件,可是目录我们也只知道文件名,要访问当前目录也要知道他的inode。故需要一直向上访问上级目录,类似于“递归”,需要把路径中所有的⽬录全部解析,出⼝是"/"根⽬录(根⽬录固定⽂件名,inode号,⽆需查找,系统开机之后就必须知道)。实际上,任何⽂件,都有路径,访问⽬标⽂件,⽐如: /home/whb/code/test/test/test.c 都要从根⽬录开始,依次打开每⼀个⽬录,根据⽬录名,依次访问每个⽬录下指定的⽬录,直到访问到test.c。这个过程叫做Linux路径解析。路径谁提供?你访问⽂件,都是指令/⼯具访问,本质是进程访问,进程有CWD!进程提供路径。你open⽂件,提供了路径.软硬链接创建软硬连接观察,对比它们的inode: 硬链接abc和def的链接状态完全相同,他们被称为指向⽂件的硬链接。内核记录了这个连接数,inode:68511212 的硬连接数为2。硬连接是新文件名与和目标文件inode编号的映射关系;创建硬连接,就是建立映射关系,并且把inode的链接数增加;我们在删除⽂件时⼲了两件事情:在⽬录中将对应的记录删除,将硬连接数-1,如果为0,则将对应的磁盘释放。如上图我们可以发现当前目录下的 . 和下级目录下的 .. 都连接着该目录故他的连接数是3软连接那么软连接是什么?软连接是一个独立的文件,文件内容是目标文件的路径。有点类似于快捷方式.一个目录被创建时,会产生三个连接。其中两个“目录名”和"."指向自己,还有一个".."指向上一级目录。目录可以创建软连接,但是不能创建硬连接!否则会形成环路问题,除非是系统自己建立。————————————————版权声明:本文为CSDN博主「new null」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/2301_80468112/article/details/154292952
-
一、基本框架iptables 是 Linux 系统中常用的防火墙工具,工作在用户空间,用来编写规则,基于内核的 netfilter 框架实现数据包的过滤、转换和修改。其核心框架由 “四表五链” 构成,规则的匹配和执行遵循特定顺序,同时支持自定义链以灵活管理规则。二、iptables 中的链“链” 是数据包流转过程中经过的检查点,每条链对应数据包处理的特定阶段,由内核自动创建,即内置链。对应内核中的每一个勾子函数 (INPUT,OUTPUT,FORWARD,PREROUTING,POSTROUTING)。除了内置链外,用户还可创建自定义链,用于对内置链进行扩展或补充,可实现更灵活的规则组织管理机制;只有钩子函数调用自定义链时,才会生效,即需通过内置链的规则引用自定义链。1、PREROUTING 链数据包进入本机后,路由选择之前经过的链。用于提前修改数据包(如 DNAT),或标记数据包。即主机接收到数据是否是给我们的,是否要修改(ip或port),相当于进站安检。2、INPUT 链当路由判断数据包的目标地址是本机时,经过此链。用于控制哪些外部数据包可进入本机(如允许 SSH 连接)。即接收到的数据经过路由分析,是本机处理,相当与上车。3、FORWARD 链当路由判断数据包需要经过本机转发(本机既非源也非目标)时,经过此链。用于控制转发行为(如网关服务器的转发规则)。即接收到的数据经过路由分析,不是本机处理,本机只做转发,相当于转车。4、OUTPUT 链本机产生的数据包(如应用程序发送的请求)在离开本机前经过此链。用于控制本机对外发送的数据包。即本机处理完的数据经过路由分析,直接传输出去,相当于下车。5、POSTROUTING 链数据包经过路由选择后,离开本机前的最后一条链。用于修改源地址(如 SNAT,将内网地址转换为外网地址)。即数据传输出去的时候,是否需要修改(ip或port),相当于出站安检。三、iptables 中的表iptables 的表用于分类管理不同功能的规则,每个表关联特定的链,主要包括以下四类:filter、nat、mangle、raw。优先级从高到低依次是:raw > mangle > nat > filter。1、filter 表(过滤规则表)最常用的表,也是默认表,根据预定义的规则过滤符合条件的数据包,是防火墙的核心功能。仅关联 3 条链:INPUT、OUTPUT、FORWARD。2、nat 表(地址转换表)用于实现网络地址转换(NAT),包括源地址转换(SNAT)、目标地址转换(DNAT)等。关联 3 条链:PREROUTING(路由前修改目标地址)、POSTROUTING(路由后修改源地址)、OUTPUT(本机产生的数据包的地址转换)。3、mangle 表(修改表)修改数据标记位规则表,修改数据报文(修改数据包的元数据,如 TTL、服务类型、标记等),可辅助路由或过滤。关联所有 5 条链(INPUT、OUTPUT、FORWARD、PREROUTING、POSTROUTING)。4、raw 表(原始表)用于关闭 nat 表启用的连接跟踪机制,减少性能消耗,加快封包穿越防火墙速度,仅处理不需要追踪的数据包。关联 2 条链:PREROUTING、OUTPUT。四、链表对应关系表 可支持的链raw PREROUTING, OUTPUTmangle PREROUTING, POSTROUTING, INPUT, OUTPUT, FORWARDnat PREROUTING, POSTROUTING, INPUT, OUTPUTfilter INPUT, FORWARD, OUTPUT五、数据包流转顺序当一个数据包进入网卡时,数据包首先进入PREROUTING链,内核根据数据包目的IP判断是否需要传送出去;如果数据包是进入本机的,则会进入INPUT链,然后交由本机的应用程序处理;如果数据包是要转发的,且内核允许转发,则数据包在PREROUTING链之后到达FORWARD链,再经由POSTROUTING链输出本机的应用程序往外发送数据包,会先进入OUTPUT链,然后到达POSTROUTING链输出数据包的走向数据流入,本机接收的数据包(目标是本机):PREROUTING 链(raw→mangle→nat)→ 路由判断(目标是本机)→ INPUT 链(mangle→filter)→ 进入本机应用程序。数据流出,本机发出的数据包(源是本机):本机应用程序 → OUTPUT 链(raw→mangle→nat→filter)→ 路由选择 → POSTROUTING 链(mangle→nat)→ 离开本机。数据转发,转发的数据包(经过本机转发):外部数据包 → PREROUTING 链(raw→mangle→nat)→ 路由判断(需要转发)→ FORWARD 链(mangle→filter)→ 路由选择 → POSTROUTING 链(mangle→nat)→ 离开本机。 六、规则匹配顺序iptables 中,每条链内的规则按从上到下的顺序依次匹配:当数据包匹配到某条规则时,会执行该规则的 “目标”(如 ACCEPT、DROP、跳转至其他链等),并停止后续规则的匹配(除非目标是 RETURN,会返回原链继续匹配)。若数据包不匹配链中任何规则,则执行该链的 “默认策略”。七、策略的设置方式1、iptables命令组成iptables 完整命令由以下部份组成:iptables [-t Table] -子命令 <链> <规则策略> [动作]即通过 iptables 命令,在某个表的某个链上设置某条过滤规则字段说明iptables:iptables命令Table:具体要操作的表,用 -t 指定,raw|mangle|nat|filter,默认 filterChain:具体要操作的链,PREROUTING|INPUT|FORWARD|OUTPUT|POSTROUTINGRule:具体规则,由匹配条件和目标组成,如果满足条件,就执行目标中的规则,目标用 -j 指定动作:基本动作ACCEPT|DROP|RETURN2、命令格式指定表-t|--table table #指定表 raw|mangle|nat|filter,如果不显式指定,默认是filter操作链-N|--new-chain chain #添加自定义新链-X|--delete-chain [chain] #删除自定义链(要求链中没有规则)-P|--policy chain target #设置默认策略,对filter表中的链而言,其默认策略有ACCEPT|DROP-E|--rename-chain old-chain new-chain #重命名自定义链,引用计数不为0的自定义链不能被重命名-L|--list [chain] #列出链上的所有规则-S|--list-rules [chain] #列出链上的的有规则-F|--flush [chain] #清空链上的所有规则,默认是所有链-Z|--zero [chain [rulenum]] #置0,清空计数器,默认操作所有链上的所有规则操作具体规则-A|--append chain rule-specification #往链上追加规则-I|--insert chain [rulenum] rule-specification #往链上插入规则,可以指定编号,默认插入到最前面-C|--check chain rule-specification #检查链上的规则是否正确-D|--delete chain rule-specification #删除链上的规则-D|--delete chain rulenum #根据编号删除链上的规则-R|--replace chain rulenum rule-specification #根据链上的规则编号,使用新的规则替换原有规则其它选项-h|--help #显示帮助-V|--version #显示版本-v|--verbose #显示详细信息-n|--numeric #以数字形式显示IP和端口,默认显示主机名和协议名,否则容易遭受hosts解析影响--line-numbers #显示每条规则编号-j|--jump # 决定了数据包在满足特定条件后的命运。ACCEPT:允许数据包通过。DROP:丢弃数据包,不给出任何回应。等客户端测试多次后,主动放弃。REJECT:直接拒绝数据包,并向发送方发送一个错误响应。查看规则选项-L #显示规则条目,后面可以接需要查看的链,不写的话表示所有链。如果规则不为空,单独 -L 选项显示时有可能会很慢,这是因为需要对主机名和服务名进行反解导致的,-n #选项查看规则的时候 主机名和端口不做解析,介于此,可以实现规避上面的问题。-v #显示详细信息--line-numbers #显示规则的标号-S #打印规则,编写命令给我们打印出来,方便我们去学习-t #指定查看的表3、默认策略默认策略(Policy)是当数据包不匹配链中任何规则时的默认处理行为,仅适用于内置链(自定义链无默认策略)。设置命令iptables -P 链名 策略AI写代码bash常用策略:ACCEPT(允许通过)、DROP(直接丢弃,不返回任何信息)、REJECT(拒绝并返回错误信息)。示例# 设置 INPUT 链默认拒绝所有未匹配的数据包iptables -P INPUT DROPAI写代码bash注:默认规则(iptables -P)是 ACCEPT,不建议修改,容易出现 “自杀” 现象。4、常见策略 设置命令# 在第1行插入一条规则iptables -t 表名 -I 链名 策略 # 在原来规则后面去追加iptables -t 表名 -A 链名 策略AI写代码bash常用策略:ACCEPT(允许通过)、DROP(直接丢弃,不返回任何信息)、REJECT(拒绝并返回错误信息)。示例# 在INPUT 链的 filter 表上设置过滤规则,将来自 10.0.0.112 的数据包丢弃掉iptables -t filter -A INPUT -s 10.0.0.12 -j DROPAI写代码bash八、自定义链配置1、创建自定义链创建命令iptables -N 自定义链名AI写代码bash示例# 创建一个名为NGINX_RULES的自定义链iptables -N NGINX_RULESAI写代码bash2、使用自定义链需通过内置链的规则引用自定义链(目标为自定义链名),数据包匹配到该规则时会跳转到自定义链处理。示例# 示例:在 INPUT 链中添加规则,将所有 TCP 80 端口的数据包跳转至 NGINX_RULES 链处理iptables -A INPUT -p tcp --dport 80 -j NGINX_RULESAI写代码bash注:自定义链中若规则匹配,按目标处理;若不匹配,会返回原链继续匹配后续规则。3、删除自定义链删除前需满足以下两个条件:自定义链中无任何规则(需先清空);无其他链的规则引用该自定义链(需先删除引用)。示例# 清空自定义链的规则iptables -F NGINX_RULES # 删除引用该链的规则(如 INPUT 链中指向 NGINX_RULES 的规则)iptables -D INPUT -p tcp --dport 80 -j NGINX_RULES # 删除自定义链iptables -X NGINX_RULES————————————————版权声明:本文为CSDN博主「siriuuus」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_38024995/article/details/154353503
-
一、Linux账号基础1.1 账号类型Linux系统中的账号主要分为三类,不同类型账号的权限和用途差异显著:管理员账号(root):UID(用户标识符)固定为0,是系统中权限最高的账号,拥有对系统的完全控制权限,可执行创建/删除账号、修改系统配置、安装软件等所有操作。需特别注意,root权限过大,日常操作应避免频繁使用,以防误操作引发风险。系统账号:用于运行系统服务和进程的账号,如bin、daemon、ftp、mysql等,其UID范围通常为1-999(不同Linux发行版可能略有差异)。这类账号一般不允许登录系统,仅为保障系统服务正常运行而存在。例如,mysql账号专门用于运行MySQL数据库服务。普通用户账号:由管理员创建,供用户日常使用,UID范围通常为1000及以上。普通用户的权限受严格限制,仅能在自身家目录(/home/用户名)下执行创建、修改、删除文件等操作,若需执行超出权限的操作,需通过sudo命令临时获取管理员权限。1.2 关键文件Linux系统通过多个核心文件存储账号相关信息,修改这些文件需谨慎操作:/etc/passwd:系统用户账号信息的核心文件,所有用户都可读取。该文件每行对应一个用户账号,格式为“用户名:密码占位符:UID:GID:用户说明:家目录:登录Shell”。例如,“zhangsan:x:1001:1001:Zhang San:/home/zhangsan:/bin/bash”,其中“x”表示密码存储在/etc/shadow文件中。/etc/shadow:存储用户密码的加密文件,仅root用户可读取和修改,极大提升了密码安全性。每行格式为“用户名:加密密码:密码最后修改时间:密码最小有效期:密码最大有效期:密码过期前警告天数:密码过期后宽限天数:账号失效时间:保留字段”。/etc/group:存储用户组信息的文件,所有用户可读取。每行格式为“组名:密码占位符:GID:组内成员”,其中组密码占位符若为“x”,表示组密码存储在/etc/gshadow文件中。/etc/gshadow:存储用户组密码的加密文件,仅root用户可读取和修改,用于管理组的访问权限。/etc/default/useradd:用户账号创建的默认配置文件,定义了创建新用户时的默认家目录(如/home)、默认登录Shell(如/bin/bash)、默认用户组等信息。管理员可修改此文件定制默认创建规则。/etc/skel/:存放新用户家目录默认模板文件的目录,当创建新用户时,该目录下的文件(如.bashrc、.bash_profile、.bash_logout等)会自动复制到新用户的家目录中,确保新用户拥有基本的环境配置。二、用户账号管理实操2.1 账号创建:useradduseradd命令用于创建新的Linux用户账号,语法格式为:useradd [选项] 用户名常用选项:-u:指定用户的UID,需确保UID未被占用,例如“useradd -u 1002 lisi”创建UID为1002的用户lisi。-g:指定用户的初始组(主组),可通过组名或GID指定,初始组信息会记录在/etc/passwd文件中,如“useradd -g 1001 lisi”将lisi的主组设为GID为1001的组。-G:指定用户的附加组(次要组),用户可属于多个附加组,多个组之间用逗号分隔,如“useradd -G sudo,wheel lisi”将lisi加入sudo和wheel附加组。-d:指定用户的家目录,默认家目录为“/home/用户名”,若需自定义,可使用“useradd -d /opt/zhangsan zhangsan”将zhangsan的家目录设为/opt/zhangsan。-s:指定用户的登录Shell,默认Shell为/bin/bash,若要创建无法登录的用户(如系统服务账号),可指定Shell为/sbin/nologin,例如“useradd -s /sbin/nologin mysql”。-c:添加用户的备注信息,如“useradd -c "Company Employee" wangwu”备注wangwu为公司员工。-m:强制创建用户的家目录,即使/etc/default/useradd中默认不创建,也会生成家目录并复制/etc/skel/下的模板文件,如“useradd -m zhaoliu”。-r:创建系统账号,系统账号的UID会在1-999范围内自动分配,且默认不创建家目录,如“useradd -r nginx”创建运行Nginx服务的系统账号。示例:创建一个普通用户zhangsan,UID为1003,主组为users(GID为100),附加组为sudo,家目录为/home/zhangsan,备注为“Ordinary User”,登录Shell为/bin/bash:useradd -u 1003 -g users -G sudo -d /home/zhangsan -c "Ordinary User" -s /bin/bash zhangsanAI写代码bash2.2 密码管理:passwdpasswd命令用于修改用户密码,语法格式为:passwd [选项] 用户名,普通用户仅可修改自身密码,root用户可修改所有用户密码。常用选项:-d:清空用户密码,清空后用户无需密码即可登录,存在安全风险,需谨慎使用,如“passwd -d zhangsan”。-l:锁定用户账号,锁定后用户无法登录系统,通过在/etc/shadow文件的密码字段前添加“!”实现,如“passwd -l zhangsan”。-u:解锁被锁定的用户账号,移除/etc/shadow文件密码字段前的“!”,如“passwd -u zhangsan”。-e:强制用户在下次登录时修改密码,登录后系统会提示用户重新设置密码,如“passwd -e zhangsan”。-n 天数:设置密码的最小有效期,即密码修改后至少经过指定天数才能再次修改,如“passwd -n 7 zhangsan”设置zhangsan的密码7天内不可修改。-x 天数:设置密码的最大有效期,超过指定天数后密码失效,用户需重新修改,如“passwd -x 90 zhangsan”设置密码90天有效期。-w 天数:设置密码过期前的警告天数,到期前指定天数系统会提醒用户修改密码,如“passwd -w 7 zhangsan”提前7天警告。-i 天数:设置密码过期后的宽限天数,宽限期内用户仍可登录但需修改密码,超过宽限期账号锁定,如“passwd -i 3 zhangsan”宽限3天。示例:root用户为zhangsan设置密码:输入“passwd zhangsan”,按提示输入两次密码(密码输入时不显示),密码需符合复杂度要求(如长度≥8位,包含字母、数字、特殊符号)。passwd -l zhangsan #锁定zhangsan账号passwd -e zhangsan #强制zhangsan下次登录修改密码AI写代码bash2.3 账号修改:usermodusermod命令用于修改已存在用户的账号信息,语法格式为:usermod [选项] 用户名,其选项与useradd命令类似,可修改UID、用户组、家目录等。常用选项:-u:修改用户的UID,如“usermod -u 1004 zhangsan”将zhangsan的UID改为1004。-g:修改用户的主组,如“usermod -g admin zhangsan”将zhangsan的主组改为admin组。-G:修改用户的附加组,使用“-G”时会覆盖原附加组,若要保留原附加组并添加新组,需结合“-a”选项(append),如“usermod -aG dev zhangsan”将zhangsan添加到dev附加组,同时保留原有附加组。-d:修改用户的家目录,若需将原家目录内容移动到新目录,需结合“-m”选项,如“usermod -d /home/zs -m zhangsan”将zhangsan的家目录改为/home/zs,并迁移原有文件。-s:修改用户的登录Shell,如“usermod -s /bin/sh zhangsan”将zhangsan的登录Shell改为/bin/sh。-c:修改用户的备注信息,如“usermod -c "Technical Support" zhangsan”更新zhangsan的备注为技术支持。-l:修改用户名,如“usermod -l zs zhangsan”将用户名从zhangsan改为zs,需注意修改后家目录名不会自动变更,需手动修改。-L:锁定用户账号,与passwd -l功能类似,如“usermod -L zhangsan”。-U:解锁用户账号,与passwd -u功能类似,如“usermod -U zhangsan”。示例:将zhangsan的附加组添加dev组,修改备注为“Developer”,登录Shell改为/bin/bash:usermod -aG dev -c "Developer" -s /bin/bash zhangsanAI写代码bash2.4 账号删除:userdeluserdel命令用于删除Linux用户账号,语法格式为:userdel [选项] 用户名常用选项:-r:删除用户的同时删除其家目录和邮件目录,若不使用此选项,仅删除账号信息,家目录会保留,如“userdel -r zhangsan”彻底删除zhangsan账号及相关文件。注意事项:删除用户前,需确保该用户未处于登录状态,若用户正在登录,删除可能失败或导致系统异常,可通过“who”或“w”命令查看当前登录用户,使用“kill”命令终止其进程后再删除。若用户拥有正在运行的服务进程,删除前需先停止相关服务,避免进程残留引发问题。示例:userdel -r lisi #彻底删除lisi账号及其家目录AI写代码bash三、用户组管理实操3.1 组的概念与分类用户组是将多个用户集合在一起的逻辑单位,通过组管理可批量分配权限,简化权限管理操作。Linux用户组分为两类:主组(初始组):用户创建时默认所属的组,每个用户仅能有一个主组,主组信息存储在/etc/passwd文件的GID字段中。若创建用户时未指定主组,系统会自动创建一个与用户名同名的组作为主组。附加组(次要组):用户可加入的额外组,一个用户可属于多个附加组,附加组信息存储在/etc/group文件中。通过附加组,用户可获得多个不同的权限集合。3.2 组创建:groupaddgroupadd命令用于创建新的用户组,语法格式为:groupadd [选项] 组名常用选项:-g:指定组的GID,确保GID未被占用,如“groupadd -g 1005 dev”创建GID为1005的dev组。-r:创建系统组,系统组的GID范围通常为1-999,如“groupadd -r nginx”创建Nginx服务的系统组。示例:groupadd -g 1006 testgroup #创建一个名为testgroup的普通用户组,GID为1006AI写代码bash3.3 组修改:groupmodgroupmod命令用于修改已存在用户组的信息,语法格式为:groupmod [选项] 组名常用选项:-g:修改组的GID,如“groupmod -g 1007 dev”将dev组的GID改为1007。-n:修改组的名称,如“groupmod -n newdev dev”将dev组的名称改为newdev。示例:groupmod -g 1008 -n newtest testgroup #将testgroup组的名称改为newtest,GID改为1008AI写代码bash3.4 组删除:groupdelgroupdel命令用于删除用户组,语法格式为:groupdel 组名注意事项:不能删除用户的主组,若要删除某组,需先将所有以该组为主组的用户修改主组或删除用户。确保组内无用户(包括主组和附加组用户)时再删除,避免残留关联信息。示例:groupdel newtest #删除newtest组(需确保该组不是任何用户的主组且无附加组用户)AI写代码bash3.5 用户组归属调整:gpasswdgpasswd命令用于管理用户组的成员,可添加或删除组内用户,语法格式为:gpasswd [选项] 组名常用选项:-a 用户名:将指定用户添加到组中,如“gpasswd -a zhangsan dev”将zhangsan添加到dev组。-d 用户名:将指定用户从组中删除,如“gpasswd -d zhangsan dev”将zhangsan从dev组中移除。-M 用户名1,用户名2,...:批量设置组内成员,会覆盖原组内成员,如“gpasswd -M zhangsan,lisi dev”将dev组的成员设为zhangsan和lisi。-A 用户名1,用户名2,...:指定组的管理员,组管理员可添加/删除组内成员,如“gpasswd -A zhangsan dev”指定zhangsan为dev组管理员。示例:将lisi添加到dev组,将wangwu从dev组删除,指定zhangsan为dev组管理员:gpasswd -a lisi dev gpasswd -d wangwu dev gpasswd -A zhangsan devAI写代码bash四、Linux权限管理核心4.1 权限的基本概念Linux权限用于控制不同用户(所有者、所属组、其他用户)对文件或目录的操作权限,核心分为读(r)、写(w)、执行(x)三种基本权限,不同权限对应不同的操作范围:读权限(r,对应数字4): 文件:允许读取文件内容,如使用cat、more、less等命令查看文件。目录:允许列出目录内的文件和子目录,如使用ls命令查看目录内容。写权限(w,对应数字2): 文件:允许修改文件内容,如使用vi、echo等命令编辑文件,也可删除文件(需目录有写权限)。目录:允许在目录内创建、删除、重命名文件或子目录,如使用touch、mkdir、rm等命令。执行权限(x,对应数字1): 文件:允许执行该文件,若为可执行程序(如Shell脚本、二进制文件),可直接运行,如“./script.sh”。目录:允许进入该目录,如使用cd命令切换到该目录,若没有执行权限,即使有读权限也无法进入目录。权限的归属对象分为三类,依次对应文件的所有者、所属组、其他用户:所有者(Owner):创建文件或目录的用户,对文件拥有最高控制权,可修改权限和归属。所属组(Group):文件或目录所属的用户组,组内用户共享该组的权限。其他用户(Others):除所有者和所属组用户外的其他所有用户,权限受最严格限制。4.2 权限的表示方法4.2.1 符号表示法符号表示法通过“r、w、x”组合表示权限,共9个字符,分为三组,每组3个字符,依次对应所有者、所属组、其他用户的权限。例如,“-rwxr-xr--”的含义解析如下:第一个字符“-”:表示文件类型,“-”为普通文件,“d”为目录,“l”为软链接,“b”为块设备文件,“c”为字符设备文件等。第2-4个字符“rwx”:所有者权限,拥有读、写、执行权限。第5-7个字符“r-x”:所属组权限,拥有读、执行权限,无写权限(“-”表示无对应权限)。第8-10个字符“r--”:其他用户权限,仅拥有读权限,无写和执行权限。4.2.2 数字表示法数字表示法将r、w、x权限分别对应数字4、2、1,通过权限对应的数字相加得到权限值,共三位数字,依次对应所有者、所属组、其他用户的权限值。例如:rwxr-xr--:所有者权限4+2+1=7,所属组权限4+0+1=5,其他用户权限4+0+0=4,因此数字表示为754。rw-r--r--:所有者6(4+2),所属组4,其他用户4,数字表示为644(普通文件默认权限)。rwxrwxr-x:所有者7,所属组7,其他用户5,数字表示为775。drwxr-xr-x:目录默认权限,数字表示为755,第一个字符“d”表示目录。4.3 权限查看:ls -l使用“ls -l”命令可查看文件或目录的详细信息,包括权限、所有者、所属组、大小、修改时间等。例如:ls -l /home/zhangsan/test.txt #-rwxr-xr-- 1 zhangsan dev 1024 Nov 6 10:00 /home/zhangsan/test.txtAI写代码bash各字段含义依次为:权限(-rwxr-xr--)、硬链接数(1)、所有者(zhangsan)、所属组(dev)、文件大小(1024字节)、修改时间(11月6日10:00)、文件路径和名称。若要查看目录的权限,可直接执行“ls -l 目录路径”,如“ls -l /home/zhangsan”。五、权限修改实操5.1 修改权限:chmodchmod命令用于修改文件或目录的权限,语法格式有两种:符号格式和数字格式。5.1.1 符号格式语法:chmod [归属对象][操作符][权限] 文件/目录归属对象:u(所有者)、g(所属组)、o(其他用户)、a(所有用户,默认)。操作符:+(添加权限)、-(移除权限)、=(设置权限,覆盖原有权限)。权限:r(读)、w(写)、x(执行)。示例:chmod u+w test.txt #给test.txt文件的所有者添加写权限chmod g-x test.txt #移除test.txt所属组的执行权限chmod o=rx test.txt #给其他用户设置读和执行权限chmod a+r test.txt 或 chmod +r test.txt #给所有用户添加读权限chmod -R u+x dev_dir#给目录dev_dir及其下所有文件和子目录的所有者添加执行权限(-R递归修改)AI写代码bash5.1.2 数字格式语法:chmod [权限数字] 文件/目录,权限数字为三位,对应所有者、所属组、其他用户的权限值。示例:chmod 754 test.txt #将test.txt权限设置为rwxr-xr--(754) chmod -R 775 dev_dir #将目录dev_dir权限设置为rwxrwxr-x(775),并递归修改子内容 chmod 744 script.sh #将脚本script.sh设置为所有者可执行(其他用户无执行权限)————————————————版权声明:本文为CSDN博主「UCH1HA」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/yzbtom01/article/details/154479323
-
一、核心定位:Vue 实例作为组件的“宇宙中心”在 Vue 中,this 指向当前组件的实例对象。这个实例是 Vue 框架为您创建的、管理组件所有状态和行为的统一上下文环境。您可以将其理解为一个精心设计的“黑匣子”或“宇宙中心”,所有组件相关的操作都通过这个中心的接口(this)进行。设计哲学:通过提供一个统一的 this 上下文,Vue 极大地简化了组件开发的复杂度。开发者不需要关心数据如何绑定、更新如何调度,只需要通过 this 来“声明”你的意图。二、深度原理剖析:this 的绑定魔法Vue 如何确保在方法中访问的 this 总是指向组件实例?这并非 JavaScript 的自然行为,而是 Vue 在组件初始化阶段精心策划的结果。1. 初始化阶段的“代理”与“绑定”当我们执行 new Vue(options) 时,Vue 内部会进行一系列关键操作(以下是高度简化的伪代码):// 伪代码:模拟 Vue 构造函数的核心逻辑function Vue(options) { const vm = this; // vm 代表 Vue 实例 // 1. 将选项混入实例 vm.$options = options; // 2. 初始化数据响应式系统(核心中的核心) initData(vm); // 3. 关键步骤:处理 methods,进行 this 绑定 initMethods(vm, options.methods); // 4. 初始化计算属性、侦听器等 // ...} function initMethods(vm, methods) { for (const key in methods) { // 将每个方法挂载到实例上,并通过 bind 永久绑定 this vm[key] = methods[key].bind(vm); // 魔法发生在这里! // 现在,无论这个方法如何被调用,其内部的 this 都被锁定为 vm }} function initData(vm) { let data = vm.$options.data; data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}; // 代理:使得 this.xxx 能直接访问 this._data.xxx for (const key in data) { proxy(vm, '_data', key); // 通过 Object.defineProperty 进行代理 } // 将 data 变为响应式 observe(data);}AI写代码javascript运行关键解读:**bind 的威力**:methods 中的每个函数都被显式地通过 .bind(vm) 将 this 永久绑定到实例。这是解决 this 指向问题的根本。数据代理:当你访问 this.message 时,Vue 通过代理机制将其转换为访问 this._data.message。这创造了一种“数据直接挂在实例上”的直观体验。2. 箭头函数的“免疫”现象及其原理箭头函数是 ES6 的语法糖,它的核心特性之一是没有自己的 this 绑定,其内部的 this 继承自外部的词法作用域。export default { data() { return { count: 0 }; }, methods: { // ✅ 正确:普通函数,this 被 Vue 绑定到实例 incrementRegular() { this.count++; // this 指向组件实例 }, // ❌ 危险:箭头函数,this 在定义时就被词法绑定,无法被 Vue 重写 incrementArrow: () => { // 这里的 this 不会是组件实例! // 在严格模式下可能是 undefined,非严格模式下可能是 window(浏览器) // 或 global(Node.js) console.log(this); // undefined 或 Window this.count++; // TypeError: Cannot read properties of undefined } }}AI写代码javascript运行export default { created() { // 这个普通函数的 this 可以被 Vue 绑定 const regularFunc = function() { console.log(this); }; // 这个 this 可以被 bind 影响 // 这个箭头函数的 this 在定义时就已确定(继承自 created 函数的作用域) const arrowFunc = () => { console.log(this); }; // 这个 this 在定义时已固定,bind 无效 }}AI写代码javascript运行当你在 methods 中使用箭头函数时:methods: { brokenMethod: () => { // 这个箭头函数在定义时,它的外部作用域是模块作用域(通常是 undefined 或 global)。 // 由于箭头函数的 this 是词法决定的,Vue 的 bind 操作无法覆盖它。 console.log(this); // 不会是组件实例! }}AI写代码javascript运行根本原因:Vue 的 bind 操作只对普通函数有效,而箭头函数对 bind 调用是“免疫”的。Vue 无法改变词法作用域决定的 this。三、this 的能力图谱:实例的完整 API组件实例是一个功能极其丰富的对象。我们可以将其 API 系统化地分为以下几个层面:API 类别 核心成员 作用与说明数据相关 this.$data, this.$props 访问响应式数据和属性(通常直接通过 this.xxx 代理访问)DOM/组件引用 this.$refs 访问模板中标记的 DOM 元素或子组件实例事件通信 this.$emit, this.$on, this.$off 组件间通信的核心机制生命周期 this.$forceUpdate(), this.$nextTick() 强制刷新、等待 DOM 更新应用级依赖 this.$router, this.$store, this.$http 通过插件注入的全局服务(如 Vue Router, Vuex, Axios)高级特性 this.$slots, this.$scopedSlots, this.$attrs, this.$listeners 用于高级组件开发,处理插槽、属性透传等简单举例:1. 访问响应式数据 (data, props)export default { props: ['userName'], // 来自父组件 data() { return { count: 0, message: 'Hello' }; }, methods: { updateData() { // 访问 data 中定义的数据 this.count = 10; this.message = 'Updated'; // 访问 props 中接收的数据 console.log(this.userName); // 注意:不要直接修改 props(除非是对象/数组,但也不推荐) // this.userName = 'New Name'; // ❌ 反模式 } }}AI写代码javascript运行2. 调用方法 (methods) 和获取计算属性 (computed)export default { data() { return { firstName: '张', lastName: '三' }; }, computed: { // 计算属性像数据一样被访问 fullName() { return this.firstName + this.lastName; } }, methods: { greet() { // 调用其他方法 this.formatMessage(); // 访问计算属性 alert(`你好,${this.fullName}!`); }, formatMessage() { // 方法逻辑 } }}AI写代码javascript运行3. 操作 DOM 与子组件 ($refs)export default { methods: { focusInput() { // 访问模板中 ref="myInput" 的元素 this.$refs.myInput.focus(); }, callChildMethod() { // 访问子组件实例并调用其方法 this.$refs.childComponent.someMethod(); } }, mounted() { // 组件挂载后,$refs 才可用 this.focusInput(); }}AI写代码javascript运行4. 事件通信 ($emit, $on, $off)export default { methods: { submitForm() { // 向父组件发送事件 this.$emit('form-submitted', { data: this.formData }); // 全局事件总线(如果已设置) this.$bus.$emit('global-event', data); } }}AI写代码javascript运行5. 应用级全局属性 ($router, $store, $api)当使用了 Vue Router、Vuex 或注入了全局属性时:export default { methods: { navigateToAbout() { // 访问 Vue Router 实例 this.$router.push('/about'); }, updateUser() { // 访问 Vuex Store this.$store.dispatch('user/update', this.userData); }, async fetchData() { // 访问全局注入的 API 实例 const data = await this.$api.get('/data'); } }}AI写代码javascript运行6. $nextTick 的深度理解 这是 Vue 异步更新机制的关键。当您修改了响应式数据后,DOM 并不会立即更新,而是被推入一个队列。this.$nextTick 接收一个回调函数,这个回调会在下一次 DOM 更新循环结束后执行。这是保证能获取到最新 DOM 的唯一方式。export default { methods: { updateMessage() { this.message = 'Updated'; // 数据已变,但 DOM 还未渲染 // 错误:此时 DOM 还是旧的 console.log(this.$el.textContent); // 可能是旧值 this.$nextTick(() => { // 正确:此时 DOM 已更新 console.log(this.$el.textContent); // 'Updated' }); } }}AI写代码javascript运行四、范式转变:组合式 API 中对 this 的“祛魅”Vue 3 的组合式 API 是革命性的,它从根本上改变了我们与组件实例交互的方式。1. 为什么 setup() 中没有 this?import { ref, onMounted, getCurrentInstance } from 'vue'; export default { setup() { // ❌ 这将是 undefined console.log(this); // undefined // ✅ 使用 ref 创建响应式数据 const count = ref(0); // ✅ 使用生命周期钩子函数 onMounted(() => { console.log('组件挂载完毕'); }); // ✅ 在极少数需要访问实例的情况下(不推荐) const instance = getCurrentInstance(); console.log(instance); // 获取当前组件实例的代理对象 return { count // 必须 return 才能在模板中使用 }; }}AI写代码javascript运行2. 新的心智模型:显式优于隐式组合式 API 鼓励“显式”声明依赖,而不是依赖“隐式”的 this。// 选项式 API (Implicit)export default { data() { return { count: 0 } }, methods: { increment() { this.count++; // 隐式地通过 this 访问 count } }} // 组合式 API (Explicit)import { ref } from 'vue';export default { setup() { // 1. 显式地声明响应式数据 const count = ref(0); // 2. 显式地声明函数 const increment = () => { count.value++; // 显式地通过 .value 访问 ref }; // 3. 显式地暴露给模板 return { count, increment }; }}AI写代码javascript运行优势:更好的 TypeScript 支持:类型推导不再需要魔法。逻辑提取与复用:函数和响应式状态可以轻松提取到外部函数。更小的压缩体积:方法名可以被压缩,而 this 上的属性名通常不行。3. 获取实例的应急方案在极少数情况下(如需要访问插件注入的 $router),可以使用 getCurrentInstance,但通常不推荐,因为它破坏了组合式函数的纯粹性。import { getCurrentInstance } from 'vue'; export default { setup() { const instance = getCurrentInstance(); // 获取当前实例代理 // 通过 .proxy 访问实例上的属性(相当于选项式 API 中的 this) console.log(instance.proxy.$router); // 主要用于高级库开发,普通业务代码应避免使用。 }}AI写代码javascript运行五、专家级陷阱与性能优化1. 闭包陷阱export default { data() { return { importantData: 'secret' }; }, methods: { setupEventListener() { // ❌ 陷阱:将包含 this 的方法传递给外部库 externalLib.on('update', this.handleUpdate); }, handleUpdate() { console.log(this.importantData); // 即使组件销毁,这个方法仍可能被调用,this 指向原实例,可能导致内存泄漏。 } }, beforeUnmount() { // ✅ 解决方案:必须清理! externalLib.off('update', this.handleUpdate); }}AI写代码javascript运行2. 方法作为 Prop 的潜在问题将方法作为 prop 传递给子组件时,this 的绑定需要小心。// 父组件methods: { parentMethod() { console.log(this); // 指向父组件实例 }} // 子组件export default { props: ['parentMethod'], created() { // 直接调用,parentMethod 内的 this 仍指向父实例,这是符合预期的。 this.parentMethod(); }}AI写代码javascript运行3. 异步回调中的 this 丢失export default { data() { return { value: '' }; }, methods: { // ❌ 常见错误:异步回调中 this 指向改变 fetchData() { setTimeout(function() { // 普通函数,this 指向 Window(或严格模式下 undefined) this.value = 'New Value'; // 错误! }, 1000); }, // ✅ 解决方案1:使用箭头函数(继承外部 this) fetchDataCorrect() { setTimeout(() => { this.value = 'New Value'; // 正确! }, 1000); }, // ✅ 解决方案2:提前保存 this fetchDataCorrect2() { const self = this; // 或使用 vm setTimeout(function() { self.value = 'New Value'; // 正确! }, 1000); } // ✅ 解决方案3:使用 .bind(this) fetchDataCorrect3() { setTimeout(function() { this.value = 'New Value'; // 正确! }.bind(this), 1000); } }}AI写代码javascript运行总结:Vue 中 this 的演进与哲学维度 选项式 API (Vue 2) 组合式 API (Vue 3)核心心智模型 “面向实例”编程:所有东西都组织在 this 上下文中。 “函数式”编程:通过导入函数和引用与组件交互。this 的角色 组件的统一入口,是框架魔法的体现。 被“祛魅”,在 setup 中不存在,鼓励显式逻辑。优势 结构规整,易于初学者理解单个组件的结构。 逻辑复用性极佳,TypeScript 支持友好,代码组织更灵活。劣势 逻辑关注点分散,大型组件易形成“面条代码”,类型推导困难。 学习曲线更陡峭,需要更好的代码组织能力。作为专家的建议:新手到专家之路:深刻理解选项式 API 中 this 的绑定原理,是解决诡异 Bug 的钥匙。现代开发范式:拥抱组合式 API 的显式思想,理解其“没有 this”的设计是框架的一种进化,旨在解决大规模应用的复杂性。底层原理:无论是哪种 API,Vue 的响应式系统、依赖收集、异步更新队列这些底层机制始终是核心。this 只是访问这些机制的一个(正在演变的)接口。希望这份深入的分析能帮助您彻底掌握 Vue 中 this 的精髓。————————————————版权声明:本文为CSDN博主「safestar2012」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/safestar2012/article/details/154454241
-
1、简介Spring Cloud 来源于 Spring,质量、稳定性、持续性都可以得到保证。Spirng Cloud 天然支持 Spring Boot,更加便于业务落地。相比于其它框架,Spring Cloud 对微服务周边环境的支持力度最大。使用门槛较低核心特性:分布式/版本化配置,服务注册和发现,路由。服务和服务之间的调用,负载均衡。断路器,分布式消息传递2、Eureka:注册中心三个核心角色:服务注册中心(Eureka Server):Eureka的服务端应用,提供服务注册和发现功能服务提供者(Service Provider):将自身的服务注册到Eureka中,从而使消费方能找到服务消费者(Server Consumer):消费方从注册中心获取服务列表,找到消费服务自我保护机制:“好我不如赖活着”:该模式被激活后,它不会从注册列表中剔除因长时间没收到心跳导致租期过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式旨在避免因网络分区故障导致服务不可用的问题。3、CAP原则:强一致性,可用性,分区容错性CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。Eureka与Zookeeper区别:Eureka:保证AP:Eureka 各个节点是平等的,几个节点挂掉不会影响正常工作,只要有一台 Eureka 存在,就可以保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点没有正常的心跳,那么 Eureka 就会认为客户端与注册中心出现了故障,此时会出现以下几种情况: Eureka 不再从注册列表中移出因长时间没收到心跳而应该过期的服务Eureka 仍然能够接受新服务的注册和查询要求,但是不会被同步到其他节点上(即保证当前节点依然可用) 当网络稳定时,当前实例新的注册信息会被同步到其他节点中Zookeeper:保证CP:当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的信息,但不能就收服务直接 down 掉不可用。也就是说服务注册的可用性要高于一致性。Master节点故障 à 选举时间过长 à 服务瘫痪 à 最终恢复,时间过长,不能容忍总结:eureka可以很好的应对网络故障导致部分节点市区联系的情况,而不会想zookeeper那样使整个服务瘫痪。4、Ribbon:负载均衡(常用于微服务或分布式集群)英文名称为Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行置负载均衡实现RestTemplate@LoadBalanced //ribbon在微服务启动的时候去加载我们自定义的ribbon@RibbonClient(name="SPRINGCLOUD-PROVIDER-DEPT",configuration= DuanRule.class)5、Feign:Feign集成了RibbonRibbon+eureka是面向微服务编程,而Feign是面向接口编程。调用微服务访问的两种方法:微服务的名字【Ribbon】,接口和注解【Feign】@EnableFeignClients(basePackages = {"com.duan.springcloud"})6、Hystrix:熔断器服务雪崩:一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩。服务熔断(服务端:保险丝):@HystrixCommand(fallbackMethod = "hystrixGet")一旦调用的服务抛出错误信息,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法服务降级(客户端):@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceBackFactory.class)当某个服务熔断或关闭后,服务不再被调用,此时,再客户端准备一个FallBackFactory,返回一个默认值,集体服务下降,但是好歹能用,比挂掉强7、Dashboard流监控:服务监控@ EnableHystrixDashboard开启仪表盘监控注解监控界面输入要监控的微服务(http://localhost:8001/hystrix.stream)一圆:实心圆:健康程度:绿色<黄色<橙色<红色 递减(监控请求流量)一线:曲线:用来记录流量2分钟内的变化,可以用来观察流量的上升下降的趋势图:左三:成功数、熔断数、错误请求数,右三:超时数,线程池拒绝数,失败异常数 右下方:服务请求频率,断路状态8、Zuul:路由网关包含了对请求的路由和过滤两个主要功能:路由负责将外部请求转发到具体的实例上,是实现外部访问统一入口的基础,过滤负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。注意:Zuul服务最终还是会注册到Eureka提供:代理+路由+过滤三大功能routes: mydept.serviceId: springcloud-provider-dept mydept.path: /mydept/**ignored-services: springcloud-provider-dept #不能使用这个路径访问 ignored 忽略prefix: /duan #设置共用的前缀9、Config:分客户端和服务端两个部分:集中管理配置文件以及动态调整配置服务端:又称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并未客户端提供配置信息,加密,解密信息等访问接口客户端:通过指定的配置中心管理应用资源以及业务相关配置,配置服务器默认采用git来存储信息,有助于版本管理,可以通过git客户端工具管理配置访问内容Git环境搭建:注册GITEE码云 à 新建仓库 à 克隆/下载SSH à服务端连接Git配置:克隆到本地文件夹:$ git clone git@gitee.com:duan0719/springcloud-config.git提交本地文件到码云:git add . à git status à git commit -m “description” à git push origin masterserver.port: 3344spring. Application.name: springcloud-config-serverspring.cloud.config.server.git.uri: https://gitee.com/duan0719/springcloud-config.gitspring.cloud.config.server.git.force-pull: truespring.cloud.config.server.git.username: duan0719spring.cloud.config.server.git.password: DX19950717客户端连接服务端访问远程:bootstrap.yml系统级别的配置:spring.cloud.config.uri: http://localhost:3344spring.cloud.config.name: config-clientspring.cloud.config.profile: devspring.cloud.config.label: masterapplication.yml用户级别配置:spring.application.name: springcloud-config-client-3355获取config-client.yml中属性值:@Value("${spring.application.name}")private String applicationName;@Value("${eureka.client.service-url.defaultZone}")private String eurekaServer;@Value("${server.port}")private String port;————————————————版权声明:本文为CSDN博主「众俗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/m0_46359551/article/details/154349780
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签