Redis 核心数据类型:从命令、结构到实战应用

文章目录

  • 一、前言
  • [二、String 字符串](#二、String 字符串)
    • [2.1 常用 String 命令](#2.1 常用 String 命令)
    • [2.2 String 的存储结构](#2.2 String 的存储结构)
    • [2.3 String 应用场景](#2.3 String 应用场景)
  • [三、List 列表](#三、List 列表)
    • [3.1 常用 List 命令](#3.1 常用 List 命令)
    • [3.2 List 的存储结构](#3.2 List 的存储结构)
    • [3.3 List 应用场景](#3.3 List 应用场景)
  • [四、Hash 哈希](#四、Hash 哈希)
    • [4.1 常用 Hash 命令](#4.1 常用 Hash 命令)
    • [4.2 Hash 的存储结构](#4.2 Hash 的存储结构)
    • [4.3 Hash 应用场景](#4.3 Hash 应用场景)
  • [五、Set 无序集合](#五、Set 无序集合)
    • [5.1 常用 Set 命令](#5.1 常用 Set 命令)
    • [5.2 Set 应用场景](#5.2 Set 应用场景)
  • [六、ZSet 有序集合](#六、ZSet 有序集合)
    • [6.1 常用 Zset 命令](#6.1 常用 Zset 命令)
    • [6.2 Zset 应用场景](#6.2 Zset 应用场景)

一、前言

Redis 提供了多种核心数据结构,每种都有不同的特点和适用场景:

  • String:是带长度信息的二进制安全字符串,既能存储文本,也能存储任意二进制数据。
  • List:双端链表结构,按插入顺序有序,支持队列和栈操作,但不去重。
  • Hash:由 field-value 组成的字典,field 唯一,适合存储对象属性,有去重作用。
  • Set:无序集合,元素唯一,自动去重,不保证顺序。
  • ZSet :有序集合,元素唯一,通过 member 保证唯一性,通过 score 排序,既去重又能按权重排序。
数据类型 是否有序 元素是否唯一 底层特性/用途 去重情况
String - 二进制安全字符串,支持文本/二进制 不涉及
List 有序(插入顺序) 双端队列,支持队列/栈操作 不去重
Hash field 唯一 字典结构,适合存储对象属性 field 去重
Set 无序 集合结构,元素唯一 元素自动去重
ZSet 有序(按 score) 有序集合,元素唯一且按 score 排序 元素自动去重

二、String 字符串

String 是 Redis 最基本的数据类型,它是二进制安全的,可以存储任何类型的数据,包括文本、数字、序列化的对象或二进制数据。String 值的最大长度为 512MB。

2.1 常用 String 命令

基本操作命令:

cs 复制代码
# 基本操作
SET key value
GET key
DEL key

# 计数操作
INCR key
INCRBY key increment
DECR key
DECRBY key decrement

# 条件设置
SETNX key value    # 仅当 key 不存在时设置
SETEX key seconds value  # 设置值并指定过期时间

位操作命令(常用来实现签到 bitmap):

cs 复制代码
# 设置或清除 key 的 value 在 offset 处的 bit 值
SETBIT key offset value

# 获取 key 的 value 在 offset 处的 bit 值
GETBIT key offset

# 统计字符串被设置为 1 的 bit 数
BITCOUNT key [start end]

2.2 String 的存储结构

Redis 根据字符串的内容和长度选择不同的存储方式:

  • int:字符串长度 ≤ 20 且可以转换为整数时使用(节省空间、运算快)
  • embstr:字符串长度 ≤ 44 时使用,对象和字符串内存一次性分配,内存分配和访问效率高
  • raw:字符串长度 > 44 时使用,标准的动态字符串(独立分配字符串)

2.3 String 应用场景

1. 对象存储

bash 复制代码
# 存储 JSON 格式的对象
SET user:10001 '{"name":"gulu","sex":"female","age":100}'

# 获取对象
GET user:10001

对象在应用层序列化成字符串后,Redis 只负责存储,读写 Redis 时就像操作一个字符串一样。不需要额外设计数据结构,实现简单,而且 JSON 天然跨语言,任何语言都能解析,因此在不同环境下迁移方便。

但它的缺点也很明显:

  • 更新不方便 :如果要改 age,必须先把整个 JSON 取出来,应用层解析成对象,再改字段,改完再序列化写回 Redis。
  • 无法局部修改:Redis 本身不能直接定位 JSON 中的某个字段。
  • 占内存略多:JSON 文本比结构化存储更占空间。

这种方式更适合字段固定、不常变动的对象缓存,主要是"存一下,再整体读出来"的使用方式,比如缓存一篇文章的完整内容、一个角色的静态信息。而对于需要频繁更新、且常常只改某个字段的情况,用 Redis 的 Hash 类型会更合适,因为它支持对单个属性的高效读写。

2. 累加器(计数器)

String 可以直接进行自增、自减操作,非常适合用来做计数。适用于文章阅读数、视频播放量、点赞数、库存数等需要累加/递增的场景。

sh 复制代码
# 统计文章阅读数:每次阅读 +1
incr article:1001:reads  

# 一次增加 100
incrby article:1001:reads 100

3. 分布式锁

利用 SETNX(set if not exists)+ 过期时间,可以实现简单的分布式锁。适用于在分布式系统中,防止多个进程/服务同时操作同一个资源(例如订单创建、库存扣减)。

cs 复制代码
# 加锁:只有当 lock 键不存在时才能成功设置,并自动过期 30 秒
set lock:order123 "uuid-abc" nx ex 30  

# 释放锁(注意要判断是不是自己加的锁)
if (get lock:order123 == "uuid-abc") {
    del lock:order123
}

4. 位运算(Bitmaps)

通过 setbit/getbit/bitcount,可以用一个字符串高效表示大量布尔状态,非常省空间。高效存储布尔信息,适合签到、活跃度、状态统计。

cs 复制代码
# 用户 10001 在 2021 年 6 月 1 日签到
setbit sign:10001:202106 1 1  

# 统计该用户 6 月签到天数
bitcount sign:10001:202106  

# 查看 6 月 2 日是否签到(1=已签到,0=未签到)
getbit sign:10001:202106 2

三、List 列表

List 是一个双向链表结构,支持从列表两端进行高效的插入和删除操作。在列表头部或尾部操作的时间复杂度为 O(1),查找中间元素的时间复杂度为 O(n)。

3.1 常用 List 命令

cs 复制代码
# 两端操作
LPUSH key value [value ...]
RPUSH key value [value ...]
LPOP key
RPOP key

# 返回从队列的 start 和 stop 之间的元素
LRANGE key start stop

# 获取列表中指定下标的元素。
LINDEX key index

# 返回列表的长度(即有多少个元素)
LLEN key

# LPOP 的阻塞版本,这个命令会在给定 list 无法弹出任何元素的时候阻塞连接
BLPOP key [key ...] timeout
BRPOP key [key ...] timeout

# 从存于 key 的列表里移除前 count 次出现的值为 value 的元素
LREM key count value

# 截取列表,只保留指定范围的元素,其余的都会被删除。
LTRIM key start stop
  • 参数说明

    • start:起始下标(从 0 开始)。
    • stop:结束下标(包含 stop 位置的元素)。
  • 下标规则

    • 下标从 0 开始。
    • -1 表示最后一个元素,-2 表示倒数第二个,以此类推。
  • brpop 阻塞:

    • 当链表中没有数据时,请求连接会被挂起,连接被阻塞
    • 需要从另一个连接往里面 push 数据,让链表不为空
    • 这时候被阻塞的连接才会解除阻塞并返回

3.2 List 的存储结构

Redis 从 3.2 起把 list 的底层改为 quicklist 。quicklist 可以理解为:由多个 ziplist 节点串起来的双向链表。这样既能保持链表在首尾插入/删除的高效性,又能通过 ziplist 节点把多个元素紧凑存储在连续内存中,减少指针开销,从而节省大量内存。

  • 当元素数量较少或元素较小时,使用 ziplist 存储以节省内存
  • 当元素数量较多或元素较大时,使用双向链表存储以保证性能

配置参数:

  • list-max-ziplist-size:决定何时从 ziplist 转换为链表
  • list-compress-depth:列表压缩深度,0 表示不压缩

quicklist 源码:

c 复制代码
/* Minimum ziplist size in bytes for attempting compression. */
#define MIN_COMPRESS_BYTES 48

typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;
    unsigned int sz;             /* ziplist size in bytes */
    unsigned int count : 16;     /* count of items in ziplist */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can't compress; too small 
*/
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;        /* total count of all entries in all 
ziplists */
    unsigned long len;          /* number of quicklistNodes */
    int fill : QL_FILL_BITS;              /* fill factor for individual 
nodes */
    unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to 
compress;0=off */
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;

quicklist 的每个节点(quicklistNode)都包含一些关键字段:

  • 指向前后节点的指针(保证双向链表结构)。
  • zl:存放数据的 ziplist(紧凑数组)。
  • sz:当前 ziplist 的字节大小。
  • count:当前 ziplist 存放的元素个数。
  • encoding:标记是否压缩(RAW 表示未压缩,LZF 表示使用 LZF 算法压缩)。

在压缩策略上,Redis 会根据配置和元素大小来决定是否对节点进行压缩:

  • 如果元素长度 小于 48 字节,一般不压缩(因为压缩反而浪费 CPU)。
  • 如果压缩前后大小差距 不超过 8 字节,也不压缩(因为收益太小)。
  • 其余情况会用 LZF 算法压缩,以达到节省内存的效果。

3.3 List 应用场景

1. 栈(先进后出 FILO)

cs 复制代码
# LPUSH + LPOP 或 RPUSH + RPOP
LPUSH mystack "item1"
LPUSH mystack "item2"
LPOP mystack  # 返回 "item2"

2. 队列(先进先出 FIFO)

cs 复制代码
# LPUSH + RPOP 或 RPUSH + LPOP
LPUSH myqueue "item1"
LPUSH myqueue "item2"
RPOP myqueue  # 返回 "item1"

3. 阻塞队列(blocking queue)

cs 复制代码
# 生产者
LPUSH tasks "task1"

# 消费者(阻塞等待任务)
BRPOP tasks 30  # 30秒超时

4. 获取固定窗口记录(战绩)

cs 复制代码
# 在某些业务场景下,需要获取固定数量的记录;比如获取最近50条战绩;这些记录需要按照插入的先后顺序返回;
# 添加新记录
LPUSH user:10001:activities "activity at $(date)"

# 保持最近50条记录
LTRIM user:10001:activities 0 49

# 查看所有记录
LRANGE user:10001:activities 0 -1

为了保证命令的原子性,通常使用 Lua 脚本或 Pipeline 执行多个命令:

bash 复制代码
-- Lua 脚本实现原子操作
local record = KEYS[1]
redis.call("LPUSH", "user:activities", record)
redis.call("LTRIM", "user:activities", 0, 49)
return redis.call("LRANGE", "user:activities", 0, -1)

四、Hash 哈希

Redis Hash 就像一个小型的字典,里面存放的是 字段(field)和值(value) 。它最适合用来表示对象:比如你要存一个用户,可以把 nameagegender 分别作为字段保存。这样,Redis 的一个 key(如 user:1001)就能对应整个用户对象。

sh 复制代码
HSET user:1001 name "gulu" 
HSET user:1001 age 21
HSET user:1001 gender "female"

和把对象整体存成 JSON 字符串相比,Hash 更灵活:

  • 可以只修改某一个字段(比如直接更新 age),不用先取出整个对象再写回。
  • Redis 在存储小字段时会自动用紧凑的方式节省内存。
  • 支持直接对字段进行增删改查,效率高,也更方便。

另外,Hash 的容量非常大,每个 Hash 可以存储 2^32 - 1 个字段值对(40 多亿)。

4.1 常用 Hash 命令

cs 复制代码
# 字段操作
HSET key field value         # 设置哈希中指定字段的值
HGET key field               # 获取哈希中指定字段的值
HDEL key field [field ...]   # 删除哈希中指定的字段

# 批量操作
HMSET key field value [field value ...]  # 设置多个哈希字段值
HMGET key field [field ...]              # 获取多个哈希字段值
HGETALL key                              # 获取哈希中所有字段和值

# 计数操作
HINCRBY key field increment  # 为哈希中指定字段的值增加整数

# 获取哈希中字段的数量
HLEN key

# 返回 Hash 中所有字段的名字
HKEYS key

# 返回 Hash 中所有字段对应的值
HVALS key

# 判断 Hash 中是否存在某个字段,存在返回1,不存在返回0
HEXISTS key field

示例:

cs 复制代码
127.0.0.1:6379> hmset user:10001 name gulu age 21 weight 200
OK
127.0.0.1:6379> hgetall user:10001
1) "name"
2) "gulu"
3) "age"
4) "21"
5) "weight"
6) "200"
127.0.0.1:6379> HINCRBY user:10001 weight 100
(integer) 300

4.2 Hash 的存储结构

Redis 根据 Hash 的大小和内容选择存储方式:

  1. ziplist :当字段数量 ≤hash-max-ziplist-entries(默认512)且所有字段和值的字符串长度都 ≤hash-max-ziplist-value(默认64)时使用。

  2. hashtable:当不满足上述条件时使用,即标准的哈希表实现,操作更快但占用更多内存。

4.3 Hash 应用场景

1. 对象存储

cs 复制代码
# 存储用户信息
HMSET user:10001 name "gulu" age 99 sex "female"

# 获取用户信息
HGETALL user:10001

# 修改用户年龄
HSET user:10001 age 100

2. 购物车实现

cs 复制代码
# 将用户id作为 key
# 商品id作为 field
# 商品数量作为 value
# 注意:这些物品是按照我们添加顺序来显示的;
# 添加商品:
hmset MyCart:10001 40001 1 cost 5099 desc "戴尔笔记本14-3400"
lpush MyItem:10001 40001
 
# 增加数量:
hincrby MyCart:10001 40001 1
 
# 减少商品数量
hincrby MyCart:10001 40001 -1

# 获取购物车商品数量
hlen MyCart:10001

# 删除商品:
hdel MyCart:10001 40001
lrem MyItem:10001 1 40001

# 获取所有物品:
lrange MyItem:10001

# 40001 40002 40003
hget MyCart:10001 40001
hget MyCart:10001 40002
hget MyCart:10001 40003

# 获取所有商品
HGETALL MyCart:10001

五、Set 无序集合

Set 是一个无序的字符串集合,不允许重复的元素集合,适合去重、集合运算(交并差)。

  • 无序的字符串集合
  • 元素唯一,不允许重复
  • 支持集合运算(交集、并集、差集)

内部编码

  • 如果所有元素都是整数且元素数 ≤ set-max-intset-entries(默认 512),使用 intset(紧凑数组);
  • 否则使用 dict(哈希表)。

5.1 常用 Set 命令

cs 复制代码
# 元素操作
SADD key member [member ...]   # 向集合添加一个或多个成员
SREM key member [member ...]   # 从集合(Set)里删除一个或多个指定元素
SISMEMBER key member           # 判断 member 是否是集合的成员

# 集合运算
SINTER key [key ...]           # 返回给定所有集合的交集
SUNION key [key ...]           # 返回给定所有集合的并集
SDIFF key [key ...]            # 返回给定所有集合的差集


# 获取集合的成员数
SCARD key

# 返回集合中的所有成员
SMEMBERS key

# 随机返回集合中一个或多个成员,不删除
SRANDMEMBER key [count]

# 随机移除并返回集合中一个或多个成员
SPOP key [count]
  • 删除不存在的元素不会报错,只是返回 0。

5.2 Set 应用场景

1. 抽奖系统

cs 复制代码
# 添加抽奖用户
SADD lottery:20230612 10001 10002 10003 10004 10005

# 查看所有参与用户
SMEMBERS lottery:20230612

# 抽取3名幸运用户
SRANDMEMBER lottery:20230612 3

# 抽取并移除一等奖1名,二等奖2名
SPOP lottery:20230612 1  # 一等奖
SPOP lottery:20230612 2  # 二等奖

2. 社交关系

cs 复制代码
# 用户A的关注列表
SADD follow:A B C D E

# 用户C的关注列表
SADD follow:C A B F

# 共同关注(交集)
SINTER follow:A follow:C

# A可能认识的人(差集:C关注但A未关注的人)
SDIFF follow:C follow:A

# 全部关注(并集)
SUNION follow:A follow:C

3. 标签系统

cs 复制代码
# 给文章添加标签
SADD article:10001:tags "redis" "database" "cache"

# 给用户添加兴趣标签
SADD user:20001:interests "programming" "redis" "python"

# 推荐相关文章(基于标签匹配)
SINTER article:10001:tags user:20001:interests

六、ZSet 有序集合

Zset 是有序的字符串集合,每个成员都关联一个分数(score),用于排序。成员是唯一的,但分数可以重复。

内部实现

  • 小数据量下(≤ zset-max-ziplist-entries 默认 128,且字符串长度 ≤ zset-max-ziplist-value 默认 64)使用 ziplist
  • 大数据量下使用 skiplist + dict 组合:skiplist 提供按 score 的有序遍历 (O(log n)),dict 用于按 member 查找 (O(1))。
  • 数据少的时候,节省空间; O ( n ) O(n) O(n)
  • 数量多的时候,访问性能; O ( 1 ) O(1) O(1) or O ( l o g 2 n ) O(log_{2}{n}) O(log2n)

6.1 常用 Zset 命令

cs 复制代码
# 元素操作
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]  # 向有序集合添加一个或多个成员
ZREM key member [member ...]  # 从有序集合中移除一个或多个成员

# 范围操作
ZRANGE key start stop [WITHSCORES]     # 返回有序集中指定区间内的成员(从小到大)
ZREVRANGE key start stop [WITHSCORES]  # 返回有序集中指定区间内的成员(从大到小)
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]  # 返回有序集中指定分数区间的成员

# 排名操作
ZRANK key member      # 返回有序集中成员的排名(从小到大)
ZREVRANK key member   # 返回有序集中成员的排名(从大到小)
ZSCORE key member     # 返回有序集中成员的分数值


# 为有序集中成员的分数值加上增量
ZINCRBY key increment member

# 返回有序集的成员数量
ZCARD key

# 分数在指定区间[min, max]内的成员数量
ZCOUNT key min max

6.2 Zset 应用场景

1. 排行榜系统

cs 复制代码
# 添加用户得分
ZADD leaderboard:20230612 1000 "user1"
ZADD leaderboard:20230612 850 "user2"
ZADD leaderboard:20230612 920 "user3"

# 增加用户得分
ZINCRBY leaderboard:20230612 50 "user1"

# 获取前十名用户
ZREVRANGE leaderboard:20230612 0 9 WITHSCORES

# 获取用户排名(从高到低)
ZREVRANK leaderboard:20230612 "user1"

# 获取指定分数区间的用户
ZRANGEBYSCORE leaderboard:20230612 800 900 WITHSCORES

2. 延时队列

延时队列就是把一条消息存进去,等到指定的时间再取出来处理。常见应用场景有:

  • 下单 30 分钟未支付,自动取消订单;
  • 消息推送延迟一段时间再发送;
  • 任务重试机制(失败后延迟几秒再尝试)。

在 Redis 里,最常用的数据结构是 ZSET(有序集合)

  • member:存放消息内容(比如序列化成 JSON 字符串);
  • score:存放消息的"到期时间戳"。

将消息序列化成一个字符串作为 zset 的 member;这个消息的到期处理时间作为 score,然后用多个线程轮询 zset 获取到期的任务进行处理。

python 复制代码
import time, uuid, json
import redis

r = redis.Redis()

# 添加延时任务
def delay(msg):
    msg["id"] = str(uuid.uuid4())        # 确保唯一
    value = json.dumps(msg)
    retry_ts = time.time() + 5           # 5秒后执行
    r.zadd("delay-queue", {value: retry_ts})

# 轮询执行任务
def loop():
    while True:
        now = time.time()
        # 取一个到期任务
        values = r.zrangebyscore("delay-queue", 0, now, start=0, num=1)
        if not values:
            time.sleep(1)
            continue
        value = values[0]
        # 删除成功才处理(避免多线程重复执行)
        if r.zrem("delay-queue", value):
            msg = json.loads(value)
            handle_msg(msg)  # 用户自定义的任务处理函数
  • 多线程同时取任务时,会发生竞争:两个线程可能取到同一条消息,但只有一个能成功删除。
  • 优化方法:用 Lua 脚本把 "取出任务 + 删除任务" 做成一个原子操作,避免并发冲突。

命令行实现:

cs 复制代码
# 添加延时任务
ZADD delay_queue $(($(date +%s)+60)) "task1"   # 1分钟后执行
ZADD delay_queue $(($(date +%s)+300)) "task2"  # 5分钟后执行

# 循环处理
while true; do
    current_timestamp=$(date +%s)
    tasks=$(ZRANGEBYSCORE delay_queue 0 $current_timestamp)
    if [ -n "$tasks" ]; then
        for task in $tasks; do
            echo "处理任务: $task"
            ZREM delay_queue "$task"
        done
    else
        sleep 1
    fi
done

3. 分布式定时器

定时任务的数量可能非常大,如果所有任务都放在一个 Redis 实例中,单机会成为瓶颈。

因此,做法是 把任务分散存储在多个 Redis 实例中

  1. 生产者:将定时任务按照某种规则(比如任务 ID 的 hash 值)分配到不同的 Redis 实例里。

    • 这样可以让不同的 Redis 节点分摊压力。
  2. Dispatcher(调度进程):为每个 Redis 实例分配一个专门的进程。

    • 负责定时扫描 Redis,找到已经到期的任务。
    • 然后将这些任务分发给消费者去处理。
  3. 消费者:真正执行任务逻辑,比如发通知、清理数据、触发回调等。

通过"任务分片 + 分布式调度",系统能够高效支撑大量的定时任务,而不会压垮单台 Redis。

3. 时间窗口限流

时间窗口限流是一种常见的频率控制手段,用于限制用户在指定时间范围内的操作次数。例如,可以规定某个用户在 1 分钟内最多调用接口 10 次,或在 10 秒内最多发送 5 条消息。通过这种方式,可以有效防止恶意请求或过度访问,保障系统的稳定性和公平性。

实现方式:利用 Redis 的有序集合(ZSet)

  1. 每次用户行为发生时

    • 往 ZSet 里插入一条记录,score 和 member 都是当前时间戳。
  2. 清理过期行为

    • 删除 ZSet 中那些时间戳在「当前时间 - 窗口长度」之前的记录。
    • 这样 ZSet 中只保留"窗口期内"的行为。
  3. 统计行为次数

    • 直接用 ZCARD 统计 ZSet 的数量。
    • 如果超过限制,就拒绝。
  4. 设置过期时间

    • 给 key 设置 expire,保证冷用户不会长时间占用内存。
cs 复制代码
-- Lua脚本实现时间窗口限流
local function is_action_allowed(red, userid, action, period, max_count)
    local key = table.concat({"rate_limit", userid, action}, ":")
    local now = tonumber(red:call("TIME")[1])
    
    red:init_pipeline()
    -- 记录当前操作
    red:call("ZADD", key, now, now)
    -- 移除时间窗口外的记录
    red:call("ZREMRANGEBYSCORE", key, 0, now - period)
    -- 获取当前窗口内的操作次数
    red:call("ZCARD", key)
    -- 设置键的过期时间,避免内存浪费。
    red:call("EXPIRE", key, period + 1)
    
    local res = red:commit_pipeline()
    return res[3] <= max_count
end

这种方式会在 ZSet 中存储窗口内的所有请求记录。如果用户数量庞大且请求频繁,ZSet 会迅速膨胀,带来较大的内存压力。可以考虑用 漏斗限流(Leaky Bucket) 或 令牌桶 等算法来优化。

相关推荐
傻啦嘿哟2 小时前
Python SQLite模块:轻量级数据库的实战指南
数据库·python·sqlite
小牛马爱写博客2 小时前
DNS 服务器与 DHCP 服务器详解及配置指南
linux·运维·服务器·dns·dhcp
维尔切2 小时前
HAProxy 负载均衡器
linux·运维·数据库·负载均衡
什么半岛铁盒2 小时前
C++项目:仿muduo库高并发服务器-------Channel模块实现
linux·服务器·数据库·c++·mysql·ubuntu
倔强的石头_2 小时前
【金仓数据库产品体验官】Windows 安装 KingbaseES V9R1C10 与 Oracle 兼容特性实战
数据库
2503_924806852 小时前
动态IP使用中 报错407 怎么办???
服务器·tcp/ip·php
QQ12958455043 小时前
服务器跨域问题CORS的解决
运维·服务器
山海不说话3 小时前
Java后端面经(八股——Redis)
java·开发语言·redis
小白银子3 小时前
零基础从头教学Linux(Day 42)
linux·运维·服务器·网络·nginx