2024年11月

GitHub 仓库链接

https://github.com/mattn/anko

1.
anko
是干嘛用的?

anko
是一个可以让 Go 项目支持脚本语言的小工具。换句话说,就是我们可以给 Go 项目加点“脚本魔法”,在程序跑起来之后还能动态地改代码逻辑。比如,你在写一个应用,想让用户可以随时调整设置或控制程序的某些行为,而不需要每次都去改代码重新编译,这时候就可以用
anko

2. 为什么会用到
anko

有时候我们的项目需要
灵活一点
。比如:

  • 做一个游戏,想让用户自己定义规则。
  • 写一个自动化脚本,想让用户随时调整参数。
  • 做后台管理工具,管理员可以直接在网页上写脚本来控制一些业务流程。

如果这些逻辑写在代码里,就得不停地改代码重启服务。而用
anko
就可以把这些逻辑写成脚本,用户想怎么改就怎么改,还不用重启,轻松方便。


3. 如何开始使用
anko

anko
安装起来也很简单,只需要几行代码就能用上。首先安装:

go get github.com/mattn/anko

然后我们在代码里引入:

import "github.com/mattn/anko/vm"


4. 让
anko
跑起来

假设我们有一段小脚本,想动态地执行它。下面是一个简单的例子:

package main

import (
    "fmt"
    "github.com/mattn/anko/vm"
)

func main() {
    env := vm.NewEnv() // 创建一个新的脚本环境

    // 写一段小脚本代码
    script := `
x = 5
y = 10
z = x + y
z
`

    // 执行脚本
    result, err := env.Execute(script)
    if err != nil {
        fmt.Println("出错了:", err)
    } else {
        fmt.Println("脚本运行结果:", result) // 输出:15
    }
}


5. 用 Go 变量控制脚本

如果想在脚本里使用 Go 程序的变量,可以用
Define
方法定义变量,然后在脚本里直接用。例如:

env := vm.NewEnv()
env.Define("name", "Anko") // 在脚本里定义 name 变量

script := `
"Hello, " + name + "!"
`

result, err := env.Execute(script)
fmt.Println(result) // 输出 "Hello, Anko!"


6. 让脚本调用 Go 函数

不仅可以传变量,还可以把 Go 的函数给脚本用。举个例子,假如我们有个打招呼的函数
greet

package main

import (
    "fmt"
    "github.com/mattn/anko/vm"
)

func greet(name string) string {
    return "Hello, " + name
}

func main() {
    env := vm.NewEnv()
    env.Define("greet", greet) // 把 greet 函数传给脚本

    script := `
greet("Anko")
`

    result, err := env.Execute(script)
    fmt.Println(result) // 输出 "Hello, Anko"
}


7. 用
anko
实现简单的逻辑

anko
也支持一些基本的控制语句,比如
if

for

script := `
sum = 0
for i = 1; i <= 5; i++ {
    sum += i
}
sum
`

result, _ := env.Execute(script)
fmt.Println("Sum is:", result) // 输出 15


8.
anko
的优缺点

优点

  • 灵活
    :可以在不重启程序的情况下改代码逻辑,非常适合需要频繁调整规则或逻辑的场景。
  • 易于集成
    :可以直接把 Go 的函数和变量传递给脚本,让脚本和 Go 程序无缝结合。
  • 语法简单
    :大多数人可以快速上手,用 Go 写代码的同学用这个库没啥学习成本。

缺点

  • 性能限制
    :解释器相对慢一些,不适合执行复杂、频繁的计算任务。
  • 功能不如高级脚本语言
    :没有像 JavaScript 或 Python 那么强大的功能,主要适合轻量级的动态任务。


9.
anko
适合哪些场景?

  • 动态配置
    :比如管理系统里定义一些规则,不用每次都改代码。
  • 业务规则引擎
    :很多应用需要灵活配置规则,
    anko
    是一个轻量级的选择。
  • 自动化脚本
    :运行一些自动化任务,允许用户在界面里直接编写脚本控制任务。


总结

anko
是一个让 Go 支持脚本的好工具。它的轻量、灵活和简单特性,让我们可以在 Go 应用里嵌入脚本语言,用户可以自由定义一些规则或逻辑,非常适合后台管理、自动化任务、游戏规则等应用场景。

业余时间用 .net core 写了一个在线客服系统。我把这款业余时间写的小系统丢在网上,陆续有人找我要私有化版本,我都给了,毕竟软件业的初衷就是免费和分享。

后来我索性就发了一个100%私有化版直接给大家下载,方便大家自己部署。同时,为了方便暂时不想自己私有化部署的朋友使用,我免费搭建了一个在线环境给大家使用。

今天我查了下在线使用环境的数据库,累计的处理消息条数居然达到了创纪录的 489933 条!! 48 万余条!!

我十分震惊这个数量,因为这个在线环境我挂上去之后除了正常版本更新,平时基本不管,也不需要维护什么的,今天想起来看一下,居然处理了这么多消息!

虽然系统完全是免费的,免费给客户使用,但是达到这样一个成绩,自己的小系统有这个的用户,也很欣慰。

有朋友说:如果一个程序员编写的系统 有人用,就已经超过了 99% 的程序员,哈哈,欣慰了。


我是怎么做到的

系统架构

服务端程序除了提供一般数据增删改查能力之外,重要的是需要实现稳定的消息中件间,稳定的站点状态、客服状态、访客状态等上下文数据在内存中的管理,以及稳定的TCP/IP长连接维护机制。
综合考虑技术指标和开发效率,我采用了 .net core 做为服务端程序,SQL Server 做为数据库的方案。
.net core 目前已经具备了完善的技术指标,并且能够提供其它开发平台无法比拟的开发效率。得益于整个 .net 技术体系,使我靠一己之力完成整个系统成为可能。

数据库我采用了 SQL Server ,它能够借助 Entity Framework Core 与 .net core 无缝融合,提供无与伦比的开发效率。同时,在数据处理的性能上,完全能够满足各项技术指标的要求,在日常使用和运维上,也提供了 Oracle 和 MySQL 无法比拟的便利性和工作效率。

客服端程序我使用了 WPF 框架。相比 WinForms 程序,WPF 提供了更高的开发效率和更完备的基础框架,使得画面呈现、数据处理、模块解耦更容易。同样,基于 .net 体系的 WPF 所提供的极高的开发效率是其它开发平台无法提供的。对于需要一天十几个小时稳定运行不退出的程序而言,原生客户端程序的稳定性也是基于 Web 页面的程序所无法相比的。


在上文中,我们提到系统可以横向扩展,具备弹性扩容的能力。在初期可以以较低的配置环境运行,用户增加时,可以在不影响原有结构的基础上,无感的横向扩展。亦或因服务规模的减小而收缩。

下面我将从系统的整体结构来阐述这个问题:

从上图可以看出这是一个典型的分布式部署系统。客户网站中嵌入的 JavaScript 文件在运行是地,首先连接路由服务器,路由服务器保存了一张表,每个站点所应该使用的应用服务器地址。就好像DNS服务器一样,告诉客户网站你应该向哪里发起真正的连接请求。接着,客户网站中嵌入的 JavaScript 程序向对应的应用服务器发起连接,上报访客状态,接收服务器指令。
那么很容易理解,我们只需要扩展我们的应用服务器数量,就可以轻松的增加系统的承载能力。反之也可以进行缩减操作。

在处理客户的私有化部署需求时,我们也可以根据容易的实际需要,取消路由服务器、取消文件服务器、取消CDN内容分发网络。以一个简单的方案来应对访问量不高的中小企业需求。在这个基础上,我们还可以借助现在的云服务器,实现弹性配置,以一个较低配置的服务器开始运行,甚至将数据库和缓存服务完全部署在一台服务器上使用,在使用过程中发现确有需要,先增加单台服务器的配置,不能满足需求的,再分离数据库和缓存服务,还不能满足要求的,则增加路由服务器,增加应用服务器。

消息传输

首先我们回顾一下 TCP 协议,TCP 报文格式一般如下所示:
其中的 ACK ,表示对报文是否送达的一个回应。
在这里插入图片描述
ACK是TCP标头中的标志和字段。 发送一个消息至少需要一个标头,再加上所有较低层的内容。

下图则显示了 TCP 通信时,客户端和服务端之间报文传送的过程。
从图中可以看到,发出的消息,和回应的消息,都会有一个编号,如:#1、#2
在ACK报文回应时,它回附带上所收到的报文的编号,那么发送端只需根据收到的ACK报文中的编号,就能判定报文是否送达,已经所送达的数据包。如果在一定时间内,没有收到回应的ACK消息,则发送端会在一定时间内重新尝试发送。
在这里插入图片描述

网络异常的处理

这种情况最好处理。因为客户端程序异常退出会直接引发 ConnectionReset 的 Socket 异常。我们只需要在服务端捕获这个异常进行处理即可:

 public bool Send(byte[] data)
        {
            // 连接已经断开了
            try
            {
                _networkStream.Write(data, 0, data.Length);
            }
            catch (Exception ex)
            {
                OnDisconnected(ex);
                return false;
            }

            return true;
        }

对于这种情况,我们只需要检测 Socket 对象的 Connected 属性。
但是需要特别注意:Socket 对象的 Connected 属性获取从 Socket 最后一个 i/o 操作到的的连接状态。 当它返回时 false , Socket 要么从未连接,要么不再处于连接状态。当 Socket 从另一个线程断开连接时,它可能会在操作中止后返回。
如果需要确定连接的当前状态,请发出非阻止的零字节发送调用。 如果调用成功返回或引发 WAEWOULDBLOCK 错误代码 (10035) ,则套接字仍处于连接状态;否则,将不再连接套接字。

我们可以通过实现一个定时心跳,来对网络链路进行检测:

_heartbeatTimer = new Timer((state) =>
            {
                HeartbeatMessage heartbeatMessage = new HeartbeatMessage();
                Send(heartbeatMessage);

            }, null, 3000, 3000);

在定时器发送心跳时,如果网络链路中断,我们可以收到以下消息:

 private void _socketClient_Disconnected(object sender, EventArgs e)
        {
            if (_heartbeatTimer != null)
                _heartbeatTimer.Dispose();

            if (_socketClient != null)
            {
                _socketClient.Close();
                _socketClient = null;
            }
        }

只需针对 Disconnected 事件,进行处理,将两端的状态,置于等待即可。


简介下这个 .net 开发的小系统

https://kf.shengxunwei.com/

  • 可以追踪正在访问网站或使用 APP 的所有访客,收集他们的浏览情况,使客服能够主动出击,施展话术,促进成单。
    访* 客端在 PC 支持所有新老浏览器。包括不支持 WebSocket 的 IE8 也能正常使用。
  • 移动端支持所有手机浏览器、APP、各大平台的公众号对接。
  • 支持访客信息互通,可传输访客标识、名称和其它任意信息到客服系统。
  • 具备一线专业技术水平,网络中断,拔掉网线,手机飞行模式,不丢消息。同类软件可以按视频方式对比测试。

希望能够打造:
开放、开源、共享。努力打造 .net 社区的一款优秀开源产品。

钟意的话请给个赞支持一下吧,谢谢~

大家好,我是程序员鱼皮。今天来聊聊 Nginx 技术,这是一个企业项目必用,但是却经常被程序员忽略的技术。学好 Nginx,可以助你在求职中脱颖而出。

或许你会想:“Nginx 不就是用来部署网站的服务器嘛?这有何难?”

但其实这不过是九牛一毛罢了,Nginx 的实用操作和使用技巧还多着呢,下面这篇文章,就带大家轻松入门 Nginx、并且循序渐进地学习 Nginx 真正的用法!

推荐观看本文对应的视频版本,有更多操作演示哦:
https://bilibili.com/video/BV1TW1LYkE59

一、Nginx 入门 - 牛刀小试

首先要了解什么是 Nginx?注意读音,是 Engine X,而不是恩静因克斯。

根据官方定义,它是世界上最受欢迎的 Web 服务器、高性能负载均衡器、反向代理、API 网关和内容缓存。

虽然听不懂,但是感觉很厉害的样子。

简单来说,Nginx 不仅能部署网站,而且相比其他的 Web 服务器,它能够用更少的资源,同时处理更多用户的请求,让网站速度更快更稳定,这也是企业选择 Nginx 的原因。

下面我们就牛刀小试,用 Nginx 启动一个网站!

1、Nginx 安装

首先我们需要安装 Nginx ,先到官网中根据操作系统下载一个稳定版本的压缩包,下载完成之后解压一下。

如果是 Windows 系统,双击 exe 文件启动即可;如果是 Mac 或 Linux 系统,可以打开终端并进入该目录,手动编译安装后执行 Nginx 命令启动。

当然也可以使用第三方的包管理工具,比如 Chocolatey(Windows)、Homebrew(Mac)、Yum(Linux)。

或者使用现成的服务器运维面板,比如宝塔 Linux,可以傻瓜式一键安装:

2、修改网页文件

启动成功后,我们访问本机域名
localhost:80
(80 为默认端口,可以省略),就可以看到 Nginx 为我们提供的默认网站了。

那如果想自己修改网页内容,怎么办呢?

我们要找到 Nginx 的大脑,也就是配置文件。进入配置目录
conf
,就可以看到配置文件
nginx.conf
了。配置文件由块和指令组成,可以通过修改配置实现各种功能,比如通过 location 块和 root 指令配置网站文件的根路径:

我们找到这个 index.html 文件,修改网页的内容并保存:

重新访问就可以看到效果啦!

看到这里,恭喜你,已经超过 30% 的程序员了!

二、Nginx 常用操作 - 明劲

下面,我们要成为 Nginx 明劲武者。所谓明劲,就是要熟悉 Nginx 的基本配置和常用操作,能够满足企业开发中的大多数需求,如果你的目标是开发岗,那么学完下面这些就足够找工作了。

1、静态文件服务

我们开发好的网站,通常包含像 HTML、CSS、JavaScript、图片等文件,由于这些文件的内容在存储时是固定的,被称为静态文件。

如果你要让别人访问到开发好的网站,只把网站文件放到服务器上还是不够的,还需要一个 Web 服务器,能够接受用户的访问请求,并找到对应位置的文件进行响应。

Nginx 最基本的功能,就是作为 Web 服务器提供静态文件服务。

打开 Nginx 的配置文件
nginx.conf
,添加 location 块,用于根据请求地址处理请求。比如我们通过 root 指令定义静态文件根目录,通过 index 指令定义默认首页文件:

server {
 listen       80;
 server_name localhost;

 location / {
   root /tmp/nginx/html;  # 指定静态文件根目录
   index index.html;  # 默认首页
}
}

保存配置,然后执行
nginx -s reload
命令来重载配置,再次访问网站时就会返回刚配置的目录下的首页文件。

企业项目中,需要为特定路径定义不同的处理规则,location 块的配置会更复杂。支持根据请求路径的特定部分、正则表达式等进行匹配,比如到特定目录去寻找图片:

server {
 listen 80;                          # 监听 80 端口
 server_name example.com;            # 指定域名

 # 根路径的配置,返回静态文件
 location / {
   root /var/www/html;             # 指向静态文件的根目录
   index index.html;               # 默认首页文件
   try_files $uri $uri/ =404;     # 如果文件不存在,则返回 404
}

 # 处理以 /images/ 开头的请求
 location /images/ {
   root /var/www/assets/images/;  # 指向图片目录
}

 # 正则匹配,处理以 .php 结尾的请求
 location \.php$ {
   include fastcgi_params;          # 包含 FastCGI 参数
   fastcgi_pass 127.0.0.1:9000;    # 将请求转发到 FastCGI 处理程序
   fastcgi_index index.php;         # 设置 FastCGI 的默认索引文件
   fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;  # 定义脚本文件名
}
}

2、反向代理

Nginx 的另一个常用功能是用作反向代理服务器。什么是反向代理呢?一句话:Nginx 作为中介,帮后端服务器接受请求。

反向代理有什么作用呢?

首先是请求转发和解决跨域。比如在 location 块中添加 proxy_pass 配置,可以将 Nginx 在 80 端口收到的 /api 路径的请求转发到本地 8080 端口的后端服务。

server {
   listen 80;  # 监听 80 端口
   server_name localhost;  # 替换为你的域名或 IP 地址

   location /api {
       proxy_pass http://localhost:8080;  # 代理到本地的 8080 端口
  }
}

这样就隐藏了后端服务器的 IP 地址,让客户端完全感知不到后端服务器的存在,更加安全。

而且还能让前端和后端的域名统一,解决了跨域问题。

反向代理还可以用于实现负载均衡。由于企业项目的流量巨大,通常需要有多台后端服务器。Nginx 可以作为高性能网关,统一接收请求,并将请求按照特定规则转发到不同的后端服务器进行处理,从而分散了请求压力,避免单一服务器过载。

在 Nginx 中实现负载均衡非常简单,首先通过 upstream 块定义了一个名为 backend 的服务器组,其中包含两个后端服务器,然后通过反向代理配置将请求转发到这个服务器组即可:

upstream backend {
 server localhost:8080;  # 第一个后端服务器
 server localhost:8081;  # 第二个后端服务器
}

server {
 listen 80;
 server_name localhost;

 location /api {
   proxy_pass http://backend;  # 代理到负载均衡的后端服务器
}
}

这样每次都访问同一个地址,会交替返回两种不同的内容,这是因为 Nginx 的默认负载均衡算法是轮询,请求会被平均转发到两个不同的服务进行处理。

反向代理还有更多的作用,比如缓存常见请求的响应、减少后端负担,集中处理 SSL 加密、认证和日志记录等功能,后面会依次讲解。

3、改写请求和响应

第三个 Nginx 的常用功能是改写请求和响应。在请求到达服务器或响应返回给客户端之前,Nginx 可以对其进行修改。

改写请求与响应有什么作用呢?有几个比较典型的场景:

1)控制浏览器缓存

首先,
设置响应头
可以帮助我们控制浏览器缓存。通过 Nginx 的
add_header
指令,可以为响应添加自定义的 HTTP 头部,从而指导浏览器如何处理缓存。比如设置缓存有效期为 30 天:

location /images/ {
   root /tmp/nginx/html;
   expires 30d;  # 设置缓存有效期为 30 天
   add_header Cache-Control "public";  # 设置缓存头
}

这样,当用户访问图片时,浏览器会在本地缓存这些图片,下次访问时就不用访问服务器了,提高速度并减少对服务器的请求。

2)重定向

请求重定向允许我们将请求从一个地址自动引导到另一个地址,常见的应用场景包括将 HTTP 请求重定向到 HTTPS,或者将旧地址重定向到新地址。

在 Nginx 中,可以使用
return
指令 + 302 状态码配置重定向:

location /old-page {
   return 302 https://codefather.cn  # 重定向到新页面
}

当用户访问某个过期页面时,会被重定向自动跳转到新网站。

3)URI 重写

比重定向更高级一些,Nginx 提供了
rewrite
指令,支持正则表达式,可以非常灵活地将请求重写为不同的路径或网站。比如将
/api/v1/users
的请求重写为
/api/users

location /api/v1/ {
   rewrite ^/api/v1/(.*)$ /api/$1 break;  # 将 /api/v1/ 的请求重写为 /api/
}

这样一来,后端就不用再关注
/api/v1/
的存在了,这种方法在网站迁移或者结构调整时非常有用。大家也不用去记忆改写的具体语法,随用随查就行。


看到这里,恭喜你,超过 60% 的程序员了。

三、Nginx 高级操作 - 暗劲

下面,我们要成为 Nginx 的暗劲高手。所谓暗劲,又分为 2 种境界。

  • 熟悉 Nginx 的各种特性和高级配置,能更快速地配置和管理 Nginx,为小圆满

  • 熟悉 Nginx 工具和模块生态,能够灵活运用 Nginx 进行架构设计、并巧妙地解决各种需求,为大圆满。

暗劲境界的高手,挑战大厂开发、架构师、高级系统管理员岗位,不成问题。

1、Nginx 高级配置

我们先挑战小圆满,Nginx 的配置项实在是太多了,这里我就挑选几个相对实用的来讲解。

1)日志记录

为了分析网站流量、用户行为和报错信息,我们可以开启 Nginx 日志功能。分为访问日志和错误日志。

访问日志会记录所有请求的信息,更全面,可以通过修改 access_log 指令调整日志存储路径:

http {
 log_format custom_format 'yupi $remote_addr - $remote_user [$time_local] "$request" '
                         '$status $body_bytes_sent "$http_referer" '
                         '"$http_user_agent" "$http_x_forwarded_for"';
 
 access_log /rap/access.log custom_format;  # 配置访问日志

 server {
   listen       80;
   server_name localhost;
 
   location / {
     root /tmp/nginx/html;  # 指定静态文件根目录
     index index.html;  # 默认首页
  }
}
}

而错误日志仅记录 Nginx 在处理请求时遇到的问题,错误又分为 8 个级别:

可以为不同的级别指定不同的日志输出路径:

access_log /rap/access.log custom_format;  # 配置访问日志
error_log /rap/error.log error;  # 配置错误日志

开启日志功能后,就能直接在文件中查看日志了。

2)访问控制

如果有恶意用户攻击我们的网站,怎么办?

莫慌,Nginx 提供了访问控制功能,可以使用
allow

deny
指令对 IP 访问进行限制,比如不让 127.0.0.1 这个 IP 访问:

server {
   listen 80;
   server_name localhost;

   location / {
       # 拒绝特定 IP 地址
       deny 127.0.0.1;
       # 除了写具体 ip 外,也可以写网段
       deny 192.168.1.0/24;
       # 允许所有其他 IP 地址
       allow all;
       proxy_pass http://localhost:8081;
   
  }
}

这样一来,攻击者就访问不了网站了!

3)限流

为了保护网站,我们还可以使用 Nginx 的限流功能。比如下面这段配置,通过定义请求限流区域并应用于根路径,限制每个 IP 地址在一分钟内最多只能发送 2 个请求。

# 定义限流区域,使用客户端的二进制 IP 地址作为唯一标识
# zone=one:10m 表示创建一个名为 "one" 的内存区域,大小为 10MB
# rate=2r/m 表示每个 IP 地址每分钟最多允许 2 个请求
limit_req_zone $binary_remote_addr zone=one:10m rate=2r/m;

server {
 listen 80;  # 监听 80 端口,接收 HTTP 请求
 server_name localhost;  # 设置服务器名称为 localhost

 location / {  # 配置根路径的请求处理
   # 应用限流配置,使用之前定义的 "one" 区域
   # burst=10 表示可以允许最多 10 个额外请求超出正常限速
   # nodelay 表示在突发请求情况下,这 10 个请求将立即被处理,不会被延迟
   limit_req zone=one burst=10 nodelay;

   # 将请求转发到本地的 8080 端口
   proxy_pass http://localhost:8080;  # 反向代理请求到后端服务
}
}

这样后端服务就不被流量激增影响,能够提高系统的稳定性。

4)虚拟主机

在企业开发中,我们为了节省成本,经常会在同一台服务器上部署多个网站项目,这时就需要使用 Nginx 的虚拟主机功能了。

每个网站通常就是一个虚拟主机,会有一个 server_name 名称对应访问网站的域名,比如我这里配置 2 台虚拟主机:

# 虚拟主机1
server {
   listen 80;                           # 监听 80 端口
   server_name localhost;               # 配置域名为 example.com

   root /tmp/nginx/html;            # 网站根目录
   index localhost.html;                # 默认首页
}

# 虚拟主机2
server {
   listen 80;                           # 监听 80 端口
   server_name 127.0.0.1;               # 配置域名为 another.com

   root /tmp/nginx/html;            # 网站根目录
   index 127.html;                      # 默认首页
}

配置虚拟主机后,Nginx 就能够根据请求的域名找到对应的网站配置,并处理请求。

5)其他

除了上面这些,还有很多企业开发中可能会用到的 Nginx 高级配置和技巧。

比如可以:

  • 通过后端响应缓存配置,让 Nginx 直接从缓存中读取数据来响应请求,这样能够显著提升性能、减少服务器压力。

  • 通过正向代理的设置,Nginx 可以作为 “跳板机”,帮客户端发起请求,从而访问原本无法直接访问的资源。

  • 通过自定义错误页面,能够给用户提供更友好的错误提示信息。

此外,Nginx 支持 WebSocket、HTTPS 和 HTTP/2 等多种协议,还可以配置 Gzip 压缩来减少传输的数据量,进一步优化性能。

最后,Nginx 自身也支持一系列性能调优的配置,比如工作进程与连接数配置,可以从容应对高并发和大流量场景。

worker_processes auto; # 自动检测 CPU 核心数,设置工作进程数

events {
   worker_connections 2048; # 每个工作进程的最大连接数
}

2、Nginx 工具和模块生态

想成为 Nginx 大圆满高手,就要懂得利用工具和生态,比如可视化工具、模块和开源项目。

首先,Nginx 的配置和运维对初学者来说可能比较复杂,这时可以利用 Nginx 官方推出的 Nginx Amplify、轻量级的 Nginx-UI 或者宝塔 Linux 服务器管理面板等可视化工具,通过图形界面来更直观地查看配置、分析流量和性能指标,从而提高操作和运维效率。

Nginx UI 面板

其次,Nginx 的功能并不是一成不变的,我们可以通过各种各样的模块来扩展它的能力,比如我们常用于健康检查的 nginx_upstream_check_module 模块、实现 JavaScript 语言扩展的 njs 模块。

但手动安装模块的过程是比较繁琐的,需要下载源码并进行编译。

这种情况下,我们就可以选择 OpenResty 这样一个基于 Nginx 的高性能 Web 平台,它集成了大量模块、依赖项和 Lua 脚本库,能够让你直接在 Nginx 里开发复杂的业务逻辑,充分利用 Nginx 的非阻塞 I/O 模型来提升应用的性能,适合超高并发的场景。

比如下图是网上的一个基于 OpenResty 实现的灰度发布架构:

四、Nginx 原理 - 化劲

想要突破为化劲强者,你需要去理解 Nginx 的核心原理,甚至是去钻研那晦涩难懂的 C 语言源码。

当然,为了应对面试,现在很多程序员迫不得已朝着化劲强者进发。

原理的学习就不是几分钟的视频能搞定的了,但是我可以帮大家划划重点。

  • 负载均衡机制

  • 事件驱动模型

  • 请求处理流程

  • 多进程架构

  • 进程间通信机制

  • 限流机制

  • 缓存机制

  • 压缩机制

  • 资源复用

能搞懂这些,并且融会贯通,你就能够更自如地优化 Nginx 的性能和可用性等等,也就超过 99% 的程序员了。

当然,编程是学不完的,真正的 Nginx 绝世高手,可以给 Nginx 贡献代码,甚至是自立门户、手写 Nginx 的竞品。

我相信看到这里的小伙伴中肯定会出现绝世高手~

结尾

最后,我把这份 Nginx 学习路线文字版、以及常问的面试题都放到了自己的小博客,还有更多学习路线也可免费获取。

希望对大家有帮助,学会的话也还请给本文一个点赞支持哦~

更多编程学习资源


title: Nuxt.js 应用中的 components:extend 事件钩子详解
date: 2024/11/1
updated: 2024/11/1
author:
cmdragon

excerpt:
components:extend 是 Nuxt.js 中的一个生命周期钩子,允许开发者扩展新的组件到项目中。通过这个钩子,开发者可以动态地添加额外的组件,从而增强项目的功能和灵活性。

categories:

  • 前端开发

tags:

  • Nuxt
  • 组件
  • 钩子
  • 动态
  • 扩展
  • 生命周期
  • Vue


image
image

扫描
二维码
关注或者微信搜一搜:
编程智域 前端至全栈交流与成长

components:extend
钩子详解

components:extend
是 Nuxt.js 中的一个生命周期钩子,允许开发者扩展新的组件到项目中。通过这个钩子,开发者可以动态地添加额外的组件,从而增强项目的功能和灵活性。


目录

  1. 概述
  2. components:extend 钩子的详细说明
  3. 具体使用示例
  4. 应用场景
  5. 注意事项
  6. 关键要点
  7. 总结


1. 概述

components:extend
钩子使开发者能够在 Nuxt.js 项目中动态地添加新的组件。这种灵活性使得项目能够根据需求进行扩展和修改,适应不同的功能需求。

2. components:extend 钩子的详细说明

2.1 钩子的定义与作用

  • 定义
    :
    components:extend
    是 Nuxt.js 的一个钩子,用于扩展和添加新的组件至项目中。
  • 作用
    : 使开发者可以在项目中动态地添加新增的组件,增加应用的功能性和灵活性。

2.2 调用时机

  • 执行环境
    : 在组件解析时触发,适合进行组件的扩展和添加。
  • 挂载时机
    : 该钩子在应用启动前被调用,确保新的组件设置在应用运行之前生效。

2.3 参数说明

  • components
    : 该参数包含当前项目中的组件配置信息,开发者能够对其进行添加、修改或删除操作。

3. 具体使用示例

3.1 扩展组件示例

// plugins/componentsExtend.js
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hooks('components:extend', (components) => {
    // 扩展新的组件
    components.push({
      name: 'CustomComponent',
      path: './components/CustomComponent.vue'
    });

    console.log('Extended components:', components);
  });
});

在这个示例中,我们使用
components:extend
钩子向现有的组件列表中添加了一个新的组件
CustomComponent
。这个组件位于
./components/CustomComponent.vue
文件中,可以在项目中随意使用。

4. 应用场景

  1. 功能扩展
    : 在需要时动态地添加新组件,以加强项目的功能。
  2. 共享组件
    : 针对多个模块或页面创建共享的组件,从而提高代码重用率。
  3. 模块化设计
    : 在构建大型应用时,根据需求动态创建并扩展组件。

5. 注意事项

  • 组件命名
    : 确保添加的组件不会与已有组件冲突,适当使用命名空间。
  • 性能考虑
    : 动态添加多个组件可能会影响性能,应合理组织组件结构。
  • 团队协作
    : 与团队成员沟通,确保对新增组件的了解和使用。

6. 关键要点

  • components:extend
    钩子为 Nuxt.js 开发者提供了一种动态扩展组件的方式。
  • 通过合理利用此钩子,可以提高项目的灵活性和可维护性。

7. 总结

components:extend
钩子使得 Nuxt.js 项目可以灵活地添加新的组件,提升了应用的扩展性。通过有效地管理组件,开发者能够创建更加模块化和可维护的项目结构。

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:
编程智域 前端至全栈交流与成长
,阅读完整的文章:
Nuxt.js 应用中的 components:extend 事件钩子详解 | cmdragon's Blog

往期文章归档:

1.简介

随着Unity6的发布,URP17也已经可以上手使用,相对旧的版本改动较大的是加入了

RenderGraph、STP、Foveated rendering、GPU Resident Drawer等功能,部分功能只需要开关参数即可使用,

而GRD更像是Gpu driven管线下的SRP Batches升级,RenderGraph相较于HDRP之前使用的版本换了一套API。

最大的不同是,使用URP17编写Feature时,必须依赖于RenderGraph进行编写,接下来就来介绍一下。

1.1 相关Demo

目前URP17比较容易找到的学习Demo如下:

2.RenderGraph

打开任意URP的示例场景查看,RenderGraphView上各图标含义如下:

  1. 说明这是一个外部置入的RenderTexture
  2. 红色方块说明存在写入操作
  3. 绿色方块指存在读取操作(红绿方块说明读写操作)
  4. 该图标说明标记了全局RenderTexture

而顶部表明当前渲染一帧的各个Pass,左侧是各类RT。

URP17同时保留了旧的Feature逻辑与RenderGraph逻辑(打开任意pass文件为例):

public classDistortTunnelPass_Tunnel : ScriptableRenderPass
{
classPassData
{
publicRenderer tunnelObject;publicMaterial tunnelMaterial;
}
#pragma warning disable 618, 672 //Type or member is obsolete, Member overrides obsolete member //Unity calls the Configure method in the Compatibility mode (non-RenderGraph path) public override voidConfigure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescripor)
{
}
//Unity calls the Execute method in the Compatibility mode public override void Execute(ScriptableRenderContext context, refRenderingData renderingData)
{
}
#pragma warning restore 618, 672 //Unity calls the RecordRenderGraph method to add and configure one or more render passes in the render graph system. public override voidRecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
}
}

参考时忽略掉Configure和Execute的逻辑,执行逻辑关注RecordRenderGraph函数。

2.1 操作方式的改变

在RenderGraph中,之前的RTHandle由于不在该系统中托管,进入RenderGraph的材质都需要调用API进行转换,

转换为RendeGraph的RT后,无需考虑释放操作:

RenderTextureDescriptor textureProperties = new RenderTextureDescriptor(Screen.width, Screen.height, RenderTextureFormat.Default, 0);
TextureHandle textureHandle
= UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "My texture", false);

相关文档:

https://docs.unity3d.com/Manual/urp/render-graph-create-a-texture.html

此外RenderGraph对于空调用的pass,也会剔除进行优化,使用者需要手动标记以防止被剔除。

2.1 RecordRenderGraph

在该函数内可组织渲染逻辑,pass相关的逻辑需放在对应的代码块中,例如:

using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out_))
{
builder.UseTexture(rt1);
builder.SetRenderAttachment(resourceData.activeColorTexture,
0);

builder.SetRenderFunc
<PassData>((data, context) =>{
MaterialPropertyBlock materialPropertyBlock
= newMaterialPropertyBlock();
materialPropertyBlock.SetTexture(
"_BlitTexture", rt1);
materialPropertyBlock.SetVector(
"_BlitScaleBias", new Vector4(1, 1, 0, 0));

context.cmd.DrawProcedural(Matrix4x4.identity, material,
0, MeshTopology.Triangles, 3, 1, materialPropertyBlock);
});
}

URP提供了多种RenderPass,例如处理光栅化相关逻辑使用RasterRenderPass组织相关逻辑。

在RenderPass的代码块中可使用builder对象配置RenderTarget、标记材质的读写等

而具体的pass绘制逻辑则在SetRenderFunc代码块中。

RecordRenderGraph内可以调用多次AddRenderPass,但URP并没有整理旧API的代码和相关工具类,

以至于容易使用旧的API导致报错,这点需要注意。

3.编写Feature

3.1 Blit与SetTarget

从前有句俗话“切RT的性能消耗相当于半个pass”,Unity SRP在几个版本的升级都在逐渐强调不切RenderTarget直接绘制,

如Cockpit Demo的屏幕空间描边。

3.2 屏幕模糊Demo

下面通过屏幕模糊Demo案例,演示URP17下pass的编写。

通过外部EnqueuePass的方式,在场景中通过控制器脚本添加该Pass,

MyBlurSceneController.cs:

usingUnityEngine;usingUnityEngine.Rendering.Universal;usingUnityEngine.Rendering;public classMyBlurSceneController : MonoBehaviour
{
publicMaterial material;
[Range(
2, 15)] public int blurPasses = 3;
[Range(
0, 4)] public int downSample = 0;
[Range(
0.0f, 10f)] public float offset = 0.2f;public RenderPassEvent injectionPoint =RenderPassEvent.BeforeRenderingPostProcessing;public int injectionPointOffset = 0;public ScriptableRenderPassInput inputRequirements =ScriptableRenderPassInput.Color;public CameraType cameraType =CameraType.Game;privateMyBlurPass mMyBlurPass;private voidOnEnable()
{
SetupPass();

RenderPipelineManager.beginCameraRendering
+=OnBeginCamera;
}
private voidOnDisable()
{
RenderPipelineManager.beginCameraRendering
-=OnBeginCamera;
}
public virtual voidSetupPass()
{
mMyBlurPass
= newMyBlurPass();

mMyBlurPass.renderPassEvent
= injectionPoint +injectionPointOffset;
mMyBlurPass.material
=material;

mMyBlurPass.ConfigureInput(inputRequirements);
}
public virtual voidOnBeginCamera(ScriptableRenderContext ctx, Camera cam)
{
if (mMyBlurPass == null || material == null)return;if ((cam.cameraType & cameraType) == 0) return;

mMyBlurPass.blurPasses
=blurPasses;
mMyBlurPass.downSample
=downSample;
mMyBlurPass.offset
=offset;

cam.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(mMyBlurPass);
}
}

MyBlurPass.cs:

usingUnityEngine;usingUnityEngine.Rendering.RenderGraphModule;usingUnityEngine.Rendering;usingUnityEngine.Rendering.Universal;usingUnityEngine.Rendering.RenderGraphModule.Util;public classMyBlurPass : ScriptableRenderPass
{
public classPassData
{
publicTextureHandle tempRt1;publicTextureHandle tempRt2;
}
publicMaterial material;
[Range(
2, 15)] public int blurPasses = 3;
[Range(
1, 4)] public int downSample = 1;
[Range(
0.0f, 10f)] public float offset = 0.2f;public override voidRecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
var resourceData = frameData.Get<UniversalResourceData>();var passData = newPassData();var w = Screen.width >>downSample;var h = Screen.height >>downSample;

RenderTextureDescriptor textureProperties
= new RenderTextureDescriptor(w, h, RenderTextureFormat.Default, 0);
passData.tempRt1
= UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "MyBlurPassTempRt1", false);

textureProperties
= new RenderTextureDescriptor(w, h, RenderTextureFormat.Default, 0);
passData.tempRt2
= UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "MyBlurPassTempRt2", false);var rt1 =passData.tempRt1;var rt2 =passData.tempRt2;//将屏幕RT Blit到rt1上 var para = new RenderGraphUtils.BlitMaterialParameters(resourceData.activeColorTexture, rt1, material, 0);
renderGraph.AddBlitPass(para,
"MyBlurPassBlitFirst");

material.SetFloat(
"_SampleOffset", offset);//模糊迭代 for (int i = 0; i < blurPasses - 1; ++i)
{
para
= new RenderGraphUtils.BlitMaterialParameters(rt1, rt2, material, 0);
renderGraph.AddBlitPass(para, $
"MyBlurPassBlit_{i}");var tmp =rt1;
rt1
=rt2;
rt2
=tmp;
}
//通过直接绘制的方式,将模糊RT绘制到屏幕上 using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out_))
{
builder.UseTexture(rt1);
builder.SetRenderAttachment(resourceData.activeColorTexture,
0);

builder.SetRenderFunc
<PassData>((data, context) =>{
MaterialPropertyBlock materialPropertyBlock
= newMaterialPropertyBlock();
materialPropertyBlock.SetTexture(
"_BlitTexture", rt1);
materialPropertyBlock.SetVector(
"_BlitScaleBias", new Vector4(1, 1, 0, 0));

context.cmd.DrawProcedural(Matrix4x4.identity, material,
0, MeshTopology.Triangles, 3, 1, materialPropertyBlock);
});
}
}
}

接着在ShaderGraph中连出模糊的逻辑,注意Blit对应的参数_BlitTexture、_BlitScaleBias:

最后在场景中挂载控制器以及材质球,即可使用该模糊Pass。