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实现的完整代码:
package main
import (
"context"
"fmt"
"strconv"
"time"
"github.com/redis/go-redis/v9"
)
var (
RedisClient *redis.Client
ctx = context.Background()
)
const continuesCheckKey = "uid_%d" //存储到redis的key
func init() {
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
})
RedisClient = rdb
}
func main() {
//通过一些业务逻辑获取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)
}
解释分析:
context
:提供了上下文用于处理请求的截止时间、取消信号等。fmt
:用于格式化输出。strconv
:用于字符串和基本数据类型之间的转换。time
:用于时间相关操作。github.com/redis/go-redis/v9
:这是一个 Go 语言的 Redis 客户端库。RedisClient
:用于与 Redis 数据库进行交互的客户端。ctx
:一个全局的上下文变量。continuesCheckKey
:一个常量字符串模板,用于构建存储在 Redis 中的用户签到数据的键名。init()
函数:
main()
函数:
Checkin()
函数。Checkin()
函数:
key
。expAt
,为今天的零点后48小时。使用 Incr()
命令递增用户的连续签到数,并设置过期时间为 expAt
。getUserCheckInDays()
函数获取用户的连续签到天数,并格式化输出。getUserCheckInDays()
函数:
key
。Get()
命令获取用户的连续签到天数(以字符串形式存储)。strconv.Atoi()
将获取到的字符串转换为整数返回。beginningOfDay()
函数: