Rust在写库时实现缓存的操作方法

 更新时间:2024年01月06日 15:37:51   作者:Star-tears  
Moka是一个用于Rust的高性能缓存库,它提供了多种类型的缓存数据结构,包括哈希表、LRU(最近最少使用)缓存和 支持TTL(生存时间)缓存,这篇文章给大家介绍Rust在写库时实现缓存的相关知识,感兴趣的朋友一起看看吧

GPT4.0+Midjourney绘画+国内大模型 会员永久免费使用!
如果你想靠AI翻身,你先需要一个靠谱的工具!

Rust在写库时实现缓存

依赖

在写库时,实现一个缓存请求,需要用到全局变量,所以我们可以添加cratelazy_static

Cargo.toml添加以下依赖

1
2
3
4
5
6
[dependencies]
chrono = "0.4.31"
lazy_static = "1.4.0"
reqwest = { version = "0.11.23", features = ["blocking", "json"] }
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use std::{sync::Mutex, collections::HashMap};
use chrono::{DateTime, Utc};
use lazy_static::lazy_static;
use serde_json::Value;
lazy_static! {
    static ref REQUESTS_RESPONSE_CACHE: Mutex<HashMap<String, RequestsResponseCache>> =
        Mutex::new(HashMap::new());
}
pub struct RequestsResponseCache {
    pub response: Value,
    pub datetime: DateTime<Utc>,
}
pub fn get_requests_response_cache(url: &str) -> Result<Value, reqwest::Error> {
    let mut cache = REQUESTS_RESPONSE_CACHE.lock().unwrap();
    if let Some(cache_entry) = cache.get(url) {
        let elapsed = Utc::now() - cache_entry.datetime;
        if elapsed.num_seconds() > 3600 {
            let response: Value = reqwest::blocking::get(url)?.json()?;
            let res = response.clone();
            let cache_entry = RequestsResponseCache {
                response,
                datetime: Utc::now(),
            };
            cache.insert(url.to_string(), cache_entry);
            return Ok(res);
        }
    }
    let response: Value = reqwest::blocking::get(url)?.json()?;
    let res = response.clone();
    let cache_entry = RequestsResponseCache {
        response,
        datetime: Utc::now(),
    };
    cache.insert(url.to_string(), cache_entry);
    Ok(res)
}

使用了 lazy_static 宏创建了一个静态的全局变量 REQUESTS_RESPONSE_CACHE,这个全局变量是一个 Mutex 包裹的 HashMap,用来存储请求的响应缓存。这个缓存是线程安全的,因为被 Mutex 包裹了起来,这样就可以在多个线程中安全地访问和修改这个缓存。

接着定义了一个 RequestsResponseCache 结构体,用来表示缓存中的一个条目,其中包含了响应数据 response 和缓存的时间戳 datetime

然后定义了一个 get_requests_response_cache 函数,用来从缓存中获取请求的响应。它首先尝试从缓存中获取指定 url 的响应数据,如果缓存中有对应的条目,并且距离上次缓存的时间超过了 3600 秒(1 小时),则重新发起请求并更新缓存,然后返回响应数据。如果缓存中没有对应的条目,或者缓存的时间未超过 3600 秒,则直接发起请求并更新缓存,然后返回响应数据。

这样就提供了一个简单的请求响应的缓存功能,能够在需要时缓存请求的响应数据,并在一定时间内有效,从而减少对远程服务的重复请求,提高程序性能。

补充:

rust缓存库moka简介

关于moka

“Moka” 是一个用于 Rust 的高性能缓存库,它提供了多种类型的缓存数据结构,包括哈希表、LRU(最近最少使用)缓存和 支持TTL(生存时间)缓存。
以下是一些 “moka” 库的特点和功能:

  • 多种缓存类型: “moka” 提供了多种缓存类型,包括哈希表缓存、LRU 缓存和 TTL 缓存。你可以根据具体的需求选择适合的缓存类型。
  • 线程安全: “moka” 库是线程安全的,可以在多线程环境中使用,不需要额外的同步措施。
  • 高性能: “moka” 的设计目标之一是提供高性能的缓存实现。它经过优化,能够在高并发场景下快速处理缓存操作。
  • 可配置性: “moka” 允许你根据需要对缓存进行配置,如容量限制、缓存项的最大生存时间等。

moka的github地址:moka

moka的使用示例

1.事件通知:
支持在缓存项发生过期淘汰、用户主动淘汰、缓存池大小受限强制淘汰时,触发回调函数执行一些后续任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
use moka::{notification::RemovalCause, sync::Cache};
use std::time::{Duration,Instant};
fn main() {
    // 创建一个缓存项事件监听闭包
    let now = Instant::now();
    let listener = move |k, v: String, cause| {
        // 监听缓存项的触发事件,RemovalCause包含四种场景:Expired(缓存项过期)、Explicit(用户主动移除缓存)、Replaced(缓存项发生更新或替换)、Size(缓存数量达到上限驱逐)。
        println!(
            "== An entry has been evicted. time:{} k: {:?}, v: {:?},cause:{:?}",
            now.elapsed().as_secs(),
            k,
            v,
            cause
        );
        // 针对不同事项,进行处理。
        // match cause {
        //     RemovalCause::Expired => {}
        //     RemovalCause::Explicit => {}
        //     RemovalCause::Replaced => {}
        //     RemovalCause::Size => {}
        // }
    };
    //缓存生存时间:10s
    let ttl_time = Duration::from_secs(10);
    // 创建一个具有过期时间和淘汰机制的缓存
    let cache: Cache<String, String> = Cache::builder()
        .time_to_idle(ttl_time)
        .eviction_listener(listener)
        .build();
    // insert 缓存项
    cache.insert("key1".to_string(), "value1".to_string());
    cache.insert("key2".to_string(), "value2".to_string());
    cache.insert("key3".to_string(), "value3".to_string());
    // 5s后使用key1
    std::thread::sleep(Duration::from_secs(5));
    if let Some(value) = cache.get(&"key1".to_string()) {
        println!("5s: Value of key1: {}", value);
    }
    cache.remove("key3");
    println!("5s: remove key3");
    // 等待 6 秒,让缓存项key2过期
    std::thread::sleep(Duration::from_secs(6));
    // 尝试获取缓存项 "key1" 的值
    if let Some(value) = cache.get("key1") {
        println!("11s: Value of key1: {}", value);
    } else {
        println!("Key1 has expired.");
    }
    // 尝试获取缓存项 "key2" 的值
    if let Some(value) = cache.get("key2") {
        println!("11s: Value of key2: {}", value);
    } else {
        println!("Key2 has expired.");
    }
    // 尝试获取缓存项 "key3" 的值
    if let Some(value) = cache.get("key3") {
        println!("11s: Value of key3: {}", value);
    } else {
        println!("Key3 has removed.");
    }
    // 空置9s后
    std::thread::sleep(Duration::from_secs(11));
    // 再次尝试获取缓存项 "key1" 的值
    if let Some(value) = cache.get("key1") {
        println!("22s: Value of key1: {}", value);
    } else {
        println!("Key1 has expired.");
    }
}

运行结果:

5s: Value of key1: value1
== An entry has been evicted. time:5 k: "key3", v: "value3",cause:Explicit
5s: remove key3
== An entry has been evicted. time:10 k: "key2", v: "value2",cause:Expired
11s: Value of key1: value1
Key2 has expired.
Key3 has removed.
== An entry has been evicted. time:21 k: "key1", v: "value1",cause:Expired
Key1 has expired.

2.支持同步并发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
use moka::sync::Cache;
use std::thread;
fn value(n: usize) -> String {
    format!("value {}", n)
}
fn main() {
    const NUM_THREADS: usize = 3;
    const NUM_KEYS_PER_THREAD: usize = 2;
    // Create a cache that can store up to 6 entries.
    let cache = Cache::new(6);
    // Spawn threads and read and update the cache simultaneously.
    let threads: Vec<_> = (0..NUM_THREADS)
        .map(|i| {
            // To share the same cache across the threads, clone it.
            // This is a cheap operation.
            let my_cache = cache.clone();
            let start = i * NUM_KEYS_PER_THREAD;
            let end = (i + 1) * NUM_KEYS_PER_THREAD;
            thread::spawn(move || {
                // Insert 2 entries. (NUM_KEYS_PER_THREAD = 2)
                for key in start..end {
                    my_cache.insert(key, value(key));
                    println!("{}",my_cache.get(&key).unwrap());
                }
                // Invalidate every 2 element of the inserted entries.
                for key in (start..end).step_by(2) {
                    my_cache.invalidate(&key);
                }
            })
        })
        .collect();
    // Wait for all threads to complete.
    threads.into_iter().for_each(|t| t.join().expect("Failed"));
    // Verify the result.
    for key in 0..(NUM_THREADS * NUM_KEYS_PER_THREAD) {
        if key % 2 == 0 {
            assert_eq!(cache.get(&key), None);
        } else {
            assert_eq!(cache.get(&key), Some(value(key)));
        }
    }
}

结果:

value 2
value 3
value 0
value 4
value 1
value 5

并发读写cahce中的数据不会产生异常。

3.下面是moka库example中给出的异步示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
use moka::future::Cache;
#[tokio::main]
async fn main() {
    const NUM_TASKS: usize = 16;
    const NUM_KEYS_PER_TASK: usize = 64;
    fn value(n: usize) -> String {
        format!("value {}", n)
    }
    // Create a cache that can store up to 10,000 entries.
    let cache = Cache::new(10_000);
    // Spawn async tasks and write to and read from the cache.
    let tasks: Vec<_> = (0..NUM_TASKS)
        .map(|i| {
            // To share the same cache across the async tasks, clone it.
            // This is a cheap operation.
            let my_cache = cache.clone();
            let start = i * NUM_KEYS_PER_TASK;
            let end = (i + 1) * NUM_KEYS_PER_TASK;
            tokio::spawn(async move {
                // Insert 64 entries. (NUM_KEYS_PER_TASK = 64)
                for key in start..end {
                    // insert() is an async method, so await it.
                    my_cache.insert(key, value(key)).await;
                    // get() returns Option<String>, a clone of the stored value.
                    assert_eq!(my_cache.get(&key), Some(value(key)));
                }
                // Invalidate every 4 element of the inserted entries.
                for key in (start..end).step_by(4) {
                    // invalidate() is an async method, so await it.
                    my_cache.invalidate(&key).await;
                }
            })
        })
        .collect();
    // Wait for all tasks to complete.
    futures_util::future::join_all(tasks).await;
    // Verify the result.
    for key in 0..(NUM_TASKS * NUM_KEYS_PER_TASK) {
        if key % 4 == 0 {
            assert_eq!(cache.get(&key), None);
        } else {
            assert_eq!(cache.get(&key), Some(value(key)));
        }
    }
}

到此这篇关于Rust在写库时实现缓存的文章就介绍到这了,更多相关Rust缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://blog.csdn.net/qq_51173321/article/details/135287874

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • Rust生成随机数的项目实践

    Rust生成随机数的项目实践

    Rust标准库中并没有随机数生成器,常见的解决方案是使用rand包,本文主要介绍了Rust生成随机数的项目实践,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Rust调用C程序的实现步骤

    Rust调用C程序的实现步骤

    本文主要介绍了Rust调用C程序的实现步骤,包括创建C函数、编译C代码、链接Rust和C代码等步骤,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • 使用systemd部署r-nacos的操作方法

    使用systemd部署r-nacos的操作方法

    r-nacos是一个用rust实现的nacos服务,我们用它平替java nacos以降低服务占用内存,提升服务的稳定性,这篇文章主要介绍了使用systemd部署r-nacos,需要的朋友可以参考下
    2024-03-03
  • rust语言基础pub关键字及Some语法示例

    rust语言基础pub关键字及Some语法示例

    这篇文章主要为大家介绍了rust语言基础pub关键字及Some语法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • Rust使用Sled添加高性能嵌入式数据库

    Rust使用Sled添加高性能嵌入式数据库

    这篇文章主要为大家详细介绍了如何在Rust项目中使用Sled库,一个为Rust生态设计的现代、高性能嵌入式数据库,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • 深入探究在Rust中函数、方法和关联函数有什么区别

    深入探究在Rust中函数、方法和关联函数有什么区别

    在 Rust 中,函数、方法和关联函数都是用来封装行为的,它们之间的区别主要在于它们的定义和调用方式,本文将通过一个简单的rust代码示例来给大家讲讲Rust中函数、方法和关联函数区别,需要的朋友可以参考下
    2023-08-08
  • Rust duckdb和polars读csv文件比较情况

    Rust duckdb和polars读csv文件比较情况

    duckdb在数据分析上,有非常多不错的特质,1、快;2、客户体验好,特别是可以同时批量读csv在一个目录下的csv等文件,今天来比较下Rust duckdb和polars读csv文件比较的情况,感兴趣的朋友一起看看吧
    2024-06-06
  • Rust之模式与模式匹配的实现

    Rust之模式与模式匹配的实现

    Rust中的模式匹配功能强大且灵活,它极大地提高了代码的表达力和可读性,本文主要介绍了Rust之模式与模式匹配,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Rust 函数详解

    Rust 函数详解

    函数在 Rust 语言中是普遍存在的。Rust 支持多种编程范式,但更偏向于函数式,函数在 Rust 中是“一等公民”,函数可以作为数据在程序中进行传递,对Rust 函数相关知识感兴趣的朋友一起看看吧
    2021-11-11
  • Rust如何使用Sauron实现Web界面交互

    Rust如何使用Sauron实现Web界面交互

    Sauron 是一个多功能的 Web 框架和库,用于构建客户端和/或服务器端 Web 应用程序,重点关注人体工程学、简单性和优雅性,这篇文章主要介绍了Rust使用Sauron实现Web界面交互,需要的朋友可以参考下
    2024-03-03

最新评论