基于C語言實現的KV存儲引擎(一)

基于C語言實現的KV存儲引擎

  • 項目簡介
  • 整體架構
  • 網絡模塊的實現
    • recator
    • proactor
    • Ntyco

項目簡介

本文主要是基于 C 語言來實現一個簡單的 KV 存儲架構,目的就是將網絡模塊跟實際開發結合起來。

首先我們知道對于數據的存儲可以分為兩種方式,一種是在內存中進行存儲,像 Redis 這種,是一種內存型數據庫,主要也是以 KV 的形式進行存儲,提升了對應的數據的訪問效率;另一種就是在磁盤中進行存儲,像 MySQL 這種,是一種關系型數據庫,更多的是以一種表的形式去組織的數據庫,也是一種主流的數據庫,但是訪問的速度就稍微慢一點兒。

那么當前已經有眾多的數據庫的存在了,我們又為什么需要去實現一個自己的 KV 存儲引擎呢?

對于 Redis,MySQL 這些數據庫來說,他們可以適用于多種數據類型,我們可以理解為是一個非常大類別的實現,如果我們當前只是需要存儲某些類別的數據,比如我們就單純的進行一些短鏈接映射存儲,用戶的一些信息存儲,在某些時候需要快速進行讀取,我們就沒必要使用 Redis 和 MySQL 這種的數據庫存儲的方式,也就避免了使用它們需要去考慮的一些問題,同樣,我們只是單純的去進行某些數據的存儲,我們就可以自己進行優化,將對應的性能提升到最優。

我們自己實現的 KV 存儲其實跟 Redis 中的 KV 存儲的思想很相像,都是以鍵值對的方式去進行存儲,比如說我們需要訪問某一張圖片,知道鏈接,輸入對應的鏈接,就可以訪問這張圖片,其實這就是一種 KV 存儲的方式。

整體架構

基于上面的了解,我們就可以來梳理一下我們的 KV 存儲的一個整體架構,其實整個流程也可以去理解為一個請求響應的流程,我么需要進行存儲,首先就要發送需要存儲的數據,然后服務端進行存儲,返回給我們對應的信息(成功或者失敗),我們后續訪問就可以直接進行看到對應的信息,我們就可以簡單的理解為下面這種架構:

在這里插入圖片描述

網絡模塊的實現

在前面的文章當中我們介紹了幾種網絡高并發的實現方式,reactorio_uringNtyco(協程),在我們的實現當中這幾種實現方式都會被使用到。

首先我們需要明白的一點就是,對于網絡模塊與 KV 引擎模塊我們是要進行分開的,網絡模塊只進行網絡模塊的工作就可以了,而 KV 引擎模塊在進行協議的解析工作。

recator

在前面的章節當中,我們已經實現過 reactor 了,reacor 本質上就是將對應的 IO 管理轉化為對事件的管理,其實它的思想就是使用了 IO 多路復用模型,對讀寫事件進行監聽,通過回調函數的調用,異步的去處理讀寫事件,不去過分的占用核心線程的工作。

首先就是對應的封裝工作,對于一個事件來說,肯定有對應的 fd,回調函數,對應的接收和發送緩沖區,我們將其封裝在一個 struct 中,方便后續進行調用。

#ifndef __SERVER_H__
#define __SERVER_H__#define BUFFER_LENGTH		1024#define ENABLE_KVS 1typedef int (*RCALLBACK)(int fd);struct conn {int fd;char rbuffer[BUFFER_LENGTH];int rlength;char wbuffer[BUFFER_LENGTH];int wlength;RCALLBACK send_callback;union {RCALLBACK recv_callback;RCALLBACK accept_callback;} r_action;int status;
};#if ENABLE_KVS
int kvs_request(struct conn *c);
int kvs_response(struct conn *c);
#endif#endif

我們在這一塊兒也提供了兩個接口kvs_requestkvs_response,從命名就可以可以看出,他其實就是接受服務端發送過來的請以及響應,那么我們接收到對應的請求以后,就需要進行協議的制定,如何對數據進行解析,很明顯kvs_request接口中就需要與我們的 KV 引擎模塊相連接,接收到請求以后就需要調用到 KV 引擎模塊進行協議的解析工作。

我們來看具體的實現代碼:

reactor.c

#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h>
#include <errno.h>
#include <sys/time.h>
#include "server.h"#define CONNECTION_SIZE			1024#define MAX_PORTS			20#define TIME_SUB_MS(tv1, tv2)  ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)typedef int (*msg_handler)(char* msg, int length, char* response);
static msg_handler kvs_handler;// 請求處理
int kvs_request(struct conn *c) {// printf("recv: %d, %s\n", c->rlength, c->rbuffer);c->wlength = kvs_handler(c->rbuffer, c->rlength, c->wbuffer);return c->wlength;
}// 響應處理
int kvs_response(struct conn *c) {}int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd);int epfd = 0;
struct timeval begin;struct conn conn_list[CONNECTION_SIZE] = {0};
// fdint set_event(int fd, int event, int flag) {if (flag) {  // non-zero addstruct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);} else {  // zero modstruct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);}
}int event_register(int fd, int event) {if (fd < 0) return -1;conn_list[fd].fd = fd;conn_list[fd].r_action.recv_callback = recv_cb;conn_list[fd].send_callback = send_cb;memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH);conn_list[fd].rlength = 0;memset(conn_list[fd].wbuffer, 0, BUFFER_LENGTH);conn_list[fd].wlength = 0;set_event(fd, event, 1);
}// listenfd(sockfd) --> EPOLLIN --> accept_cb
int accept_cb(int fd) {struct sockaddr_in  clientaddr;socklen_t len = sizeof(clientaddr);int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);//printf("accept finshed: %d\n", clientfd);if (clientfd < 0) {printf("accept errno: %d --> %s\n", errno, strerror(errno));return -1;}event_register(clientfd, EPOLLIN);  // | EPOLLETif ((clientfd % 1000) == 0) {struct timeval current;gettimeofday(&current, NULL);int time_used = TIME_SUB_MS(current, begin);memcpy(&begin, &current, sizeof(struct timeval));printf("accept finshed: %d, time_used: %d\n", clientfd, time_used);}return 0;
}int recv_cb(int fd) {memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH );int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);if (count == 0) { // disconnectprintf("client disconnect: %d\n", fd);close(fd);epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // unfinishedreturn 0;} else if (count < 0) { // printf("count: %d, errno: %d, %s\n", count, errno, strerror(errno));close(fd);epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);return 0;}conn_list[fd].rlength = count;// printf("RECV: %s\n", conn_list[fd].rbuffer);#if ENABLE_KVSkvs_request(&conn_list[fd]);#endif set_event(fd, EPOLLOUT, 0);return count;
}int send_cb(int fd) {
#if ENABLE_KVSkvs_response(&conn_list[fd]);#endifint count = 0;if (conn_list[fd].wlength != 0) {count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);}set_event(fd, EPOLLIN, 0);//set_event(fd, EPOLLOUT, 0);return count;
}int init_reactor_server(unsigned short port) {int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(port); // 0-1023, if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {printf("bind failed: %s\n", strerror(errno));}listen(sockfd, 10);//printf("listen finshed: %d\n", sockfd); // 3 return sockfd;
}int recator_entry(unsigned short port, msg_handler handler) {kvs_handler = handler;epfd = epoll_create(1);int i = 0;for (i = 0;i < MAX_PORTS; i++) {int sockfd = init_reactor_server(port + i);conn_list[sockfd].fd = sockfd;conn_list[sockfd].r_action.recv_callback = accept_cb;set_event(sockfd, EPOLLIN, 1);}gettimeofday(&begin, NULL);while (1) { // mainloopstruct epoll_event events[1024] = {0};int nready = epoll_wait(epfd, events, 1024, -1);int i = 0;for (i = 0;i < nready;i ++) {int connfd = events[i].data.fd;if (events[i].events & EPOLLIN) {conn_list[connfd].r_action.recv_callback(connfd);} if (events[i].events & EPOLLOUT) {conn_list[connfd].send_callback(connfd);}}}
}

kvstore.h

#ifndef __KV_STORE__
#define __KV_STORE__#define NETWORK_RECATOR 0
#define NETWORK_PROACTOR 1
#define NETWORK_NTYCO 2#define NETWORK_TYPE NETWORK_RECATORtypedef int (*msg_handler)(char* msg, int length, char* response);const char* command[] = {"SET", "GET", "DEL", "MOD", "EXIST"
};const char* response[] = {};#endif

kvstore.c

#include <stdio.h>
#include <stdlib.h>
#include "kvstore.h"extern int recator_entry(unsigned short port, msg_handler handler);
extern int ntyco_start(unsigned short port, msg_handler handler);
extern int proactor_entry(unsigned short port, msg_handler handler);
/*
* @brief 協議解析
* @param msg 消息體
* @param length 消息體長度
* @param response 響應體
* @return 0 成功 -1 失敗
*/
// 協議解析
int kvs_protocal(char* msg, int length, char* response)
{printf("recv: %d, %s\n", length, msg);}int main(int argc, char* argv[])
{if (argc != 2) {printf("Usage: %s <port>\n", argv[0]);return -1;}if (NETWORK_TYPE == NETWORK_RECATOR) {recator_entry(atoi(argv[1]), kvs_protocal);} else if (NETWORK_TYPE == NETWORK_PROACTOR) {proactor_entry(atoi(argv[1]), kvs_protocal);} else if (NETWORK_TYPE == NETWORK_NTYCO) {ntyco_start(atoi(argv[1]), kvs_protocal);}return 0;
}
  • kvstore.c文件當中主要就是對于 KV 引擎的一個實現,當然 main 函數也是用在這當中的,kvs_protocal是我們的具體協議解析的函數,我們要實現其與網絡模塊的互聯,就可以采用函數指針的方式,無論是reactorproactor或者是協程,其實都采用這種方式,C 語言并不想 C++ 那樣有包裝器,所以在這兒我們就使用函數指針的方法來操作。
  • kvstore.h其實就是定義與實現的分離,我們當前是仿照 Redis 協議進行制定的;
  • recator.c主要就是kvs_request模塊,實現了與 KV 引擎的互聯,但是網絡模塊又是與 KV 引擎模塊是解耦的。
    在這里插入圖片描述

proactor

procator.c

#include <stdio.h>
#include <liburing.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>#define EVENT_ACCEPT   	0
#define EVENT_READ		1
#define EVENT_WRITE		2#define ENTRIES_LENGTH		1024
#define BUFFER_LENGTH		1024typedef int (*msg_handler)(char* msg, int length, char* response);
static msg_handler kvs_handler;struct conn_info {int fd;int event;
};int init_proactor_server(unsigned short port) {	int sockfd = socket(AF_INET, SOCK_STREAM, 0);	struct sockaddr_in serveraddr;	memset(&serveraddr, 0, sizeof(struct sockaddr_in));	serveraddr.sin_family = AF_INET;	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);	serveraddr.sin_port = htons(port);	if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {		perror("bind");		return -1;	}	listen(sockfd, 10);return sockfd;
}int set_event_recv(struct io_uring *ring, int sockfd,void *buf, size_t len, int flags) {struct io_uring_sqe *sqe = io_uring_get_sqe(ring);struct conn_info accept_info = {.fd = sockfd,.event = EVENT_READ,};io_uring_prep_recv(sqe, sockfd, buf, len, flags);memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info));}int set_event_send(struct io_uring *ring, int sockfd,void *buf, size_t len, int flags) {struct io_uring_sqe *sqe = io_uring_get_sqe(ring);struct conn_info accept_info = {.fd = sockfd,.event = EVENT_WRITE,};io_uring_prep_send(sqe, sockfd, buf, len, flags);memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info));
}int set_event_accept(struct io_uring *ring, int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags) {struct io_uring_sqe *sqe = io_uring_get_sqe(ring);struct conn_info accept_info = {.fd = sockfd,.event = EVENT_ACCEPT,};io_uring_prep_accept(sqe, sockfd, (struct sockaddr*)addr, addrlen, flags);memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info));}int proactor_entry(unsigned short port, msg_handler handler) {kvs_handler = handler;int sockfd = init_proactor_server(port);struct io_uring_params params;memset(&params, 0, sizeof(params));struct io_uring ring;io_uring_queue_init_params(ENTRIES_LENGTH, &ring, &params);struct sockaddr_in clientaddr;	socklen_t len = sizeof(clientaddr);set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);char buffer[BUFFER_LENGTH] = {0};while (1) {io_uring_submit(&ring);struct io_uring_cqe *cqe;io_uring_wait_cqe(&ring, &cqe);struct io_uring_cqe *cqes[128];int nready = io_uring_peek_batch_cqe(&ring, cqes, 128);  // epoll_waitint i = 0;for (i = 0;i < nready;i ++) {struct io_uring_cqe *entries = cqes[i];struct conn_info result;memcpy(&result, &entries->user_data, sizeof(struct conn_info));if (result.event == EVENT_ACCEPT) {set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);//printf("set_event_accept\n"); //int connfd = entries->res;set_event_recv(&ring, connfd, buffer, BUFFER_LENGTH, 0);} else if (result.event == EVENT_READ) {  //int ret = entries->res;if (ret == 0) {close(result.fd);} else if (ret > 0) {// printf("set_event_recv ret: %d, %s\n", ret, buffer); // 協議解析char response[BUFFER_LENGTH] = {0};ret = kvs_handler(buffer, ret, response);set_event_send(&ring, result.fd, response, ret, 0);}}  else if (result.event == EVENT_WRITE) {int ret = entries->res;//printf("set_event_send ret: %d, %s\n", ret, buffer);set_event_recv(&ring, result.fd, buffer, BUFFER_LENGTH, 0);}}io_uring_cq_advance(&ring, nready);}
}

有了前面recator模塊的理解,我們現在就只需要在已經實現過的io_uring代碼中進行一些修改即可,將協議解析的內容添加進去。

Ntyco

當前協程實現是采用的一個 github 上開源的網絡協程庫組件來進行實現的,也是將對應的協議解析的內容添加進去即可:https://github.com/wangbojing/NtyCo,感興趣的可以將對應的代碼下載下來進行使用。

ntyco.c

#include "nty_coroutine.h"
#include <arpa/inet.h>typedef int (*msg_handler)(char* msg, int length, char* response);
static msg_handler kvs_handler;void server_reader(void *arg) {int fd = *(int *)arg;int ret = 0;while (1) {char buf[1024] = {0};ret = recv(fd, buf, 1024, 0);if (ret > 0) {// printf("read from server: %.*s\n", ret, buf);// 協議解析位置char response[1024] = {0};int slength = kvs_handler(buf, ret, response);ret = send(fd, response, slength, 0);if (ret == -1) {close(fd);break;}} else if (ret == 0) {	close(fd);break;}}
}void server(void *arg) {unsigned short port = *(unsigned short *)arg;int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) return ;struct sockaddr_in local, remote;local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;bind(fd, (struct sockaddr*)&local, sizeof(struct sockaddr_in));listen(fd, 20);printf("listen port : %d\n", port);while (1) {socklen_t len = sizeof(struct sockaddr_in);int cli_fd = accept(fd, (struct sockaddr*)&remote, &len);nty_coroutine *read_co;nty_coroutine_create(&read_co, server_reader, &cli_fd);}
}int ntyco_start(unsigned short port, msg_handler handler) {kvs_handler = handler;nty_coroutine *co = NULL;nty_coroutine_create(&co, server, &port);nty_schedule_run();
}

接下來可以看一下對應的實踐成果:
在這里插入圖片描述
當前與網絡模塊的關聯已經實現完畢,后續更新請看下一篇文章。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917175.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917175.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917175.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c++和python聯合編程示例

安裝 C與 Python 綁定工具 pip install pybind11這其實相當于使用 python 安裝了一個 c的庫 pybind11,這個庫只由頭文件構成&#xff0c; 支持基礎數據類型傳遞以及 python 的 numpy 和 c的 eigen 庫之間的自動轉換。 編寫 CMakeList.txt cmake_minimum_required(VERSION 3.14)…

【OD機試題解法筆記】貪心歌手

題目描述 一個歌手準備從A城去B城參加演出。 按照合同&#xff0c;他必須在 T 天內趕到歌手途經 N 座城市歌手不能往回走每兩座城市之間需要的天數都可以提前獲知。歌手在每座城市都可以在路邊賣唱賺錢。 經過調研&#xff0c;歌手提前獲知了每座城市賣唱的收入預期&#xff1a…

AI: 告別過時信息, 用RAG和一份PDF 為LLM打造一個隨需更新的“外腦”

嘿&#xff0c;各位技術同學&#xff01;今天&#xff0c;我們來聊一個大家在使用大語言模型&#xff08;LLM&#xff09;時都會遇到的痛點&#xff1a;知識過時。 無論是像我一樣&#xff0c;用 Gemini Pro 學習日新月異的以太坊&#xff0c;還是希望它能精確掌握某個特定工具…

深度學習(魚書)day08--誤差反向傳播(后三節)

深度學習&#xff08;魚書&#xff09;day08–誤差反向傳播&#xff08;后三節&#xff09;一、激活函數層的實現 這里&#xff0c;我們把構成神經網絡的層實現為一個類。先來實現激活函數的ReLU層和Sigmoid層。ReLU層 激活函數ReLU&#xff08;Rectified Linear Unit&#xff…

C# 中生成隨機數的常用方法

1. 使用 Random 類&#xff08;簡單場景&#xff09; 2. 使用 RandomNumberGenerator 類&#xff08;安全場景&#xff09; 3. 生成指定精度的隨機小數 C# 中生成隨機數的常用方法&#xff1a; 隨機數類型實現方式示例代碼特點與適用場景隨機整數&#xff08;無范圍&#xf…

Flink 算子鏈設計和源代碼實現

1、JobGraph &#xff08;JobManager&#xff09; JobGraph 生成時&#xff0c;通過 ChainingStrategy 連接算子&#xff0c;最終在 Task 中生成 ChainedDriver 鏈表。StreamingJobGraphGeneratorcreateJobGraph() 構建jobGrapch 包含 JobVertex setChaining() 構建算子鏈isCha…

對接八大應用渠道

背景最近公司想把游戲包上到各個渠道上&#xff0c;因此需要對接各種渠道&#xff0c;渠道如下&#xff0c;oppo、vivo、華為、小米、應用寶、taptap、榮耀、三星等應用渠道 主要就是對接登錄、支付接口&#xff08;后續不知道會不會有其他的&#xff09;&#x…

學習:入門uniapp Vue3組合式API版本(17)

42.打包發行微信小程序的上線全流程 域名 配置 發行 綁定手機號 上傳 提交后等待&#xff0c;上傳 43.打包H5并發布上線到unicloud的前端頁面托管 完善配置 unicloud 手機號實名信息不一致&#xff1a;請確保手機號的實名信息與開發者姓名、身份證號一致&#xff0c;請前往開…

SOLIDWORKS材料明細表設置,屬于自己的BOM表模板

上一期我們了解了如何在SOLIDWORKS工程圖中添加材料明細表?接下來&#xff0c;我們將進行對SOLIDWORKS材料明細表的設置、查看縮略圖、模板保存的深度講解。01 材料明細表設置菜單欄生成表格后左側菜單欄會顯示關于材料明細表的相關設置信息。我們先了解一下菜單欄設置詳情&am…

全棧:Maven的作用是什么?本地倉庫,私服還有中央倉庫的區別?Maven和pom.xml配置文件的關系是什么?

Maven和pom.xml配置文件的關系是什么&#xff1a; Maven是一個構建工具和依賴管理工具&#xff0c;而pom.xml&#xff08;Project Object Model&#xff09;是Maven的核心配置文件。 SSM 框架的項目不一定是 Maven 項目&#xff0c;但推薦使用 Maven進行管理。 SSM 框架的項目可…

超越 ChatGPT:智能體崛起,開啟全自主 AI 時代

引言 短短三年,生成式 AI 已從對話助手跨越到能自主規劃并完成任務的“智能體(Agentic AI)”時代。這場演進不僅體現在模型規模的提升,更在于系統架構、交互范式與安全治理的全面革新。本文按時間線梳理關鍵階段與核心技術,為您呈現 AI 智能體革命的脈絡與未來趨勢。 1. …

一杯就夠:讓大腦瞬間在線、讓肌肉滿電的 “Kick-out Drink” 全解析

一杯就夠&#xff1a;讓大腦瞬間在線、讓肌肉滿電的 “Kick-out Drink” 全解析“每天清晨&#xff0c;當鬧鐘還在哀嚎&#xff0c;你舉杯一飲&#xff0c;睡意像被扔出擂臺——這&#xff0c;就是 Kick-out Drink 的全部浪漫。”清晨 30 分鐘后&#xff0c;250 mL 常溫水里溶解…

系統開機時自動執行指令

使用 systemd 創建一個服務單元可以讓系統開機時自動執行指令&#xff0c;假設需要執行的指令如下&#xff0c;運行可執行文件&#xff08;/home/demo/可執行文件&#xff09;&#xff0c;并輸入參數&#xff08;–input/home/config/demo.yaml&#xff09;&#xff1a; /home/…

Docker 初學者需要了解的幾個知識點 (七):php.ini

這段配置是 php.ini 文件中針對 PHP 擴展和 Xdebug 調試工具的設置&#xff0c;主要用于讓 PHP 支持數據庫連接和代碼調試&#xff08;尤其在 Docker 環境中&#xff09;&#xff0c;具體解釋如下&#xff1a;[PHP] extensionpdo_mysql extensionmysqli xdebug.modedebug xdebu…

【高階版】R語言空間分析、模擬預測與可視化高級應用

隨著地理信息系統&#xff08;GIS&#xff09;和大尺度研究的發展&#xff0c;空間數據的管理、統計與制圖變得越來越重要。R語言在數據分析、挖掘和可視化中發揮著重要的作用&#xff0c;其中在空間分析方面扮演著重要角色&#xff0c;與空間相關的包的數量也達到130多個。在本…

dolphinscheduler中一個腳本用于從列定義中提取列名列表

dolphinscheduler中&#xff0c;我們從一個mysql表導出數據&#xff0c;上傳到hdfs, 再創建一個臨時表&#xff0c;所以需要用到列名定義和列名列表。 原來定義兩個變量&#xff0c;不僅繁鎖&#xff0c;還容易出現差錯&#xff0c;比如兩者列序不對。 所以考慮只定義列定義變量…

JavaWeb(蒼穹外賣)--學習筆記16(定時任務工具Spring Task,Cron表達式)

前言 本篇文章是學習B站黑馬程序員蒼穹外賣的學習筆記&#x1f4d1;。我的學習路線是Java基礎語法-JavaWeb-做項目&#xff0c;管理端的功能學習完之后&#xff0c;就進入到了用戶端微信小程序的開發&#xff0c;用戶端開發的流程大致為用戶登錄—商品瀏覽&#xff08;其中涉及…

靈敏度,精度,精確度,精密度,精準度,準確度,分辨率,分辨力——概念

文章目錄前提總結前提 我最近在整理一份數據指標要求的時候&#xff0c;總是混淆這幾個概念&#xff1a;靈敏度&#xff0c;精度&#xff0c;精確度&#xff0c;精密度&#xff0c;精準度&#xff0c;準確度&#xff0c;分辨率&#xff0c;分辨力&#xff0c;搜了一些文章&…

python-異常(筆記)

#后續代碼可以正常運行 try:f open("xxx.txt","r",encodingutf-8)except:print("except error")#捕獲指定異常&#xff0c;其他異常報錯程序中止&#xff0c;管不到 try:print(name) except NameError as you_call:print("name error"…

[lvgl_player] 用戶界面(LVGL) | 播放器核心設計

docs&#xff1a;基于LVGL的音樂播放器 本項目是為嵌入式設備設計的音樂播放系統&#xff0c;采用LVGL圖形庫構建用戶界面。 系統支持播放WAV格式音頻文件&#xff0c;具備播放列表管理功能&#xff0c;可實現播放/暫停控制、曲目切換等核心操作。 用戶可通過交互界面實時調…