当前位置: 首页 > news >正文

使用 Rust 实现的基础的List 和 Watch 机制

本文分享自天翼云开发者社区《使用 Rust 实现的基础的List 和 Watch 机制》。作者:l****n

使用 Rust 实现的基础的List 和 Watch 机制

介绍

在日常的开发过程中,有一个很重要的任务是能够通过Rust语言实现K8s中的各种生态组件,在这个过程中,既需要能过够了解K8S的工作原理也需要能够知道rust的语言特性。因此,在这个过程中有很多值得探讨的知识点。

在这里,第一步,我们将探索如何使用 Rust 实现一个类似于 Kubernetes 的 list 和 watch 机制。我们将通过 WebSocket 实现实时的消息推送,并使用一些关键的 Rust 异步编程模型来处理事件和连接管理。

我们首先默认大家能够了解rust语言的基本特性。下文中,将针对rust的知识点展开进行探讨。

目标

  • 理解 WebSocket 连接的建立和管理。
  • 学习如何通过 WebSocket 推送消息。
  • 掌握消息缓存和处理的实现方式。
  • 了解如何使用 Rust 实现一个高效的事件分发系统。
  • 理解K8S中的数据一致性保障方法
  • 了解本机制的不足,以及后续如何进行改进

理解问题

什么是 list 和 watch

  • List:列出当前所有资源的状态。
  • Watch:实时监控资源的变化,一旦有资源变化,就会立即通知客户端。

使用场景

  • 自动化运维:实时监控系统资源状态,触发自动化运维操作。
  • 应用监控:实时获取应用状态,及时处理异常,在很多的系统设计场景中,能够减少耦合。
  • K8S中的相应设计:K8S中,对相应资源的通知的基础即为list and watch机制。本人在学习K8S源码的第一步就是学习这一套设计架构。

分析问题

\当然,通过简单的代码仅仅通过http进行主动连接也可实现这个功能。但在目前阶段,我们希望能够设计一个高效的、稳定的、可扩展的list and watch体系,因此我们需要考虑以下几个关键问题。

关键问题

  1. 如何建立和管理 我们服务器和客户端的连接?通过什么方式进行?
  2. 如何实现高效的消息推送机制?
  3. 如何处理消息缓存和订阅管理?

技术选型

  • 语言:Rust
  • Web 框架:warp框架
  • WebSocket实现和框架:tokio-tungstenite、warp
  • 异步编程:tokio、管道机制

设计代码结构

针对以上这个需求,结合目前kunos-system的需求我们阐释如下

  • 有以下几个资源,Node、Task(Task是一个shell命令、镜像运行命令的载体)、Job(Task的上层资源,一个Job包含多个Task,类似于K8s中的replicaset)我们需要对这几个资源的状态进行推送。
  • 能够在服务器建立起来一个watch and list服务器,能够推送各种事件
  • 能够

组件设计

  1. Broker:管理 WebSocket 订阅者和事件分发。
    pub struct Broker<R: Resource + Clone + Serialize + Send + Sync  + 'static> {// 下游的订阅者列表,用于发送websocket信息subscribers: Arc<RwLock<HashMap<Topic, HashMap<Uuid, WsSender>>>>,// 事件的缓冲流event_sender: UnboundedSender<(Topic, WatchEvent<R>)>,
    }
    
  2. Watcher:对不同资源类型进行管理和操作。
    pub struct Watcher {// 为不同的事件建立不同的brokerpub node_broker: Arc<Broker<Node>>,pub task_broker: Arc<Broker<Task>>,pub job_broker: Arc<Broker<Job>>,pub exec_broker: Arc<Broker<TaskExecRequest>>,
    }
    
  3. WebSocket 客户端:与服务器交互,接收实时事件。

基本原理

websocket路由入口

let node_subscribe = warp::path!("watch" / "node").and(warp::ws()).map(move |ws: warp::ws::Ws| {let node_broker_clone = Arc::clone(&node_broker_clone);ws.on_upgrade(move |socket| async move {node_broker_clone.subscribe("node".to_string(), socket).await;})},
);
1. warp::path!("watch" / "node")

*这部分代码定义了一个路径过滤器,用于匹配路径 /watch/node 的 HTTP 请求。warp::path!是 Warp 框架提供的一个宏,用于简化路径定义。这里的"watch" / "node"表示请求路径必须是/watch/node` 才能匹配这个过滤器。

2. .and(warp::ws())

这一部分代码将路径过滤器与 WebSocket 协议过滤器组合起来。warp::ws() 过滤器会匹配 WebSocket 握手请求并提取一个 warp::ws::Ws 类型,表示 WebSocket 配置。这表示我们的这个路径将为一个websocket接口。

  • warp::ws() 过滤器用于匹配并提取 WebSocket 握手请求,确保该请求是 WebSocket 协议请求。
3. .map(move |ws: warp::ws::Ws| { ... })

.map 方法用于将前面的过滤器组合结果映射到一个新的处理逻辑中。这里的 move |ws: warp::ws::Ws| { ... } 是一个闭包,用于处理 WebSocket 请求。

  • move 关键字确保闭包捕获其环境中的所有变量的所有权,因为这些变量将在异步操作中使用。
  • ws: warp::ws::Ws 参数是从前面的 warp::ws() 过滤器中提取的 WebSocket 配置。
4. ws.on_upgrade(move |socket| async move { ... })

ws.on_upgrade 方法用于将 WebSocket 协议升级请求处理为 WebSocket 连接。它接受一个闭包作为参数,当 WebSocket 握手成功后,这个闭包会被调用。在官方定义中,这个方法主要用于自定义一个函数对建立后的websocket连接进行一定的操作,因此我们在这里将建立连接后一切操作,比如保持连接,发送信息等。

/// Finish the upgrade, passing a function to handle the `WebSocket`.
///
/// The passed function must return a `Future`.
pub fn on_upgrade<F, U>(self, func: F) -> impl Reply
whereF: FnOnce(WebSocket) -> U + Send + 'static,U: Future<Output = ()> + Send + 'static,
{WsReply {ws: self,on_upgrade: func,}
}
  • move |socket| async move { ... } 是一个异步闭包,它将在 WebSocket 连接成功升级后执行。
  • socket 参数表示已经升级的 WebSocket 连接。
5. node_broker_clone.subscribe("node".to_string(), socket).await;

在异步闭包内部,调用 node_broker_clone subscribe` 方法,将新的 WebSocket 连接订阅到节点(node)主题中。后续我们将展开讲解

  • "node".to_string() 将节点主题名称转换为字符串。
  • socket 参数表示当前的 WebSocket 连接。
  • await 关键字等待异步订阅操作完成。

websocket连接处理

上面说到,我们通过 ws.on_upgrade(move |socket| async move { ... })这个方法在连接建立之后进行处理,其中可以知道,我们处理的方法如下所示。

pub async fn subscribe(&self, topic: Topic, socket: warp::ws::WebSocket) {let (ws_sender, mut ws_receiver) = socket.split();let (tx, mut rx) = mpsc::unbounded_channel::<Message>();let subscriber_id = Uuid::new_v4();
​{let mut subs = self.subscribers.write().await;subs.entry(topic.clone()).or_default().insert(subscriber_id, tx);}
​let subscribers = Arc::clone(&self.subscribers);tokio::task::spawn(async move {while let Some(result) = ws_receiver.next().await {match result {Ok(message) => {// 处理有效的消息if message.is_text() {println!("Received message from client: {}",message.to_str().unwrap());}}Err(e) => {// 处理错误eprintln!("WebSocket error: {:?}", e);break;}}}println!("WebSocket connection closed");subscribers.write().await.get_mut(&topic).map(|subscribers| subscribers.remove(&subscriber_id));});
​tokio::task::spawn(async move {let mut sender = ws_sender;
​while let Some(msg) = rx.recv().await {let _ = sender.send(msg).await;}});}
  • websocket连接处理 let (ws_sender, mut ws_receiver) = socket.split();这里使用原生的代码,将已经建立起来的socket进行分割,因为websocket是双向连接,因此获得针对这个socket的发送端(ws_sender)和接收端(ws_receiver)。

  • 建立连接并保存

    let (tx, mut rx) = mpsc::unbounded_channel::<Message>();
    let subscriber_id = Uuid::new_v4();
    ​
    {let mut subs = self.subscribers.write().await;subs.entry(topic.clone()).or_default().insert(subscriber_id, tx);
    }
    

    在这里,我们建立了个一个管道,并将subscriber的信息进行保存,这里的 mpsc::unbounded_channel::<Message>();类似于golang中的channel,他会生成一个发送者、一个接收者,当往发送者发送消息的时候,接收者会受到该消息并进行一定处理。因此我们将subscriber的发送者(tx)保存至内存里。

  • 建立消息发送机制

    tokio::task::spawn(async move {let mut sender = ws_sender;
    ​while let Some(msg) = rx.recv().await {let _ = sender.send(msg).await;}});
    

    这个就是很简单了,通过如果rx收到了消息,则向websocket的subscriber进行发送。该任务是以新协程任务的方式启动的,在后台持续运行

  • 建立websocket连接保活机制

    let subscribers = Arc::clone(&self.subscribers);tokio::task::spawn(async move {while let Some(result) = ws_receiver.next().await {match result {Ok(message) => {// 处理有效的消息if message.is_text() {println!("Received message from client: {}",message.to_str().unwrap());}}Err(e) => {// 处理错误eprintln!("WebSocket error: {:?}", e);break;}}}println!("WebSocket connection closed");subscribers.write().await.get_mut(&topic).map(|subscribers| subscribers.remove(&subscriber_id));});
    

    这里我们仍然在后台启动一个守护协程,用于保活websocket连接,一旦发生了连接失效,则注销消息发送机制,删除subscribers缓存中的订阅者。

消息推送机制

  • 事件推送
    事件推送时候将允许调用相关事件的推送地址,向推送端发送消息

    pub async fn produce_node_event(&self, event: WatchEvent<Node>) {self.node_broker.produce("node".to_string(), event).await;}pub async fn produce_task_event(&self, event: WatchEvent<Task>) {self.task_broker.produce("task".to_string(), event).await;}pub async fn produce_job_event(&self, event: WatchEvent<Job>) {self.job_broker.produce("job".to_string(), event).await;}
    

    当收到消息的时候,不直接处理消息,而是将放入缓存队列中(一个消息无界流)

    pub async fn produce(&self, topic: Topic, event: WatchEvent<R>) {if let Err(e) = self.event_sender.send((topic.clone(), event.clone())) {eprintln!("Failed to send event: {}", e);}}
    
  • 事件分发
    同样的。将启动一个协程,用于从和event_sender对应的event_receiver中获取消息,推送给订阅者。

    • 获取订阅者的列表并依次发送
    • 如果发现发送失败,则将这个订阅者从缓存中删除
    fn start_event_dispatcher(broker: Arc<Self>, mut event_receiver: UnboundedReceiver<(Topic, WatchEvent<R>)>) {tokio::spawn(async move {while let Some((topic, event)) = event_receiver.recv().await {let event_json = serde_json::to_string(&event).unwrap();let subscribers_list;{let subscribers = broker.subscribers.read().await;subscribers_list = subscribers.get(&topic).cloned().unwrap_or_default();}let mut invalid_subscribers = vec![];for (id, ws_sender) in subscribers_list {if ws_sender.send(warp::ws::Message::text(event_json.clone())).is_err() {invalid_subscribers.push(id);}}if !invalid_subscribers.is_empty() {let mut subscribers = broker.subscribers.write().await;if let Some(subscribers) = subscribers.get_mut(&topic) {for id in invalid_subscribers {subscribers.remove(&id);}}}}});}
    

客户端

客户端的代码就是建立起来一个订阅者关注相关事件的动态。在相应的代码中,可以使用该方法。本方法最终返回的是一个无界流 Stream<Item = WatchEvent<R>>,用于得到服务器推送过来的事件类型

pub async fn list_and_watch<R>(api_client: &ApiClient, resource_name: &str) -> impl Stream<Item = WatchEvent<R>>
whereR: Resource + Clone + DeserializeOwned + 'static + Send,
{// 先通过 HTTP 获取资源列表let initial_resources = get_resource_list::<R>(api_client).await;// 解析要连接WebSocket服务器的URLlet url = Url::parse(&*format!("{}/{}", api_client.watch_url, resource_name)).expect("Invalid URL");// 连接到WebSocket服务器println!("watch url is {}", url);let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");println!("Watch client connected");let (mut write, read) = ws_stream.split();let (tx, rx) = mpsc::unbounded_channel();// 先发送初始资源列表match initial_resources {Ok(res) => tx.send(WatchEvent::Restarted(res)).unwrap(),Err(e) => eprintln!("list resource failed, {}", e),};// 将 WebSocket 读流转换为消息事件流tokio::spawn(async move {read.for_each(|message| async {match message {Ok(msg) => {if msg.is_text() {let text = msg.to_text().unwrap();match serde_json::from_str::<WatchEvent<R>>(text) {Ok(event) => {tx.send(event).unwrap();}Err(e) => {eprintln!("Failed to parse message: {:?}", e);}}}}Err(e) => {eprintln!("Error receiving message: {:?}", e);}}}).await;});// 保持 WebSocket 连接活跃tokio::spawn(async move {loop {if let Err(e) = write.send(WatchMessage::Text(String::new())).await {eprintln!("Error sending ping: {:?}", e);break;}tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;}});tokio_stream::wrappers::UnboundedReceiverStream::new(rx)
}

使用验证

不足分析

经过上面的介绍,我们可以看到这个基础的list and watch机制能够正确运行。但是,和K8S、ETCD中广泛使用的list and watch相比仍然缺少一个机制来保证list和watch的一致性。

请考虑这样一种情况我们的服务器中会源源不断地产生数据d1,d2,d3,...,dn。当我们使用list时候,能够感知到d1,d2,d3,此时我们完成list,开始建立watch。加入在开始建立watch这个阶段,即使可能是几毫秒的时间但服务器生成了d4,而在watch建立起来后,只能接收到d5,d6,...。这就导致了数据的遗失。

在 Kubernetes 中,List 和 Watch 操作结合使用时,需要使用一个revision机制以确保资源的变更不会被遗漏。理解 List 和 Watch 操作时 revision(即 resourceVersion)的具体含义和管理方式对于保证一致性至关重要。revision的存在有着如下的意义:

  1. 数据版本控制revision 是 Etcd 的全局递增计数器,用于标识数据的当前版本。当进行数据的修改、更新操作时候,revision会+1
  2. 一致性视图:确保返回的数据是一致的快照视图,表示在该 revision 之前的所有操作都已完成。

revision 与 List 和 Watch 的关系

  1. List 操作
    • 返回资源列表和当前的全局 revision,作为 resourceVersion
    • 确保获取到的资源是该 revision 时刻的一致视图。
  2. Watch 操作
    • 使用 List 操作返回的resourceVersion` 作为起点。
    • 从该 resourceVersion 开始监听资源的变化,确保在List Watch` 之间的变更不会丢失。

List 操作的 revision

当进行 List 操作时,Kubernetes API Server 从 Etcd 获取当前资源的状态及其resourceVersion 。这个 resourceVersion 是 Etcd 当前的全局revision 。它表示在此 revision 之前的所有操作都已经完成,并确保返回的数据是这个revision` 时刻的一致视图。

Watch 操作的 revision

Watch 操作使用 List 操作返回的 resourceVersion 作为起点,从该版本开始监听资源的变化。这确保了从 List 到 Watch 之间的变更不会被遗漏。

示例流程

  1. List 操作
    • API Server 从 Etcd 获取指定资源的当前状态。
    • Etcd 返回包含所有资源对象的列表和一个全局 revision ,这个 revision 将作为resourceVersion`。
  2. Watch 操作
    • API Server 使用 List 操作返回的 resourceVersion(revision) 作为起点,开始监听资源的变化。
    • Etcd 返回从指定 revision` 开始的所有变更事件。

总结

  • revision:标识数据版本,确保数据一致性。
  • List 和 WatchList 获取资源和 revisionWatch 从该 revision 开始监听变化,确保变更的连续性和一致性。

相关文章:

使用 Rust 实现的基础的List 和 Watch 机制

本文分享自天翼云开发者社区《使用 Rust 实现的基础的List 和 Watch 机制》。作者:l****n 使用 Rust 实现的基础的List 和 Watch 机制 介绍 在日常的开发过程中,有一个很重要的任务是能够通过Rust语言实现K8s中的各种生态组件,在这个过程中,既需要能过够了解K8S的工作原理也…...

flask下的MySQL增查配置

flask下的MySQL增删配置 添加数据 @app.route(/add) def add_data():u = UserInfo()new_user1 = UserInfo(nickname=flask_test1, mobile=13888888888, signature=理想, create_time=datetime.now(), role_id=1)new_user2 = UserInfo(nickname=flask_test2, mobile=13999999999…...

解码C语言指针

一、指针的定义与本质 1. 指针是什么? 指针是一种 存储变量内存地址 的特殊变量。所有数据存储在内存中,每个内存单元都有唯一地址(编号),指针通过记录地址实现对数据的间接访问。 2. 指针的核心作用直接操作内存:动态内存分配、硬件编程等。 提高效率:传递大对象时避免…...

windows下Qt调用fftw库

环境:Windows 11 Qt:6.8.3 程序中需要用到fftw库来进行傅里叶变换,通过网上的资料,配置了很久一直没成功,后来发下还是没有配置正确,最后终于成功,顺便记录一下 1.下载fftw3.3.5 http://www.fftw.org/install/windows.html,根据自已编译器版本下载32位或64位,我的Qt …...

AT_agc056_c [AGC056C] 01 Balanced

不难设 \(d_i\) 为前缀 \(i\) 中 \(0/1\) 数量的差值,显然有两个限制:\(d_{l - 1} = d_r\) \(|d_{i - 1} - d_i| \le 1\)可以差分约束,事实上,直接跑差分约束就可以得到字典序最小的构造了,这也算本题的价值之一。...

模拟费用流(s ver.)

1或许会在我彻底学会模拟费用流后同步发表于洛谷,希望退役前能等到彻底学会的那一天。推荐阅读:网络流——Alex_Wei 模拟费用流一般通过题目性质来快速找到增广路,进而降低单次增广的时间复杂度,不太能降低增广轮数。 消圈定理:一轮增广结束后,若残量网络上无负环,则当前…...

自推流SRS6.0配置相关

前提条件: 开通 1935、1985、8080 、8000(UDP)、 1900 、 8443端口;后面2个是开通https必须配置的。mkdir -p /data/www (本人习惯) 下载最新版 srs: git clone -b develop https://gitee.com/ossrs/srs.gitcd /data/www/srs/trunk ./configure make至此,srs安装成功。…...

火山引擎多模态数据湖:基于 Daft 与 Lance,构筑 AI 时代数据湖新范式

在 AI 技术飞速发展的当下,数据作为 AI 的 “燃料”,其形态与处理方式正发生深刻变革。本篇文章来自火山引擎LAS团队琚克俭在“2025AICon 全球人工智能开发与应用大会”分享,主要围绕 “AI 场景下多模态数据处理” 主题,介绍LAS团队基于 Daft+ Lance打造的多模态数据湖方案…...

doris窗口函数 LAG()(取上一条)和 LEAD()(取下一条)函数

在 Apache Doris 中,若需实现 “相邻数据间取上一条 / 下一条数据的字段值”,核心是利用 窗口函数(Window Function) 中的 LAG()(取上一条)和 LEAD()(取下一条)函数。这两个函数专门用于在有序的数据集内,获取当前行相邻行的指定字段值,无需手动关联表,效率更高。场…...

debmirror工具

安装mtli:~# apt install -y debmirror 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 将会同时安装下列软件: ed liblockfile-simple-perl liblog-agent-perl 下列【新】软件包将被安装: debmirror ed liblockfile-simple-perl liblog-a…...

centos7卸载openjdk-java11

[root@localhost ~]# rpm -qa|grep -i openjdk java-11-openjdk-headless-11.0.23.0.9-2.el7_9.x86_64 java-11-openjdk-devel-11.0.23.0.9-2.el7_9.x86_64 java-11-openjdk-11.0.23.0.9-2.el7_9.x86_64# 卸载命令, yum remove 会同时卸下该包及其不再被其它已安装包依赖的包。…...

jenkins的安装和配置

windows 安装 jenkins 自动化构建部署至linux服务器上 一、环境准备1、git安装环境 参考链接 https://www.cnblogs.com/yuarvin/p/12500038.html 2、maven安装环境,包括jdk环境安装 参考链接 https://www.cnblogs.com/yuarvin/p/7837963.html 3、Jenkins 安装环境 参考链接 ht…...

深入解析:【Day 52 】Linux-Jenkins

深入解析:【Day 52 】Linux-Jenkinspre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important;…...

本土开发者如何选择代码管理工具?Gitee与GitHub深度对比解析

本土开发者如何选择代码管理工具?Gitee与GitHub深度对比解析 在数字化转型浪潮下,代码管理工具已成为开发者日常工作的必备基础设施。面对国内外众多选择,新手开发者往往陷入选择困难。本文将从本土化开发视角,深入剖析主流代码管理工具的差异化优势,为开发者提供科学的选…...

MES系统核心组件

核心总览 在半导体工厂中,MES(Manufacturing Execution System,制造执行系统) 是最高层的指挥中枢,它负责管理生产线上从投料开始到成品产出的全部作业流程。而其他各种“XX Server”则是MES这个大脑指挥下的专业功能模块,负责处理特定类型的任务和数据。 各服务器功能详…...

易基因:多组学整合分析揭示DNA甲基化与基因组改变在肿瘤进化中的协同驱动机制|Nat Genet/IF29重磅

大家好,这里是专注表观组学十余年,领跑多组学科研服务的易基因。 近日,伦敦大学学院癌症研究所Nnennaya Kanu和弗朗西斯克里克研究所Peter Van Loo团队合作在国际遗传学Top期刊《自然遗传学》(Nature Genetics)发表题为“DNA methylation cooperates with genomic alterat…...

AI 视频模型大比拼(2025年7月版):价格、效果与分辨率

AI 视频模型大比拼(2025年7月版):价格、效果与分辨率pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", mon…...

为什么芯片行业需要私有化部署软件?

在芯片研发过程中,每天产生的大量设计数据、测试结果和知识产权,构成了企业的核心竞争优势。一旦这些数据泄露,不仅可能导致数百万美元的研发投入付诸东流,更可能让企业在全球竞争中失去领先地位。 近年来,芯片行业面临的数据安全挑战日益严峻。根据行业报告,2024年芯片设…...

C++ std::string

C++ 标准库中的 std::string 是处理字符串的核心类,封装了字符串的存储、管理和操作,相比 C 风格的 char* 更安全、易用。 1、基本概念 1.1 基本特性 std::string 定义在 <string> 头文件中(属于 std 命名空间),本质是对动态字符数组的封装动态大小:自动扩容,无需…...

MathType数学公式编辑器v7.9.1

MathType全球最受欢迎的专业数学公式编辑器工具软件,可视化公式编辑器轻松创建数学方程式和化学公式。兼容Office Word、PowerPoint、Pages、Keynote、Numbers 等700多种办公软件,用于编辑数学试卷、书籍、报刊、论文、幻灯演示等文档轻松编写各种复杂的物理公式、化学方程式…...

git常见冲突场景及解决办法 - 指南

git常见冲突场景及解决办法 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; f…...

有关字节的基础知识

什么是字节位(bit):是计算机 内部数据 存储的最小单位,11001100是一个八位二进制数。字节(byte):是计算机中 数据处理 的基本单位,习惯上用大写B来表示。字符:是指计算机中使用的字母、数字、字和符号 1bit表示一位 1Byte表示一个字节 1B=8b 1024B=1KB 1024KB=1M 102…...

strip去符号前后对比

strip去符号前后对比 strip 是 binutils 中用于给二进制文件(可执行程序、静态库、动态库)去符号信息的工具。它只修改符号表,不碰指令内容,因此去除符号后的二进制文件中,各部分指令和原来是完全一一对应的,各section大小和偏移也不变,各符号的偏移也不变,只是原本的指…...

2025 ICPC网络赛第一场 L cover

给一个长度为 \(n\) 的序列 \(\{a_n\}\) 和 \(m\) 个操作,其中第 \(i\) 个操作是把区间 \([l_i,r_i]\) 都赋值为 \(c_i\)。 现在按顺序遍历每个操作,每个操作可执行可不执行。 最大化序列的颜色段数,即 \(1+\sum\limits_{i=2}^n[a_{i-1}\not=a_i]\)。 \(1\leq a_i,c_i\leq n…...

文件自动同步软件用哪个好,高效选择指南

内容概要 在数字化办公日益普及的今天,文件自动同步软件已成为提升团队协作效率的重要工具。面对市场上琳琅满目的选择,“文件自动同步软件用哪个好”成为了许多企业IT管理者和个人用户共同关注的问题。本指南将深入剖析各类文件自动同步软件的核心优势,特别推荐如Ftrans FT…...

【初赛】指针 - Slayer

指针的性质是理解其行为和使用方式的核心,主要包括以下几个方面: 1. 指针是存储地址的变量 指针的本质是一个变量,但其存储的不是数据本身,而是另一个变量(或内存单元)的内存地址。例如:int a = 10; int* p = &a; 中,p 存储的是 a 的内存地址(如 0x7ffd6b6a45c4)…...

国产化FPGA-2050-基于JFMK50T4(XC7A50T)的核心板

基于JFMK50T4(XC7A50T)的核心板(IEB-PS-3051-邮票孔) 一、核心板概述板卡基于JFMK50T4国产化FPGA芯片,设计的一款工业级核心板,板卡集成主芯片、电源、DDR、配置芯片,大大减轻客户的扩展开发困难。丰富的IO和4个GTP,让用户轻易设计PCIe的数据卡,AD卡,结合本公司国产…...

hbase学习2

一、表管理操作创建表 hbase create 表名, 列族1, 列族2, ...示例:创建student表,有info和score两个列族 create student, info, score 2. 查看所有表 hbase list 3. 查看表描述 hbase describe 表名 describe student 4. 检查表是否存在 hbase exists 表名 5. 禁用/启用表 h…...

基于Python+Vue开发的健身房管理系统源码+运行步骤

项目简介该项目是基于Python+Vue开发的健身房管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Python的健身房管理系统项目,大学生可以在实践中学习和提升…...

2025年纷享销客生态伙伴大会无锡站圆满举办!

近日,2025年纷享销客生态伙伴大会(无锡站)成功举办。大会以“智享未来,领创 CRM 新纪元”为主题,吸引了众多生态伙伴齐聚一堂,共话 CRM 行业新趋势,共探 AI 赋能下的客户经营新范式。一、数智化浪潮下的CRM变革与机遇<纷享销客经营副总裁 张睿> 纷享销客经营副总…...

英语_阅读_digital technology_待读

These days, digital technology is everywhere in our lives.如今,数字科技无处不在地出现在我们的生活中。 From smartphones to tablets, we use digital devices every day at school, at home and when hanging out with friends.从智能手机到平板电脑,我们每天在学校、…...

达梦 两个bug json 导致数据库crash 和 优化器解析or 导致结果不一样

##sample1 无法解析 json 数据库直接crash. 分析函数,定位到BUG 升级数据库软件,问题得到规避 ########sample 2 优化器解析or 导致结果不一样--测试1 原有的查询顺序,查到空行。 SQL> SELECT coltablename,DONO,colupdateable,colupdateableFROM SEBMDEV.DATAOBJECT…...

MySQL迁移至GreatSQL后,timestamp字段插入报错解析

MySQL迁移至GreatSQL后,timestamp字段插入报错解析 背景描述 某业务系统进行国产化适配,将MySQL的数据迁移到 GreatSQL 后,执行 INSERT INTO ,update_time传参为空时报错,报错信息为:ERROR 1048 (23000): Column update_time cannot be null ,而原来旧的MySQL环境中没有这…...

2025年文件摆渡系统哪个品牌好推荐

内容概要 在寻找文件摆渡系统的过程中,企业往往关心哪个品牌能够更好地满足其安全、高效的数据交换需求。2025年,市场上涌现出众多品牌,但“文件摆渡系统哪个品牌好”这一问题,不少企业给出了共同的答案——Ftrans Ferry跨网文件安全交换系统。该系统凭借其全面的安全防护、…...

VU9P板卡设计方案:565-基于VU9P的32@ SFP28+4@ QSFP28路光纤交换板卡

、板卡概述 板卡基于Xilinx FPGA VU9P 设计的一款32路SFP28+4路QSFP28的光纤交换板卡,用于以太网的交换功能的验证。 二、板卡原理框图三、板卡主要性能 ● 主芯片:选用 XCVU9P-2FLGB2104I(702, 76)47.5*47.5 ● 32路SFP28,支持1G、10G以太网协议; 1G,10G 自动适配。 ●…...

Python中使用列表、map和filter函数配合lambda表达式来操作集合

在 Python 中,map()、filter() 和 lambda 表达式是处理集合(如列表)的常用工具,可以快速实现 转换 和 过滤 操作。以下是具体示例:1. map() 函数:对列表元素进行转换 作用: 对列表的每个元素应用一个函数,返回转换后的元素组成的迭代器。 语法: map(function, iterabl…...

大模型decoder中权重矩阵的理解 - 实践

大模型decoder中权重矩阵的理解 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !importan…...

文件安全外发平台用哪个,最佳选择是什么?

内容概要 在探讨文件安全外发平台用哪个这一话题时,我们首先要对文件安全外发平台有一个全面的了解。这类平台主要用于确保企业文件在外部传输过程中的安全性,防止数据泄露。市面上存在多种文件安全外发平台,它们各有千秋。而要说到安全管控强大且稳定传输大文件的平台,“F…...

【初赛】数 - Slayer

0x3f3f3f3f = 1061109567 0x7f7f7f7f = 2139062143 0x7fffffff = 2147483647 0xffffffff = 4294967295 0x1fffffff = 536870911 0x1f1f1f1f = 522133279...

http连接(webFlux vs tomcat)

HTTP连接的最大数量不是一个固定的值,它取决于一个由硬件资源、操作系统配置、网络栈、以及应用程序本身共同构成的复杂系统。 简单来说:在一台配置良好的现代服务器上,使用异步非阻塞模型(如WebFlux),支持超过100万甚至更多的并发HTTP连接在理论上是可行的。 而对于传统…...

英语_阅读_Generative AI_待读

Artificial Intelligence (AI) has become part of our everyday life.人工智能(AI)已经成为我们日常生活的一部分。 It makes our smart devices smarter.它让我们的智能设备变得更聪明。 You might have already used some AI programs at school.你可能已经在学校使用过一…...

P8500 [NOI2022] 冒泡排序 题解

Description 最近,小 Z 对冒泡排序产生了浓厚的兴趣。 下面是冒泡排序的伪代码: 输入: 一个长度为 n 的序列 a[1...n] 输出: a 从小到大排序后的结果 for i = 1 to n do:for j = 1 to n - 1 doif (a[j] > a[j + 1])交换 a[j] 与 a[j + 1] 的值冒泡排序的交换次数被定义为在…...

【初赛】链表 - Slayer

链表性质知识点总结 链表是一种线性数据结构,其核心特点是数据元素(称为 “节点”)通过指针或引用连接,而非像数组那样存储在连续的内存空间中。这种结构决定了它与数组截然不同的性质,适用于频繁插入 / 删除、内存动态分配的场景。 一、链表的核心定义与结构基本构成链表…...

纷享销客CRM系统自定义APL代码破解企业深度定制难题

在许多中大型企业,尤其是央企、金融、高科技等行业,对 CRM 系统提出了更为复杂的业务流程定制需求。尽管零代码、低代码配置工具有一定的灵活性,但在面对高度复杂、深度融合业务逻辑的安全机制或特殊流程时,仍显乏力。 为此,纷享销客提供了服务端代码级定制能力,通过自定…...

第2章 zynq开发板FSBL的生成和NAND烧录

前言 由于本人较懒,记录主要是过程,由于zynq的比stm32做的人少很多,资料也少很多,我会简要介绍原理,操作流程主要由图片加少量文字组成,每一章都是在之前的章节基础上做的一、新建FSBL工程 打开vivado,打开SDK打开后会自动根据之前生成的HDF自动生成硬件平台新建一个FSB…...

工具大全

<!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><title>工具大全</title><style>/*全…...

RocketMQ vs kafka

目录背景和价值1. 更激进的“零拷贝”技术2. 更简洁的存储模型3. 更“粗糙”但高效的批处理4. 权衡取舍的可靠性保证对比总结参考资料 背景和价值 你这个问题非常好,直击了两者设计哲学的核心差异。 简单来说,Kafka 更快,并非因为它的代码效率绝对更高,而是因为它的设计目标…...

JL-32 土壤速测仪 手持便携 大容量 多参数可同时监测

JL-32 土壤速测仪 手持便携 大容量 多参数可同时监测产品概述 土壤速测仪是一款携带方便,操作简单,集采集与存储于一体的可移动式观测仪器。由手持式速测主机、土壤类传感器、USB数据线、电源适配器、便携式手提箱等部分组成。速测仪主机可通过集线器接入不同类型的传感器,互…...

直播录制神器!一款多平台直播流自动录制客户端!

StreamCap —— 一个基于 FFmpeg 和 StreamGet 的多平台直播流录制客户端,覆盖 40+ 国内外主流直播平台,支持批量录制、循环监控、定时监控和自动转码等功能。大家好,我是 Java陈序员。 现如今,观看直播已成为日常生活中的一种娱乐消遣方式,但常常由于一些不可抗的原因错过…...

101.计组--二章

101.计组--二章数据的表示和运算 "自六月份另一个学校毕业 已经有拖三个多月的计组学习 当时其实已经已有一些学习 仅仅差了一节内容结束 也确实因为这个复杂的运算各类东西 言归正传 新的学校 新的学习 开始总结"先看一下总的还是分为三大块 三步走 一.数制 编码 先…...