Redis基础与核心原理

Redis 基础与核心原理

一、Redis 是什么

Redis(Remote Dictionary Server)的本质定位

Redis 的全称是 Remote Dictionary Server,即"远程字典服务"。从名字可以看出,Redis 的核心抽象就是一个通过网络远程访问的字典(Dictionary)。它是一个开源的、基于内存的、支持持久化的键值对(Key-Value)存储系统,由 Salvatore Sanfilippo(antirez)于 2009 年开发,目前由 Redis Ltd. 维护。

Redis 最初的设计目标非常简单------解决传统关系型数据库在高并发场景下的性能瓶颈。随着生态的不断发展,Redis 已经从一个单纯的内存缓存工具,演变为一个功能丰富的多模数据平台(Multi-model Data Platform)。

官方定义的五大角色

Redis 承担以下五大核心角色:

角色 英文 说明
数据库 Database 作为持久化的内存数据库,支持事务(Transaction)、Lua 脚本、Module 扩展
缓存 Cache 最经典的用法,利用内存的高速读写特性缓存热点数据,减轻后端数据库压力
消息代理 Message Broker 通过 Pub/Sub 机制实现消息的发布与订阅,支持消息分发
流处理引擎 Streaming Engine Redis 5.0 引入的 Stream 数据类型,支持消费者组(Consumer Group),可实现类似 Kafka 的消息流处理
向量数据库 Vector Store Redis 8+ 原生支持向量搜索(Vector Search),可用于 AI/ML 场景中的相似性检索(Similarity Search)

值得注意的是,这五大角色并非互斥,在实际生产环境中,一个 Redis 实例往往同时承担多种角色。

核心特点

Redis 之所以能在工业界被广泛采用,源于以下核心特点的协同:

  1. 内存存储(In-memory Storage):所有数据驻留在内存中,读写延迟极低(微秒级)
  2. 单线程事件循环(Single-threaded Event Loop):命令执行线程为单线程,避免了锁竞争等并发问题
  3. 高性能(High Performance):官方基准测试可达百万级 QPS(Queries Per Second)
  4. 丰富数据结构(Rich Data Structures):String、List、Hash、Set、ZSet、Stream、HyperLogLog、Bitmap、Geospatial 等
  5. 持久化(Persistence):支持 RDB 快照和 AOF 日志两种持久化方式,内存数据可落盘
  6. 复制(Replication):主从复制(Master-Slave Replication),支持读写分离
  7. 集群(Cluster):Redis Cluster 提供分布式分片方案,支持水平扩展
  8. 事务(Transaction):通过 MULTI/EXEC/WATCH 提供事务支持
  9. Lua 脚本(Lua Scripting):支持原子性执行的 Lua 脚本,实现复杂逻辑
  10. Module 扩展(Module System):可加载第三方模块扩展功能,如 RediSearch、RedisJSON、RedisTimeSeries 等

Redis 与 Memcached 的关键区别

对比维度 Redis Memcached
数据结构 丰富(String/List/Hash/Set/ZSet/Stream 等) 仅支持简单的 Key-Value(String)
持久化 支持 RDB + AOF,数据可落盘 纯内存,重启数据丢失
集群 原生支持 Redis Cluster 分片 需要客户端一致性哈希分片
线程模型 命令执行单线程,IO 多线程(Redis 6+) 多线程
内存管理 自定义内存分配器(jemalloc),支持多种淘汰策略 Slab Allocator,LRU 淘汰
事务 支持 MULTI/EXEC/WATCH 不支持事务
消息模型 支持 Pub/Sub 和 Stream 不支持
Lua 脚本 支持 不支持
适用场景 缓存 + 数据库 + 消息队列等多角色 纯缓存场景

总结:Memcached 是一个纯粹的内存缓存系统,而 Redis 是一个多功能的内存数据平台。在新项目中,Redis 几乎完全取代了 Memcached 的生态位。


二、Redis 为什么这么快

Redis 的高性能并非由单一因素决定,而是多个设计决策协同作用的结果。下面逐一拆解。

2.1 基于内存

内存 vs SSD vs HDD 的访问延迟对比
存储介质 典型访问延迟 数量级
CPU L1 Cache 0.5 ns 纳秒级
CPU L2 Cache 5 ns 纳秒级
内存(DRAM) 100 ns 纳秒级
SSD(Solid State Drive) 100,000 ns (0.1 ms) 微秒级
HDD(Hard Disk Drive) 10,000,000 ns (10 ms) 毫秒级

从上表可以看出,内存的访问速度是 SSD 的约 1000 倍,是 HDD 的约 100,000 倍。这意味着:即使磁盘数据库使用了再精妙的索引结构(如 B+Tree),其访问延迟也无法与直接操作内存相提并论。

Redis 快的本质不是算法快,而是数据存储位置不同

很多人误以为 Redis 快是因为使用了某种"黑科技算法"。实际上,Redis 快的根本原因是数据存储在内存中。内存的随机访问延迟约为 100 纳秒,而磁盘的随机访问延迟约为 10 毫秒,两者差了 5 个数量级。这意味着,即使 Redis 使用最简单的线性查找,在内存中的效率也远超磁盘数据库使用 B+Tree 的效率。

当然,Redis 并非只靠内存------它在数据结构和网络模型上也做了大量优化,这些优化让 Redis 把内存的性能潜力发挥到了极致。

伪代码:内存读取 vs 磁盘读取的耗时对比
pseudocode 复制代码
// 内存读取
function memoryRead(key):
    address = hash(key) % memoryTableSize    // O(1) 哈希定位
    value = memory[address]                  // 约 100ns
    return value

// 磁盘读取(即使有 B+Tree 索引)
function diskRead(key):
    rootPage = readFromDisk(rootPageOffset)  // 第1次磁盘IO,约 10ms
    childPage = readFromDisk(rootPage.child) // 第2次磁盘IO,约 10ms
    leafPage = readFromDisk(childPage.child) // 第3次磁盘IO,约 10ms
    value = leafPage.find(key)
    return value

// 耗时对比:
// memoryRead:   ~100ns = 0.0001ms
// diskRead:     ~30ms
// 内存读取比磁盘读取快约 300,000 倍

2.2 单线程模型

为什么 Redis 选择单线程:瓶颈在网络 IO 而非 CPU

Redis 的核心设计哲学是:对于内存数据库来说,性能瓶颈不在 CPU 计算,而在网络 IO。一次 Redis 命令的典型执行流程如下:

  1. 从网络读取请求(网络 IO)------ 微秒级
  2. 解析命令(CPU)------ 纳秒级
  3. 在内存中查找并执行命令(CPU + 内存)------ 纳秒级
  4. 将结果写回网络(网络 IO)------ 微秒级

可以看到,CPU 计算只占极小的比例,真正耗时的是网络 IO。因此,用多线程来加速 CPU 计算意义不大,反而会引入复杂的并发控制问题。

单线程避免的问题
  1. 锁竞争(Lock Contention):多线程访问共享数据结构需要加锁,锁竞争会导致线程频繁阻塞和唤醒,降低吞吐量
  2. 上下文切换(Context Switch):线程切换需要保存和恢复寄存器、缓存等状态,频繁切换浪费 CPU 资源
  3. Cache Miss:多线程在不同 CPU 核心上运行,可能导致 CPU Cache Line 的频繁失效(False Sharing),增加内存访问延迟
Redis 6 的多线程 IO:仅网络 IO 多线程,命令执行仍单线程

Redis 6 引入了 IO 多线程(Threaded I/O),但需要注意的是:多线程仅用于网络读写,命令的执行仍然是单线程的。这样既提升了网络 IO 的吞吐量,又保证了命令执行的原子性和无锁特性。

伪代码:单线程事件循环的核心逻辑
pseudocode 复制代码
// Redis 单线程事件循环的核心逻辑
function eventLoop():
    while server is running:
        // 1. 通过 epoll 收集就绪的事件
        readyEvents = epoll_wait(epfd, events, timeout)

        // 2. 遍历所有就绪事件
        for event in readyEvents:
            if event.type == ACCEPT:
                // 新连接到达
                clientFd = accept(listenFd)
                setNonBlocking(clientFd)
                epoll_ctl(epfd, ADD, clientFd, READ_EVENT)

            else if event.type == READ:
                // 客户端请求到达
                request = read(event.fd)
                command = parseCommand(request)

                // 关键:命令执行是单线程的,无需加锁
                result = executeCommand(command)

                // 将结果加入写缓冲区
                appendToWriteBuffer(event.fd, result)

            else if event.type == WRITE:
                // 将缓冲区数据写回客户端
                flushWriteBuffer(event.fd)

        // 3. 执行定时任务
        processTimedEvents()

2.3 IO 多路复用

Linux IO 模型演进:select -> poll -> epoll

在介绍 Redis 的 IO 模型之前,先理解 Linux IO 多路复用的演进:

select(早期方案)

  • 使用固定长度的位图(bitmap)表示文件描述符(File Descriptor, FD)集合
  • 每次调用需要将整个 FD 集合从用户态拷贝到内核态
  • 支持的最大 FD 数量受限(默认 1024,由 FD_SETSIZE 决定)
  • 时间复杂度 O(n),每次都需要遍历全部 FD

poll(改进方案)

  • 用动态数组替代位图,取消了 1024 的数量限制
  • 但仍需将整个 FD 集合拷贝到内核态
  • 时间复杂度仍为 O(n)

epoll(现代方案)

  • 使用红黑树管理 FD,支持百万级连接
  • 通过事件回调机制(Callback),只返回就绪的 FD,时间复杂度 O(1)
  • 使用 mmap 共享内存区域,避免用户态和内核态之间的数据拷贝
  • 支持 ET(边缘触发)和 LT(水平触发)两种模式
Redis 默认使用 epoll,一个线程监听数万 TCP 连接

Redis 在 Linux 上默认使用 epoll 作为 IO 多路复用的实现(在 macOS 上使用 kqueue,在 Windows 上使用 select)。epoll 的高效事件通知机制使得 Redis 仅用一个线程就能同时监听数万个 TCP 连接,并在连接就绪时进行处理。

epoll 的 ET(边缘触发)vs LT(水平触发)模式
模式 英文 触发条件 特点
LT Level Triggered 只要缓冲区有数据,就持续触发 编程简单,不容易漏事件;但可能频繁触发
ET Edge Triggered 缓冲区从空变为非空时触发一次 效率更高,但必须一次性读完所有数据,否则会丢失事件

Redis 使用的是 LT(水平触发) 模式。原因在于 Redis 的事件循环是单线程的,LT 模式编程更简单,不需要担心一次性未读完数据导致的事件丢失问题。

伪代码:epoll 事件循环伪代码
pseudocode 复制代码
// epoll 事件循环伪代码
function epollEventLoop():
    // 1. 创建 epoll 实例
    epfd = epoll_create1(0)

    // 2. 将监听套接字注册到 epoll
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenFd, {
        events: EPOLLIN,         // 关注可读事件
        data: { type: ACCEPT }   // 标记为连接事件
    })

    // 3. 事件循环
    while True:
        // 等待事件就绪,返回就绪的 FD 列表
        // 只返回有事件的 FD,不需要遍历全部连接
        readyList = epoll_wait(epfd, maxEvents, timeout)

        for event in readyList:
            if event.data.type == ACCEPT:
                // 新连接到达
                clientFd = accept(listenFd)
                setNonBlocking(clientFd)  // 设置为非阻塞
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientFd, {
                    events: EPOLLIN,
                    data: { type: CLIENT, fd: clientFd }
                })

            else if event.data.type == CLIENT:
                if event.events & EPOLLIN:
                    // 客户端数据可读
                    data = read(event.data.fd)
                    if data.length > 0:
                        processCommand(data)
                    else:
                        // 连接关闭
                        epoll_ctl(epfd, EPOLL_CTL_DEL, event.data.fd)

                if event.events & EPOLLOUT:
                    // 可以向客户端写数据
                    flushWriteBuffer(event.data.fd)

2.4 非阻塞 IO

accept()/read()/write() 全部非阻塞

Redis 将所有网络套接字都设置为非阻塞模式(Non-blocking IO)。这意味着:

  • accept():如果没有新连接到达,立即返回 EAGAIN,不会阻塞等待
  • read():如果缓冲区没有数据,立即返回 EAGAIN,不会阻塞等待
  • write():如果缓冲区已满,立即返回 EAGAIN,不会阻塞等待

非阻塞 IO 配合 epoll 的事件通知机制,使得 Redis 的事件循环永远不会因为某个慢连接而卡住。

为什么一个线程就能处理大量连接

核心原理:不要等待,轮询就绪。传统阻塞 IO 模型中,一个线程只能处理一个连接,因为 read() 会阻塞。而 Redis 的做法是:

  1. 将所有连接设置为非阻塞
  2. 用 epoll 监听所有连接的就绪状态
  3. 只有当连接有数据可读/可写时,才去处理
  4. 处理完立即返回事件循环,继续处理其他连接

这样,一个线程可以高效地在数万个连接之间切换处理,没有任何阻塞等待的时间浪费。

2.5 高效数据结构

Redis 底层不是简单 HashMap,而是精心优化的数据结构

很多人以为 Redis 就是把数据放在一个大的 HashMap 里,实际上 Redis 对每种数据类型的底层实现都做了深度的优化,针对不同场景使用不同的编码方式,在内存占用和操作效率之间取得了极佳的平衡。

数据结构概览表
数据类型 底层编码 核心优势
String SDS(Simple Dynamic String) O(1) 获取长度;二进制安全;杜绝缓冲区溢出;预分配+惰性释放减少内存重分配
Hash ListPack(小数据)/ Hashtable(大数据) ListPack 紧凑存储省内存;Hashtable O(1) 查找;自动根据数据量切换编码
List QuickList(双向链表 + ListPack) 兼顾链表的快速插入删除和 ListPack 的紧凑存储;节点间用 ziplist/ListPack 压缩
Set IntSet(纯整数)/ Hashtable(混合类型) IntSet 有序紧凑存储纯整数集合;Hashtable O(1) 查找
ZSet SkipList + Hashtable SkipList O(logN) 范围查询;Hashtable O(1) 按成员查分数;双重索引

每种底层编码的核心优势简述:

SDS(Simple Dynamic String):Redis 自己实现的字符串,而非直接使用 C 语言的 char*。SDS 在头部记录了字符串长度,因此获取长度的时间复杂度为 O(1)(C 语言的 strlen 需要 O(N) 遍历)。SDS 还通过空间预分配和惰性释放策略,大幅减少了内存重分配的次数。

ListPack:Redis 7.0 用 ListPack 替代了 ziplist。ListPack 是一种紧凑的线性存储结构,所有元素紧密排列在一块连续内存中,内存利用率极高。当 Hash 中的字段数量较少时,使用 ListPack 存储比 Hashtable 节省大量内存。

QuickList:QuickList 是 List 的底层实现,它是一个双向链表,每个节点是一个 ListPack(或 ziplist)。这种设计兼顾了链表在两端快速插入/删除的优势,以及 ListPack 紧凑存储节省内存的优势。

IntSet:当 Set 中的元素全部是整数且数量较少时,使用 IntSet 存储。IntSet 是一个有序的整数数组,虽然查找是 O(logN),但内存占用极小。

SkipList(跳表):ZSet 的范围查询核心。跳表通过多级索引实现 O(logN) 的查找效率,同时保持有序性。相比于红黑树,跳表的实现更简单,范围查询更高效(只需找到起点后沿底层链表遍历即可)。


三、Redis 为什么不用 B+Tree

B+Tree 的设计目标:减少磁盘 IO

B+Tree 是为磁盘数据库量身定制的索引结构。它的核心设计目标是:将随机 IO 转化为顺序 IO,尽量减少磁盘 IO 次数

B+Tree 的每个节点通常设置为 4KB~16KB(与磁盘页大小对齐),这样一次磁盘 IO 就能读取一个完整的节点。一棵 3 层的 B+Tree(根节点常驻内存)只需 2 次磁盘 IO 就能定位到数据,可以索引约 2000 万条记录。

但这一切优化都建立在一个前提之上------数据存储在磁盘上,磁盘 IO 是主要瓶颈。

Redis 是内存数据库,不需要考虑磁盘 IO 优化

Redis 的数据全部驻留在内存中,内存的随机访问延迟约为 100 纳秒,不存在"减少 IO 次数"的问题。在内存中,随机访问和顺序访问的速度几乎一样快,B+Tree 的核心优势在内存场景下不复存在。

Hash O(1) 和 SkipList O(logN) 在内存中已经足够高效

  • Hash 表:等值查找的时间复杂度为 O(1),在内存中性能极佳
  • SkipList:范围查找的时间复杂度为 O(logN),在内存中纳秒级的操作,logN 的常数极小

以 1000 万条数据为例:

  • Hash 查找:O(1),约 100ns
  • SkipList 查找:O(logN) = O(24),约 2.4us
  • B+Tree 查找:O(logN),约 2.4us + 额外的节点分裂/合并维护开销

在内存场景下,Hash 和 SkipList 的性能已经足够好,而 B+Tree 反而因为需要维护平衡(节点分裂/合并)而增加了额外的复杂度和开销。

对比表格:Redis vs MySQL 的索引结构选择

对比维度 Redis MySQL (InnoDB)
存储介质 内存 磁盘
核心瓶颈 网络 IO 磁盘 IO
等值查找 Hash O(1) B+Tree O(logN)
范围查询 SkipList O(logN + M) B+Tree O(logN + M),叶子节点链表顺序读
维护成本 Hash 扩容 / SkipList 插入简单 B+Tree 节点分裂/合并开销大
内存效率 不同数据结构各有优化 B+Tree 节点对齐页大小,内存利用率一般
设计哲学 内存场景下追求极致速度 磁盘场景下减少 IO 次数

四、Redis 的线程模型演进

4.1 Redis 4.x 及之前:纯单线程

在 Redis 4.x 及之前的版本中,Redis 采用严格的单线程模型:

  • 网络 IO:单线程通过 epoll 处理
  • 命令执行:单线程串行执行
  • 持久化:Fork 子进程处理 RDB/AOF(但不影响主线程的命令执行)
  • 删除操作:同步删除(DEL 命令),大 Key 删除会阻塞主线程

这个阶段的优点是简单可靠,无需考虑并发安全问题;缺点是在高并发场景下,网络 IO 成为瓶颈,单线程无法充分利用多核 CPU。

4.2 Redis 6.x:IO 多线程

io-thread-read 和 io-thread-write 配置

Redis 6 引入了 Threaded I/O,通过以下配置控制:

复制代码
io-threads 4                   // IO 线程数(含主线程)
io-threads-do-reads yes        // 是否开启读线程

注意:io-threads 的值包含主线程本身。例如设置为 4,表示 1 个主线程 + 3 个 IO 线程。

多线程仅处理网络读写,命令执行仍单线程

Threaded I/O 的工作流程如下:

  1. 主线程通过 epoll 获取就绪的客户端连接
  2. 读阶段:将客户端的读任务分配给 IO 线程,IO 线程负责读取和解析请求
  3. 执行阶段:主线程串行执行所有命令(这是保证原子性的关键)
  4. 写阶段:将写回任务分配给 IO 线程,IO 线程负责将结果写回客户端

关键保证:命令执行仍然在主线程中串行进行,不存在并发安全问题。

伪代码:IO 多线程的处理流程
pseudocode 复制代码
// Redis 6 IO 多线程处理流程
function processClientsWithThreadedIO():
    // ========== 读阶段(多线程) ==========
    // 将客户端分配给 IO 线程读取请求
    for client in readableClients:
        assignToIOThread(client, READ_TASK)

    // 等待所有 IO 线程完成读取
    waitForIOThreadsCompletion()

    // ========== 执行阶段(单线程) ==========
    // 主线程串行执行所有命令
    for client in readableClients:
        while client.hasPendingCommand():
            command = client.nextCommand()
            result = executeCommand(command)     // 单线程执行,无锁
            client.setResponse(result)

    // ========== 写阶段(多线程) ==========
    // 将客户端分配给 IO 线程写回响应
    for client in writableClients:
        assignToIOThread(client, WRITE_TASK)

    // 等待所有 IO 线程完成写入
    waitForIOThreadsCompletion()

4.3 Redis 7.x:进一步优化

lazy-free(异步删除)的引入

Redis 4.0 就引入了 lazy-free 机制,但在 Redis 7.x 中得到进一步优化和广泛应用。lazy-free 的核心思想是:将耗时的删除操作放到后台线程异步执行,避免阻塞主线程

相关配置:

复制代码
lazyfree-lazy-eviction yes      // 异步淘汰
lazyfree-lazy-expire yes        // 异步过期
lazyfree-lazy-server-del yes    // 异步删除(如 rename 覆盖旧值)
replica-lazy-flush yes          // 从节点异步全量同步时异步清空
lazyfree-lazy-user-del yes      // 用户调用 DEL 时异步删除
lazyfree-lazy-user-flush yes    // 用户调用 FLUSHDB/FLUSHALL 时异步清空
多线程对大 Key 删除的优化

当一个包含数百万元素的 Hash 或 Set 被删除时:

  • 同步删除(DEL):主线程遍历并释放所有元素,可能导致数百毫秒的阻塞
  • 异步删除(UNLINK):主线程仅将 Key 从数据库中摘除(O(1)),实际的内存释放交给后台线程异步完成
pseudocode 复制代码
// 同步删除 vs 异步删除
function DEL(key):
    value = db.remove(key)           // 从字典中摘除 O(1)
    freeMemory(value)                // 同步释放内存 ------ 大 Key 时阻塞!
    return OK

function UNLINK(key):
    value = db.remove(key)           // 从字典中摘除 O(1)
    enqueueToLazyFreeQueue(value)    // 放入异步释放队列 ------ 立即返回
    return OK

// 后台 lazy-free 线程
function lazyFreeThread():
    while True:
        value = dequeueFromLazyFreeQueue()
        freeMemory(value)            // 在后台线程中释放内存,不阻塞主线程

五、Redis 的全局数据库结构

16 个默认数据库(db0 - db15)

Redis 默认提供 16 个数据库,编号从 0 到 15。客户端连接后默认使用 db0,可以通过 SELECT index 命令切换数据库。

配置项:databases 16(可在 redis.conf 中修改)

注意:Redis Cluster 模式下只能使用 db0,不支持多数据库。

每个数据库是一个 dict(字典)

每个 Redis 数据库的核心就是一个 dict(字典),即哈希表。所有 Key-Value 都存储在这个字典中。Redis 的 dict 使用链地址法(Separate Chaining)解决哈希冲突,并支持渐进式 Rehash(Incremental Rehashing),避免一次性 Rehash 导致的长时间阻塞。

过期键存储在 expires 字典中

Redis 为每个数据库维护了两个字典:

  1. dict:存储所有 Key-Value 数据
  2. expires:存储设置了过期时间的 Key 及其过期时间戳

这种分离存储的设计使得过期检查非常高效:只需在 expires 字典中查找 Key 即可判断是否过期,无需在主字典中嵌入过期时间字段。

伪代码:redisDb 结构体

pseudocode 复制代码
// Redis 数据库结构(简化版)
struct redisDb:
    dict *mainDict          // 主字典:存储所有 Key-Value
                            // Key -> RedisObject 的映射

    dict *expires           // 过期字典:存储设置了 TTL 的 Key
                            // Key -> 过期时间戳(毫秒精度)

    dict *blocking_keys     // 阻塞字典:记录正在阻塞等待的 Key
                            // 如 BLPOP 等待的列表 Key

    dict *ready_keys        // 就绪字典:阻塞 Key 被推送后标记为就绪

    dict *watched_keys      // 事务监控字典:WATCH 命令监控的 Key
                            // 用于乐观锁(Optimistic Locking)

    int id                  // 数据库编号(0-15)

    long long avg_ttl       // 平均 TTL,用于统计

    unsigned long expires_cursor  // 过期扫描游标,用于渐进式过期

// 使用示例
redisDb db0:
    mainDict = {
        "user:1"   -> RedisObject(Hash, {name: "Tom", age: 25}),
        "counter"  -> RedisObject(String, "100"),
        "session"  -> RedisObject(String, "abc123")
    }

    expires = {
        "session"  -> 1720000000000   // session 将在此时间戳过期
    }

相关推荐
蓝胖子酱2 小时前
在 Electron 中用 electron-store 实现加密存储与自定义格式文件
前端·javascript·数据库
snow@li3 小时前
Redis:数据库语法速查表 / 全景梳理
数据库·redis·缓存
Devlive 开源社区3 小时前
Nebula 1.6.0 发布:跨区域桶自动路由、秒传、文本/PDF 预览一次到位
数据库·sql
这个DBA有点耶3 小时前
导个数据中文全变问号?字符集这事儿真没那么简单
数据库·mysql·代码规范
Alluxio3 小时前
Alluxio + Anyscale Ray框架,实现跨区域训练数据读取速度20倍提升
人工智能·分布式·机器学习·缓存·ai
wuqingshun3141593 小时前
MYSQL中如果发生死锁应该如何解决?
数据库·mysql
DO_Community4 小时前
Weaviate 托管数据库现已在 DigitalOcean 上开放公测
数据库·人工智能·开源·向量数据库·weaviate
报错小能手4 小时前
索引常见面试题
数据库
Database_Cool_4 小时前
时序数据库选型:阿里云 Lindorm 时序引擎 vs InfluxDB 全维度对比
数据库·阿里云·时序数据库
其实防守也摸鱼4 小时前
渗透--损坏的对象级别鉴权漏洞(Broken Object Level Authorization, BOLA)
大数据·网络·数据库·学习·tcp/ip·安全·安全威胁分析