Redis简介

Redis(全称为Remote Dictionary Server)是一个开源的高性能键值对存储系统,具有快速、灵活和可扩展的特性。它是一个基于内存的数据结构存储系统,可以用作数据库、缓存和消息代理。

Redis 支持多种数据结构,包括字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)。这些数据结构提供了丰富的操作命令,使得开发者可以方便地处理各种数据需求。此外,Redis 还提供了两种持久化方式,即快照(Snapshotting)和日志追加(Append-only file,AOF)。快照方式将 Redis 内存数据以二进制格式写入磁盘,而 AOF 则通过追加记录 Redis 的操作命令来实现持久化。

Redis 还支持发布/订阅模式,可以用作消息代理。发布者将消息发送到指定的频道,订阅者则可以接收和处理这些消息。这种模式在构建实时通信、事件驱动系统和消息队列等场景中非常有用。此外,Redis 还可以通过主从复制和分片来实现数据的分布式存储和高可用性。主从复制可以将数据复制到多个从节点,实现读写分离和数据备份。而分片则可以将数据分布在多个 Redis 节点上,实现横向扩展和负载均衡。

案例详解

接下来进入正题,介绍大厂程序员是怎么使用Redis的,第一个经典案例是连续签到。

对于连续签到这个问题想必大家应该都不陌生,逻辑大概是用户每日有一次签到的机会,如果断签,连续签到计数将归0。连续签到的定义:每天必须在23:59:59前签到,也就是说键的过期时间需要设置为后天的0点。下面是用Golang+Redis实现的完整代码:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
package main

import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
"strconv"
"time"
)

var (
RedisClient *redis.Client
ctx = context.Background()
)

const continuesCheckKey = "uid_%d" //存储到redis的key

func InitRedis() {
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
})
RedisClient = rdb
}

func main() {

InitRedis()
//通过一些业务逻辑获取uid,这里为了方便演示,直接写死
uid := 5201314
Checkin(ctx, uid)
}

// Checkin 连续签到天数
func Checkin(ctx context.Context, uid int) {
key := fmt.Sprintf(continuesCheckKey, uid)

//用户在某一天内不能重复签到
if exists, _ := RedisClient.Exists(ctx, key).Result(); exists == 1 {
if ttl := RedisClient.TTL(ctx, key).Val(); ttl >= time.Duration(24)*time.Hour {
fmt.Printf("用户[%d]今天已经签到过了\n", uid)
return
}
}

// 1. 连续签到数+1
err := RedisClient.Incr(ctx, key).Err()
if err != nil {
fmt.Printf("用户[%d]连续签到失败", uid)
return
} else {
expAt := beginningOfDay().Add(48 * time.Hour)
// 2. 设置签到记录在后天的0点到期
if err := RedisClient.ExpireAt(ctx, key, expAt).Err(); err != nil {
panic(err)
} else {
// 3. 打印用户续签后的连续签到天数
day, err := getUserCheckInDays(ctx, uid)
if err != nil {
panic(err)
}
fmt.Printf("用户[%d]连续签到:%d(天), 过期时间:%s", uid, day, expAt.Format("2006-01-02 15:04:05"))
}
}
}

// getUserCheckInDays 获取用户连续签到天数
func getUserCheckInDays(ctx context.Context, userID int) (int, error) {
key := fmt.Sprintf(continuesCheckKey, userID)
days, err := RedisClient.Get(ctx, key).Result()
if err != nil {
return 0, err
} else {
return strconv.Atoi(days)
}
}

// beginningOfDay 获取今天0点时间
func beginningOfDay() time.Time {
now := time.Now()
y, m, d := now.Date()
return time.Date(y, m, d, 0, 0, 0, 0, time.Local)
}

解释分析:

  1. 导入包:
    • context:提供了上下文用于处理请求的截止时间、取消信号等。
    • fmt:用于格式化输出。
    • strconv:用于字符串和基本数据类型之间的转换。
    • time:用于时间相关操作。
    • github.com/redis/go-redis/v9:这是一个 Go 语言的 Redis 客户端库。
  2. 全局变量和常量:
    • RedisClient:用于与 Redis 数据库进行交互的客户端。
    • ctx:一个全局的上下文变量。
    • continuesCheckKey:一个常量字符串模板,用于构建存储在 Redis 中的用户签到数据的键名。
  3. init() 函数:
    • 初始化 Redis 客户端连接,连接到本地 Redis 服务器。
  4. main() 函数:
    • 为了演示,直接写定了一个用户ID(5201314),然后调用了 Checkin() 函数。
  5. Checkin() 函数:
    • 接受上下文和用户ID作为参数。
    • 构建用户签到的键名 key
    • 检查用户今天是否已经签到,如果已签到则判断是否距离上次签到超过24小时,如果是则表示今天已经签到,不再处理。
    • 如果用户未签到,计算签到记录过期时间 expAt,为今天的零点后48小时。使用 Incr() 命令递增用户的连续签到数,并设置过期时间为 expAt
    • 调用 getUserCheckInDays() 函数获取用户的连续签到天数,并格式化输出。
  6. getUserCheckInDays() 函数:
    • 根据用户ID构建签到数据键名 key
    • 使用 Get() 命令获取用户的连续签到天数(以字符串形式存储)。
    • 使用 strconv.Atoi() 将获取到的字符串转换为整数返回。
  7. beginningOfDay() 函数:
    • 返回当前日期的零点时间。