Redis应用 | 豆包MarsCode AI刷题

91 阅读5分钟

前言

本文章的目的是帮助和我一样第一次接触Redis的新人以最快的速度上手代码。比起课上讲的案例更加基础。

Redis是什么?

Redis(Remote Dictionary Server)是一个开源的高性能内存数据库,也可以用作缓存和消息队列系统。它以键值对的形式存储数据,并支持多种数据结构,是开发者广泛使用的后端工具。

为什么要使用Redis?

快啊。

对于访问频率高、对业务和应用关键的数据的数据(我们称之为热数据),由于Redis所有操作都在内存中完成,速度非常快,特别适合这种需要高吞吐量和低延迟的场景。

安装Redis

其实本来没有这一节,奈何Redis不支持Windows,所以特意写下来吐槽一下

可以用Docker

docker run --name redis -d -p 6379:6379 redis

Windows可以装WSL2:官方文档|在Windows上使用Redis

安装完成后,使用CLI连接:

redis-cli

默认连接到 localhost:6379。

Redis特点?

Redis基于内存的快速读写能力,适合需要实时统计和分析的数据,比如课上讲的签到和点赞统计。

并且,Redis的缓存机制可以减少对数据库的访问压力,加速查询响应。例如会话缓存或者热点数据缓存。

此外,Redis是单线程的,本来这会导致性能下降,但是我们可以用它做分布式锁。

Redis使用

创建和连接数据库:

rdb := redis.NewClient(&redis.Options{ 
    Addr: "localhost:6379",
    Password: "yourpassword", // 如果有密码才写这行 
    DB: 0,
    })

创建上下文: 这一步用于控制请求的生命周期,防止操作无限等待。

ctx := context.Background()

三板斧,设置,读取和删除:

err = rdb.Set(ctx, "key", "value", 0).Err()
val, err := rdb.Get(ctx, "key").Result()
err = rdb.Del(ctx, "key").Err()

不要忘记错误处理。

Redis哈希

注意:我不知道上课讲的HMSET是不是开发环境用的这个,但是HMSET在5.0被标记过时,改用HSET了。

我们存储的对象通常有多个字段,因此使用哈希存储。哈希类型可以存储与一个对象相关的多个字段,避免为每个字段创建单独的 Redis 键。它的好处太多了:对象的所有字段存储在一起,逻辑上更清晰不说,还可以可以一次获取多个字段值,对服务器而言更加节省内存。

假设现在我们有个新对象:

fields := map[string]interface{}{ 
    "name": "Alice", 
    "age": "30", 
    "city": "New York", 
}

操作方法:

err := rdb.HSet(ctx, "user:1001", fields).Err()
values, err := rdb.HMGet(ctx, "user:1001", "name", "age").Result()

Redis数据持久化

RDB持久化

RDB通过创建Redis数据的快照,将内存中的数据保存到一个二进制文件中,在特定时间间隔或特定条件下将数据写入磁盘。

900秒内,1次写入或更多时存储:

save 900 1

AOF持久化

AOF会将每次写操作以日志的形式追加记录到文件中。

redis.conf 文件中启用AOF:

appendonly yes
appendfilename "appendonly.aof"

并通过 appendfsync 配置AOF写入磁盘的频率:

appendfsync always/everysec/no

配置分别是:每次写操作都同步到磁盘、每秒同步一次、由操作系统决定同步时间。

Redis单线程应用

这里用SET而不是SETNX或者INCR实现加锁。

首先,我们来看最重要的特性:

Redis 的 SET 命令带有两个重要选项:NXEX

NX:表示只有键不存在时才设置键。这是加锁的核心。

EX:设置键的过期时间,避免死锁。

加锁:

SET lock_key "lock_value" NX EX 10

lock_key是锁的唯一标识。lock_value可以是随机值,用于释放锁时验证锁的所有权。EX 10:设置锁的超时时间为 10 秒。简单讲,key是锁的标识,value是客户端的标识。

检查锁是否存在:

GET lock_key

nil为不存在。

删除锁(一定要看完!):

DEL lock_key

释放锁时需要验证锁的拥有权,确保只有持有锁的线程能解锁。

那么,value是做什么的?让我们来看这样一种情况:我们在加锁的时候为锁设置了一个过期时间,以避免因持有锁的进程意外崩溃或延迟而导致的死锁问题。但是,如果一个锁因过期时间到而自动释放,但新的线程在此时获得了锁,那么它们会用同一个key。这导致旧线程不小心释放了新线程的锁,称作锁的误删。

具体来讲,在分布式锁中,每个key通常表示一个需要保护的资源,因此,假设线程A,B都想要一个资源,那么它们会使用同一个lock_key进行加锁。比如,线程A设定锁10秒过期但是10秒后A未完成,锁被释放,此时线程B使用同一个lock_key请求该资源。又过了10秒,A执行完成,如果没有lock_value,A会释放B的锁

所以,我们用一个Lua脚本来解决这个问题。Redis 的 Lua 脚本是单线程执行的,Lua 脚本中的所有命令要么全部执行,要么都不执行。其他 Redis 命令无法插入其中。太棒了,我们的问题解决了。

result, err := rdb.Eval(ctx, `
    if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("DEL", KEYS[1])
    else
        return 0
    end
`, []string{"lock_key"}, "lock_value").Result()