FPGA实现CNN卷积层:高效窗口生成模块设计与验证
我最近在从事一项很有意思的项目,我想在PFGA上部署CNN并实现手写图片的识别。而本篇文章,是我迈出的第一步。具体代码已发布在github上
模块介绍
卷积神经网络(CNN)可以分为卷积层、池化层、激活层、全链接层结构,本篇要实现的,就是CNN的卷积层中的window窗。
在卷积过程中,最复杂的就是卷积运算,也就是Filter和图片(输入)相乘然后在相加的这一步骤。
我此处的构想就是将其卷积这个步骤进行拆分:加窗、载入权重、卷积运算。因而对应3个模块,而此处实现的就是加窗这个模块。而他主要负责的功能就是:提取输入图片中的数据,生成对应的窗口。 如上图所示,对x[:,:,0]图片进行窗口提起,提取的第一个窗口(左上角第一个)就是
[ 0 0 0 0 0 1 0 0 1 ] \begin{bmatrix}0&0&0\\0&0&1\\0&0&1\end{bmatrix} 000000011
代码
- 可配置参数、输入和输出定义
STRIDE为窗口滑动的步长,KERNEL_SIZE对应输入卷积核的大小,PADDING 为补充的长度
pixel_in 为输出的图片数据,frame_start 为图片开始输入的标志,pixel_valid为输入有效标志
window_out是图片展成一维的窗口数据
module window #(parameter DATA_WIDTH = 16, // Width of each pixel dataparameter IMG_WIDTH = 32, // Width of input imageparameter IMG_HEIGHT = 32, // Height of input imageparameter KERNEL_SIZE = 3, // Size of convolution window (square)parameter STRIDE = 1, // Stride of convolutionparameter PADDING = (KERNEL_SIZE - 1) / 2 // Padding size calculated for SAME mode
)
(input wire clk, // Clock signalinput wire rst_n, // Active low resetinput wire [DATA_WIDTH-1:0] pixel_in, // Input pixel datainput wire pixel_valid, // Input pixel valid signalinput wire frame_start, // Start of new frame signaloutput reg [KERNEL_SIZE*KERNEL_SIZE*DATA_WIDTH-1:0] window_out, // Flattened window outputoutput reg window_valid // Window data valid
);
- 内部信号定义
-
输入的图片数据是一个一个输入的,用x_pos和y_pos 来记录当前pixel位于图片中的位置
-
窗口在图片上滑动,用x_window,y_window用来判断窗口目前的位置
-
line_Buffer缓存输入的数据,同时进行padding操作, 形成数据窗口,而window_buffer 在line_buffer上进行滑动,形成窗口
-
状态机,分为三个状态 IDLE, LOAD,PROCESS, 分别对应空闲,载入(开始载入数据),处理(形成window)
// Internal signals
reg [5:0] x_pos, y_pos; // Current input pixel position
reg [5:0] x_window, y_window; // Window center position
reg [DATA_WIDTH-1:0] line_buffer [0:KERNEL_SIZE][0:IMG_WIDTH+2*PADDING-1]; // Line buffer
reg [DATA_WIDTH-1:0] window_buffer [0:KERNEL_SIZE-1][0:KERNEL_SIZE-1]; // Window buffer
reg signed [6:0] src_y, src_x; // Temporary variables for coordinate calculation// State machine
reg [1:0] current_state, next_state;
localparam IDLE = 2'b00, LOAD = 2'b01, PROCESS = 2'b10;// Loop variables
integer i, j, k;
- 状态的赋值以及跳转
-
当接收到frame_start信号(图片开始输入),状态从空闲进入到LOAD状态;
-
当目前的图片数据可以已经足够,可以用来生成稳定的输出窗口时,进入到PROCESS状态
-
当目前滑窗口提取完对应数据窗口后,回到IDLE状态
注:y_pos从0到KERNEL_SIZE-1时,已经有了KERNEL_SIZE行数据了,可以进入窗口数据提取阶段;实际上可以更早进入,因为存在Padding。当y_pos=KERNEL_SIZE-Padding-1的时候,就可以进入了
// FSM state transitions
always @(posedge clk or negedge rst_n) beginif (!rst_n) current_state <= IDLE;else current_state <= next_state;
endalways @(*) begincase (current_state)IDLE: next_state = frame_start ? LOAD : IDLE;LOAD: next_state = (y_pos >= KERNEL_SIZE-1) ? PROCESS : LOAD;PROCESS: next_state = (y_window >= IMG_HEIGHT && x_window == 0) ? IDLE : PROCESS;default: next_state = IDLE;endcase
end
- 状态执行
推荐使用拆分的方法,把一个状态执行的大always块,分成很多子always块。
a. 输入图片数据位置捕获
-
当前状态为IDLE,图片即将开始输入时,将定位信号复原
-
当前状态不为IDLE, 同时输入有效,那么坐标根据情况自增
// Input pixel position tracking
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginx_pos <= 0;y_pos <= 0;end else if (current_state == IDLE && frame_start) beginx_pos <= 0;y_pos <= 0;end else if (pixel_valid && current_state != IDLE) beginif (x_pos == IMG_WIDTH-1) beginx_pos <= 0;y_pos <= y_pos + 1;end else beginx_pos <= x_pos + 1;endend
end
b. Line_Buffer 的缓冲
- 每次开启新的一行的数据,对Line_Buffer 全部复位
- 然后对对应的位置进行实际数据的填充
// Line buffer management
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginfor (i = 0; i <= KERNEL_SIZE; i = i + 1)for (j = 0; j < IMG_WIDTH + 2*PADDING; j = j + 1)line_buffer[i][j] <= 0;end else if (pixel_valid && current_state != IDLE) beginif (x_pos == 0) begin// Clear the line buffer row at the start of each new linefor (k = 0; k < IMG_WIDTH + 2*PADDING; k = k + 1)line_buffer[y_pos % (KERNEL_SIZE + 1)][k] <= 0;endline_buffer[y_pos % (KERNEL_SIZE + 1)][x_pos + PADDING] <= pixel_in;end
end
c .Window position tracking
- 复位、一帧图片的开始或即将进入PROCESS状态,对window记位进行复位
- 当前状态位PROCESS状态,同时没有超过当前图片的高度时,对window的位置进行对应的变化
// Window position tracking
always @(posedge clk or negedge rst_n) beginif (!rst_n || frame_start || (current_state == LOAD && next_state == PROCESS)) beginx_window <= 0;y_window <= 0;end else if (current_state == PROCESS && y_window < IMG_HEIGHT) beginif (x_window + STRIDE >= IMG_WIDTH) beginx_window <= 0;y_window <= y_window + STRIDE;end else beginx_window <= x_window + STRIDE;endend
end
d. window_buffer的处理
// Window generation and output
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginwindow_valid <= 0;for (i = 0; i < KERNEL_SIZE; i = i + 1)for (j = 0; j < KERNEL_SIZE; j = j + 1)window_buffer[i][j] <= 0;end else beginwindow_valid <= 0; // Defaultif (current_state == PROCESS && x_window < IMG_WIDTH && y_window < IMG_HEIGHT && y_window + (KERNEL_SIZE>>1) <= y_pos) begin// Generate windowfor (i = 0; i < KERNEL_SIZE; i = i + 1) beginfor (j = 0; j < KERNEL_SIZE; j = j + 1) beginsrc_y = y_window + i - (KERNEL_SIZE>>1);src_x = x_window + j - (KERNEL_SIZE>>1);if (src_y >= 0 && src_y < IMG_HEIGHT && src_x >= 0 && src_x < IMG_WIDTH) beginwindow_buffer[i][j] <= line_buffer[src_y % (KERNEL_SIZE + 1)][src_x + PADDING];end else beginwindow_buffer[i][j] <= 0; // Paddingendendendwindow_valid <= 1;end end
end
当window坐标没有超过图片大小,确保可以生成窗口时,获取生成。对KERNEL_SIZE>>1,等价于KERNEL_SIZE/2,表示中心位置的偏移量
e.g.
[0,0] [0,1] [0,2] [-1,-1] [-1, 0] [-1,+1]
[1,0] [1,1] [1,2] -> [ 0,-1] [ 0, 0] [ 0,+1] <- (1,1)是中心
[2,0] [2,1] [2,2] [+1,-1] [+1, 0] [+1,+1]
这样就可以将卷积索引转换为相对于中心的坐标,这样可以用于判断是否越界,从而进行padding补充
以KERNEL_SIZE=3为例
卷积核位置 | src坐标计算 | 结果 | 取值 |
---|---|---|---|
0,0 | scr_y=0+0-1=-1 | 越界 | padding |
0,1 | src_y=0+0-1=-1 | 越界 | padding |
0,2 | src_y=0+0-1=-1 | 越界 | padding |
1,0 | src_x=0+0-1=-1 | 越界 | padding |
1,1 | src_y=0,src_x=0 | 有效 | 原图[0,0] |
1,2 | src_y=0,src_x=1 | 有效 | 原图[0,1] |
2,0 | src_x=0+0-1=-1 | 越界 | padding |
2,1 | src_y=1,src_x=0 | 有效 | 原图[1,0] |
2,2 | scr_y=1,src_x=1 | 有效 | 原图[1,1] |
e. 数据窗口的展平
将原本二维的的数据(宽为KERNEL_SIZE, 高为KERNEL_SIZE, 位宽为DATA_WIDTH)的数据,按照从罪小位排在最高位的顺序,压缩成一维的数据
// Flatten window buffer for output
always @(*) beginfor (i = 0; i < KERNEL_SIZE; i = i + 1) beginfor (j = 0; j < KERNEL_SIZE; j = j + 1) beginwindow_out[(KERNEL_SIZE*KERNEL_SIZE-(i*KERNEL_SIZE+j))*DATA_WIDTH-1 -: DATA_WIDTH] = window_buffer[i][j];endend
endendmodule
测试
`timescale 1ns / 1psmodule window_tb();// 测试用参数 - 使用小尺寸便于观察parameter DATA_WIDTH = 8;parameter IMG_WIDTH = 32;parameter IMG_HEIGHT = 32;parameter KERNEL_SIZE = 3;parameter STRIDE = 1;parameter PADDING = (KERNEL_SIZE - 1) / 2;// 测试信号reg clk;reg rst_n;reg [DATA_WIDTH-1:0] pixel_in;reg pixel_valid;reg frame_start;wire [KERNEL_SIZE*KERNEL_SIZE*DATA_WIDTH-1:0] window_out;wire window_valid;// 实例化被测模块window #(.DATA_WIDTH(DATA_WIDTH),.IMG_WIDTH(IMG_WIDTH),.IMG_HEIGHT(IMG_HEIGHT),.KERNEL_SIZE(KERNEL_SIZE),.STRIDE(STRIDE),.PADDING(PADDING)) dut (.clk(clk),.rst_n(rst_n),.pixel_in(pixel_in),.pixel_valid(pixel_valid),.frame_start(frame_start),.window_out(window_out),.window_valid(window_valid));// 时钟生成initial beginclk = 0;forever #5 clk = ~clk;end// 测试数据 - 5x5图像reg [DATA_WIDTH-1:0] test_image [0:IMG_HEIGHT-1][0:IMG_WIDTH-1];// 窗口计数器integer window_count = 0;// 初始化测试图像task reset_test_image;integer i, j;beginfor(i = 0; i < IMG_HEIGHT; i = i + 1) beginfor(j = 0; j < IMG_WIDTH; j = j + 1) begintest_image[i][j] =0;endendendendtasktask init_test_image;integer i, j;beginfor(i = 0; i < IMG_HEIGHT; i = i + 1) beginfor(j = 0; j < IMG_WIDTH; j = j + 1) begintest_image[i][j] = i * IMG_WIDTH + j + 1;endendendendtask// 显示测试图像task display_test_image;integer i, j;begin$display("\n=== 4x4 Test Image ===");for(i = 0; i < IMG_HEIGHT; i = i + 1) begin$write("Row %0d: ", i);for(j = 0; j < IMG_WIDTH; j = j + 1) begin$write("%3d ", test_image[i][j]);end$display("");end$display("======================\n");endendtask// 发送一帧图像数据task send_frame;integer i, j;begin$display("Sending 4x4 frame...");init_test_image();display_test_image();// 发送frame_start信号@(posedge clk);frame_start = 1;@(posedge clk);frame_start = 0;// 逐像素发送数据for(i = 0; i < IMG_HEIGHT; i = i + 1) beginfor(j = 0; j < IMG_WIDTH; j = j + 1) begin@(posedge clk);pixel_in = test_image[i][j];pixel_valid = 1;$display("Sending pixel[%0d][%0d] = %0d at time %0t", i, j, pixel_in, $time);endend@(posedge clk);pixel_valid = 0;$display("All pixels sent at time %0t", $time);endendtask// 主测试序列initial begin$display("========================================");$display("Window Test - Focus on Last Window");$display("IMG_SIZE: %0dx%0d, KERNEL: %0dx%0d", IMG_WIDTH, IMG_HEIGHT, KERNEL_SIZE, KERNEL_SIZE);$display("Expected windows: %0d", IMG_WIDTH * IMG_HEIGHT);$display("========================================");// 初始化信号rst_n = 0;pixel_in = 0;pixel_valid = 0;frame_start = 0;reset_test_image();// 复位序列repeat(5) @(posedge clk);rst_n = 1;repeat(3) @(posedge clk);// 发送测试帧send_frame();// 等待所有窗口输出repeat(50) @(posedge clk);$display("\n========================================");$display("Test Summary:");$display("Total Windows Generated: %0d", window_count);$display("Expected Windows: %0d", IMG_WIDTH * IMG_HEIGHT);if(window_count == IMG_WIDTH * IMG_HEIGHT) begin$display("SUCCESS: All windows generated!");end else begin$display("FAILURE: Missing windows!");end$display("========================================");$finish;end// 窗口监控always @(posedge clk) beginif(window_valid) beginwindow_count = window_count + 1;$display("Window %0d: pos(%0d,%0d) at time %0t", window_count, dut.x_window, dut.y_window, $time);// 显示窗口内容$write("Window content: ");$write("[%0d %0d %0d] ", window_out[71:64], window_out[63:56], window_out[55:48]);$write("[%0d %0d %0d] ", window_out[47:40], window_out[39:32], window_out[31:24]);$write("[%0d %0d %0d]", window_out[23:16], window_out[15:8], window_out[7:0]);$display("");endend// 状态机监控reg [1:0] prev_state = 2'b00;always @(posedge clk) beginif(dut.current_state != prev_state) begincase(dut.current_state)2'b00: $display("Time %0t: State -> IDLE", $time);2'b01: $display("Time %0t: State -> LOAD", $time);2'b10: $display("Time %0t: State -> PROCESS", $time);default: $display("Time %0t: State -> UNKNOWN(%0d)", $time, dut.current_state);endcaseprev_state = dut.current_state;endend// 波形转储initial begin$dumpfile("window_tb.vcd");$dumpvars(0, window_tb);// 限制仿真时间#2000;$display("ERROR: Simulation timeout!");$finish;endendmodule
结果
输入数据
Row 0: 1 2 3 4 5
Row 1: 6 7 8 9 10
Row 2: 11 12 13 14 15
Row 3: 16 17 18 19 20
Line_Buffer 缓冲数据
Window_Buffer输出数据
valid为高,window_buffer开始提取line_buffer数据,同时输出展平的window_out;
window_buffer提取完毕,valid拉低
相关文章:

FPGA实现CNN卷积层:高效窗口生成模块设计与验证
我最近在从事一项很有意思的项目,我想在PFGA上部署CNN并实现手写图片的识别。而本篇文章,是我迈出的第一步。具体代码已发布在github上 模块介绍 卷积神经网络(CNN)可以分为卷积层、池化层、激活层、全链接层结构,本篇要实现的&…...

LeetCode 3068.最大节点价值之和:脑筋急转弯+动态规划(O(1)空间)
【LetMeFly】3068.最大节点价值之和:脑筋急转弯动态规划(O(1)空间) 力扣题目链接:https://leetcode.cn/problems/find-the-maximum-sum-of-node-values/ 给你一棵 n 个节点的 无向 树,节点从 0 到 n - 1 编号。树以长…...
2.2HarmonyOS NEXT高性能开发技术:编译优化、内存管理与并发编程实践
HarmonyOS NEXT高性能开发技术:编译优化、内存管理与并发编程实践 在HarmonyOS NEXT全场景设备开发中,高性能是跨端应用体验的核心保障。本章节聚焦ArkCompiler编译优化、内存管理工具及多线程并发编程三大技术模块,结合实战案例解析底层实现…...

BLIP-2
目录 摘要 Abstract BLIP-2 模型框架 预训练策略 模型优势 应用场景 实验 代码 总结 摘要 BLIP-2 是一种基于冻结的图像编码器和大型语言模型的高效视觉语言预训练模型,由 Salesforce 研究团队提出。它在 BLIP 的基础上进一步优化,通过轻量级…...
【Go-6】数据结构与集合
6. 数据结构与集合 数据结构是编程中用于组织和存储数据的方式,直接影响程序的效率和性能。Go语言提供了多种内置的数据结构,如数组、切片、Map和结构体,支持不同类型的数据管理和操作。本章将详细介绍Go语言中的主要数据结构与集合…...

支持向量机(SVM)例题
对于图中所示的线性可分的20个样本数据,利用支持向量机进行预测分类,有三个支持向量 A ( 0 , 2 ) A\left(0, 2\right) A(0,2)、 B ( 2 , 0 ) B\left(2, 0\right) B(2,0) 和 C ( − 1 , − 1 ) C\left(-1, -1\right) C(−1,−1)。 求支持向量机分类器的线…...

SQL中各个子句的执行顺序
select、from、 join、where、order by、group by、having、limit 解释 1) FROM (确定数据源) 查询的执行首先从FROM子句开始,确定数据的来源(表、视图、连接等)。 2) JOIN (如果有JOIN操作) 在FROM子句之后,SQL引擎会执行连接操作(JOIN),…...
PHP下实现RSA的加密,解密,加签和验签
前言: RSA下加密,解密,加签和验签是四种不同的操作,有时候会搞错,记录一下。 1.公钥加密,私钥解密 发送方通过公钥将原数据加密成一个sign参数,相当于就是信息的载体,接收方能通过si…...

本地部署消息代理软件 RabbitMQ 并实现外部访问( Windows 版本 )
RabbitMQ 是由 Erlang 语言开发的 消息中间件,是一种应用程序之间的通信方法。支持多种编程和语言和协议发展,用于实现分布式系统的可靠消息传递和异步通信等方面。 本文将详细介绍如何在 Windows 系统本地部署 RabbitMQ 并结合路由侠实现外网访问本…...
每日c/c++题 备战蓝桥杯(P2240 【深基12.例1】部分背包问题)
P2240 【深基12.例1】部分背包问题 - 详解与代码实现 一、题目概述 阿里巴巴要在承重为 T 的背包中装走尽可能多价值的金币,共有 N 堆金币,每堆金币有总重量和总价值。金币可分割,且分割后单位价格不变。目标是求出能装走的最大价值。 二、…...
Java异步编程:CompletionStage接口详解
CompletionStage 接口分析 接口能力概述 CompletionStage 是 Java 8 引入的接口,用于表示异步计算的一个阶段,它提供了强大的异步编程能力: 链式异步操作:允许将一个异步操作的结果传递给下一个操作组合操作&a…...
Java后端接受前端数据的几种方法
在前后端分离的开发模式中,前端(Vue)与后端(Java)的数据交互有多种格式,下面详细介绍几种常见的格式以及后端对应的接收方式。 一、JSON 格式 前端传输 在 Vue 里,可借助 axios 把数据以 JSO…...
Oracle OCP认证的技术定位怎么样?
一、引言:Oracle OCP认证的技术定位 Oracle Certified Professional(OCP)认证是数据库领域含金量最高的国际认证之一,其核心价值在于培养具备企业级数据库全生命周期管理能力的专业人才。随着数字化转型加速,OCP认证…...
powershell7.5@.net环境@pwsh7.5在部分windows10系统下的运行问题
文章目录 powershell7.5及更高版本和.net 9解决方案 powershell7.5及更高版本和.net 9 相对较新的.Net 9版本在老一些的windows10系统上(比如内核版本号:10.0.19044.1288以及之前的),由于默认启用了CET,导致编译运行失败,需要自己在项目中添加关闭CET的配置语句才能够顺利编译…...

基于微信小程序的垃圾分类系统
博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了六年的毕业设计程序开发,开发过上千套毕业设计程序,没有什么华丽的语言࿰…...
CSS3 渐变、阴影和遮罩的使用
全文目录: 开篇语**前言****1. CSS3 渐变 (Gradient)****1.1 线性渐变 (linear-gradient)****1.2 径向渐变 (radial-gradient)** **2. CSS3 阴影 (Shadow)****2.1 盒子阴影 (box-shadow)****2.2 文本阴影 (text-shadow)** **3. CSS3 遮罩 (Mask)****3.1 基本遮罩 (m…...
Spring Boot 全局配置文件优先级
好的,Spring Boot的全局配置文件优先级是一个非常重要的概念,它决定了在不同位置的同名配置属性以哪个为准。 Spring Boot 全局配置文件优先级核心知识点 📌 文件格式优先级: 在同一目录下,如果同时存在 application.properties 和…...

流媒体基础解析:视频清晰度的关键因素
在视频处理的过程中,编码解码及码率是影响视频清晰度的关键因素。今天,我们将深入探讨这些概念,并解析它们如何共同作用于视频质量。 编码解码概述 编码,简单来说,就是压缩。视频编码的目的是将原始视频数据压缩成较…...

grid网格布局
使用flex布局的痛点 如果使用justify-content: space-between;让子元素两端对齐,自动分配中间间距,假设一行4个,如果每一行都是4的倍数那没任何问题,但如果最后一行是2、3个的时候就会出现下面的状况: /* flex布局 两…...
C#数字金额转中文大写金额:代码解析
C#数字金额转中文大写金额:代码解析 在金融相关的业务场景中,我们常常需要将数字金额转换为中文大写金额,以避免金额被篡改,增加金额的准确性和安全性。本文将深入解析一段 C# 代码,这段代码通过巧妙的设计࿰…...

Vehicle HAL(2)--Vehicle HAL 的启动
目录 1. VehicleService-main 函数分析 2. 构建EmulatedVehicleHal 2.1 EmulatedVehicleHal::EmulatedVehicleHal(xxx) 2.2 EmulatedVehicleHal::initStaticConfig() 2.3 EmulatedVehicleHal::onPropertyValue() 3. 构建VehicleEmulator 4. 构建VehicleHalManager (1)初…...
JS中的函数防抖和节流:提升性能的关键技术
在JavaScript开发中,函数防抖和节流是两种常用的优化技术,用于处理那些可能会被频繁触发的事件,如resize、scroll、mousemove等。本文将详细介绍函数防抖和节流的概念、实现方法以及它们之间的区别。 一、什么是函数防抖和节流? …...
Android Compose开发架构选择指南:单Activity vs 多Activity
简介 掌握Jetpack Compose的Activity架构选择,是构建高性能、易维护Android应用的关键一步。在2025年的Android开发领域,随着Jetpack Compose的成熟和Android系统对多窗口模式的支持,开发者面临架构选择时需要更加全面地考虑各种因素。本文将深入探讨单Activity架构和多Act…...
【Netty系列】Reactor 模式 1
目录 一、Reactor 模式的核心思想 二、Netty 中的 Reactor 模式实现 1. 服务端代码示例 2. 处理请求的 Handler 三、运行流程解析(结合 Reactor 模式) 四、关键点说明 五、与传统模型的对比 六、总结 Reactor 模式是 Netty 高性能的核心设计思想…...
vue3 el-input type=“textarea“ 字体样式 及高度设置
在Vue 3中,如果你使用的是Element Plus库中的<el-input>组件作为文本域(type"textarea"),你可以通过几种方式来设置字体样式和高度。 1. 直接在<el-input>组件上使用style属性 你可以直接在<el-input&…...
并发解析hea,转为pdf格式
由于每次解析一个heap需要时间有点久,就写了一个自动解析程pdf的一个脚本。 down_lib.sh是需要自己写的哦,主要是用于下载自己所需程序的库,用于解析heap。 #!/bin/bash# 优化版通用解析脚本(并发加速):批…...

【C语言】详解 指针
前言: 在学习指针前,通过比喻的方法,让大家知道指针的作用。 想象一下,你在一栋巨大的图书馆里找一本书。如果没有书架编号和目录,这几乎是不可能完成的任务。 在 C 语言中,指针就像是图书馆的索引系统&…...

RabbitMQ仲裁队列高可用架构解析
#作者:闫乾苓 文章目录 概述工作原理1.节点之间的交互2.消息复制3.共识机制4.选举领导者5.消息持久化6.自动故障转移 集群环境节点管理仲裁队列增加集群节点重新平衡仲裁队列leader所在节点仲裁队列减少集群节点 副本管理add_member 在给定节点上添加仲裁队列成员&…...
刚出炉热乎的。UniApp X 封装 uni.request
HBuilder X v4.66 当前最新版本 由于 uniapp x 使用的是自己包装的 ts 语言 uts。目前语言还没有稳定下来,各种不支持 ts 各种报错各种不兼容问题。我一个个问题调通的,代码如下: 封装方法 // my-app/utils/request.uts const UNI_APP_BASE…...

Apache Kafka 实现原理深度解析:生产、存储与消费全流程
Apache Kafka 实现原理深度解析:生产、存储与消费全流程 引言 Apache Kafka 作为分布式流处理平台的核心,其高吞吐、低延迟、持久化存储的设计使其成为现代数据管道的事实标准。本文将从消息生产、持久化存储、消息消费三个阶段拆解 Kafka 的核心实现原…...