虬鬤客 – web百事通 https://www.askme-121.space web互联网之家 Tue, 25 Mar 2025 09:14:33 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.5.13 https://www.askme-121.space/wp-content/uploads/2023/12/cropped-05ee702f-4b38-40f3-915f-c8fc68b10a91-32x32.png 虬鬤客 – web百事通 https://www.askme-121.space 32 32 less、sass、scss、stylus与css的区别 https://www.askme-121.space/less%e3%80%81sass%e3%80%81scss%e3%80%81styluscss/ https://www.askme-121.space/less%e3%80%81sass%e3%80%81scss%e3%80%81styluscss/#respond Tue, 25 Mar 2025 06:37:30 +0000 https://www.askme-121.pw/?p=554 一、什么是css预处理器,有什么作用

CSS 预处理器是一种将预先定义的语法和功能添加到 CSS 中的工具。它们允许开发人员使用变量、嵌套规则、混合、继承等功能,以更高效和可维护的方式编写样式表。

CSS 预处理器的作用包括:

变量和计算:预处理器允许开发人员使用变量来存储颜色、字体、尺寸等值,以便在整个样式表中进行统一的修改。预处理器还支持数学计算,可以进行简单的加减乘除操作,提高了样式表的灵活性和可维护性。

嵌套规则:预处理器允许开发人员在样式表中使用嵌套规则,以更清晰地表示元素之间的层次关系。这减少了选择器的重复书写,并提高了代码的可读性。

混合和继承:预处理器支持混合(Mixins)功能,开发人员可以将一组样式属性定义为一个混合器,并在需要的地方进行调用。这样可以减少样式表中的重复代码,提高了代码的复用性。预处理器还支持继承(Extend)功能,可以将一个选择器的样式继承到另一个选择器中,减少了重复的样式定义。

导入和模块化:预处理器允许开发人员将多个样式文件导入到一个文件中,以更好地组织和管理样式表。这样可以将样式表分成多个模块,方便团队协作和代码的复用。

扩展语法和功能:CSS 预处理器通常提供了一些扩展的语法和功能,如条件语句、循环、函数等,以增强 CSS 的能力。这些功能可以帮助开发人员更高效地编写样式表。

总的来说,CSS 预处理器提供了一些有助于开发人员更高效、更可维护地编写样式表的功能和语法。它们可以提高样式表的灵活性、可读性和可维护性,减少了重复代码的书写,并提供了一些额外的功能来增强 CSS 的能力。

二、常见的预处理器

Less:Less 是一种动态样式语言,它扩展了 CSS 的功能,提供了变量、嵌套、混合、运算等功能。Less 使用类似于 CSS 的语法,但具有更简洁的书写方式。它可以通过 Less 编译器将 Less 文件编译为普通的 CSS 文件。Less 具有较小的学习曲线,易于上手。

Sass:Sass 是一种成熟且广泛使用的 CSS 预处理器,它提供了许多强大的功能,如变量、嵌套规则、混合、继承等。Sass 有两种语法:Sass(使用缩进)和 SCSS(使用类似于 CSS 的语法)。Sass 可以通过命令行工具或构建工具编译为普通的 CSS 文件。Sass 具有强大的功能和灵活性,适用于大型项目。

SCSS:SCSS 是 Sass 的一种语法格式,它使用类似于 CSS 的语法,但具有 Sass 的功能和特性。SCSS 可以看作是 Sass 的一种扩展,使用大括号和分号来表示代码块和语句。SCSS 与 Sass 兼容,可以在同一个项目中同时使用 Sass 和 SCSS 文件。

Stylus:Stylus 是一种简洁、灵活的 CSS 预处理器,它使用类似于 Python 的缩进语法。Stylus 提供了类似于 Sass 和 Less 的功能,如变量、嵌套规则、混合、继承等。Stylus 也可以通过命令行工具或构建工具编译为普通的 CSS 文件。Stylus 的语法非常简洁,可以减少样式表的代码量。

这些 CSS 预处理器都有庞大的社区支持和活跃的开发者社区,提供了丰富的文档、示例和插件。您可以根据个人偏好和项目需求选择适合您的 CSS 预处理器。

三、预处理器和css的对比

CSS 预处理器(如 Less、Sass、SCSS 和 Stylus)相对于原始的 CSS 语言具有一些优点和缺点。

优点:

变量和计算:预处理器可以使用变量来存储颜色、字体、尺寸等值,方便在整个样式表中进行统一的修改。预处理器还支持数学计算,可以进行简单的加减乘除操作,提高了样式表的灵活性和可维护性。

嵌套规则:预处理器允许嵌套 CSS 规则,可以更清晰地表示元素之间的层次关系,减少了选择器的重复书写,并提高了代码的可读性。

混合和继承:预处理器支持混合(Mixins)功能,可以将一组样式属性定义为一个混合器,并在需要的地方进行调用。这样可以减少样式表中的重复代码,提高了代码的复用性。预处理器还支持继承(Extend)功能,可以将一个选择器的样式继承到另一个选择器中,减少了重复的样式定义。

导入和模块化:预处理器支持将多个样式文件导入到一个文件中,可以更好地组织和管理样式表。这样可以将样式表分成多个模块,方便团队协作和代码的复用。

缺点:

学习曲线:使用预处理器需要学习其特有的语法和功能,相对于原始的 CSS 语言来说,有一定的学习曲线。这可能会增加开发人员的学习成本和项目的启动时间。

预编译过程:预处理器需要通过编译过程将预处理器代码转换为普通的 CSS 代码,这增加了构建过程的复杂性和时间消耗。每次修改样式都需要重新编译,可能会增加开发的迭代时间。

额外的工具和依赖:使用预处理器通常需要额外的工具和依赖,如编译器、构建工具等。这可能会增加项目的复杂性和维护成本。

兼容性问题:预处理器生成的 CSS 代码可能在一些旧版本的浏览器中存在兼容性问题。需要进行兼容性测试和适配,增加了开发和测试的工作量。

总结来说,CSS 预处理器具有变量和计算、嵌套规则、混合和继承、导入和模块化等优点,可以提高样式表的灵活性、可读性和可维护性。但同时也存在学习曲线、预编译过程、额外的工具和依赖、兼容性问题等缺点。开发人员可以根据项目需求和团队情况,权衡这些优缺点,选择是否使用 CSS 预处理器。

]]>
https://www.askme-121.space/less%e3%80%81sass%e3%80%81scss%e3%80%81styluscss/feed/ 0
Rust语言入门第二篇-Cargo教程 https://www.askme-121.space/rust-cargo/ https://www.askme-121.space/rust-cargo/#respond Tue, 26 Nov 2024 12:13:23 +0000 https://www.askme-121.pw/?p=553 一.Cargo 是什么

Cargo 是 Rust 的官方构建系统和包管理器。它负责管理 Rust 项目的依赖项、构建项目以及管理项目的配置。

二.Cargo教程

要使用 Cargo 创建一个新的 Rust 项目,请使用cargo new

cargo new hello_cargo

这一行命令新建了名为 hello_cargo 的目录和项目。我们将项目命名为 hello_cargo,同时 Cargo 在一个同名目录中创建项目文件。

进入目录,将会看到 Cargo 生成了两个文件和一个目录:一个 Cargo.toml 文件,一个 src 目录,以及位于 src 目录中的 main.rs 文件。

Cargo.toml文件

这个 Cargo 文件描述了一个 Rust 项目的元数据和依赖项。下面是对文件内容的解析:

1.[package] 是一个片段(section)标题,表示接下来的内容是关于项目包的配置。

name = "hello_cargo":指定了项目的名称为 “hello_cargo”
version = "0.1.0":指定了项目的版本号为 “0.1.0”。
edition = "2021":指定了 Rust 编译器所使用的 Edition(版本),这里是 2021 Edition。

2.[dependencies] 部分:

这个部分通常用于列出项目所依赖的外部库。Cargo 文件还可以包含其他部分,比如 [workspace]、[profile] 等,用于配置工作空间和编译器参数等。这些部分不是必需的,取决于项目的需要。

src/main.rs 文件

fn main() {
    println!("Hello, world!");
}

这段 Rust 代码定义了一个程序的入口点(main 函数),并在控制台打印 “Hello, world!” 消息。

构建并运行Cargo项目

cargo build

具体而言,cargo build 命令的含义是:

1.编译项目代码:Cargo 会根据项目根目录下的 Cargo.toml 文件中的配置信息,编译整个 Rust 项目的源代码。

2.生成可执行文件或库:根据项目类型(可执行程序或库),Cargo 会生成对应的可执行文件(通常在 target/debug 目录下)或者库文件(通常在 target/debug 目录下),这取决于项目的配置和 Cargo.toml 文件中的指定。

3.处理依赖关系:如果项目依赖了其他的 Rust crate(包),Cargo 会自动下载并编译这些依赖,以确保项目的所有依赖都被正确处理和链接。

4.检查代码:Cargo 会在编译过程中执行各种检查,包括语法检查、类型检查、错误检查等,以确保代码的质量和正确性。

我们也可以使用编译然后运行它,所有这些都在一个步骤中完成:cargo run

]]>
https://www.askme-121.space/rust-cargo/feed/ 0
Rust语言入门第一篇-环境搭建 https://www.askme-121.space/rust-makeup/ https://www.askme-121.space/rust-makeup/#respond Fri, 22 Nov 2024 14:35:19 +0000 https://www.askme-121.pw/?p=552 1、C++开发环境配置

Rust 语言的底层是依赖于 C/C++ 编译器的。在安装 Rust 编译器时,通常会自动安装所需的 C/C++ 编译环境,以便 Rust 能够生成可执行文件或库。因此,在安装 Rust 之前,建议确保系统已经安装了 C/C++ 编译器。

下载地址:https://github.com/niXman/mingw-builds-binaries/releases

验证C++是否环境配置成功,按下 “Win + R” 键,打开 “运行” 对话框。 输入gcc -v

2.Rust 环境配置

如果您曾经安装过 rustup,可以执行 rustup update 来升级 Rust。

配置 PATH 环境变量

在 Rust 开发环境中,所有工具都安装在 ~/.cargo/bin 目录中,您可以在这里找到包括 rustc、cargo 和 rustup 在内的 Rust 工具链。Rust 开发者通常会将该目录加入 PATH环境变量中。安装后在终端尝试执行 rustc –version

3.Rust程序

启动的过程中报错了,按下 “Win + R” 键,打开 “运行” 对话框。 输入rustup default stable-x86_64-pc-windows-gnu

代码讲解

  1. fn main() {
    这行代码定义了一个名为main的函数。在Rust程序中,main函数是程序的入口点,也是程序开始执行的地方。Rust程序从main函数开始执行,因此这个函数是必须的,没有main函数的Rust程序将无法正常运行。
  2. println!(“Hello, world!”);
    这是main函数中的一行代码,它调用了一个宏(macro)println! 来向控制台输出一段文本。在Rust中,println! 是一个宏,用于在标准输出流(通常是控制台)打印一段格式化的文本。在这里,println!(“Hello, world!”); 的作用是将字符串”Hello, world!” 输出到控制台。

println! 是一个宏,而不是普通的函数。宏在Rust中使用!来调用,例如println!、panic!等。"Hello, world!" 是一个字符串字面量(string literal),它是一个固定的文本内容,会被直接输出到控制台。

执行过程:

当你运行这段Rust程序时,操作系统会首先调用main函数。在main函数中,println!("Hello, world!");会被执行,导致字符串"Hello, world!"被输出到控制台。这是一个简单但经典的示例,用于展示如何编写并执行最基本的Rust程序。

注意事项:

  • Rust的语法相对严格和安全,因此每个语句末尾需要分号;来结束。
  • 字符串字面量(如"Hello, world!")是不可变的,它们存储在程序的只读内存区域中。
  • Rust中的宏(macro)和普通函数有所区别,宏在编译时进行代码替换,因此可以实现更灵活的代码生成和转换。
]]>
https://www.askme-121.space/rust-makeup/feed/ 0
Rust语言 https://www.askme-121.space/rust/ https://www.askme-121.space/rust/#respond Sat, 16 Nov 2024 13:55:10 +0000 https://www.askme-121.pw/?p=548

一,Rust语言是什么

Rust 是一种系统级编程语言,由 Mozilla 开发。它的设计注重安全性、并发性和高性能。Rust 最初发布于 2010年,其目标是成为一种能够替代 C 和 C++ 的编程语言,同时提供更好的内存安全性和并发支持。

以下是 Rust 目标成为一种能够替代 C 和 C++ 的编程语言的一些优势:

1. 内存安全性:Rust 通过所有权系统和借用检查器来保证内存安全。这意味着在编写 Rust 代码时,不会出现诸如空指针、野指针或者数据竞争等内存安全问题,这些问题在 C 和 C++ 中经常导致程序崩溃或安全漏洞。

2.并发性:Rust 内置了对并发编程的支持,包括线程、消息传递和共享状态。通过使用 Rust 的并发编程模型,开发者可以编写出更加安全和高效的并发代码,避免了在 C 和 C++ 中常见的并发问题,如死锁、数据竞争等。

3.性能:Rust 的设计注重性能,其编译器能够生成高效的机器码,并且在不牺牲安全性的前提下尽可能地进行优化。因此,Rust 程序通常具有与 C 和 C++ 程序相媲美的性能。

4.可移植性:Rust 支持跨平台开发,可以在多种操作系统和硬件架构上运行。这使得 Rust 成为一个适用于各种项目和环境的通用编程语言。

访问 Rust 官网给出的答案涵盖了选择 Rust 的三个主要原因:高性能、可靠性和生产力。让我对这些原因进行更详细的解释:

1.高性能:Rust 的设计旨在提供接近于 C 和 C++ 的执行效率。它通过一系列的编译器优化和内存安全机制,使得 Rust 代码能够在不损失性能的情况下实现内存安全。这意味着你可以编写高性能的应用程序,同时又能够避免常见的内存错误,如空指针、野指针、数据竞争等。

2.可靠性:Rust 引入了独特的所有权系统和借用规则,以确保在编译时捕获内存安全问题。这种静态检查机制能够在代码编译阶段就发现潜在的错误,而不是在运行时产生意外行为或崩溃。通过在编译时防止内存错误和并发问题,Rust 可以提供更可靠、更稳定的软件。

3.生产力:尽管 Rust 强调了性能和可靠性,但它也注重开发者的生产力。Rust 提供了现代化的语法和丰富的功能,使得编写和维护代码更加轻松。它支持泛型编程、模式匹配、闭包等高级特性,提供了强大的标准库和第三方库,使得开发者可以快速构建复杂的应用程序。此外,Rust 的文档和工具链也都非常完善,有助于开发者更快地上手和解决问题。

二,Rust语言能做什么?

Rust语言的设计使其适用于许多不同的领域,包括但不限于以下几个方面:

  1. 传统命令行程序:
    Rust提供了强大的系统编程功能,使其非常适合编写各种命令行工具和实用程序。
  2. Web 应用:
    借助诸如Actix、Rocket等现代Web框架,Rust可以用于构建高性能、安全的Web应用程序。
  3. 网络服务器:
    Rust的并发和异步编程特性使其成为构建高性能网络服务器的理想选择,例如使用Tokio等异步运行时。
  4. 嵌入式设备:
    Rust的低级别控制和内存安全性使其成为嵌入式系统开发的良好选择,可以用于编写嵌入式设备的驱动程序、控制程序等。
  5. 操作系统:
    Rust的内存安全性和并发性使其成为操作系统开发的有力工具。例如,Redox OS就是一个完全用Rust编写的操作系统。
  6. 游戏引擎:
    虽然Rust在游戏开发领域的应用还不如其他语言广泛,但由于其性能和安全性,一些游戏引擎开始考虑使用Rust编写关键组件。
  7. 数据库:
    Rust社区中有一些正在积极开发的数据库项目,例如RocksDB等,可以用于构建高性能的数据库系统。
  8. 并行计算:
    Rust提供了并发和并行编程的支持,使其适用于开发并行计算应用程序,例如科学计算、数据处理等。

Rust的优势:

1.卓越的性能和安全性:Rust以性能和安全性为设计目标,通过强大的编译时检查和所有权系统,可以避免常见的内存安全问题,同时保持着接近于原生编译语言的性能。
2.灵活的语法和类型系统: Rust具有灵活的语法和严格的类型系统,支持函数式编程和面向对象编程,使得开发者能够编写出简洁、高效的代码。
3.包管理和构建工具: Rust拥有Cargo作为其默认的包管理和构建工具,它提供了便捷的依赖管理和项目构建功能,帮助开发者更快速地进行开发和部署。
4.快速的编译和执行速度: Rust的编译器通常能够快速生成高效的机器码,使得开发者可以更快地进行迭代和测试。

Rust的劣势:

1.陡峭的学习曲线: Rust的所有权和生命周期等概念对于一些开发者来说可能较为陌生,需要花费一定的时间和精力来学习和掌握。
2.相对较小的开发生态和社区: 相比于Java等老牌语言,Rust的开发生态和社区规模相对较小,可能会缺乏一些成熟的解决方案和第三方库。
3.严格的安全检查可能带来的负担: 虽然Rust的严格安全检查可以避免很多常见的错误,但有时也可能会导致一些开发负担,需要开发者手动管理一些内存和生命周期相关的问题。

]]>
https://www.askme-121.space/rust/feed/ 0
SPA vs MPA https://www.askme-121.space/spa-vs-mpa/ https://www.askme-121.space/spa-vs-mpa/#respond Tue, 28 May 2024 08:30:02 +0000 https://www.askme-121.pw/?p=544 单页应用程序(SPA)越来越受欢迎。Facebook,YouTube,Twitter,GitHub和许多Google服务都是使用SPA技术构建的。但是,多页应用程序(MPA)构成了Internet上大多数网站。但是MPA的时代结束了吗?

在本文中,我将搁置围绕技术的所有宣传,并客观地对其进行评估。我将列出SPA方法的优缺点,并建议何时使用它。

SPA与MPA。有什么不同?

当MPA对您的输入做出反应或必须显示一些新内容时,它会向服务器请求一个新的HTML页面。收到标记后,浏览器将呈现新页面,使其重新加载。

AJAX(异步JavaScript和XML)允许Web应用程序仅刷新页面的一部分。

与SPA交互时,浏览器将对服务器进行AJAX调用。但是服务器无需呈现新页面,而是可以向您发送数据(通常以JSON格式)。然后,SPA将使用该数据直接在浏览器中呈现页面的某些部分。

结果是一个应用程序由一个高度交互的网页组成,该网页从未完全重新加载。

因此,现在您知道了SPA和MPA之间的基本区别,让我们更深入地了解它们的优缺点。

SPA的优势

1.快速流畅的用户体验

不难想象,持续不断的整页重新加载会如何损害用户体验。

您将在SPA中永远找不到此问题。JavaScript,CSS和HTML在应用程序的整个生命周期内仅加载一次。初始加载后,服务器仅向您发送新数据。

这减少了服务器-客户端流量,并使应用程序感觉“更智能”。

使用传统的应用程序,服务器必须为成千上万的用户生成完整的页面。但是对于SPA,渲染是在客户端完成的。这意味着服务器对用户请求的响应速度更快。

因此,只要您不使用可怕的过时硬件,SPA就会比传统的Web应用程序更快,响应速度更快。

这使它们成为将旧的桌面应用程序和企业级解决方案移植到Web而不改变其UI和工作流的理想人选。

2.本地缓存和离线功能

发出初始请求后,单页Web应用程序将缓存从服务器接收的所有数据并将其存储在本地。这样,即使Internet连接失败,SPA也可以继续运行。

当应用程序重新连接到网络时,它将本地数据与服务器同步并恢复全部功能。

您也可以部分放弃使用Cookie来支持Web Storage。尽管功能非常相似(跟踪用户,他们的设置和首选项),但是本地存储可以存储更多数据(每个cookie 5MB vs 4KB)。

它也不必通过每个HTTP请求将数据发送回服务器。这加快了静态文件(JavaScript,CSS,图像和其他媒体)的加载。

3.完美的移动适应性

到2017年底,移动互联网速度几乎是其固定速度的两倍。同时,用户都非常渴望放弃运行缓慢的应用程序。因此,移动用户将从加载速度更快的应用程序中受益最多也就不足为奇了。

而且,单页应用程序从Web移植到移动设备要容易得多。通常,您可以为应用程序的Web版本和iOS / Android版本使用相同的后端代码,这在传统方法中很少见。

最后,与MPA相比,单页UI更接近于本机应用程序。这意味着在使SPA适应移动设备时,您无需在设计上进行太多更改。

4. API重用

如果您已经拥有移动应用程序,则创建SPA也会容易得多。如果应用程序与服务器端的某些API通信,则您可能会在Web应用程序中重用相同的API,而无需对其进行重大更改。

反之亦然,如果您打算启动本机应用程序,那么当您开始移动开发时,为单页Web应用程序编写的API将会派上用场。

5.简化开发

单页应用程序开发类似于使用LEGO建造房屋。有了组件(LEGO积木)后,就可以快速构造SPA,而无需一遍又一遍地添加相同的代码片段。

另外,您不必编写用于在服务器上呈现HTML页面的自定义代码。而且从file:// URI开始开发要容易得多,因为在此阶段不需要服务器。

最后,SPA在生产中易于部署和版本化。

6.后端/前端分离

在单页应用程序中,UI与数据分开。这使您可以在不影响后端的情况下(不包括某些静态资源文件)尽可能多地修改前端并更改UI。

前端/后端开发的解耦使查找代码库中的错误变得更加容易。集成新的API并更改数据进入SPA框架并流到不同系统的方式也更加容易。全部不影响UI。

7.无痛的Chrome调试

使用Chrome调试单页应用程序要容易得多。您可以一次看到整个代码,观察网络操作,并检查页面元素以及相关数据。并且不要忘记,单页应用程序javascript框架具有自己的Chrome DevTools扩展,例如Vue.js devtools,AngularJS Batarang,Ember Inspector或React开发人员工具。

SPA缺点

1. SEO问题

长期以来,Google和其他搜索引擎在为SPA编制索引方面遇到了麻烦。他们的机器人擅长抓取静态HTML页面,但无法呈现动态内容。由于无法执行JavaScript,他们在SPA中看到的只是一个空的HTML容器。结果,单页应用程序很少将其放在搜索结果的顶部。

在过去的几年中,Google在索引JavaScript方面变得更加出色。不过,Google承认有时无法正确索引单页应用程序。更不用说其他搜索引擎尚未获得同样的SPA爬行能力。

SEO的另一个问题是,与传统应用程序相比,SPA具有有限的语义内核。这源于这样一个事实,即不可能在单个页面的网站中填充尽可能多的关键字。

要解决此问题,请选择内容以更好地满足用户要求。另外,利用元描述,标签和内部锚文本链接。或者,您可以将SPA专用于单个主题,因为Google在主题网站上的排名较高。

最后,您可能会遇到一些可共享性问题,因为Facebook的漫游器在爬取SPA方面表现异常出色。

2.内存泄漏

SPA中使用的事件侦听器可能会导致用户设备上的可用内存丢失。由于您的应用程序实质上是一个永远不会重新加载的页面,因此,SPA运行时间越长,内存泄漏就越严重。

如果您在开发过程中没有对此问题给予足够的重视,则可能会导致性能显着下降,并迅速耗尽设备的电池电量。

3.安全问题

跨站点脚本(XSS)使SPA更加容易受到黑客攻击。XSS允许侵入者将恶意的客户端脚本插入本来值得信任的应用程序中。

但是,有一些方法可以打击跨站点脚本。使用单页应用程序,您可以保护数据端点,并将客户端下载的JS分为多个独立的片段。

这样,攻击者将无法立即访问您的所有代码。

4.缓慢的第一次加载

SPA的启动速度比传统应用程序慢。这是由于客户端渲染而发生的。在您的浏览器呈现页面之前,它必须加载庞大的JS框架。这可能需要一段时间,尤其是对于大型应用程序。

但是在第一次渲染之后,SPA的速度比MPA快得多。

幸运的是,有多种方法可以加快SPA的初始化速度,例如动态加载资产和最小化脚本。

5.禁用JavaScript的有限功能

一小部分用户在其浏览器中禁用了JavaScript。原因从增加速度到安全问题。对于这样的人,您的尖端SPA将彻底无法使用。

此问题的部分解决方案是对第一次加载使用服务器端呈现。不过,禁用JS后,您的某些应用程序功能可能仍然不可用。

渐进增强哲学还允许您创建可为禁用JavaScript或使用过时的浏览器和硬件的用户提供令人满意的体验的应用程序。

6.应用程序可扩展性差

使用传统方法,应用程序的功能分布在多个页面上。您可以逐渐添加内容并扩展页面数。在不影响其他页面的情况下,更改MPA的一部分也相对容易。

例如,如果您想更改应用程序的框架,则可以逐页进行操作,直到重新设计整个MPA。

在SPA中,业务逻辑内置于易于开发和维护的独立组件中。

但是,更改应用程序的体系结构完全是另外一回事了。单个功能可能包含多个组件,更改常用元素可能会影响应用程序的其他部分。

因此,在计划阶段必须考虑SPA功能的未来更改。如果您对应用程序的前进方向有清晰的了解,那么开发人员将能够在架构定型之前考虑可能的更改。

7.导航问题

在SPA中,后退和前进按钮具有某些意外行为。“后退”按钮实际上可以加载上一页(可以使您转到一个完全不同的网站),而不是使您返回到应用程序的先前状态。

当您按下“后退”按钮时,该应用程序也可能会失去滚动位置,因此您最终会进入页面的其他部分。

在某些SPA中,您甚至无法在新标签页中打开链接,刷新页面或将其添加为书签。

幸运的是,您可以使用已经与大多数JS框架捆绑在一起的HTML5 History API来解决导航问题。

8.由于异步请求而产生的奇怪行为

SPA异步管理用户请求,这可能导致某些意外行为。当您按下按钮或单击传统应用程序中的链接时,它将停止所有当前活动并向服务器发出新请求。

但是在SPA中,服务器响应是异步发出的。这意味着他们的到达顺序可能与您要求的顺序不同。因此,如果您非常快地单击几个链接,您可能会在错误的页面上结束。或者,如果您多次按下同一按钮,则可能会遇到图形故障。您的开发人员将必须专门编写代码来处理这种情况。

结论

现在您了解为什么单页应用程序最近变得如此流行。但是在您选择SPA与MPA辩论之前,请先考虑要实现的目标。

如果您打算提供多种服务或不同类别的商品,那么创建具有多个页面的网站是合乎逻辑的解决方案。这就是为什么如此构建大量应用程序(如Amazon)的原因。

对于具有大量内容(例如新闻门户)且需要完美SEO的网站,MPA也是一个好主意。

相反,如果您希望专注于推广单一产品或服务,SPA是一种更好的解决方案。它们使您可以将令人印象深刻的功能打包到轻量级应用程序中。出色的UX使SPA成为移植高度交互式桌面应用程序和在Web上创建类似本机体验的理想选择。

这就是为什么SaaS产品中的CRM / ERP系统和交互式仪表板通常被构建为单页应用程序的原因。

总而言之,SPA是一项了不起的技术。它具有许多优点,您可以通过一支合格的开发团队轻松克服其一些弊端。

]]>
https://www.askme-121.space/spa-vs-mpa/feed/ 0
Ember.js VS Vue.js,两个开发框架如何抉择? https://www.askme-121.space/ember-js-vs-vue-js/ https://www.askme-121.space/ember-js-vs-vue-js/#respond Mon, 27 May 2024 11:37:17 +0000 https://www.askme-121.pw/?p=542 JavaScript最初是为Web应用程序而创建的。随着前端技术的发展,比起纯JavaScript 脚本,大多数开发人员更喜欢使用基于JavaScript的框架来开发Web应用,如Vue、React等。这些框架大大简化了你的代码,也使你能够完成更多的全栈工作。

使用什么类型的框架决定了应用程序的敏捷程度。因此,您必须慎重选择。在充满多样性的前端生态中,有两个框架十分引人瞩目——Ember.js和Vue.js。

开发者为什么要为选择框架而犯愁?本文,我们将就这两个框架进行对比,帮助你判断哪个框架才更适合你。

在开始对比这两个框架之前,我们应该先了解:选择一个框架的决定因素都有哪些:

  • 代码必须简单易懂
  • 应以更少的代码量产出更多的功能
  • 应提供一个布局合理的工作框架
  • 是否支持内置路由或外部插件的路由
  • 应该能够在页面加载时传输更多数据,从而使页面成为单页应用(PS:单页应用程序的使用体验更好)
  • 在单页架构中,如果用户需要共享应用子页面链接,那么框架应该具备基于URL路由不同功能的能力
  • 更严格的模板选项有助于实现双向绑定
  • 不应与任何第三方库产生冲突
  • 应该很容易测试框架内的代码
  • 应为Ajax调用提供HTTP客户端服务
  • 文档必不可少,并且应该是完整且最新的
  • 应该与浏览器的最新版本兼容

只有保证所选择的框架符合上述基本条件,才能够最大程度的确保Web应用程序的构建顺利。下面我们就来详细分析Ember.js和Vue.js框架之间的异同。

Ember.js

Ember.js是一个基于MVVM模型的开源框架,该框架主要用于创建复杂的多页面应用程序。它最大的特点是:持续推出最新的特性,并不会丢弃任何旧功能。

与大多数前端开发框架不同,使用Ember.js,您必须遵循一套严格的JS体系结构,也就是说,Ember.js并不具备高度的灵活性。不过,正是得益于这套JS体系结构,Ember.js明显更加完善、稳定,您可以使用其任意版本推出的工具与最新版本集成,却不必过分担忧兼容性问题。

因为Promise无处不在,所以你可以用简单的方式编写代码和模块,通过调用 Ember.js提供的API,以实现高度复杂的功能。

在性能处理方面,Ember.js创建了相似的绑定机制和DOM更新,允许让浏览器一次性处理它们,以提高工作性能(这样做的好处是:避免为重复工作增加算力,浪费大量时间)。

作为一个流行的前端开发框架,学习指南必不可少。Ember.js明确了一般应用程序的组织和结构,确保你不会犯任何错误。

Ember.js的模板基于Handlebar 语法,借助它可以使你轻松阅读和理解模板,同样也能使页面加载速度变得更快。除此之外,你不必每次在页面上添加或删除数据时都更新模板,因为这一切,语言本身已自动为你完成。

最后,Ember.js拥有一个活跃的社区,其定期更新框架并确保向后兼容性。

让我们花几分钟总结一下Ember.js的特点:

  • Ember.js是适用于复杂结构、多页应用程序的MVVM模型的开源框架
  • Ember.js持续提供最新功能,且不会丢弃任何旧功能
  • Ember.js遵循一套非常严格的结构框架,不能提供太高的灵活性
  • 借助Ember.js完善的控制系统可帮助你与新版本完美集成
  • Ember.js对避免使用过时的API版本有着严格的指导
  • Ember的API可帮助您以简单的方式使用复杂的功能
  • Ember.js提供高效的运算机制,以保证运行效率
  • Promise确保你使用Ember.js的任何API,以达到编写模块化脚本和简洁代码的目的
  • Ember.js是一个完全加载的前端框架
  • Ember.js框架稳定,因为所有组件都具有相同的功能和属性
  • Ember.js具有明确定义的限制,可防止您使应用程序复杂化
  • Handlebar作为Ember.js的模板语言,可使你可以轻松阅读和理解模板,且有助于更快地加载模板
  • Ember.js有一个活跃的社区,可以定期更新框架并保证其向后兼容性

Vue.js

开发人员一直在试图寻找新的框架来构建他们的应用程序,而他们的核心诉求只有一个:速度快、成本低。基于此,用Vue就够了。

这个框架很容易被开发人员理解并且能够以极低的成本使用。当然,选择它的理由还有很多:简单的编码方式、健全的帮助文档和活跃的生态系统。

在Web应用程序开发中,Vue.js在软件语言方面结合了很多优点。它的体系结构更易使用,并且,使用Vue.js 开发的应用程序很容易与现代化的应用程序集成。

Vue.js使用了服务器端渲染流的方法,这样做有两个好处:1. 使服务器具有较高的响应速度;2. 非常适合SEO(自然搜索)。通过服务器端渲染,视图直接在服务器上呈现,这十分便于搜索引擎直接抓取到这些网页内容。

Vue.JS是一个非常轻量级的框架。正因如此,它也比其他框架快得多。

如果你愿意,甚至还可以进一步减少它的体积——使用Vue.js可以将模板和编译器分离为虚拟DOM,只需部署12 KB的压缩文件,就可以在您的机器中编译模板。

Vue.js的另一个重要特点是:它可以轻松地与使用JavaScript脚本创建的Web应用程序集成,使用此框架可以轻松地对已有应用程序进行更改。

提到集成,就不得不说Vue.js与其他前端库的集成情况。Vue.js可以轻松的集成到其他前端库中,也就是说,您可以在Vue.js中插入另一个库,以弥补此框架的不足,而这项功能也使得Vue.js成为了一个更为通用的工具。

其实,上面那些都不叫事,对你来说最关心的一定是:上手难度。

正因为Vue.js的简单易用,即使是新的开发人员,也会发现使用它来构建应用程序有多么容易。

通过Vue.js,您可以返回并轻松检查语法错误。除了测试组件外,您还可以返回并检查所有状态(就任何开发人员而言,这都是一个非常重要的功能)。

最后,Vue.js也有非常详细的文档,这会帮助你快速上手开发应用程序。

同样,我们来总结一下Vue.js的特点:

  1. Vue.js能与其他应用程序集成,得益于这项优点,您可以使用它来对现有应用进行更改
  2. Vue.js轻巧且快速。通过部署解释器,还可以使它更轻量
  3. Vue.js允许您将编译器和模板分离为虚拟DOM
  4. Vue.js丰富的库和组件为你的应用程序带来更多可能
  5. Vue.js应用能够快速响应
  6. Vue.js的服务器端渲染还有助于使搜索引擎排名更高
  7. Vue.js结构简单。易于任何新开发者使用
  8. Vue.js可以帮你返回检查结果并更正错误
  9. 详细的文档有助于快速构建网页或应用程序

重点来啦!Ember.js 与Vue.js全面对比

当你需要将原有应用程序向更具现代化的框架迁移时,Vue.js可以为你提供更多帮助。它结合了许多其他框架的优点,但作为面向开发过程的框架,Vue.js不提供现成的界面元素库。但是,你仍然可以从许多第三方社区库中拿到你想要的东西。

Ember.js为您提供了一个值得信赖的成熟框架。当你的开发团队最够大时,这个框架比较合适。由于MVVM结构所致,Ember.js项目团队之间的分工非常明确。

我们很难说谁比谁更具优势。因为,选择哪个框架完全取决于你实际参与的项目类型。Ember.js和Vue.js都存在一些优缺点,通过下表,您可以更清楚的看到:

目前,这两个框架都在不断更新和迭代,作为一名开发者,我们除了要考虑每个框架自身的功能与应用场景外,更应考虑各个框架之间的兼容性,尤其对于企业项目来说,重构意味着推翻重来,对原有项目和技术体系来说都将是一场浩劫。而为了避免可能的风险,我建议选择一款相对成熟的商用控件。

]]>
https://www.askme-121.space/ember-js-vs-vue-js/feed/ 0
ansible入门详解 https://www.askme-121.space/ansible/ https://www.askme-121.space/ansible/#respond Mon, 20 May 2024 05:17:56 +0000 https://www.askme-121.pw/?p=526 Ansible 是一个配置管理和应用部署工具,功能类似于目前业界的配置管理工具 Chef,Puppet,Saltstack。Ansible 通过 Python 语言开发,Ansible 默认通过 SSH 协议管理机器,所以 不需要在客户端安装程序,只需要将 Ansible 安装在一台服务器上,就可以去管理控制其它服务器了。不需要为它配置数据库,Ansible 不会以 daemons 方式来启动或保持运行状态。

一、ansible用法

1.1.安装ansible

yum -y install epel-release
yum -y install ansible

1.2.ansible参数选项

选项解释
-m指定要执行的模块,默认为command
-a指定模块的参数
-ussh连接的用户名,默认用root,ansible.cfg中可以配置
-b,–become相当于sudo
–become-usersudo到哪个用户,默认为root
-k提示输入sudo密码,当不是NOPASSWD模式时使用
-C只是测试一下会改变什么内容,不会真正去执行
-ffork多少进程并发处理,默认为5个
-i指定hosts文件路径,默认default=/etc/ansible/hosts
–list-host只打印有哪些主机会执行这个命令,不会实际执行
–private-key私钥路径
-Tssh连接超时时间,默认是10秒
-t日志输出到该目录,日志文件名以主机命名
-vvv显示详细日志

举例:

ansible all --list-host		#列出inventory定义的所有主机
ansible all -m ping -b --become-user root	#sudo到root执行ping模块

二、相关文件介绍

2.1.配置文件

/etc/ansible/ansible.cfg		#主配置文件
/etc/ansible/hosts				#主机清单
/etc/ansible/roles/				#存放角色目录

2.1.1. /etc/ansible/ansible.cfg解释

Ansible查找ansible.cfg文件的位置及顺序

1)ANSIBLE_CONFIG:首先,Ansible命令会检查环境变量,及这个环境变量将指向的配置文件
2)./ansible.cfg:其次,将会检查当前目录下的ansible.cfg配置文件
3)~/.ansible.cfg:再次,将会检查当前用户home目录下的.ansible.cfg配置文件
4)/etc/ansible/ansible.cfg:最后,将会检查在用软件包管理工具安装Ansible时自动产生的配置文件

inventory      = /etc/ansible/hosts		#存放主机清单文件
forks          = 5						#并发执行数量
poll_interval  = 15						#回频率或轮询间隔时间,单位s
sudo_user      = root					#远程主机sudo到什么用户,默认为root
ask_sudo_pass = True					#sudo时是否需要输入密码
ask_pass      = True					#执行ansible-playbook是否需要密码.默认为no
transport      = smart
remote_port    = 22						#远程主机端口号
module_lang    = C						#模块和系统之间通信的语言
module_set_locale = False
roles_path    = /etc/ansible/roles      #默认下载的Roles存放的目录
host_key_checking = False       		#首次连接是否需要检查key认证。设置为False,第一次连接远程主机不需要输入yes
timeout = 10            				#SSH超时时间
remote_user = root      				#连接到被控端机器的用户名,默认为root
log_path = /var/log/ansible.log     	#日志文件存放路径
executable = /bin/sh        			#执行的shell环境,用户shell模块
jinja2_extensions = jinja2.ext.do,jinja2.ext.i18    #允许开启jinja2扩展模块
private_key_file = /path/to/file    	#私钥文件存储位置
system_warnings = True      			#禁用系统运行Ansible潜在问题警告
deprecation_warnings = True    			#PlayBook输出禁用“不建议使用”警告
nocolor = 1         					#输出带上颜色区别,0表示开启,1表示关闭
pipelining = False      				#是否开启pipe SSH通道优化
gathering = explicit					#不收集系统信息,默认收集
[privilege_escalation]
become=True								#是否sudo
become_method=sudo						#sudo方式
become_user=root						#sudo后变为root用户
become_ask_pass=False					#sudo后不验证密码

2.1.2. /etc/ansible/hosts格式

[DB]					#表示DB组,下面的主机均属于该组
192.168.1.2
192.168.1.3
[fastdfs]
192.168.1.2
192.168.1.3  GROUP_NAME="g1"	#定义局部变量
192.168.1.4 ansible_ssh_user=ops ansible_ssh_port=22 ansible_become=true ansible_become_method=sudo	#ansible_become是否允许提权;ansible_become_method指定提权的方式
[k8s]
192.168.1.[4:10]		#[4:10]表示连续的主机,包含4和10
[webserver]
192.168.1.11:2222		#默认为22端口,如果不是,需指定
[linux_group:children]		#children是ansible内置变量,可以实现多个组的调用,linux_group是定义的组名
DB
k8s
[DB:vars]
mysql_port="3306"		#组变量
[all:vars]				#定义全局变量
db_master_ip="{{groups['DB'][0]}}"	#取DB组中的第一个IP			
ansible_ssh_user="devops"			

指定自定义主机清单文件

ansible -i myhost.ini --list-host

2.2.可执行文件

/usr/bin/ansible			#主程序
/usr/bin/ansible-doc		#查看文档
/usr/bin/ansible-galaxy		#连接https://galaxy.ansible.com/下载相应的roles
/usr/bin/ansible-playbook	#调用playbook剧本
/usr/bin/ansible-vault		#文件加密工具

三、配置基于root用户Key的验证

以root用户登录到主控端机器,ssh-keygen生成密钥对,通过ssh-copy-id 远程主机,将公钥拷贝到远程主机

四、ansible命令执行过程

  • 加载配置文件/etc/ansible/ansible.cfg
  • 加载模块文件,如command,ping
  • 通过ansible将模块或命令生成对应的临时py文件,并将该文件传输至远程服务器对应执行用户的$HOME/.ansible/tmp/ansible-tmp-数字 目录下,命名为XXX.PY文件
  • 给文件+x执行权限
  • 执行并返回结果
  • 删除临时py文件,退出

五、执行状态

绿色:执行成功并且不需要做改变的操作
黄色:执行成功并且对目标主机做变更
红色:执行失败

六、常用模块

ansible 192.168.1.2 -m ping -k		#测试主机是否可以通信;默认通过key验证,-k指定以用户名密码验证
ansible all -m ping			#检测主机清单文件中的所有主机
ansible db -m ping			#只检测db组内的主机
ansible *ser* -m ping		#支持通配符
ansible 'db:web' -m ping	#取db和web组的并集
ansible 'db:&web' -m ping	#取db和web组的交集
ansible 'db:!web' -m ping	#在db,不在web
ansible-doc -l				#列出所有模块
ansible-doc ping			#查看ping模块
ansible-doc -s ping			#简单了解ping模块
ansible all --list			#列出主机清单所有主机
ansible all -a 'ls /root'	#默认使用command模板,不支持变量、<、>、|、&、;等,需使用shell模块
ansible all -a 'creates=/ansible mkdir /ansible'	#不存在/ansible,则执行后面的mkdir
ansible all -a 'chdir=/root ls'		#切换到/root下执行ls

ansible all -m shell -a 'echo $HOSTNAME'
ansible all -m script -a '/etc/ansible/scripts/host.sh'	#会自动把主控端的脚本拷贝到被控端,并执行
ansible all -m copy -a 'src=/etc/hosts dest=/ansible/'	#拷贝本机文件到远程主机
ansible 192.168.119.134 -m copy -a "src=/etc/ansible dest=/tmp owner=root group=root mode=0755"	# 拷贝本机目录到远程客户端
ansible all -m copy -a 'content="hello world" dest=/ansible/f1'	#把content里面的内容写入到f1文件,会覆盖之前的内容
ansible all -m fetch -a 'src=/etc/hosts dest=files/ flat=yes'	#拷贝远程主机文件到当前路径的files/目录下;flat只拷贝hosts文件本身,不拷贝hosts文件所在的绝对路径

ansible all -m file -a 'path=/ansible/f2 state=touch'	#创建文件
ansible all -m file -a 'path=/ansible/f2 state=absent'	#删除文件
ansible all -m file -a 'path=/ansible/dir1 state=directory'	#创建目录
ansible all -m file -a 'src=/etc/hosts dest=/tmp/host state=link'	#创建软链接
ansible 10.93.62.156 -m hostname -a 'name=Master'		#修改主机名

ansible all -m cron -a 'minute=* weekday=2,4 job="/usr/bin/wall test warning" name=testcron'	#定时任务
ansible all -m cron -a 'disabled=true job="/usr/bin/wall test warning" name=testcron'		#取消定时任务
ansible all -m cron -a 'state=absent job="/usr/bin/wall test warning" name=testcron'		#删除定时任务

ansible all -m yum -a 'name=vsftpd update_cache=yes'		#yum装包,更新缓存
ansible all -m yum -a 'name=vsftpd state=removed'			#卸载包

ansible all -m user -a 'name=zhangsan shell=/bin/bash'		#创建用户

ansible web -m command -a "chdir=/tmp pwd"		#切换目录,执行命令

指定远程主机用户,并sudo到root用户
注意:需提前去远程主机配置允许普通用户sudo到root

echo "devops ALL=(ALL)  NOPASSWD: ALL" >> /etc/sudoers		#配置免密sudo
ansible all -a 'ls /root' -u devops -k -b	#-a指定命令;-u指定远程主机用户;-b执行sudo,默认sudo到root用户

七、ansible-galaxy

ansible-galaxy collection install nginxinc.nginx_core -c			#下载角色;-c忽略证书认证
ansible-galaxy list			#列出本机所有角色

八、ansible-vault

ansible-vault encrypt hello.yml		#加密yaml文件,需设置密码
ansible-vault view hello.yml		#查看yaml文件内容
ansible-vault edit hello.yml		#修改yaml文件内容
ansible-vault rekey hello.yml		#修改加密口令
ansible-vault decrypt hello.yml		#解密yaml文件

九、ansible-playbook

9.1.核心元素

hosts		#执行的远程主机列表
tasks		#任务集
templates	#模板
handlers	#也是task列表,和一般的task并没有什么区别.Handlers由通知者进行notify, 如果没有被notify,handlers不会执行.不管有多少个通知者进行了notify,等到 play 中的所有 task 执行完成之后,handlers 也只会被执行一次
tags		#打标签,通过标签调用特定的任务;多个任务可以共用一个标签
vars		#定义变量

9.2.playbook格式

---                      #习惯写法,区分不同的档案
# 创建文件
- hosts: db,myql         #选择hosts文件中的主机;可以选择多个主机,使用逗号分隔
  remote_user: root      #指定远程主机用什么用户执行
  gather_facts: no		 #收集远程主机信息,默认为yes

  tasks:                        #任务列表
    - name: create file         #任务说明
      file: path=/ansible/f2 state=touch  mode=0500      #使用file模块,创建文件;mode设置权限
    - name: test1
      shell: echo "{{ groups['mysql'][0] }}" >> /tmp/t1.txt		#获取[mysql]组的第一个成员
      
- hosts: redis:!redis[0]		#排除redis组的第一个成员

9.3.常用技巧

1、ignore_errors忽略错误信息

任务执行失败,不会影响后面任务的执行

- name: create file         
  file: path=/ansible1/f2 state=touch        
  ignore_errors: True

2、拷贝文件使用相对路径

src使用相对路径,会去与该yaml文件同级的files目录下寻找,通常把要拷贝的文件统一放到files目录下

- name: copy hosts
  copy: src=hosts dest=/ansible/ backup=yes

3、handlers+notify解决修改配置文件不生效的问题

当执行copy模板后,通过notify发送通知给名字为restart httpd service的handlers,执行对应的service模板,重启httpd服务

tasks:
  - name: copy conf file
    copy: src=httpd.conf dest=/etc/httpd/conf/ backup=yes
    notify:
      - restart httpd service
  - name: start service
    service: name=httpd state=started enabled=yes
handlers:
  - name: restart httpd service
    service: name=httpd state=restarted

4、tags打标签

特殊tags:always #无论如何都会运行

- name: copy hosts
  copy: src=hosts dest=/ansible/
  tags: copyfile

调用多个标签用逗号分隔

ansible-playbook -t copyfile,sql file.yml

5、when条件判断

根据操作系统的类型来执行对应的任务

tasks:
  - name: create file for RedHat
    file: path=/ansible/redhat state=touch
    when: ansible_os_family == "RedHat"
  - name: create file for ubuntu
    file: path=/ansible/ubuntu state=touch
    when: ansible_os_family == "Ubuntu"

自定义条件判断,根据action的值去判断

6、迭代:with_items

当需要有重复性执行的任务时,可以使用迭代机制

示例:创建多个文件

tasks:
  - name: create files
    file: path=/ansible/{{ item }} state=touch
    with_items:
      - file1
      - file2

示例:迭代嵌套子变量

tasks:
  - name: create group
    group: name={{ item }}
    with_items:
      - g1
      - g2
  - name: create user
    user: name={{ item.name }} group={{ item.group }}
    with_items:
      - { name: "user1", group: "g1"}
      - { name: "user2", group: "g2"}

7、become提权

- name: copy hosts
  copy: src=hosts dest=/ansible/
  become: yes
  become_user: root		#提升为root权限,类似sudo

8、lineinfile(类似于sed)

regexp正则匹配,如果匹配上,则整行替换,类似sed的c操作;如果未匹配,则在末尾添加,类似sed的a操作

- name: modify file
  lineinfile: dest=/ansible/file1 regexp="zabbix soft" line="zabbix soft nproc 65535"

playbook的一个bug:如果内容中有:号会报错,用下面的写法可以避免

- name: add paas sudo
  lineinfile:
    path: /etc/sudoers
    regexp: "^paas "
    line: "paas ALL=(ALL) NOPASSWD: ALL"
    state: present		#present表示要添加或修改匹配的行,默认为present
    backup: yes		#备份文件

同时修改多行

- name: config /etc/sysctl.conf
  lineinfile:
    path: /etc/sysctl.conf
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
    backup: yes
  with_items:
    - { regexp: '^vm.swappiness', line: 'vm.swappiness = 10' }
    - { regexp: '^vm.dirty_ratio', line: 'vm.dirty_ratio = 60' }

9、register注册变量,返回shell脚本执行结果

使用 register 注册变量,名为 result,会把shell模块的输出存入到result
debug模块输出详情

tasks:
  - name: install mysql
    shell: cd /data/shell/ && sh InstallMySql.sh
    register: result
  - name: show install mysql debug
    debug: var=result.stdout_lines verbosity=0
result.stdout			#标准正确输出保存到字符串中
result.stdout_lines		#标准正确输出保存到列表中,输出更美观
result.stderr_lines		#标准错误输出保存到列表中
verbosity		#为1,代表-v;为2,代表-vv;为3,代表-vvv

10、failed_when关键字

’ failed_when’不会影响shell模块的执行过程,只会在条件成立时影响shell模块最终的执行状态,以便停止playbook的运行。

---
- hosts: db
  remote_user: root
  tasks:
  - debug:
      msg: "I execute normally"
  - shell: "echo 'This is a string for testing error' && exit 1"
    register: result
    failed_when: result.rc != 0		
  - debug:
      msg: "I never execute,Because the playbook has stopped

类似于shell中的

if [[ $? -ne 0 ]];then
	exit 1
fi

判断操作系统

- hosts: redhat
  gather_facts: True
  tasks:
   - name: copy redhat7 packages
     copy: 
         src: /tmp/redhat7.tar
         dest: /tmp/redhat7.tar
         mode: '0755'
     when: ansible_distribution == "RedHat" and ansible_distribution_major_version == "7"

匹配多个条件

1、两个条件都满足

failed_when: ' "error" in result.stdout and "fail" in result.stdout'

2、满足两个条件中的任意一个

failed_when: ' "error" in result.stdout or "fail" in result.stdout'

11、任务委派功能delegate_to

实现在当前playbook主机组外的机器上执行操作

run_noce: true 在一个主机上面只执行一次一个任务. ,如果没有这个参数的话,每个playbook中的组的主机都会执行一次

- name: test
  shell: echo "1" >> /root/a.txt
  delegate_to: 127.0.0.1
  run_once: true

委派Ansible所在的机器(即主控端)执行shell里面的操作

12、本地操作功能local_action或connection

Ansible默认只会对控制机器执行操作,但如果在这个过程中需要在Ansible本机执行操作呢?细心的读者可能已经想到了,可以使用delegate_to(任务委派)功能呀。没错,是可以使用任务委派功能实现。不过除了任务委派之外,还可以使用另外一种功能实现,这就是local_action关键字。

- name: add host record to center server
local_action:shell 'echo "192.168.1.100 test.xyz.com " >> /etc/hosts'

当然也可以使用connection:local方法,如下:

- name: add host record to center server
shell: 'echo "192.168.1.100 test.xyz.com " >> /etc/hosts'
connection: local

13、判断文件是否存在

如果不存在,则创建

- name: Check a.txt exists
  stat:
    path: /etc/ansible/a.txt
  register: file_status
- name: create a.txt
  shell: touch a.txt
  when: file_status.stat.exists == False

14、判断指定路径是否为一个目录

- hosts: test
  vars:
    testpath1: "/testdir/test"
    testpath2: "/testdir"
  tasks:
    - debug:
        msg: "file"
      when: testpath1 is file
    - debug:
        msg: "directory"
      when: testpath2 is directory

15、include

调用其它yml,例如:在tasks目录有main.yml和upgrade_sql.yml,则可以在main.yml中调用upgrade_sql.yml

- include: upgrade_sql.yml

16、block模块

将多个任务组合成一个块,并且可以对这个块做条件判断,以及当块里面的任务失败时,进行失败处理

1、当when的判断语句一样时,可以将任务合并

- name: block的用法
  hosts: node
  tasks:
    - debug:
      msg: "task1 not in block"
    - block:
        - debug:
            msg: "task2 in block1"
        - debug:
            msg: "task3 in block1"
      when: 2 > 1

2、”错误处理”功能

- hosts: testuser
  remote_user: root
  tasks:
  - block:
      - command: /bin/false
      - debug:
          msg: 'I never execute, due to the above task failing'
    rescue:				#当上面的模块失败时,该关键字下面的任务将被执行
      - command: /bin/false
      - debug:
          msg: 'I also never execute'
    always:				#不管block块是否执行成功,该关键字定义的任务都要被执行。
      - debug:
          msg: "This always executes"
    when: 2>1

17、pre_tasks在roles之前执行

如果想在调用roles之前执行tasks任务,得用pre_task,不然会跳过tasks,直接调用roles

 - hosts: web
   pre_tasks:
     - name: pre check
       shell: echo "预检查"
   roles:
     - nginx

18、set_fact设置变量

获取本机IP地址,并赋值给变量host_ip_address

- hosts: mysql
  tasks:
    - name: get host ip address
      shell: "ip addr |awk '/inet /' |sed -n '2p' |awk -F' ' '{print $2}' |awk -F'/' '{print $1}'"
      register: host_ip
      changed_when: false
    - name: set host_ip_address variable
      set_fact:
        host_ip_address: "{{ host_ip.stdout }}"
    - name: show host_ip_address
      debug: msg={{ host_ip_address }}

19、validate: 在复制之前执行检测,如果要引用目标文件名,则使用 %s

- name: copy nginx.conf to {{ NGINX_DATA }}/conf
  template:
    src: nginx.conf.j2
    dest: "{{ NGINX_DATA }}/conf/nginx.conf"
    backup: yes
    validate: 'nginx -t -c %s'

9.4.使用变量

变量名由字母、数字、下划线组成,只能以字母开头

打印所有变量

ansible web -m debug -a 'var=vars'

a.系统facts收集

系统变量在playbook中可以直接通过{{ 名字 }}调用,如{{ ansible_hostname }}

ansible web -m setup			#打印所有系统信息
ansible web -m setup -a 'filter=ansible_hostname'				#获取主机名
ansible web -m setup -a 'filter=ansible_all_ipv4_addresses'		#获取IP地址

在模板中使用

"{{ ansible_default_ipv4['address'] }}"		#获取第一个IPv4地址

b.在yaml文件中通过vars定义

- hosts: db
  remote_user: root
  vars:
    - filename: f4

  tasks:
    - name: create file
      file: path=/ansible/{{ filename }} state=touch

c.在/etc/ansible/group_vars/all定义[推荐使用]

定义的是全局变量,主机清单中的主机均可以调用

ANSIBLE_DIR: "/opt/ansible"     #存放ansible拷贝的文件
SERVICE_USER: "admin"           #启动服务的用户
DATA_DIR: "/app"                #数据主目录
JDK_DIR: "{{ DATA_DIR }}/jdk"   #jdk数据路径

d.在/etc/ansible/hosts中定义

http_port为针对单台主机设置的变量,nodename为针对web组所有主机设置的变量

[web]
10.93.91.80    http_port=80
10.93.65.2     http_port=81
[web:vars]
nodename=www
[all:vars]				#定义全局变量
web_master_ip="{{groups['web'][0]}}"	#取web组中的第一个IP

e.通过ansible-playbook -e传入变量,优先级高于/etc/ansible/hosts【不推荐】

playbook调用变量(两个双花括号里面写变量名)

- name: create file
  file: path=/ansible/{{ filename }} state=touch

通过-e传入变量

ansible-playbook -e 'filename=f3' file.yml

9.5.常用命令

选项解释
-f并发执行数量,默认为5
–ssh-common-args指定要传递给sftp/scp/ssh的公共参数(例如ProxyCommand)
-i指定主机清单文件,如果安装了ansible工具,不指定默认使用/etc/ansible/hosts文件
ansible-playbook -C file.yml		#只检测可能会发生的改变,并不真正执行
ansible-playbook file.yml			#执行playbook
ansible-playbook file.yml --list-hosts 			#列出运行任务的主机
ansible-playbook file.yml --limit 10.93.65.2 	#只在指定的主机执行
ansible-playbook file.yml --list-tasks			#查看任务列表
ansible-playbook file.yml --list-tags			#查看标签列表
python ./ansible-playbook -f 3 -i hosts-192.168.137.3 /tmp/test.yml	#通过python调用ansible-playbook,把ansible-playbook的内容放到ansible-playbook目录下即可
ansible-playbook -e @~/elk/secret.yml	# 将secret.yml中的内容解析成变量

9.6.模板templates

使用jinjia2语言,在与playbook同级的目录下创建templates目录,文件名以.j2结尾

1、简单运算

vi templates/nginx.conf.j2
这里面做了一个简单的计算,让远程主机CPU的个数乘以2

user nginx
workproceess {{ ansible_processor_vcpus*2 }}

playbook写法

- name: copy templates
  template: src=nginx.conf.j2 dest=/ansible/nginx.conf

2、for循环+if判断

for循环格式:

{% for port in ports %}
语句块1
语句块2
...
{% endfor %}

if判断格式:

单分支
{% if p.name is defined %}		#如果变量不为空,则执行里面的语句块
语句块
{% endif %}

#多分支
{% if db_type== "mysql" %}
DB_IP=192.168.1.2
{% elif db_type== "oracle" %}
DB_IP=192.168.1.3
{% endif %}

playbook写法:

- hosts: all
  remote_user: root
  vars:
    ports:
      - port: 80
        name: web1
      - port: 81

  tasks:
  - name: copy templates
    template: src=nginx.conf.j2 dest=/ansible/nginx.conf

模板文件:

{% for p in ports %}
server {
    listen {{ p.port }}

{% if p.name is defined %}			
    servername {{ p.name }}
{% endif %}
}
{% endfor %}

3、在模板中获取主机组的IP

groups[‘web’]可以获取hosts文件中的web主机组定义的主机

{% if groups['web'] is defined and groups['web'][0] is defined %}		#如果web组定义,且至少有一个主机
upstream web {
{% for ip in groups['web'] %}
    server {{ ip }}:{{ WEB_PORT}} fail_timeout=5s max_fails=3;
{% endfor %}
{% endif %}

4、在模板中定义列表

{% for service_name in ['web','mysql'] %}
{{ service_name }}
{% endfor %}

5、获取主机组数量

"{{ groups['master'] | length }}"

十、roles

用于层次性、结构化地组织playbook,可以看成是一大堆playbook的集合。分别将变量、文件、模板、任务放置于单独的目录中

]]>
https://www.askme-121.space/ansible/feed/ 0
自动化工具ansible安装和常用模块汇总 https://www.askme-121.space/ansible-simple/ https://www.askme-121.space/ansible-simple/#respond Wed, 15 May 2024 08:27:26 +0000 https://www.askme-121.pw/?p=521 ansible 是什么

Ansible 是一种 IT 自动化工具。它可以配置系统,部署软件以及协调更高级的 IT 任务,
例如持续部署,滚动更新。Ansible 适用于管理企业 IT 基础设施,从具有少数主机的小规
模到数千个实例的企业环境。Ansible 也是一种简单的自动化语言,可以完美地描述 IT 应
用程序基础结构。

ansible 的好处

简单易读:基于 YAML 文本编写,易于阅读,非专业的开发人员也可以编写。
功能强大:它可以用于管理配置,软件安装,流程自动化
无代理:不需要在客户端安装额外的 agent
跨平台支持:支持 linux,Windows,Unix 和网络设备

ansible 架构

  1. 控制节点(Control Node):控制节点是执行 Ansible 操作的主机。它通常是您的工作站或服务器,运行 Ansible 命令和管理配置的主机。
  2. 主机(Hosts):也称为目标节点,是您希望管理和配置的远程服务器或设备。Ansible 通过 SSH 连接到这些主机执行任务。
  3. Inventory(清单):清单是一个定义了 Ansible 将要管理的主机的文件。它可以包含主机的 IP 地址、主机名、组等信息。清单文件可以是静态的(手动编写)或者动态的(由脚本或其他工具生成)。
  4. Playbooks(剧本):Playbooks 是 Ansible 的核心概念之一,它们是用 YAML 格式编写的文件,用于描述一系列任务的执行。每个任务都描述了在目标主机上应该执行的操作,比如安装软件包、配置文件、启动服务等。
  5. 模块(Modules):模块是 Ansible 的工作单元,用于执行特定的任务。Ansible 提供了丰富的内置模块,可以用于系统管理、网络管理、云管理等各种场景。例如,”yum” 模块用于在基于 RPM 的系统上安装软件包,而 “copy” 模块用于复制文件到远程主机。

ansible 安装

主机端(master):192.168.7.42
被控端(node):192.168.7.99、192.168.7.217、192.168.7.42

在主机端安装

yum install -y epel-release
yum install -y ansible
#ansible配置文件:
/etc/ansible/ansible.cfg
/etc/ansible/hosts
生成秘钥:
ssh-keygen -t rsa  #在/root/.ssh/目录下生成秘钥
#被控端:(复制主机端公钥到被控端)
ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.7.99  

或者

scp /root/.ssh/id_rsa.pub 192.168.7.99:/root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys

本机也要操作

ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.7.42

或者

cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys

测试

ssh 192.168.7.99 'ifconfig'
ssh 192.168.7.217 'ifconfig'

配置ansible 主机清单

cat /etc/ansible/hosts  //主机清单
[testhosts]   //按组分
192.168.7.42
192.168.7.99
192.168.7.217
[lbtest]
192.168.7.[42-45]   表示42-45内的ip

ansible -i <host_pattern> [-f forks] [-m module_name] [-a args]
  -i:指定主机清单的路径,默认为/etc/ansible/hosts(可省略)
  -m module:默认为command
  -f forks :默认为5个主机同时执行
例如:ansible testhosts -m command -a 'service salt-minion start'

ansible testhosts -u root -k -m shell -a ‘ps axu|grep salt’

备注:
-u 指定用户名
-k 指定密码
-m 指定模块
-a 指定参数

command不支持管道,此时可以用shell

使用children

cat /etc/ansible/hosts
[tests:children]   //定义子项
test1
test2
test3
[test1]
192.168.7.42
[test2]
192.168.7.99
[test3]
192.168.7.217

ansible支持主机列表的正则匹配

*/all 全量
: 逻辑或
& 逻辑与
! 逻辑非
[] 切片
~ 以~开头

如:
ansible all -m ping  #所有默认inventory文件中的机器
ansible "*" -m ping  #同上
ansible 121.28.13.* -m  ping #所有122.28.13.X机器

ansible  web1:web2  -m  ping  #所有属于组web1或属于web2的机器
ansible  web1:!web2  -m  ping #属于组web1,但不属于web2的机器
ansible  web1&web2  -m  ping  #属于组web1又属于web2的机器

ansible webserver[0]  -m  ping    #属于组webserver的第1台机器
ansible webserver[0:5]  -m  ping  #属于组webserver的第1到4台机器
ansible "~(beta|web)\.example\.(com|org)"  -m ping

ansible 常用模块

官方模块文档:https://docs.ansible.com/ansible/2.9/modules/list_of_all_modules.html

下面是总结的我常用的模块

file:用于配置文件属性
script:用于在远程机器上执行本地脚本
yum:用于安装软件包
cron:配置计划任务
copy:复制文件到远程主机
command:在远程主机上执行命令,不支持管道
raw:类似于command模块,支持管道
user:配置用户
group:配置用户组
service:用于管理服务
ping:用于检测远程主机是否存活  如:ansible test1 -m ping
setup:查看远程主机的基本信息,获取到的主机信息,其中的KEY都可以在playbook中被当作变量引用  如: {{ ansible_all_ipv4_addresses }}
mount:配置挂载点
script 模块:用于在远程机器上执行本地脚本
script模块:在指定节点上执行/root/a.sh脚本(该脚本是在ansible控制节点上的)
fetch:从远程主机拉取文件到本地
archive模块用于压缩文件
unarchive模块用于解压文件
selinux:设置selinux防火墙
iptables:设置iptables防火墙
synchronize:使用rsync同步文件
get_url:主要用于从http、ftp、https服务器上下载文件(类似于wget)
filesystem:在块设备上创建文件系统

file模块

group:定义文件/目录的属组
owner:定义文件/目录的属主
mode:定义文件/目录的权限
path/name:必选项,定义文件/目录的路径
recurse:递归设置文件的属性,只对目录有效
state:定义文件状态
  - directory:如果目录不存在,创建目录
  - touch:如果文件不存在,创建一个新文件
  - absent:删除文件或目录
  - link:创建软连接
  - hard:创建硬链接(src指定要被链接的源文件路径,dest:被链接到的路径,只应用于state=link情况)
  - file:即使文件不存在,也不会被创建
示例:
    ansible test1 -m file -a "src=/etc/fstab dest=/tmp/fstab state=link"
    ansible test1 -m command  -a 'ls /tmp/ -lh'   
    ansible test -m file -a "name=/ane/soft  state=directory"
    ansible test1 -m file -a "path=/tmp/fstab state=absent" 
    ansible test1 -m file -a "path=/tmp/test state=touch"
    ansible test1 -m file -a 'path=/tmp/d1 state=directory owner=root group=root mode=755'   //创建目录
    ansible test1 -m command  -a 'path=/tmp/'
    ansible test1 -m file -a 'path=/tmp/d1 state=absent '   删除目录

command模块

creates:一个文件名,当该文件存在,则该命令不执行
free_form:要执行的linux指令
chdir:在执行指令之前,先切换到该指定的目录
ansible test2 -m command -a 'chdir=/ane   tar zcf 666.tar.gz 666.txt'
ansible test2 -m command -a 'ls -l /ane'
removes:一个文件名,当该文件不存在,则该选项不执行
示例:
 ansible test -a "/sbin/reboot"

yum模块

enablerepo:启用某个源
name:要进行操作的软件包的名字,也可以传递一个url或者一个本地的rpm包的路径
state:定义软件包状态
present:安装
absent:删除
latest:安装最新的
示例:
    ansible test -m yum -a 'name=httpd state=latest'
    ansible test -m yum -a 'name="@Development tools" state=present'
    ansible test -m yum -a 'name=http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present'

service模块

arguments:给命令行提供一些选项
enabled:是否开机启动  yes|no
name:必选项,服务名称
runlevel:运行级别
sleep:如果执行了restarted,在则stop和start之间沉睡几秒钟
state:对当前服务执行启动,停止、重启、重新加载等操作(started,stopped,restarted,reloaded)
示例:
ansible test -m service -a "name=httpd state=started enabled=yes"
ansible test -m service -a "name=foo pattern=/usr/bin/foo state=started"
ansible test -m service -a "name=network state=restarted args=eth0"

copy模块

src:源文件
dest:目标路径
backup:覆盖之前,是否备份原文件
owner:设定文件/目录的属主
group:设定文件/目录的属组
mode:设定文件/目录的权限
示例:
 ansible test -m copy -a "src=/srv/myfiles/foo.conf dest=/etc/foo.conf owner=foo group=foo mode=0644"
ansible test -m copy -a "src=/mine/ntp.conf dest=/etc/ntp.conf owner=root group=root mode=644 backup=yes"

cron模块

backup:对远程主机上的原任务计划内容修改之前做备份
day:日(1-31,*,*/2,……)
hour:小时(0-23,*,*/2,……)
minute:分钟(0-59,*,*/2,……)
month:月(1-12,*,*/2,……)
weekday:周(0-7,*,……)
job:要执行的任务,依赖于state=present
name:该任务的描述
special_time:指定什么时候执行,参数:reboot,yearly,annually,monthly,weekly,daily,hourly
state:确认该任务计划是创建还是删除(absent)
user:以哪个用户的身份执行
示例:
ansible test -m cron -a 'name="check dirs" hour="5,2" job="ls -alh > /dev/null"'
ansible test -m cron -a 'name="a job for reboot" special_time=reboot job="/some/job.sh"'

user模块

home:指定家目录,需要createhome为yes
groups:用户组
uid:用户UID
password:指定用户密码
name:用户名
createhome:是否创建家目录
system:是否创建为系统用户
remove:但state=absent时,删除家目录
state:创建或者删除
shell:指定用户shell环境

synchronize模块

#使用rsync同步文件,其参数如下:
archive: 归档,相当于同时开启recursive(递归)、links、perms、times、owner、group、-D选项都为yes ,默认该项为开启
checksum: 跳过检测sum值,默认关闭
compress:是否开启压缩
copy_links:复制链接文件,默认为no ,注意后面还有一个links参数
delete: 删除不存在的文件,默认no
dest:目录路径
dest_port:默认目录主机上的端口 ,默认是22,走的ssh协议
dirs:传速目录不进行递归,默认为no,即进行目录递归
rsync_opts:rsync参数部分
set_remote_user:主要用于/etc/ansible/hosts中定义或默认使用的用户与rsync使用的用户不同的情况
mode: push或pull 模块,push模的话,一般用于从本机向远程主机上传文件,pull 模式用于从远程主机上取文件
使用示例:
    src=some/relative/path dest=/some/absolute/path rsync_path="sudo rsync"
    src=some/relative/path dest=/some/absolute/path archive=no links=yes
    src=some/relative/path dest=/some/absolute/path checksum=yes times=no
    src=/tmp/helloworld dest=/var/www/helloword rsync_opts=--no-motd,--exclude=.git mode=pull

filesystem模块

在块设备上创建文件系统
选项: 
dev:目标块设备
force:在一个已有文件系统 的设备上强制创建
fstype:文件系统的类型
opts:传递给mkfs命令的选项
示例:
    ansible test -m filesystem -a 'fstype=ext2 dev=/dev/sdb1 force=yes'
    ansible test -m filesystem -a 'fstype=ext4 dev=/dev/sdb1 opts="-cc"'

mount模块

配置挂载点
选项: 
dump
fstype:必选项,挂载文件的类型 
name:必选项,挂载点 
opts:传递给mount命令的参数
src:必选项,要挂载的文件 
state:必选项 
present:只处理fstab中的配置 
absent:删除挂载点 
mounted:自动创建挂载点并挂载之 
umounted:卸载
示例:
    name=/mnt/dvd src=/dev/sr0 fstype=iso9660 opts=ro state=present
    name=/srv/disk src='LABEL=SOME_LABEL' state=present
    name=/home src='UUID=b3e48f45-f933-4c8e-a700-22a159ec9077' opts=noatime state=present
    ansible test -a 'dd if=/dev/zero of=/disk.img bs=4k count=1024'
    ansible test -a 'losetup /dev/loop0 /disk.img'
    ansible test -m filesystem 'fstype=ext4 force=yes opts=-F dev=/dev/loop0'
    ansible test -m mount 'name=/mnt src=/dev/loop0 fstype=ext4 state=mounted opts=rw'

get_url 模块

#该模块主要用于从http、ftp、https服务器上下载文件(类似于wget),主要有如下选项:
sha256sum:下载完成后进行sha256 check;
timeout:下载超时时间,默认10s
url:下载的URL
url_password、url_username:主要用于需要用户名密码进行验证的情况
use_proxy:是事使用代理,代理需事先在环境变更中定义
示例:
    get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf mode=0440
    get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf sha256sum=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c

archive模块

用于归档文件
path: /ane/soft下面是多路径的用法
  - /ane/soft
  - /ane/script/*
dest:
format: bz2、gz、tar、xz、zip
remove=yes 删除文件,默认为no
exclude_path 排除特定的目录
  - /path/to/foo/bar
  - /path/to/foo/baz
示例如下
- archive: 
  path=/ane/soft/nginx-1.8.1  
  dest=/ane/soft/nginx.tgz
  format=tar
  #remove=yes
- archive:
  path=/ane/script
  dest=/ane/script

unarchive模块

用于解压文件,模块包含如下选项:
copy:在解压文件之前,是否先将文件复制到远程主机,默认为yes。若为no,则要求目标主机上压缩包必须存在。
creates:指定一个文件名,当该文件存在时,则解压指令不执行
dest:远程主机上的一个路径,即文件解压的路径 
group:解压后的目录或文件的属组
list_files:如果为yes,则会列出压缩包里的文件,默认为no,2.0版本新增的选项
mode:解决后文件的权限
src:如果copy为yes,则需要指定压缩文件的源路径 
owner:解压后文件或目录的属主

示例如下:
- unarchive: src=foo.tgz dest=/var/lib/foo
- unarchive: src=/tmp/foo.zip dest=/usr/local/bin copy=no
- unarchive: src=https://example.com/example.zip dest=/usr/local/bin copy=no

selinux模块

开启selinux
 selinux: policy=targeted  state=enforcing
禁用selinux
 selinux: state:disabled
]]>
https://www.askme-121.space/ansible-simple/feed/ 0
PHP-FPM是如何工作的? https://www.askme-121.space/php-fpm/ https://www.askme-121.space/php-fpm/#respond Sat, 20 Apr 2024 05:43:41 +0000 https://www.askme-121.pw/?p=518 首先了解一下几个知识点。
CGI:是 Web Server 与 Web Application 之间数据交换的一种协议。
FastCGI:同 CGI,是一种通信协议,但比 CGI 在效率上做了一些优化。
PHP-CGI:是 PHP (Web Application)对 Web Server 提供的 CGI 协议的接口程序。
PHP-FPM:是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序,额外还提供了相对智能一些任务管理。

CGI工作流程

1.如果客户端请求的是 index.html,那么Web Server会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据。

2.当Web Server收到 index.php 这个请求后,会启动对应的 CGI 程序,这里就是PHP的解析器。接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以CGI规定的格式返回处理后的结果,退出进程,Web server再把结果返回给浏览器。

FastCGI工作流程

1.如果客户端请求的是 index.html,那么Web Server会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据。

2.当Web Server收到 index.php 这个请求后,FastCGI程序(FastCGI在启动时就初始化执行执行环境,每个CGI进程池各个CGI进程共享执行环境)在CGI进程池中选择一个CGI进程处理请求,再以CGI规定的格式返回处理后的结果,继续等待下一个请求。

PHP-FPM基本实现

PHP-FPM的实现就是创建一个master进程,在master进程中创建worker pool并让其监听socket,然后fork出多个子进程(work)。

这些子进程各自accept请求,子进程的处理非常简单,它在启动后阻塞在accept上,有请求到达后开始读取请求数据,读取完成后开始处理然后再返回。

在这期间是不会接收其它请求的,也就是说PHP-FPM的子进程同时只能响应一个请求,只有把这个请求处理完成后才会accept下一个请求

PHP-FPM的master进程与worker进程之间不会直接进行通信,master通过共享内存获取worker进程的信息。

比如worker进程当前状态、已处理请求数等,当master进程要杀掉一个worker进程时则通过发送信号的方式通知worker进程。

PHP-FPM可以同时监听多个端口,每个端口对应一个worker pool,而每个pool下对应多个worker进程

Worker工作流程

1.等待请求:worker进程阻塞在fcgi_accept_request()等待请求;

2.解析请求:fastcgi请求到达后被worker接收,然后开始接收并解析请求数据,直到request数据完全到达;

3.请求初始化:执行php_request_startup(),此阶段会调用每个扩展的:PHP_RINIT_FUNCTION();

4.编译、执行:由php_execute_script()完成PHP脚本的编译、执行;

5.关闭请求:请求完成后执行php_request_shutdown(),此阶段会调用每个扩展的:PHP_RSHUTDOWN_FUNCTION(),然后进入步骤(1)等待下一个请求。

Master进程管理

1.static: 这种方式比较简单,在启动时master按照pm.max_children配置fork出相应数量的worker进程,即worker进程数是固定不变的

2.dynamic: 动态进程管理,首先在fpm启动时按照pm.start_servers初始化一定数量的worker。

运行期间如果master发现空闲worker数低于pm.min_spare_servers配置数(表示请求比较多,worker处理不过来了)则会fork worker进程,但总的worker数不能超过pm.max_children。

如果master发现空闲worker数超过了pm.max_spare_servers(表示闲着的worker太多了)则会杀掉一些worker,避免占用过多资源,master通过这4个值来控制worker数

3.ondemand: 这种方式一般很少用,在启动时不分配worker进程,等到有请求了后再通知master进程fork worker进程。

总的worker数不超过pm.max_children,处理完成后worker进程不会立即退出,当空闲时间超过pm.process_idle_timeout后再退出

PHP-FPM事件管理器

1.sp管道可读事件:这个事件是master用于处理信号的

2.fpm_pctl_perform_idle_server_maintenance_heartbeat():这是进程管理实现的主要事件。

master启动了一个定时器,每隔1s触发一次,主要用于dynamic、ondemand模式下的worker管理,master会定时检查各worker pool的worker进程数,通过此定时器实现worker数量的控制

3.fpm_pctl_heartbeat():这个事件是用于限制worker处理单个请求最大耗时的,php-fpm.conf中有一个request_terminate_timeout的配置项,如果worker处理一个请求的总时长超过了这个值那么master将会向此worker进程发送kill -TERM信号杀掉worker进程,此配置单位为秒,默认值为0表示关闭此机制

4.fpm_pctl_on_socket_accept():ondemand模式下master监听的新请求到达的事件,因为ondemand模式下fpm启动时是不会预创建worker的,有请求时才会生成子进程,所以请求到达时需要通知master进程

]]>
https://www.askme-121.space/php-fpm/feed/ 0
PHP开发api签名验证 https://www.askme-121.space/php-api-sign/ https://www.askme-121.space/php-api-sign/#respond Sat, 20 Apr 2024 04:17:40 +0000 https://www.askme-121.pw/?p=517 开发过程中,我们经常会与接口打交道,有的时候是调取别人网站的接口,有的时候是为他人提供自己网站的接口,但是在这调取的过程中都离不开签名验证。

我们在设计签名验证的时候,请注意要满足以下几点:

  • 可变性:每次的签名必须是不一样的。
  • 时效性:每次请求的时效,过期作废等。
  • 唯一性:每次的签名是唯一的。
  • 完整性:能够对传入数据进行验证,防止篡改。

这里介绍一种方式,是目前国内互联网公司常用的一种方式,其中淘宝的支付宝支付接口、淘宝开放平台接口、腾讯开放平台等应用的一种方式。

一、签名参数sign生成的方法

第1步: 将所有参数(注意是所有参数),除去sign本身,以及值是空的参数,按参数名字母升序排序。

第2步: 然后把排序后的参数按参数1值1参数2值2…参数n值n(这里的参数和值必须是传输参数的原始值,不能是经过处理的,如不能将”转成”后再拼接)的方式拼接成一个字符串。

第3步: 把分配给接入方的验证密钥key拼接在第2步得到的字符串前面。

第2步: 在上一步得到的字符串前面加上验证密钥key(这里的密钥key是接口提供方分配给接口接入方的),然后计算md5值,得到32位字符串,然后转成大写.

第4步: 计算第3步字符串的md5值(32位),然后转成大写,得到的字符串作为sign的值。

举例:

假设传输的数据是/interface.php?sign=sign_value&p2=v2& p1=v1&method=cancel&p3=&pn=vn(实际情况最好是通过post方式发送),其中sign参数对应的sign_value就是签名的值。

第一步,拼接字符串,首先去除sign参数本身,然后去除值是空的参数p3,剩下p2=v2&p1=v1&method=cancel& amp;pn=vn,然后按参数名字符升序排序,method=cancel&p1=v1&p2=v2&pn=vn.

第二步,然后做参数名和值的拼接,最后得到methodcancelp1v1p2v2pnvn
第三步,在上面拼接得到的字符串前加上验证密钥key,我们假设是abc,得到新的字符串abcmethodcancelp1v1p2v2pnvn

第四步,然后将这个字符串进行md5计算,假设得到的是abcdef,然后转为大写,得到ABCDEF这个值即为sign签名值。

注意,计算md5之前请确保接口与接入方的字符串编码一致,如统一使用utf-8编码或者GBK编码,如果编码方式不一致则计算出来的签名会校验失败。

二、签名验证方法

根据前面描述的签名参数sign生成的方法规则,计算得到参数的签名值,和参数中通知过来的sign对应的参数值进行对比,如果是一致的,那么就校验通过,如果不一致,说明参数被修改过。

三、下面直接看代码

<?php

// 设置一个公钥(key)和私钥(secret),公钥用于区分用户,私钥加密数据,不能公开
$key = "c4ca4238a0b923820dcc509a6f75849b";
$secret = "28c8edde3d61a0411511d3b1866f0636";

// 待发送的数据包
$data = array(
    'username' => 'abc@qq.com',
    'sex' => '1',
    'age' => '16',
    'addr' => 'guangzhou',
    'key' => $key,
    'timestamp' => time(),
);

// 获取sign
function getSign($secret, $data) {
    // 对数组的值按key排序
    ksort($data);
    // 生成url的形式
    $params = http_build_query($data);
    // 生成sign
    $sign = md5($params . $secret);
    return $sign;
}

// 发送的数据加上sign
$data['sign'] = getSign($secret, $data);

/**
 * 后台验证sign是否合法
 * @param  [type] $secret [description]
 * @param  [type] $data   [description]
 * @return [type]         [description]
 */
function verifySign($secret, $data) {
    // 验证参数中是否有签名
    if (!isset($data['sign']) || !$data['sign']) {
        echo '发送的数据签名不存在';
        die();
    }
    if (!isset($data['timestamp']) || !$data['timestamp']) {
        echo '发送的数据参数不合法';
        die();
    }
    // 验证请求, 10分钟失效
    if (time() - $data['timestamp'] > 600) {
        echo '验证失效, 请重新发送请求';
        die();
    }
    $sign = $data['sign'];
    unset($data['sign']);
    ksort($data);
    $params = http_build_query($data);
    // $secret是通过key在api的数据库中查询得到
    $sign2 = md5($params . $secret);
    if ($sign == $sign2) {
        die('验证通过');
    } else {
        die('请求不合法');
    }
}
]]>
https://www.askme-121.space/php-api-sign/feed/ 0