Rust Web入门(五):完整的增删改查

该教程介绍了如何使用Rust进行Web开发,包括项目结构优化,如创建models、handlers和db_access目录。更新了数据库表结构,增加了可为空的字段,并使用Option包裹。定义了数据结构用于创建和更新数据,利用TryFrom和Fromtrait进行转化。实现了数据库查询和更新的业务逻辑,同时处理了Post请求的数据校验,确保输入为有效的JSON。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本教程笔记来自 杨旭老师的 rust web 全栈教程,链接如下:

https://www.bilibili.com/video/BV1RP4y1G7KF?p=1&vd_source=8595fbbf160cc11a0cc07cadacf22951

学习 Rust Web 需要学习 rust 的前置知识可以学习杨旭老师的另一门教程

https://www.bilibili.com/video/BV1hp4y1k7SV/?spm_id_from=333.999.0.0&vd_source=8595fbbf160cc11a0cc07cadacf22951

项目的源代码可以查看 git:(注意作者使用的是 mysql 数据库而不是原教程的数据库)

https://github.com/aiai0603/rust_web_mysql

今天来入门完善我们的项目,使得它可以进行完善的增删改查操作:

目录结构重构

之前我们开发了一个简单的 demo,我们把所有的代码都放在一个目录下,但是如果有多个模块的话,这样的结构就会很乱,所以现在我们需要优化一下我们的结构,我们新建三个文件夹 models 、handlers 和 db_access 分别负责每个模块的数据结构、事务处理和数据库操作,在每个文件夹下,我们编写一个 mod.rs 来依次导出我们的模块,之后我们就可以分模块编写我们的逻辑了:

|- db_access (数据库操作)
|--- course.rs
|--- mod.rs
|- handlers (事务逻辑)
|--- course.rs
|--- general.rs
|--- mod.rs
|- models (数据结构)
|--- course.rs
|--- mod.rs

更新数据结构

之后我们需要一份全新的数据结构,我们首先优化我们的数据库,新增一些可能是空的字段:

DROP TABLE IF EXISTS `course`;
CREATE TABLE `course`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `teacher_id` int(0) NOT NULL,
  `name` varchar(140) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
  `description` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `format` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `structure` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `duration` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `price` int(0) NULL DEFAULT NULL,
  `language` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `level` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `id`(`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of course
-- ----------------------------
INSERT INTO `course` VALUES (1, 1, 'First course', '2022-01-17 05:40:00', NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `course` VALUES (2, 1, 'Second course', '2022-01-18 05:45:00', NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `course` VALUES (4, 1, 'Test course', '2023-03-01 21:14:52', 'This is a course', NULL, NULL, NULL, NULL, 'English', 'Beginner');

SET FOREIGN_KEY_CHECKS = 1;

之后我们在 models/course.rs 里面更新我们的数据结构,因为有些字段可能是空的,所以我们将他们用 Option 包裹

#[derive(Serialize, Debug, Clone, sqlx::FromRow)]
pub struct Course {
    pub id: i32,
    pub teacher_id: i32,
    pub name: String,
    pub time: Option<DateTime<Utc>>,
    pub description: Option<String>,
    pub format: Option<String>,
    pub structure: Option<String>,
    pub duration: Option<String>,
    pub price: Option<i32>,
    pub language: Option<String>,
    pub level: Option<String>,
}

我们新建两个数据结构用于更新和创建数据,值得注意的是,当我们需要创建一个数据的时候,我们需要检测传入的数据是不是能转化成对应的结构,所以我们使用 TryFrom 这个 trait 来转化它:

#[derive(Deserialize, Debug, Clone, sqlx::FromRow)]
pub struct CreateCourse {
    pub teacher_id: i32,
    pub name: String,
    pub description: Option<String>,
    pub format: Option<String>,
    pub structure: Option<String>,
    pub duration: Option<String>,
    pub price: Option<i32>,
    pub language: Option<String>,
    pub level: Option<String>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct UpdateCourse {
    pub name: Option<String>,
    pub description: Option<String>,
    pub format: Option<String>,
    pub structure: Option<String>,
    pub duration: Option<String>,
    pub price: Option<i32>,
    pub language: Option<String>,
    pub level: Option<String>,
}

impl TryFrom<web::Json<CreateCourse>> for CreateCourse {
    type Error = MyError;

    fn try_from(course: web::Json<CreateCourse>) -> Result<Self, Self::Error> {
        Ok(CreateCourse {
            teacher_id: course.teacher_id,
            name: course.name.clone(),
            description: course
                .description
                .clone(),
            format: course
                .format
                .clone(),
            structure: course
                .structure
                .clone(),
            duration: course
                .duration
                .clone(),
            price: course.price,
            language: course
                .language
                .clone(),
            level: course.level.clone(),
        })
    }
}

impl From<web::Json<UpdateCourse>> for UpdateCourse {
    fn from(course: web::Json<UpdateCourse>) -> Self {
        UpdateCourse {
            name: course.name.clone(),
            description: course
                .description
                .clone(),
            format: course
                .format
                .clone(),
            structure: course
                .structure
                .clone(),
            duration: course
                .duration
                .clone(),
            price: course.price,
            language: course
                .language
                .clone(),
            level: course.level.clone(),
        }
    }
}

更新业务逻辑

之后我们开始编写 db_access 相关的模块,因为实现了 From trait 和 TryFrom trait,所以我们可以直接把查询出来的数据转化成刚刚定义的数据结构。

在查询的时候使用了 query_as 这个宏,然后指定一个数据结构,当我们查询出内容时,会包装成指定的数据结构。

注意在进行更新数据的时候,我们的逻辑是,先查询出当前 id 对应的数据,如果没有这条数据则报错,之后检索当前更新的数据,如果有任何一个字段不存在则填入数据库中的数据。

use crate::error::MyError;
use crate::models::course::{Course, CreateCourse, UpdateCourse};
use sqlx::mysql::MySqlPool;

pub async fn get_courses_for_teacher_db(
    pool: &MySqlPool,
    teacher_id: i32,
) -> Result<Vec<Course>, MyError> {
    let rows: Vec<Course> = sqlx::query_as!(
        Course,
        "SELECT * FROM course
        WHERE teacher_id = ?",
        teacher_id
    )
    .fetch_all(pool)
    .await?;
    Ok(rows)
}

pub async fn get_course_details_db(
    pool: &MySqlPool,
    teacher_id: i32,
    course_id: i32,
) -> Result<Course, MyError> {
    let row = sqlx::query_as!(
        Course,
        "SELECT * FROM course
            WHERE teacher_id = ? and id = ?",
        teacher_id,
        course_id
    )
    .fetch_optional(pool)
    .await?;

    if let Some(course) = row {
        Ok(course)
    } else {
        Err(MyError::NotFound("Course didn't founded".into()))
    }
}

pub async fn post_new_course_db(
    pool: &MySqlPool,
    new_course: CreateCourse,
) -> Result<Course, MyError> {
    let data = sqlx::query_as!(
        Course,
        "INSERT INTO course (teacher_id, name, description, format, structure, duration, price, language, level)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
        new_course.teacher_id, new_course.name, new_course.description,
        new_course.format, new_course.structure, new_course.duration,
        new_course.price, new_course.language, new_course.level
        )
    .execute(pool)
    .await?;

    let row = sqlx::query_as!(
        Course,
        "SELECT * FROM course
                WHERE id = ?",
        data.last_insert_id(),
    )
    .fetch_optional(pool)
    .await?;

    if let Some(course) = row {
        Ok(course)
    } else {
        Err(MyError::NotFound("Course didn't founded".into()))
    }
}

pub async fn delete_course_db(
    pool: &MySqlPool,
    teacher_id: i32,
    id: i32,
) -> Result<String, MyError> {
    let course_row = sqlx::query!(
        "DELETE FROM course where teacher_id = ? and id=?",
        teacher_id,
        id,
    )
    .execute(pool)
    .await?;
    Ok(format!("DeletedI{:?}record", course_row))
}

pub async fn update_course_details_db(
    pool: &MySqlPool,
    teacher_id: i32,
    id: i32,
    update_course: UpdateCourse,
) -> Result<Course, MyError> {
    let current_course_row = sqlx::query_as!(
        Course,
        "SELECT * FROM course where teacher_id=? and id=?",
        teacher_id,
        id
    )
    .fetch_one(pool)
    .await
    .map_err(|_err| MyError::NotFound("Course Id not found".into()))?;

    let name: String = if let Some(name) = update_course.name {
        name
    } else {
        current_course_row.name
    };
    let description: String = if let Some(description) = update_course.description {
        description
    } else {
        current_course_row
            .description
            .unwrap_or_default()
    };
    let format: String = if let Some(format) = update_course.format {
        format
    } else {
        current_course_row
            .format
            .unwrap_or_default()
    };
    let structure: String = if let Some(structure) = update_course.structure {
        structure
    } else {
        current_course_row
            .structure
            .unwrap_or_default()
    };
    let duration: String = if let Some(duration) = update_course.duration {
        duration
    } else {
        current_course_row
            .duration
            .unwrap_or_default()
    };
    let level: String = if let Some(level) = update_course.level {
        level
    } else {
        current_course_row
            .level
            .unwrap_or_default()
    };
    let language: String = if let Some(language) = update_course.language {
        language
    } else {
        current_course_row
            .language
            .unwrap_or_default()
    };
    let price: i32 = if let Some(price) = update_course.price {
        price
    } else {
        current_course_row
            .price
            .unwrap_or_default()
    };
    let course_row = sqlx::query_as!(
        Course,
        "UPDATE course SET name = ?, description = ?, format = ?,
            structure = ?, duration = ?, price = ?, language = ?,
            level = ? where teacher_id = ? and id = ?",
        name,
        description,
        format,
        structure,
        duration,
        price,
        language,
        level,
        teacher_id,
        id
    )
    .execute(pool)
    .await;
    if let Ok(course) = course_row {
        let row = sqlx::query_as!(
            Course,
            "SELECT * FROM course
                WHERE id = ?",
            course.last_insert_id(),
        )
        .fetch_optional(pool)
        .await?;
        if let Some(course) = row {
            Ok(course)
        } else {
            Err(MyError::NotFound("Course didn't founded".into()))
        }
    } else {
        Err(MyError::NotFound("Course id not found".into()))
    }
}

最后我们将数据库操作和事务逻辑绑定起来

use crate::db_access::course::*;
use crate::error::MyError;
use crate::models::course::{CreateCourse, UpdateCourse};
use crate::state::AppState;
use actix_web::{web, HttpResponse};

pub async fn post_new_course(
    new_course: web::Json<CreateCourse>,
    app_state: web::Data<AppState>,
) -> Result<HttpResponse, MyError> {
    post_new_course_db(&app_state.db, new_course.try_into()?)
        .await
        .map(|course| HttpResponse::Ok().json(course))
}

pub async fn get_courses_for_teacher(
    app_state: web::Data<AppState>,
    params: web::Path<i32>,
) -> Result<HttpResponse, MyError> {
    // let teacher_id = i32::try_from(params.0).unwrap();
    let teacher_id = params.into_inner();
    get_courses_for_teacher_db(&app_state.db, teacher_id)
        .await
        .map(|courses| HttpResponse::Ok().json(courses))
}

pub async fn get_course_detail(
    app_state: web::Data<AppState>,
    params: web::Path<(i32, i32)>,
) -> Result<HttpResponse, MyError> {
    // let teacher_id = i32::try_from(params.0).unwrap();
    // let course_id = i32::try_from(params.1).unwrap();
    let (teacher_id, course_id) = params.into_inner();
    get_course_details_db(&app_state.db, teacher_id, course_id)
        .await
        .map(|course| HttpResponse::Ok().json(course))
}

pub async fn delete_course(
    app_state: web::Data<AppState>,
    params: web::Path<(i32, i32)>,
) -> Result<HttpResponse, MyError> {
    let (teacher_id, course_id) = params.into_inner();
    delete_course_db(&app_state.db, teacher_id, course_id)
        .await
        .map(|resp| HttpResponse::Ok().json(resp))
}

pub async fn update_course_details(
    app_state: web::Data<AppState>,
    update_course: web::Json<UpdateCourse>,
    params: web::Path<(i32, i32)>,
) -> Result<HttpResponse, MyError> {
    let (teacher_id, course_id) = params.into_inner();
    update_course_details_db(&app_state.db, teacher_id, course_id, update_course.into())
        .await
        .map(|course| HttpResponse::Ok().json(course))
}

最后我们将编写的方法绑定到路由中:

pub fn course_routes(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/courses")
            .route("/", web::post().to(post_new_course))
            .route("/{teacher_id}", web::get().to(get_courses_for_teacher))
            .route(
                "/{teacher_id}/{course_id}",
                web::get().to(get_course_detail),
            )
            .route("/{teacher_id}/{course_id}", web::delete().to(delete_course))
            .route(
                "/{teacher_id}/{course_id}",
                web::put().to(update_course_details),
            ),
    );
}

创建老师的增删改查

在完成了课程的增删改查之后,我们可以继续添加新的模块,比如我们可以对老师进行增删改查,我们新建一个表:

DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `picture_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `profile` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES (2, 'zhangshuai', 'www.baidu.com', 'test');
INSERT INTO `teacher` VALUES (3, 'zhangshuai', 'www.baidu.com', 'test');
INSERT INTO `teacher` VALUES (4, 'zhangshuai', 'www.baidu.com', 'test');
INSERT INTO `teacher` VALUES (5, 'zhangshuai', 'www.baidu.com', 'test');

SET FOREIGN_KEY_CHECKS = 1;

之后我们依次新增 数据结构、数据库方法、事务操作、路由、最后绑定到主函数中,这样我们就有了两个独立的模块,完整的代码可以查看作者的 git :

https://github.com/aiai0603/rust_web_mysql

POST的校验

现在我们有一个完整的增删改查系统了,但是有个问题就是我们的post 请求传递来的数据可能不是合法的内容,他不一定是我们需要的 json 信息,那么我们可以在自定义错误里新增一条来定义这个错误,对于这个错误我们返回一个 BAD_REQUEST 的错误代码

pub enum MyError {
    DBError(String),
    ActixError(String),
    NotFound(String),
    InvalidInput(String),
}

#[derive(Debug, Serialize)]
pub struct MyErrorResponse {
    error_message: String,
}

impl MyError {
    fn error_response(&self) -> String {
        match self {
            MyError::DBError(msg) => {
                println!("Database error occurred: {:?}", msg);
                "Database error".into()
            }
            MyError::ActixError(msg) => {
                println!("Server error occurred: {:?}", msg);
                "Internal server error".into()
            }
            MyError::NotFound(msg) => {
                println!("Not found error occurred: {:?}", msg);
                msg.into()
            }
            MyError::InvalidInput(msg) => {
                println!("Invalid Input error occurred: {:?}", msg);
                msg.into()
            }
        }
    }
}
impl error::ResponseError for MyError {
    fn status_code(&self) -> StatusCode {
        match self {
            MyError::DBError(_) | MyError::ActixError(_) => StatusCode::INTERNAL_SERVER_ERROR,
            MyError::NotFound(_) => StatusCode::NOT_FOUND,
            MyError::InvalidInput(_) => StatusCode::BAD_REQUEST,
        }
    }

    fn error_response(&self) -> HttpResponse {
        HttpResponse::build(self.status_code()).json(MyErrorResponse {
            error_message: self.error_response(),
        })
    }
}

之后我们在主函数里加入这个错误的注册:我们在收到数据的时候,查看传递来的数据是不是 json 格式或者不合法的 json 数据,如果是的话,直接返回我们的 InvalidInput 错误,而不再进行路由的处理了

let app = move || {
        App::new()
            .app_data(shared_data.clone())
            .app_data(web::JsonConfig::default().error_handler(|_err, _req| {
                MyError::InvalidInput(" please  provide valid json input".to_string()).into()
            }))
            .configure(general_routes)
            .configure(course_routes)
            .configure(teacher_routes)
    };
<think>好的,我现在需要帮用户回答如何通过LevelDB库做一些有趣的小demo来学习这个项目。首先,用户之前已经询问过嵌入式键值数据库的设计要求,现在他们可能想通过实际操作来深入理解LevelDB的具体应用和机制。 我需要先确定用户的背景。他们可能是开发者,有一定编程经验,但对LevelDB还不熟悉,想通过实践项目来学习。用户可能希望这些demo既能展示LevelDB的功能,又能帮助理解其内部工作原理,比如存储引擎、数据持久化、性能优化等。 接下来,我需要考虑哪些demo既简单又有趣,同时涵盖LevelDB的关键特性。比如,简单的键值存储操作是基础,但可能不够有趣。可以考虑一些实际应用场景,比如日志系统、缓存机制,或者结合其他技术如HTTP服务器,这样用户能看到LevelDB在真实环境中的应用。 同时,用户可能对LevelDB的高级功能感兴趣,比如批量写入、压缩、前缀扫描等。需要设计一些demo来展示这些特性,比如实现一个简单的数据库事务,或者性能测试比较不同配置的效果。此外,结合其他工具或语言绑定,比如Python,可能降低入门门槛,让用户更容易上手。 还需要考虑用户可能遇到的难点,比如编译和配置LevelDB的环境,或者在多线程环境下处理并发写入。在建议demo时,可能需要提醒用户注意这些方面,或者提供一些解决思路,比如使用线程池或锁机制。 另外,用户可能希望通过这些demo学习如何优化LevelDB的性能,比如调整参数或使用布隆过滤器。因此,设计一个性能调优的demo,比较不同配置下的读写速度,会很有帮助。 最后,总结这些demo需要覆盖的主要知识点,并确保它们由浅入深,帮助用户逐步掌握LevelDB的使用和原理。可能需要列出每个demo的学习目标,比如理解LSM-Tree结构、数据持久化机制、并发处理等,这样用户在学习每个项目时能明确目标。</think>通过 LevelDB 库可以设计许多有趣的小 Demo,既能学习其核心 API 和存储机制,又能探索其在实际场景中的应用。以下是一些适合学习的实践方向及具体示例: --- ### **1. 基础键值存储操作** **目标**:熟悉 LevelDB 的增删改查、批量写入和迭代器机制。 **Demo 示例**: - **实现一个命令行 KV 工具**: 支持 `put key value`、`get key`、`delete key`、`scan prefix` 等命令,使用 LevelDB 的 `Put/Get/Delete` 接口和 `Iterator` 实现范围扫描。 ```cpp leveldb::DB* db; leveldb::Options options; options.create_if_missing = true; leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db); // 写入 db->Put(leveldb::WriteOptions(), "name", "Alice"); // 读取 std::string value; db->Get(leveldb::ReadOptions(), "name", &value); // 前缀扫描 leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions()); for (it->Seek("user_"); it->Valid() && it->key().starts_with("user_"); it->Next()) { std::cout << it->key().ToString() << ": " << it->value().ToString() << std::endl; } ``` --- ### **2. 日志存储系统** **目标**:利用 LevelDB 的高写入性能,实现顺序日志的持久化存储。 **Demo 示例**: - **日志记录器**: 将日志按时间戳作为 Key(如 `timestamp:log_level:message`)写入 LevelDB,支持按时间范围检索日志。 ```python # 使用 Python 绑定(plyvel) import plyvel import time db = plyvel.DB('/tmp/logdb', create_if_missing=True) timestamp = int(time.time() * 1000) db.put(f'{timestamp}:INFO:User login'.encode(), b'User Alice logged in') # 查询最近 5 分钟的日志 start_key = f'{int(time.time() - 300)*1000}:'.encode() end_key = f'{int(time.time())*1000 + 1}:'.encode() for key, value in db.iterator(start=start_key, stop=end_key): print(key.decode(), value.decode()) ``` --- ### **3. 缓存系统** **目标**:结合内存缓存(如 LRU)和 LevelDB 持久化,实现二级缓存。 **Demo 示例**: - **LRU + LevelDB 的混合缓存**: 内存缓存热点数据,冷数据自动存入 LevelDB。 ```cpp class HybridCache { public: HybridCache(size_t lru_size) : lru_(lru_size) {} std::string Get(const std::string& key) { auto val = lru_.get(key); if (val.empty()) { db_->Get(leveldb::ReadOptions(), key, &val); lru_.put(key, val); // 回填到 LRU } return val; } void Put(const std::string& key, const std::string& value) { lru_.put(key, value); db_->Put(leveldb::WriteOptions(), key, value); } private: LRUCache lru_; // 内存缓存 leveldb::DB* db_; // LevelDB 实例 }; ``` --- ### **4. 简单数据库事务** **目标**:利用 `WriteBatch` 实现原子性操作。 **Demo 示例**: - **账户转账事务**: 批量更新两个账户余额,确保原子提交或回滚。 ```cpp leveldb::WriteBatch batch; batch.Put("account_A", "950"); // 原余额 1000,转出 50 batch.Put("account_B", "1050"); // 原余额 1000,转入 50 leveldb::Status s = db->Write(leveldb::WriteOptions(), &batch); if (!s.ok()) { // 事务回滚或重试逻辑 } ``` --- ### **5. 性能测试工具** **目标**:对比 LevelDB 在不同参数下的读写性能。 **Demo 示例**: - **Benchmark 工具**: 测试不同 `write_buffer_size` 或 `compression` 配置对写入速度的影响。 ```cpp leveldb::Options options; options.write_buffer_size = 4 * 1024 * 1024; // 调整写入缓冲区大小 options.compression = leveldb::kSnappyCompression; // 启用压缩 // 写入 10 万条随机 Key-Value,统计耗时 auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < 100000; ++i) { db->Put(leveldb::WriteOptions(), std::to_string(i), "data..."); } auto end = std::chrono::high_resolution_clock::now(); std::cout << "Time: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << " ms"; ``` --- ### **6. 结合网络服务** **目标**:将 LevelDB 嵌入 HTTP 服务,实现 RESTful API。 **Demo 示例**: - **简易 HTTP 键值服务**: 使用 C++ 的 [cpp-httplib](https://github.com/yhirose/cpp-httplib) 库暴露 API: ```cpp httplib::Server svr; // GET /key/:id svr.Get("/key/:id", [&](const httplib::Request& req, httplib::Response& res) { std::string value; leveldb::Status s = db->Get(leveldb::ReadOptions(), req.path_params.at("id"), &value); res.set_content(s.ok() ? value : "Key not found", "text/plain"); }); // POST /key svr.Post("/key", [&](const httplib::Request& req, httplib::Response& res) { auto json = nlohmann::json::parse(req.body); db->Put(leveldb::WriteOptions(), json["key"], json["value"]); res.set_content("OK", "text/plain"); }); svr.listen("0.0.0.0", 8080); ``` --- ### **7. 数据备份与恢复** **目标**:利用 LevelDB 的快照(Snapshot)或 `ldb` 工具实现数据迁移。 **Demo 示例**: - **定期备份数据库**: 调用 `CreateSnapshot` 或直接复制数据库目录: ```bash # 使用 LevelDB 自带的 ldb 工具导出数据 ldb --db=/tmp/mydb dump > mydb_dump.txt # 恢复数据 ldb --db=/tmp/newdb load < mydb_dump.txt ``` --- ### **8. 学习 LSM-Tree 机制** **目标**:通过手动触发 Compaction 或观察 SSTable 文件变化,理解 LevelDB 存储引擎。 **Demo 示例**: - **监控 SSTable 生成**: 1. 设置 `options.max_file_size = 2MB` 限制 MemTable 大小。 2. 持续写入数据直到触发 MemTable 转 SSTable(Level-0)。 3. 使用 `ldb` 工具查看生成的 `.ldb` 文件内容: ```bash ldb --db=/tmp/testdb scan --from=0 --to=z ``` --- ### **9. 多线程并发测试** **目标**:验证 LevelDB 在多线程环境下的稳定性。 **Demo 示例**: - **并发写入冲突测试**: 启动多个线程同时写入不同 Key,观察是否出现数据损坏: ```cpp void write_thread(leveldb::DB* db, int thread_id) { for (int i = 0; i < 1000; ++i) { std::string key = "thread" + std::to_string(thread_id) + "_" + std::to_string(i); db->Put(leveldb::WriteOptions(), key, "data"); } } // 启动 10 个线程 std::vector<std::thread> threads; for (int i = 0; i < 10; ++i) { threads.emplace_back(write_thread, db, i); } ``` --- ### **10. 结合 Bloom Filter** **目标**:通过调整 `options.filter_policy` 优化查询性能。 **Demo 示例**: - **启用布隆过滤器加速查询**: 在打开数据库时设置布隆过滤器,对比查询耗时差异: ```cpp leveldb::Options options; options.filter_policy = leveldb::NewBloomFilterPolicy(10); // 每 Key 10 位 leveldb::DB::Open(options, "/tmp/testdb", &db); // 测试存在性查询(如查询不存在的 Key) auto start = std::chrono::high_resolution_clock::now(); db->Get(leveldb::ReadOptions(), "non_existent_key", &value); auto end = std::chrono::high_resolution_clock::now(); ``` --- ### **学习建议** 1. **从简单到复杂**:先完成基础增删改查,再逐步实现事务、性能优化等功能。 2. **结合源码**:通过调试 LevelDB 源码(如 `db_impl.cc`、`version_set.cc`)理解 Compaction 和 SSTable 管理逻辑。 3. **可视化工具**:使用 `leveldb-rs`(Rust 实现)的 Web 界面或自定义工具观察数据分布。 4. **对比其他数据库**:与 RocksDB(LevelDB 优化版)、SQLite 对比设计差异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼老萌新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值