2024年1月

利用WebAssembly的导入导出功能可以灵活地实现宿主JavaScript程序与加载的单个wasm模块之间的交互,那么如何在宿主程序与多个wasm之间传递和共享数据呢?这就需要使用到Global这个重要的对象了。

一、数值类型全局变量
二、将JavaScript函数设置为全局变量
三、利用全局变量处理字符串

一、数值类型全局变量

Global全局变量支持多种值类型,包括数组(i32/i64和f32/f64)、向量和引用类型(externref和funcref)。下面的实例利用Global提供了全局计数的功能。在WebAssembly Text Format (WAT)文件app.wat中,我们从宿主JavaScript应用中导入了一个i32类型的可读写(mut表示可以修改)的全局变量,导入路径为“imports.counter”,我们将其命名为$counter。在用于自增的导出函数increment中,我们通过执行global.get指令读取全局变量的值,并将其加1之后,执行global.set指令对全局变量重新赋值。

(module
   (global $counter (import "imports" "counter") (mut i32))
   (func (export "increment")
       (i32.add (global.get $counter) (i32.const 1))
       (global.set $counter)
   )
)

在index.html文件中,我们在页面中添加了一个“Increment”按钮,并利用一个<span>显式计算器当前的值。JavaScript脚本通过调用WebAssembly.Global构造函数将代表全局变量的Global对象创建出来后,调用WebAssembly.instantiateStreaming加载app.wat编译生成的app.wasm模块文件,并将此Global对象包含在导入对象中。

<html>
    <head></head>
    <body>
        <span id="counter">0</span>
        <button id="btnInc">Increment</button>
        <script>
            const globalCounter = new WebAssembly.Global({ value: "i32", mutable: true }, 0);
            WebAssembly
                .instantiateStreaming(fetch("app.wasm"), {"imports":{"counter":globalCounter}})
                .then(results => {
                   document.getElementById("btnInc").onclick = ()=>{
                        results.instance.exports.increment();
                        document.getElementById("counter").innerText = globalCounter.value;
                   };
                });
        </script>
    </body>
</html>

wasm模块充成功导入后,我们注册了按钮的click事件,使之在调用导出的increment函数后,重新刷新计数器的值。如下图所示,针对“Increment”的每次点击都将计数器加1(
源代码
)。

image

二、将JavaScript函数设置为全局变量

除了四种数值类型,Global还支持两种引用类型externref和funcref,利用externref可以将宿主应用提供的任意JavaScript对象作为全局变量,下面的实例演示利用这种方式实现了与类似的功能。如下面的代码片段所示,新的app.wat导入了一个类型为externref的全局变量,对应着数组应用提供的一个用来对全局计数自增的Javascript函数。

(module
   (func $apply (import "imports" "apply") (param externref))
   (global $increment (import "imports" "increment") externref)
   (func $main
        (call $apply (global.get $increment))
   )
   (start $main)
)

由于JavaScript函数的引用在.wasm模块中并不能直接执行,所以我们不得不导入一个apply函数“回传”到宿主应用中执行。我们修改的应用用来统计导入的wasm模块的数量,所以我们在入口函数$main中利用apply调用了全局变量$increment引用的函数。

在index.html,我们在页面中添加了一个“Load”按钮来加载app.wat编译生成的app.wasm模块。JavaScript脚本利用counter变量表示加载的wasm模块数量,并通过调用WebAssembly.Global构造函数创建了rexternref类型的全局变量,其值为一个对counter自增的函数。

<html>
    <head></head>
    <body>
        <p>There are totally <span id="counter" style= "color: read”>0</span> wasm modules loaded. </p>
        <button id="btnLoad">Load</button>
        <script>
            var counter = 0;
            const globalIncrement = new WebAssembly.Global({ value: "externref"}, ()=>counter++);
            var apply = func => func();
            document.getElementById("btnLoad").onclick = ()=>{
                WebAssembly
                    .instantiateStreaming(fetch("app.wasm"), {"imports":{"increment":globalIncrement,"apply": apply }})
                    .then(_=>{
                        document.getElementById("counter").innerText = counter;
                    })
                };
        </script>
    </body>
</html>

我们将这个Global对象包含到导入的对象中,并在导入成功后刷新显式的计数器,所以程序运行后将会显式当前加载的wasm模块数量(
源代码
)。

image

三、利用全局变量处理字符串

WebAssembly目前并没有提供针对字符串类型的直接支持,而是单纯地将其作为字节序列看到。目前字符串在宿主程序与wasm模块之间的传递只有通过Memory来实现。由于Javascript具有处理字符串的能力,wasm模块可以将字符串作为externref回传到宿主程序进行处理。在接下来演示的程序中,我们在app.wat中定义一个“字符类型(实际上是externref类型)”的全局变量,导出的greet函数通过调用导入的print函数将其输出。

(module
   (func $print (import "imports" "print") (param externref))
   (global $message (import "imports" "message") (mut externref))
   (func (export "greet")
        (call $print (global.get $message))
   )
)

在index.html中,我们在页面上放置了三个按钮,让它们在命名为“greet”的<div>中分别显示“Good Morning”、“Good Afternoon”和“Good Evening”三条问候语。具体的问候语通过函数print输出,它的参数就是代表输出文本的字符串。

<html>
    <head></head>
    <body>
        <div id="greet"></div>
        <button id="btnMorning">Morning</button>
        <button id="btnAfternoon">Afternoon</button>
        <button id="btnEvening">Evening</button>
        <script>
            var print = (msg) => {
                console.log(msg);
                document.getElementById("greet").innerText = msg;
            }
            const globalMsg = new WebAssembly.Global({ value: "externref", mutable: true }, "Good Morning!");
            console.log(globalMsg.value);
            WebAssembly
                .instantiateStreaming(fetch("app.wasm"), {"imports":{"message":globalMsg, "print":print}})
                .then(results => {
                    var greet = results.instance.exports.greet;
                    console.log(greet);
                    document.getElementById("btnMorning").onclick = ()=>{
                        globalMsg.value = "Good Morning!";
                        greet();
                    };
                    document.getElementById("btnAfternoon").onclick = ()=>{
                        globalMsg.value = "Good Afternoon!";
                        greet();
                    };
                    document.getElementById("btnEvening").onclick = ()=>{
                        globalMsg.value = "Good Evening!";
                        greet();
                    };
                });
        </script>
    </body>
</html>

我们定义了一个externref类型的Global对象来引用带输出的问候语文本,并在加载app.wasm木块使将其包含到导入对象中。三个按钮的click事件处理程序通过调用导出的greet函数输出对于的问候语,但是在调用此函数之前会对Global对象进行相应的赋值(
源代码
)。

image

WebAssembly入门笔记[1]:与JavaScript的交互
WebAssembly入门笔记[2]:利用Memory传递字节数据
WebAssembly入门笔记[3]:利用Table传递引用
WebAssembly入门笔记[4]:利用Global传递全局变量

写代码是一个充满挑战的事情,在这段充满挑战的旅途中,我们都渴望找到那个提升幸福感的秘密。没准是更先进或是更快的工具,希望本期热点速递的开源项目,能给你带来启迪和乐趣,上菜!

第一个上场的是一款用 Rust 写的代码编辑器:Zed,因为它足够快、足够先进。全栈不等于全占 Expo 让你用一套代码到处运行,轻松拿下客户端。用过 Git 的小伙伴都知道,起项目时一个好的
.gitignore
文件能让你省去好多烦恼,GitHub 官方维护的 gitignore 拿去直接用。最后,热评的 JSON 可视化工具 JSON Crack 也不要错过呀!

对了,贾扬清博士开源的对话式 AI 搜索工具(Demo)有时间也可以玩一下,感受下新型搜索工具的交互模式。

  • 本文目录
    • 1. 开源新闻
      • 1.1 500 行对话式 AI 搜索工具
      • 1.2 “被”下架的开源项目
    • 2. GitHub 热搜项目
      • 2.1 超快的代码编辑器:Zed
      • 2.2 快速开发移动端:Expo
      • 2.3 用 Python 画架构图:Diagrams
      • 2.4 Git 忽略文件的集合:gitignore
      • 2.5 大语言模型课程:LLM-course
    • 3. HelloGitHub 热评
      • 3.1 优雅的 JSON 数据可视化工具:JSON Crack
      • 3.2 可爱的 Mac 菜单栏日历:Itsycal
    • 4. 往期回顾

下面,让我们一起看看,过去一周开源领域都发生了什么,关注开源最新动态、品热搜开源项目。

1. 开源新闻

1.1 500 行对话式 AI 搜索工具

该项目是贾扬清博士(PyTorch、TensorFlow 的核心贡献者之一)用 Python 做的 AI 搜索服务,仅作为对话式搜索的演示。后端用的是 FastAPI 框架,集成了多个自己服务比如 Mixtral-8x7b 模型和必应搜索的 API。

GitHub 地址→
https://github.com/leptonai/search_with_lepton

小插曲,在贾扬清博士放出演示地址后,AI 搜索引擎 Perplexity 创始人认为这款产品参考了自家的 UI 界面设计,贾扬清博士也对此做出回应,表示他无意进入这个市场,该项目更多是作为演示和宣传自家 LeptonAI 云服务的一部分,并且从一开始就计划开源该项目。(然后这个项目就开源了)

1.2 “被”下架的开源项目

近期,海尔向一位海外开发者发出律师函,要求他从 GitHub 下架他维护的开源项目:Home Assistant。该项目是一个开源智能家电自动化平台,可以让用户控制海尔的智能家电,包括空调、净化器、冰箱等。

GitHub 地址→
https://github.com/Andre0512/hOn

还有,国内的网易云音乐 API 开源项目:NeteaseCloudMusicApi,被网易云音乐公司起诉侵权,要求删除。该项目是作者用 Node.js 封装的第三方网易云音乐 API,很多网易云音乐的开源客户端都依赖它,目前该项目已删库,仅留下一句:“保护版权,此仓库不再维护”。

2. GitHub 热搜项目

2.1 超快的代码编辑器:Zed

主语言:Rust

Star:15.2k

周增长:1w+

这是一款用 Rust 编写的代码编辑器,但并不只是 Rust 语言的编辑器。它的核心优势是在协作方面的强大,包括多人共享工作区、语音和文字聊天频道以及屏幕共享功能。它还具备丰富的功能,如支持 GitHub Copilot、精确的代码高亮、自动缩进、内置终端、Vim 模式和多种主题。性能方面更是出色,充分利用 CPU 和 GPU 资源,让启动和编辑过程流畅无比。


更刺激的是,Zed 编辑器的作者团队可不简单!他们由 GitHub Atom(5.9w Star 编辑器)和 tree-sitter(1.4w Star 语法解析框架)的开发者组成。该项目刚刚在上周正式开源,结果仅仅短短一周时间,它已经获得了超过一万个 Star!

您是否曾经为命名一个变量、方法或类而挣扎过?找到表达性和简洁性之间的完美平衡了吗?您并不孤单。我们通过 GitHub Copilot Chat 扩展(需要订阅)在最新的 Visual Studio 预览版中解决了这个普遍的挑战。人工智能支持的重命名建议,这个功能不只是建议名字;它了解您的标识符是如何使用的,以及您的代码风格,以提供无缝地适合您的代码库的标识符。这就像有一个专门的命名专家在你身边,增强你的代码,同时保持你的编码流畅。

AI 在命名中的力量

我们的新功能利用人工智能来分析您的标识符的当前使用情况和代码的命名风格。通过了解标识符在代码中的使用方式,它可以生成一系列富于表达性的名称建议。我们的目标是帮助您提高代码的可读性和可维护性,确保其在未来几年的寿命和易于理解。

它是如何工作的

在 Visual Studio 中,只需选择任何标识符,右键单击,然后选择 Rename (Ctrl+R, Ctrl+R)。您将看到一个根据代码上下文定制的命名建议列表。选择您最喜欢的名称并按 Enter。

试一试

该功能使用 GitHub Copilot 来分析代码并生成重命名建议。你需要一个活跃的 GitHub Copilot 订阅。确保你正在使用 Visual Studio 预览1和更新 GitHub Copilot Chat Extension(如果它已经安装)。通过进入 Options >  GitHub >  Copilot chat , 并勾选“Enable rename suggestions”选项来启用该特性。

注意:此特性在 Visual Studio 17.9 Preview 1中可用。目前在预览2中有一个已知的问题。我们正在努力尽快解决这个问题。

您的反馈很重要

我们很想听听您对这个特性如何影响您的编码工作流程的想法。您的反馈将有助于我们继续改进所提供的重命名建议的功能和质量。请与我们分享您的想法!

我们感谢您花时间报告问题/建议,并希望您在使用 Visual Studio 时继续给我们反馈,告诉我们您喜欢什么以及我们可以改进什么。您的反馈对于帮助我们使 Visual Studio 成为最好的工具至关重要!您可以通过 开发者社区与我们分享反馈:通过报告任何错误或问题,或分享您对新功能或现有功能的改进的建议 。

您可以通过在 YouTube, Twitter, LinkedIn, Twitch 和 Microsoft Learn上关注我们与 Visual Studio 团队保持联系。

原文链接:https://devblogs.microsoft.com/visualstudio/ai-powered-rename-suggestions/

wmproxy

wmproxy
已用
Rust
实现
http/https
代理,
socks5
代理, 反向代理, 负载均衡, 静态文件服务器,
websocket
代理,四层TCP/UDP转发,内网穿透等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

设计目标

通过多层代理的代理结构,构建出属于自己的网络通道。

多层代理能做什么

多层代理(也称为IP多级代理)是一种网络代理技术

  • 它通过多个代理服务器来接收和发送数据包,从而隐藏真实IP地址。每个代理服务器都可以处理一个或多个网络请求,通过这种方式,可以在防火墙后面传输数据,同时隐藏真实IP地址和用户身份。这种技术通常用于企业网络和互联网上的安全传输。

  • 当前多层代理的工作原理是在应用层上进行代理传输,从而将原始IP地址和用户身份隐藏起来。在发送数据时,发送方的IP地址会被伪装成目标IP地址的某个IP地址,而接收方的IP地址则会被隐藏在多个IP地址中。这种层层转发网络流量的方式,使得最终目标服务器无法直接获取到请求的真实IP地址,从而增加了网络的安全性和匿名性。

但是请注意,使用多层代理也可能会导致网络连接速度变慢,因为数据需要通过多个代理服务器进行传输。同时如果代理服务器被攻击或出现故障,也可能会影响网络的稳定性和可靠性。因此在选择是否使用多层代理时,需要综合考虑其优缺点,并根据实际需求做出决策。

如何部署

通常了解一个程序如何运行最快的方式除了官方文档,另一种就是查找当前程序的help,通常
wmproxy --help
或者
wmproxy -h
就可以查询到帮助信息。此时我们是子命令代理,通过
wmproxy proxy -h
,可以查询控制消息

Usage: wmproxy.exe proxy [-s=ARG] [-b=ARG] [-c=ARG] [--flag=ARG] [-S=ARG] [--user=ARG] [--pass=ARG] [
--udp-bind=ARG] [--map-http-bind=ARG] [--map-https-bind=ARG] [--map-tcp-bind=ARG] [--map-proxy-bind=ARG
] [--map-cert=ARG] [--map-key=ARG] [--ts] [--tc] [--two-way-tls] [--domain=ARG] [--cert=ARG] [--key=ARG
] [--mappings=ARG]... [--control=ARG] [--disable-stdout] [--disable-control] [-v] [--default-level=ARG
]

代理类, 一个代理类启动一种类型的代理
    -s, --server-id=ARG       代理id
                              [default: 0]
    -b, --bind=ARG            代理绑定端口地址
    -c, --center-addr=ARG     中心代理绑定端口地址
        --flag=ARG            代理种类, 如http https socks5
    -S, --server=ARG          连接代理服务端地址
        --user=ARG            用于socks验证及中心服务器验证
        --pass=ARG            用于socks验证及中心服务器验证
        --udp-bind=ARG        udp的绑定地址
        --map-http-bind=ARG   内网http的映射地址
        --map-https-bind=ARG  内网https的映射地址
        --map-tcp-bind=ARG    内网tcp的映射地址
        --map-proxy-bind=ARG  内网代理的映射地址
        --map-cert=ARG        内网映射的证书cert
        --map-key=ARG         内网映射的证书key
        --ts                  连接服务端是否启用tls
        --tc                  接收客户端是否启用tls
        --two-way-tls         双向认证是否启用
        --domain=ARG          tls证书所用的域名
        --cert=ARG            公开的证书公钥文件
        --key=ARG             隐私的证书私钥文件
        --mappings=ARG

此时我们绑定端口分为两种

    -b, --bind=ARG            代理绑定端口地址
    -c, --center-addr=ARG     中心代理绑定端口地址
  • 其中
    -b
    是绑定来自于第三方代理客户端发起(http/https/socks)代理请求的信息
  • 其中
    -c
    是绑定来自于客户端与服务端建立的代理请求信息。是服务器内部间构建的信息通道。
  • 其中
    -S
    或者
    --server
    表示该程序需连接到代理服务端,将数据请求发送到服务器处理。
  • 其中
    --ts
    表示连接服务端时是否启用tls
  • 其中
    --tc
    表示接收客户端时是否需要tls,这两个需要正确配对并设置相应的
    --cert

    --key

以下流程图,展示数据的处理流程,其中代理B的角色可以是0个也可以是若干个。

sequenceDiagram
participant Client as 用户端
participant A as 代理端A<br/>监听-b 8090
participant B as 代理端B<br/>监听"-c 8091"
participant C as 代理端C<br/>监听"-c 8092"
B->>C: 主动连接127.0.0.1:8092<br/>建立内部通道
A->>B: 主动连接127.0.0.1:8091<br/>建立内部通道
Client->>A: 发起HTTP代理请求
A->>B: 构建内部通讯协议<br/>创建唯一id
B->>C: 构建代理A与C中转通道<br/>双向绑定中转数据
C->>C: 解析数据,并处理代理
C->>B: 返回代理数据
B->>A: A与C的中转通道
A->>Client: 解析数据转成代理请求发送
  • 代理C的启动命令:
wmproxy proxy -c :8092
  • 代理B的启动命令:
wmproxy proxy -c :8091 -S 127.0.0.1:8092
  • 代理A的启动命令:
wmproxy proxy -b :8090 -S 127.0.0.1:8091

通过如何我们就构建出了一条内部的代理通道,此时通过curl测试

curl.exe --proxy http://127.0.0.1:8090 https://www.baidu.com -I

可以得到以下正确返回200信息

HTTP/1.1 200 OK
Server: wenmeng
content-length: 0

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 277
Content-Type: text/html
Date: Fri, 26 Jan 2024 02:27:04 GMT
Etag: "575e1f71-115"
Last-Modified: Mon, 13 Jun 2016 02:50:25 GMT
Pragma: no-cache
Server: bfe/1.0.8.18

源码剥析

关于server_id

因为在构建tcp唯一映射的时候,是通过本地的自增id来进行处理。

pub fn calc_next_id(&mut self) -> u64 {
    let id = self.next_id;
    self.next_id = self.next_id.wrapping_add(2);
    Helper::calc_sock_map(self.option.server_id, id)
}

即当出口处理的服务器如果前端有多条客户端的时候存在冲突的可能的。就比如id均为2,那么服务端将无法正确处理是来自哪个客户端的数据,也无法进行相应的转发。所以此处我们引入server_id的概念,通过server_id与id进行组合运算,得出系统内的唯一id,保证数据没有冲突。

let mut map = HashMap::<u64, Sender<ProtFrame>>::new();
关于中心客户端
  • 源码:
    center_client
  • 类名:
    CenterClient
  • 功能:它负责将客户端的数据转化成服务器内部的通讯,并将内部的通讯转化成客户端的数据。
  • 构建:当仅它有
    -b
    监听第三方客户端的时候构建。
  • 核心函数:
async fn inner_serve<T>(
    option: &ProxyConfig,
    stream: T,
    sender: &mut Sender<ProtFrame>,
    receiver_work: &mut Receiver<(ProtCreate, Sender<ProtFrame>)>,
    receiver: &mut Receiver<ProtFrame>,
    mappings: &mut Vec<MappingConfig>,
) -> ProxyResult<()>
关于中心服务端
  • 源码:
    center_server
  • 类名:
    CenterServer
  • 功能:它负责将服务器内部的数据进行解析,并发起http代理的请求,此处他为出口
  • 构建:当仅他的目标上级服务器不存在时且
    -c
    参数被设置进行构建。即
    -S
    没有设置
  • 核心函数:
pub async fn inner_serve<T>(
    stream: T,
    option: ProxyConfig,
    sender: Sender<ProtFrame>,
    mut receiver: Receiver<ProtFrame>,
    mut receiver_work: Receiver<(ProtCreate, Sender<ProtFrame>)>,
    mappings: Arc<RwLock<Vec<MappingConfig>>>,
) -> ProxyResult<()>
关于中心转发服
  • 源码:
    center_trans
  • 类名:
    CenterTrans
  • 功能:它负责将来自代理客户端的请求转发给代理服务端
  • 构建:当且仅当
    -c
    被设置且
    -S
    被设置,即监听中心客户端又连接中心服务端,充当转发功能,由于不存在任何解析,性能高。
  • 核心函数:
pub async fn serve<T>(&mut self, mut stream: T) -> ProxyResult<()>
where
    T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
    let mut server = if self.tls_client.is_some() {
        let connector = TlsConnector::from(self.tls_client.clone().unwrap());
        let stream = HealthCheck::connect(&self.server).await?;
        // 这里的域名只为认证设置
        let domain = rustls::ServerName::try_from(
            &*self
                .domain
                .clone()
                .unwrap_or("soft.wm-proxy.com".to_string()),
        )
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid dnsname"))?;

        let outbound = connector.connect(domain, stream).await?;
        MaybeHttpsStream::Https(outbound)
    } else {
        let outbound = HealthCheck::connect(&self.server).await?;
        MaybeHttpsStream::Http(outbound)
    };

    tokio::spawn(async move {
        let _ = tokio::io::copy_bidirectional(&mut stream, &mut server).await;
    });
    Ok(())
}

小结

多层代理可以帮助我们在有限的情况下构建出更稳定的代理通道,可以更好的保护源站,也可以利用该方法给企业构建出稳定的内网通道。也可以在统一出口的情况下保护内网的数据。

点击
[关注]

[在看]

[点赞]
是对作者最大的支持

本文分享自华为云社区《
基于volcano实现节点真实负载感知调度
》,作者: 可以交个朋友。

背景

默认调度器调度器视某个节点的空闲可调度资源=节点可分配资源 - SUM(节点上已调度Pod们的request),当某个Pod处于pending状态待调度时,默认调度器根据Pod中指定的request值和各个节点的空闲可调度资源比较,如果某个节点空闲可调度资源 < pod的request值,则节点不可被调度,反之则可能被调度。

从这里可以看出,默认调度主要是依据各个pod创建时设置的request值,可能导致:

  • 业务实际负载需要的资源远大于创建时指定的request值,导致节点部署过密,影响业务运行稳定性;
  • 业务实际负载需要的资源小于创建时候指定的request值,导致节点部署稀疏,造成资源浪费

基于volcano节点真实负载感知调度方案介绍

默认调度器基于上述调度策略的主要原因是,k8s自己没有真实去获取节点真实资源消耗,导致无法实现更合理的节点的空闲可调度资源=节点可分配资源 - 节点真实资源使用。开源Prometheus可以获取到各个节点的真实负载情况,基于volcano调度插件的能力可以实现基于应用能够基于真实负载调度,在资源满足的情况下,Pod优先被调度至真实负载低的节点,集群各节点负载趋于均衡。

image.png

CCE集群开启负载感知调度

限制

已创建v1.21及以上版本的集群

已安装Volcano 1.11.14及以上版本的插件

已安装CCE云原生监控插件(kube-prometheus-stack),并选择server模式

开启负载感知调度

安装Volcano调度器、云原生监控插件(安装server模式,agent模式没有custom-metrics API)

image.png

集群通过Custom Metrics API提供资源指标,修改adapter-config的configMap,添加自定义指标采集规则。配置项与密钥->命名空间选择 “monitoring” ->找到user-adapter-config 点击 “更新”

image.png

编辑->添加新规则

image.png

- seriesQuery: '{__name__=~"node_cpu_seconds_total"}'resources: 
overrides:
instance:
resource: node
name:
matches: node_cpu_seconds_total
as: node_cpu_usage_avg
metricsQuery: avg_over_time((
1 - avg (irate(<<.Series>>{mode="idle"}[5m])) by (instance))[10m:30s])- seriesQuery: '{__name__=~"node_memory_MemTotal_bytes"}'resources:
overrides:
instance:
resource: node
name:
matches: node_memory_MemTotal_bytes
as: node_memory_usage_avg
metricsQuery: avg_over_time(((
1-node_memory_MemAvailable_bytes/<<.Series>>))[10m:30s])

CPU平均利用率采集规则

node_cpu_usage_avg: 表示节点的CPU平均利用率,该指标名不可修改。

metricsQuery: avg_over_time((1 - avg (irate(<<.Series>>{mode=“idle”}[5m])) by (instance))[10m:30s]):为节点CPU平均利用率的查询语句。当前metricsQuery表示查询所有节点最近10分钟的CPU平均利用率,如果希望调整平均值的计算周期,可以修改上述标红的10m。(30s是分辨率)

Memory平均利用率采集规则:

node_memory_usage_avg: 表示节点的Memory利用率,该指标名不可修改。

metricsQuery:avg_over_time(((1-node_memory_MemAvailable_bytes/<<.Series>>))[10m:30s]) 为节点Memory平均利用率的查询语句。

当前metricsQuery表示查询所有节点最近10分钟的Memory平均利用率,如果希望调整平均值的计算周期为,可以修改上述标红的10m。(30s是分辨率)

新部署metrics-api-server负载,使其加载user-adapter-config的最新配置

image.png

开启负载感知调度能力。配置中心->调度配置->默认调度器”volcano”->资源利用率优化调度->支持负载感知调度

image.png

image.png

说明:

负载感知调度根据CPU、Memory真实负载信息对节点进行打分排序,优先选择负载更低的节点参与调度。

如果我们更偏向于将负载调度到cpu真实负载低的节点,或内存真实负载低的节点,可以通过调整权重来影响节点打分,负载优先选择得分最高的节点参与调度

节点打分公式:

节点得分=负载感知策略权重 *((1 - CPU资源利用率) * CPU权重 + (1 - Memory资源利用率) * 内存权重)/(CPU权重 + 内存权重)

真实负载阈值,从CPU和内存两方面限制节点真实负载的水位,防止节点压力过高,真实负载阈值的生效方式分为“软约束“和“硬约束“

软约束:节点 CPU、内存真实负载达到阈值后,新的任务优先被分配至真实负载未达到阈值的节点,但是该节点依然允许调度。

硬约束:节点 CPU、内存真实负载达到阈值后,该节点不允许调度新的任务。

效果验证

环境准备

创建1个负载 cpu:request 0.1 limit 6,使其调度在 “192.168.64.81” 这个节点上,节点CPU request的分配率:42.48% 实际占用率 76.9%

image.png

image.png

创建1个负载 cpu:request 4 limit 4,使其调度在 “192.168.64.219” 这个节点上,节点CPU request的分配率:91.15% 实际占用率 1.4%

image.png

image.png

验证未开启负载感知调度时,新建负载的调度情况

创建1个负载,CPU request 0.1 limit 0.1 内存 request 100MiB limit 100MiB

image.png

查看工作负载的调度情况,发现负载调度到了cpu request分配率低,但是实际CPU占用率高的节点 “192.168.64.81” 上

image.png

再添加1个副本,仍然调度到"192.168.64.81"上

image.png

验证开启负载感知调度时,新建负载的调度情况

开启负载感知调度,CPU 真实负载阈值设置为70% 硬约束

image.png

创建1个负载,CPU request 0.1 limit 0.1 内存 request 100MiB limit 100MiB

image.png

信息填写完整后,点击右上角yaml创建,添加一行shcedulerName: volcano

image.png

查看工作负载调度情况,发现负载调度到了cpu request分配率高,但是实际CPU占用率低的节点 “192.168.64.219” 上

image.png

再添加1个副本,仍然调度在节点 “192.168.64.219” 上

image.png

点击关注,第一时间了解华为云新鲜技术~