redis学习笔记(八)—— C++ 操作 Redis

redis-plus-plus 库

C++ 操作 Redis 的库有很多,这里使用 redis-plus-plus 库

Github 地址: https://github.com/sewenew/redis-plus-plus

安装 hiredis

redis-plus-plus 是基于 hiredis 实现的(hiredis 是一个 C语言实现的 redis 客户端)

shell 复制代码
apt install libhiredis-dev  # Ubuntu
yum install hiredis-devel.x86_64 # Centos

下载 redis-plus-plus 源码

shell 复制代码
git clone https://github.com/sewenew/redis-plus-plus.git

编译安装 redis-plus-plus

Ubuntu

shell 复制代码
cd redis-plus-plus # 进入redis-plus-plus 目录
make bulid
cd bulid
cmake ..
make
make install

这里使用 cmake 编译,安装 cmake

shell 复制代码
sudo apt install cmake

构建完成后,在 /usr/local/include/ 中会存在 sw 目录,内部包含 redis-plus-plus 的一些列头文件;

在 /usr/local/lib/ 中存在一系列 libredis 库文件

使用 redis-plus-plus 库

在使用 g++ 编译或者编写 makefile 时,要使用redis-plus-plus 库,g++就要带上库文件的路径(redis-plus-plus、hiredis)

makefile 复制代码
test:test.cc
	g++ -o $@ $^ /usr/local/lib/libredis++.a /usr/lib/x86_64-linux-gnu/libhiredis.a

测试:ping命令

使用redis-plus-plus操作 redis 数据库:创建 sw::redis::Redis 对象,调用成员函数

以 ping 命令为例,测试 redis服务器的连通性

cpp 复制代码
#include <iostream>
#include <string>
#include <sw/redis++/redis++.h>
int main()
{
    sw::redis::Redis redis("tcp://127.0.0.1:6379"); // 创建 Redis 对象
    std::string result = redis.ping();
    std::cout << result << std::endl;
    return 0;
}

Redis 构造函数:参数是一个字符串,表示 url(协议名://IP地址:端口号)

redis 客户端与服务器端,通信使用的是 tcp协议,默认端口号为 6379(配置文件中可修改)
redis-plus-plus 库使用起来还是非常简单的,创建sw::redis::Redis,调用其成员函数即可。

通用命令

set

cpp 复制代码
bool set(const StringView &key,
         const StringView &val,
         bool keepttl,
         UpdateType type = UpdateType::ALWAYS);
bool set(const StringView &key, const StringView &val,
		const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
         UpdateType type = UpdateType::ALWAYS);

参数

  • key-val :要设置的 key-val 键值对
  • keepttl : 在更新一个已存在的 key 的值时,对于原本设置的过期时间,是否保留
  • type : 表示更新策略,ALWAYS (总是更新)、IF_EXISTS (键存在时更新,相当于 XX 选项)、IF_NOT_EXISTS (剪枝不存在时更新,相当于 NX 选项)、 NEVER(从不更新)
  • ttl : 在设置 k-v 键值对时,设置过期时间

StringView : 只读字符串

get

cpp 复制代码
sw::redis::OptionalString sw::redis::Redis::get(const sw::redis::StringView &key);

参数 :要获取的 key

返回值:sw::redis::OptionalString

在 redis 中执行 get 命令,当key不存在时,返回 nil

这里 redis-plus-plus 进行了封装,当结果为 nil 时,就无法从 OptionalString 中获取值(通过调用成员方法 value,获取其中的字符串);

OptionalString 还重载了 operator bool,可以向bool值那样被使用(当结果为 nil 时,OptionalString就为false

exists

cpp 复制代码
long long exists(const sw::redis::StringView &key);
long long exists<T>(std::initializer_list<T> il);  // 初始化列表
long long exists<Input>(Input first, Input last);  // 迭代器区间

参数

exists 可以删除一个 key,同时也可以删除多个 key;

在传递参数 时,可以只传递一个 key ,也可以使用**初始化列表****,还可以使用一段 迭代器区间

返回值 : 存在的key 的个数

del

cpp 复制代码
long long del(const sw::redis::StringView &key);
long long del<T>(std::initializer_list<T> il);
long long del<Input>(Input first, Input last);

参数:和 exists 一样,可以传递一个 key,初始化列表,迭代器区间

返回值:删除的key 的个数

使用实例

cpp 复制代码
void test1(Redis &redis)
{
    redis.flushall(); // 清空redis数据
    redis.set("key1", "111");
    redis.set("key2", "222");
    redis.set("key3", "333");
    
    std::string key1 = "key1";
    auto result1 = redis.get(key1);
    if (result1)
        cout << "get " << key1 << " - " << result1.value() << endl;
    else
        cout << key1 << " 不存在" << endl;
    std::string key2 = "key4";
    auto result2 = redis.get(key2);
    if (result2)
        cout << "get " << key2 << " - " << result2.value() << endl;
    else
        cout << key2 << " 不存在" << endl;
    
    auto exist1 = redis.exists("key2");
    cout << "exists1 : " << exist1 << endl;
    auto exist2 = redis.exists({"key1", "key2", "key3", "key4", "key5"});
    cout << "exists2 : " << exist2 << endl;
    std::vector<std::string> keys = {"key1", "key2", "key3", "key4", "key5"};
    auto exist3 = redis.exists(keys.begin(), keys.end());
    cout << "exists3 : " << exist3 << endl;  
}

expire

cpp 复制代码
bool expire(const sw::redis::StringView &key, const std::chrono::seconds &timeout);
bool expire(const sw::redis::StringView &key, long long timeout);

参数 : 给一个 key 设置过期时间,可以直接传递一个整数(单位:秒);也可以使用 C++ 标准库中的时间间隔类型

返回值:表示是否设置成功

ttl

cpp 复制代码
long long ttl(const sw::redis::StringView &key)

返回值:返回对应 key 的剩余过期时间

keys

cpp 复制代码
void keys<Output>(const sw::redis::StringView &pattern, Output output)

参数

pattern :通配表达式,*表示匹配任意多个字符、?表示匹配任意一个字符 ...

output : 插入迭代器,keys 获取的结果都插入到指定位置

在使用的过程中,通过辅助函数构造迭代器即可。(std::back_inserter、std::font_inserter、std::insert_iterator)

type

cpp 复制代码
std::string sw::redis::Redis::type(const sw::redis::StringView &key)

获取 key 对应 value 的数据类型

使用实例

cpp 复制代码
void test2(Redis &redis)
{
    redis.flushall();
    redis.set("key1", "111");
    redis.set("key2", "222");
    redis.set("key3", "333");

    // redis.expire("key1",3);
    redis.expire("key1", std::chrono::seconds(3));
    auto ttl1 = redis.ttl("key1");
    cout << ttl1 << endl;

    std::vector<std::string> out;
    redis.keys("*", std::back_inserter(out));
    for (auto &e : out)
        cout << e << endl;
    auto type = redis.type("key1");
    cout << type << endl;
}

string 相关操作

set/get

cpp 复制代码
bool set(const StringView &key,
         const StringView &val,
         bool keepttl,
         UpdateType type = UpdateType::ALWAYS);
bool set(const StringView &key, const StringView &val,
		const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
         UpdateType type = UpdateType::ALWAYS);
sw::redis::OptionalString sw::redis::Redis::get(const sw::redis::StringView &key);

mset/mget

cpp 复制代码
void mset<T>(std::initializer_list<T> il)
void mset<Input>(Input first, Input last)

mset 支持一次设置多个 key-value 键值对;

参数:可以是一个初始化列表(每一个元素都是一个 std::pair 类型),也可以是一段迭代器区间。

cpp 复制代码
void mget<T, Output>(std::initializer_list<T> il, Output output);
void mget<Input, Output>(Input first, Input last, Output output);

mget 获取多个 key 对应的 value 值;

参数

  • key : 初始化列表,或者一段迭代器区间
  • output : 插入迭代器,mget 获取的结果都插入到指定位置
cpp 复制代码
void test1(Redis &redis)
{
    redis.flushall();
    // redis.mset({std::make_pair("k1", "v1"), std::make_pair("k2", "v2"), std::make_pair("k3", "v3")});
    std::vector<std::pair<std::string,std::string>> vct  = {std::make_pair("k1", "v1"), std::make_pair("k2", "v2"), std::make_pair("k3", "v3")};
    redis.mset(vct.begin(),vct.end());
    std::vector<sw::redis::OptionalString> result;
    redis.mget({"k1","k2","k3","k4"},std::back_inserter(result));
    for(auto& e : result)
    {
        if(e)
            cout << e.value() << endl;
        else
            cout << "nil" << endl;
    }
}

mget 的返回值中可能存在 nil,这里使用 sw::redis::OptionalString 来存储结果

getrange/setrange

cpp 复制代码
std::string getrange(const sw::redis::StringView &key, long long start, long long end);
long long setrange(const sw::redis::StringView &key, long long offset, const sw::redis::StringView &val);

getrange 获得 key 对应 value(string类型)字符串中,区间[start, end]的子串

setrange 设置(替换) key 对应 value 字符串中,offset 位置后的子串(长度根据 val 长度而定)

incr/decr

cpp 复制代码
void test2(Redis &redis)
{
    redis.flushall();
    redis.set("key", "hello world!!!");
    std::string result = redis.getrange("key", 5, 10);
    cout << result << endl;
    redis.setrange("key1", 6, "redis");
    result = redis.getrange("key1", 0, -1);
    cout << result << endl;

    redis.set("key2", "100");
    redis.incr("key2");        // +1
    redis.incrby("key2", 100); // +100
    auto a = redis.get("key2");
    if (a)
        cout << a.value() << endl;
}

例如 decr/decrby/incrbyfloat 相关的API,这里就不一一展示的,redis-plus-plus 在函数命名上都与命令保持一致,使用起来非常方便。

hash 相关操作

hset/hget/hmset/hmget

hset

cpp 复制代码
long long hset(const sw::redis::StringView &key, const std::pair<sw::redis::StringView, sw::redis::StringView> &item);
long long hset<T>(const sw::redis::StringView &key, std::initializer_list<T> il);
long long hset(const sw::redis::StringView &key, const sw::redis::StringView &field, const sw::redis::StringView &val);
std::enable_if<!std::is_convertible<Input, sw::redis::StringView>::value, long long>::type hset<Input>(const sw::redis::StringView &key, Input first, Input last);

参数

  • key: sw::redis::StringView 只读字符串
  • val:std::pair 类型的键值对;初始化列表(其中可以是{"filed","value"},也可以是一个个的键值对);field、value;还可以是一段初始化列表

返回值:返回设置成功的键值对个数

这里 hset 可以设置多个 field-value 键值对,在初始化列表和迭代器区间中就可以是一个个的std::pair 键值对。

hmset

cpp 复制代码
void hmset<T>(const sw::redis::StringView &key, std::initializer_list<T> il);
void hmset<Input>(const sw::redis::StringView &key, Input first, Input last);

hget

cpp 复制代码
sw::redis::OptionalString hget(const sw::redis::StringView &key, const sw::redis::StringView &field)

根据 key、field 获取对应的 value 值

返回值:sw::redis::OptionalString 类型(返回值可能为 nil)

hmget

cpp 复制代码
void hmget<T, Output>(const sw::redis::StringView &key, std::initializer_list<T> il, Output output);
void hmget<Input, Output>(const sw::redis::StringView &key, Input first, Input last, Output output);

hmget,获取多个field 对应的 value值(获取的value值可能为 nil

cpp 复制代码
void test(Redis &redis)
{
    redis.flushall();
    // hset hget
    redis.hset("key", std::make_pair("f1", "v1"));
    // redis.hset("key",{"f2","v2"});
    redis.hset("key", {std::make_pair("f2", "v2"), std::make_pair("f3", "v3")});
    redis.hset("key", "f4", "v4");
    auto value = redis.hget("key", "f1");
    if (value)
        cout << value.value() << endl;
    // hmset、hmget
    // redis.hmset("key", {std::make_pair("aaa", "111"), std::make_pair("bbb", "222"), std::make_pair("ccc", "333")});
    std::vector<std::pair<std::string, std::string>> fileds = {std::make_pair("aaa", "111"), std::make_pair("bbb", "222"), std::make_pair("ccc", "333")};
    redis.hmset("key", fileds.begin(), fileds.end());
    std::vector<sw::redis::OptionalString> result;
    redis.hmget("key", {"aaa", "bbb", "ccc", "ddd", "eee"}, std::back_inserter(result));
    for (auto &e : result)
        if (e)
            cout << e.value() << endl;
}

hkeys/hvals/hgetall

hkeys:获取 key 对应的 hash 表中,所有的filed

cpp 复制代码
void hkeys<Output>(const sw::redis::StringView &key, Output output);

hvals:获取 key 对应的 hash 表中所有的 value

cpp 复制代码
void hvals<Output>(const sw::redis::StringView &key, Output output)

hgetall:获取 key 对应 hash 表中的所有 key-value 键值对

cpp 复制代码
void hgetall<Output>(const sw::redis::StringView &key, Output output)
cpp 复制代码
void test2(Redis &redis)
{
    redis.flushall();
    redis.hset("key", {std::make_pair("aaa", "111"), std::make_pair("bbb", "222"), std::make_pair("ccc", "333")});
    std::vector<std::string> keys;
    redis.hkeys("key", std::back_inserter(keys));
    cout << "hkeys : " << endl;
    for (auto &e : keys)
        cout << e << endl;
    std::vector<std::string> vals;
    redis.hvals("key", std::back_inserter(vals));
    cout << "hvals : " << endl;
    for (auto &e : vals)
        cout << e << endl;

    std::vector<std::pair<std::string, std::string>> all;
    redis.hgetall("key", std::back_inserter(all));
    cout << "hgetall : " << endl;
    for (auto &e : all)
        cout << e.first << " : " << e.second << endl;
}

list 相关操作

lpush/lpop/lrpush/rpop

lpush

cpp 复制代码
long long lpush(const sw::redis::StringView &key, const sw::redis::StringView &val);
long long lpush<T>(const sw::redis::StringView &key, std::initializer_list<T> il);
long long lpush<Input>(const sw::redis::StringView &key, Input first, Input last);

lpush 从 list 左侧插入数据,可以依次插入多个数据。

lpop

cpp 复制代码
sw::redis::OptionalString lpop(const sw::redis::StringView &key);

lpp 从 list 左侧删除一个数据。

rpush/rpop

cpp 复制代码
long long rpush(const sw::redis::StringView &key, const sw::redis::StringView &val);
long long rpush<T>(const sw::redis::StringView &key, std::initializer_list<T> il);
long long rpush<Input>(const sw::redis::StringView &key, Input first, Input last);
sw::redis::OptionalString rpop(const sw::redis::StringView &key);

lrange

cpp 复制代码
void lrange<Output>(const sw::redis::StringView &key, long long start, long long stop, Output output);

获取 list 指定区间的元素。(索引支持负数)

使用实例

cpp 复制代码
void test1(Redis &redis)
{
    redis.flushall();
    redis.lpush("key", {"111", "222", "333", "444", "555"});
    redis.lpop("key");
    std::vector<std::string> result;
    redis.lrange("key", 0, -1, std::back_inserter(result));
    for (auto &e : result)
        cout << e << endl;
}

lindex / llen / linsert / lrem

lindex 根据索引获取 list 中对应元素

cpp 复制代码
sw::redis::OptionalString lindex(const sw::redis::StringView &key, long long index);

llen 获取 list 的长度(元素个数)

cpp 复制代码
long long llen(const sw::redis::StringView &key);

linsert:在值 pivot 之前或者之后,插入值 val

cpp 复制代码
long long linsert(const sw::redis::StringView &key, sw::redis::InsertPosition position, const sw::redis::StringView &pivot, const sw::redis::StringView &val);

lrem:删除 list 中的元素 val(支持删除多个)

cpp 复制代码
long long lrem(const sw::redis::StringView &key, long long count, const sw::redis::StringView &val);

count

  • >0 :从左到右,依次删除 |count| 个元素 val
  • =0 :删除所有的元素 val
  • <0:从右到左,依次删除 |count| 个元素 val

使用实例

cpp 复制代码
void test2(Redis &redis)
{
    redis.flushall();
    redis.lpush("key", {"111", "222", "222", "333", "111"});
    // 获取索引 2 对应的元素
    auto index = redis.lindex("key", 2);
    if (index)
        cout << index.value() << endl;
    // 获取 list 中的元素个数
    int len = redis.llen("key");
    // 在元素 111 之后插入元素 999
    redis.linsert("key", sw::redis::InsertPosition::AFTER, "111", "999");
    // 从左往右,删除一个元素 111
    redis.lrem("key",1,"111");
    // 删除所有的 222
    redis.lrem("key",0,"222");

    std::vector<std::string> members;
    redis.lrange("key",0,-1,std::back_inserter(members));
    cout << "list members : ";
    for(auto& e : members)  cout << e << " ";
    cout << endl;
}

blpop/brpop

阻塞版本的 lpop/rpop

cpp 复制代码
sw::redis::OptionalStringPair blpop(const sw::redis::StringView &key, const std::chrono::seconds &timeout = std::chrono::seconds{0});
sw::redis::OptionalStringPair blpop(const sw::redis::StringView &key, long long timeout);
sw::redis::OptionalStringPair blpop<T>(std::initializer_list<T> il, const std::chrono::seconds &timeout = std::chrono::seconds{0});
sw::redis::OptionalStringPair blpop<T>(std::initializer_list<T> il, long long timeout);
sw::redis::OptionalStringPair blpop<Input>(Input first, Input last, const std::chrono::seconds &timeout = std::chrono::seconds{0});
sw::redis::OptionalStringPair blpop<Input>(Input first, Input last, long long timeout);

blpop 可以阻塞等待多个 key,也可以设置 timeout (阻塞时间)

brpop 和 blpop 方法使用一样,只是删除的位置不同。

set 相关操作

sadd

cpp 复制代码
long long sw::redis::Redis::sadd(const sw::redis::StringView &key, const sw::redis::StringView &member);
inline long long sw::redis::Redis::sadd<const char *>(const sw::redis::StringView &key, std::initializer_list<const char *> il);
long long sw::redis::Redis::sadd<std::vector<std::string>::iterator>(const sw::redis::StringView &key, std::vector<std::string>::iterator first, std::vector<std::string>::iterator last);

sadd 向 set 中添加一个或者多个元素(member)

smembers

cpp 复制代码
void sw::redis::Redis::smembers<std::back_insert_iterator<std::vector<std::string>>>(const sw::redis::StringView &key, std::back_insert_iterator<std::vector<std::string>> output);

smembers 获取 set 当中的所有元素

spop / srandmember

spop 随机删除 set 中的一个元素并返回

srandmember 随机返回 set 中的一个元素

cpp 复制代码
sw::redis::OptionalString sw::redis::Redis::spop(const sw::redis::StringView &key);
sw::redis::OptionalString sw::redis::Redis::srandmember(const sw::redis::StringView &key);

使用实例

cpp 复制代码
void test1(Redis &redis)
{
    redis.flushall();
    redis.sadd("key", "111");
    redis.sadd("key", {"111", "222", "333"});
    vector<string> members = {"aaa", "bbb", "ccc"};
    redis.sadd("key", members.begin(), members.end());
    vector<string> out;
    redis.smembers("key", std::back_inserter(out));
    for (auto &e : out)
        cout << e << endl;
    auto randMember = redis.srandmember("key");
    if (randMember)
        cout << "randMenber : " << randMember.value() << endl;
    auto popMember = redis.spop("key");
    if (popMember)
        cout << "spop : " << popMember.value() << endl;
}

sinter/sunion/sdiff

对 set 求 交、并、差集

cpp 复制代码
void sinter<T, Output>(std::initializer_list<T> il, Output output);
void sinter<Input, Output>(Input first, Input last, Output output);
void sunion<T, Output>(std::initializer_list<T> il, Output output);
void sunion<Input, Output>(Input first, Input last, Output output);
void sdiff<T, Output>(std::initializer_list<T> il, Output output);
void sdiff<Input, Output>(Input first, Input last, Output output);

使用实例

cpp 复制代码
void test2(Redis &redis)
{
    redis.flushall();
    redis.sadd("k1", {"111", "222", "333"});
    redis.sadd("k2", {"222", "555", "888"});
    vector<string> sinter;
    redis.sinter({"k1", "k2"}, std::back_inserter(sinter));
    cout << "sinter : ";
    for (auto &e : sinter)
        cout << e << " ";
    cout << endl;
    vector<string> sunion;
    redis.sunion({"k1", "k2"}, std::back_inserter(sunion));
    cout << "sunion : ";
    for (auto &e : sunion)
        cout << e << " ";
    cout << endl;
    vector<string> sdiff;
    redis.sdiff({"k1", "k2"}, std::back_inserter(sdiff));
    cout << "sdiff : ";
    for (auto &e : sdiff)
        cout << e << " ";
    cout << endl;
}

此外例如 sinterstore、sunionstore、sdiffstore,将结果存储到指定 key 中,相比于 sinter/sunion/sdiff 参数多了一个 key(表示结果要存储的 key)

zset 相关操作

zadd

cpp 复制代码
long long zadd<T>(const sw::redis::StringView &key, std::initializer_list<T> il, sw::redis::UpdateType type = sw::redis::UpdateType::ALWAYS, bool changed = false);
long long zadd(const sw::redis::StringView &key, const sw::redis::StringView &member, double score, sw::redis::UpdateType type = sw::redis::UpdateType::ALWAYS, bool changed = false);
long long zadd<Input>(const sw::redis::StringView &key, Input first, Input last, sw::redis::UpdateType type = sw::redis::UpdateType::ALWAYS, bool changed = false);

zadd 向 zset 中添加元素(member 和 score),可以添加多个元素(初始化列表、迭代器区间)

此外还可以设置更新策略(ALWAYS、IF_EXISTS、IF_NOT_EXISTS、NEVER),这里更新是对于 score 的更新。

zrange

cpp 复制代码
void zrange<Output>(const sw::redis::StringView &key, long long start, long long stop, Output output);

zrange 获取 zset 中指定区间 [start, stop] 的元素;

注意:这里 插入迭代器 output,指向 string 就是获取 member;指向 std::pair<string,string> 类型就是获取 member 和 score。

使用实例

cpp 复制代码
void test1(Redis &redis)
{
    redis.flushall();
    redis.zadd("key", {std::make_pair("王者荣耀", "80"), std::make_pair("穿越火线", "95"), std::make_pair("和平精英", "90")});
    // vector<string> out;
    vector<string> members;
    redis.zrange("key", 0, -1, std::back_inserter(members));
    for (auto &e : members)
        cout << e << " ";
    cout << endl;

    vector<std::pair<string, string>> items;
    redis.zrange("key", 0, -1, std::back_inserter(items));
    for (auto &e : items)
        cout << e.first << " : " << e.second << endl;
}

zcard/zrem/zscore/zrank

zcard 获取 zset 中的元素个数

cpp 复制代码
long long zcard(const sw::redis::StringView &key);

zrem 根据 member 删除 zset 中元素

cpp 复制代码
long long zrem(const sw::redis::StringView &key, const sw::redis::StringView &member);

此外,还可以删除 zset 中 score 在指定区间的元素(zremrangebyscore );根据索引删除 zset 中的元素(zremrangebyrank

zscore:获取 zset 中指定 member 对应的 score

cpp 复制代码
sw::redis::OptionalDouble zscore(const sw::redis::StringView &key, const sw::redis::StringView &member);

zrank : 获取 zset 中元素 member 的索引

cpp 复制代码
sw::redis::OptionalLongLong zrank(const sw::redis::StringView &key, const sw::redis::StringView &member);

这里就不一一演示这些方法了,总体使用起来还是比较简单的;详细内容可以参考 redis 官网

本篇文章到这里就结束了,感谢支持

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

相关推荐
GLDbalala20 小时前
GPU PRO 5 - 1.3 Particle-Based Simulation of Material Aging 笔记
笔记
一定要AK20 小时前
CSS 入门到精通全章节学习笔记(含 CSS3 核心特性)
css·笔记·学习
red_redemption20 小时前
自由学习记录(157)
学习·pyenv管理python
敲代码的小小酥20 小时前
深入解析 Redis 热点 Key:从原理到企业级高可用架构实践
redis·架构
艾莉丝努力练剑20 小时前
【Linux线程】Linux系统多线程(三):Linux线程 VS 进程,线程控制
java·linux·运维·服务器·c++·学习·ubuntu
徒 花21 小时前
Python知识学习07
windows·python·学习
浮芷.21 小时前
Flutter 框架跨平台鸿蒙开发 - 急救指南应用
学习·flutter·华为·harmonyos·鸿蒙
航Hang*21 小时前
网络安全技术基础——第3章:网络攻击技术
运维·网络·笔记·安全·web安全·php
小红的布丁21 小时前
MySQL 和 Redis 数据一致性,以及 Redis 与 ZooKeeper 分布式锁对比
redis·分布式·mysql
嵌入式×边缘AI:打怪升级日志21 小时前
Linux 常用命令学习笔记(续):查找、压缩、vi 编辑器与其他命令
linux·笔记·学习