Redis 编译安装与使用

Redis 是一个常用的开源内存数据库,常被用作缓存,读写性能极快。支持数据持久化,可以将内存中的数据保存在磁盘中。支持如下数据类型:字符串、哈希、列表、集合、有序集合。

image

编译安装

获取源码,编译,安装到指定目录。编译选项可查看 Redis 源码 README.md 中 Building Redis 章节。

git clone https://github.com/redis/redis.git
cd redis
# BUILD_TLS=yes 启用 TLS
# USE_SYSTEMD=yes 开启支持 systemd
make CFLAGS="-O0 -g" MALLOC=jemalloc BUILD_TLS=yes USE_SYSTEMD=yes
make install PREFIX=/home/postgres/redis   # 安装到指定目录

需要安装依赖:

  • build-essential
  • libsystemd-dev

配置 redis.conf,还有非常多的配置项,这里只列出最常用的几个:

port 6379      # 监听端口
dir /home/postgres/redis/data   # 数据目录
logfile /home/postgres/redis/redis.log  # 日志文件
pidfile /home/postgres/redis/redis.pid  # pid 文件
maxmemory 2gb    # 最大内存
timeout 300      # 客户端空闲断开连接超时时间,单位秒
loglevel debug   # 日志级别
databases 16     # 数据库数量

启动 Redis,使用默认配置:

postgres@slpc:~/redis$ ./bin/redis-server
60088:C 06 May 2025 17:09:50.146 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
60088:C 06 May 2025 17:09:50.146 * Redis version=8.0.0, bits=64, commit=e91a340e, modified=0, pid=60088, just started
60088:C 06 May 2025 17:09:50.146 # Warning: no config file specified, using the default config. In order to specify a config file use ./bin/redis-server /path/to/redis.conf
60088:M 06 May 2025 17:09:50.146 * monotonic clock: POSIX clock_gettime
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis Open Source
  .-`` .-```.  ```\/    _.,_ ''-._      8.0.0 (e91a340e/0) 64 bit
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 60088
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           https://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

60088:M 06 May 2025 17:09:50.148 * Server initialized
60088:M 06 May 2025 17:09:50.148 * Ready to accept connections tcp

启动 Redis,使用自定义配置:

postgres@slpc:~/redis$ redis-server redis.conf   # 启动 Redis,使用自定义配置
postgres@slpc:~/redis$ ls
bin  data  redis.conf  redis.log  redis.pid    # Redis 启动后,生成了 redis.log 以及 redis.pid 文件,数据文件在 data 目录下
postgres@slpc:~/redis$ tail -f redis.log
60303:C 06 May 2025 17:13:50.450 * Configuration loaded
60303:M 06 May 2025 17:13:50.451 * monotonic clock: POSIX clock_gettime
60303:M 06 May 2025 17:13:50.452 * Running mode=standalone, port=6379.
60303:M 06 May 2025 17:13:50.452 * Server initialized
60303:M 06 May 2025 17:13:50.453 . The AOF directory appendonlydir doesn't exist
60303:M 06 May 2025 17:13:50.453 * Ready to accept connections tcp

通过客户端连接 Redis,验证:

postgres@slpc:~/redis/bin$ redis-cli
127.0.0.1:6379> ping    # 测试连接
PONG
127.0.0.1:6379> set mykey hangzhou
OK
127.0.0.1:6379> get mykey
"hangzhou"

Redis 的基本使用

字符串类型是最基本的类型,一个 key 对应一个 value。

# 字符串类型,一个键最大能存储 512MB
127.0.0.1:6379> set tianjin "shentongdb"
OK
127.0.0.1:6379> get tianjin
"shentongdb"

哈希是一个键值对集合。

127.0.0.1:6379> hmset db beijing kingbasedb tianjing shentongdb wuhan damengdb
OK
127.0.0.1:6379> hgetall db  # 获取在哈希表中指定 key 的所有字段和值
1) "beijing"
2) "kingbasedb"
3) "tianjing"
4) "shentongdb"
5) "wuhan"
6) "damengdb"

127.0.0.1:6379> hmget db beijing  # 获取所有给定字段的值
1) "kingbasedb"

列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

127.0.0.1:6379> lpush database dameng   # 将一个或多个值插入到列表头部
(integer) 1
127.0.0.1:6379> lpush database kingbase
(integer) 2
127.0.0.1:6379> lrange database 0 10
1) "kingbase"
2) "dameng"

集合是 String 类型的无序集合,不允许重复成员。集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是 O(1)。

127.0.0.1:6379> sadd city tianjing  # 将一个元素添加到集合
(integer) 1
127.0.0.1:6379> sadd city beijing
(integer) 1
127.0.0.1:6379> sadd city hangzhou
(integer) 1
127.0.0.1:6379> smembers city  # 返回集合中的所有成员
1) "tianjing"
2) "beijing"
3) "hangzhou"

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。Redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是 O(1)。

127.0.0.1:6379> zadd mycity 1 nanjing  # 向有序集合添加一个或多个成员,或者更新已存在成员的分数
(integer) 1
127.0.0.1:6379> zadd mycity 2 guilin
(integer) 1
127.0.0.1:6379> zadd mycity 5 hangzhou
(integer) 1
127.0.0.1:6379> zadd mycity 3 qingdao
(integer) 1
127.0.0.1:6379> zadd mycity 4 tianjin
(integer) 1
127.0.0.1:6379> zrange mycity 0 10 withscores  # 返回有序集合中指定区间内的成员及分数
 1) "nanjing"
 2) "1"
 3) "guilin"
 4) "2"
 5) "qingdao"
 6) "3"
 7) "tianjin"
 8) "4"
 9) "hangzhou"
10) "5"

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

127.0.0.1:6379> pfadd base "beijing" "tianjin" "qingdao" "xian" "nanjing"
(integer) 1
127.0.0.1:6379> pfcount base
(integer) 5

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

127.0.0.1:6379> subscribe mychannel   # 订阅消息
1) "subscribe"
2) "mychannel"
3) (integer) 1
1) "message"     # 接收到消息
2) "mychannel"
3) "oscar"

postgres@slpc:~/works/opensource/redis$ redis-cli  # 发布消息
127.0.0.1:6379> publish mychannel "oscar"
(integer) 1

Redis 事务可以一次执行多个命令,并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set db postgres
QUEUED
127.0.0.1:6379> get db
QUEUED
127.0.0.1:6379> exec
1) OK
2) "postgres"

主流程

Redis 主流程如下,启动后会做一些初始化工作,然后进入事件循环,等待命令请求。

main(int argc, char **argv)
--> spt_init(argc, argv);       // 初始化进程标题,通过 ps 命令看到的进程名称
--> zmalloc_set_oom_handler(redisOutOfMemoryHandler); // 设置内存分配失败时的自定义处理函数
--> initServerConfig();  // 设置 Redis 的全局默认配置参数
--> ACLInit();   // ACL(访问控制列表)子系统的初始化
--> moduleInitModulesSystem();  // 初始化模块系统
--> connTypeInitialize();   // 初始化连接类型
--> initServer();       // 服务端初始化
    --> ThreadsManager_init();
    --> createSharedObjects();
    --> adjustOpenFilesLimit();
    --> aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);  // 事件循环初始化
        --> aeApiCreate(eventLoop)
            --> epoll_create(1024);   // 创建 epoll 实例
    --> evictionPoolAlloc();
    --> slowlogInit();
    --> applyWatchdogPeriod();
--> ACLLoadUsersAtStartup();
--> initListeners();  // 初始化网络监听,注册监听事件
    --> connListen(listener)   // 创建监听套接字
    --> createSocketAcceptHandler(listener, connAcceptHandler(listener->ct))
        --> aeCreateFileEvent(server.el, sfd->fd[j], AE_READABLE, accept_handler,sfd)
            --> aeApiAddEvent(eventLoop, fd, mask)
                --> epoll_ctl(state->epfd,op,fd,&ee)
--> InitServerLast();
--> redisSetCpuAffinity(server.server_cpulist);
--> setOOMScoreAdj(-1);
--> aeMain(server.el);
    while (!eventLoop->stop)
        aeProcessEvents(eventLoop, AE_ALL_EVENTS | AE_CALL_BEFORE_SLEEP | AE_CALL_AFTER_SLEEP);
--> aeDeleteEventLoop(server.el);

Redis 服务端会调用 aeApiPoll 函数等待事件,当事件发生时,会调用 aeApiPoll 函数,该函数会调用 epoll_wait 函数,该函数会阻塞当前线程,直到有事件发生。调用栈如下:

aeApiPoll(aeEventLoop * eventLoop, struct timeval * tvp) (redis\src\ae_epoll.c:93)
aeProcessEvents(int flags, aeEventLoop * eventLoop) (redis\src\ae.c:398)
aeMain(aeEventLoop * eventLoop) (redis\src\ae.c:495)
main(int argc, char ** argv) (redis\src\server.c:7553)

Redis 与 PostgreSQL 的对比

Redis 与 PostgreSQL 相比,两者的应用场景和技术实现都有非常大的差异。

在应用场景上,Redis 核心场景为高速缓存(比如缓存数据库查询结果),而 PostgreSQL 核心场景为事务型应用、OLTP 场景,存储结构化数据。虽然 Redis 也可以持久化存储数据,但是其优势场景依然是作为缓存使用。为什么 Redis 非常快呢?因为它简单,没有复杂的查询和事务处理,没有 WAL 日志,没有 MVCC,无需解析器,不用生成执行计划,最重要的一点,它的全量数据是存放在内存中的,不用像 PostgreSQL,buffer 中没有还需要访问磁盘,这当然快了。为什么 Redis 可以把全量数据放在内存中呢?因为它的设计初衷就是作为缓存使用,既然是缓存,丢就丢了,不用考虑持久化的问题,当然 Redis 还是支持持久化的,毕竟宕机重启后,有持久化其重建缓存也快呀,当前考虑的点不单单是这一点。相比 PostgreSQL,其精简了大多数的处理过程,使得 Redis 的读写速度非常快。

在功能上,Redis 与 PostgreSQL 支持的数据类型有较大区别,Redis 支持 string、hash、list、set、zset、bitmap、hyperloglog、geo 等数据类型,而 PostgreSQL 支持的数据类型更多,如数组、JSON、JSONB、XML、UUID、HSTORE、CLOB、BLOB 等。

在持久化上,Redis 以内存为主,可进行持久化配置(AOF 会削弱 Redis 缓存性能),而 PostgreSQL 以磁盘为主,支持 ACID 事务。

在事务支持上,PostgreSQL 支持完整的 ACID 事务,支持隔离级别,而 Redis 支持简单事务,无法回滚,无隔离级别。

在资源消耗上,Redis 对内存要求较高,全量数据在内存中,内存必须足够,而 PostgreSQL 对内存要求较低,内存作为缓存使用,磁盘作为持久化存储,内存不足时会将数据淘汰到磁盘,从而降低内存压力。

典型应用场景:

用户请求 -> Redis 查询缓存 -> 未命中 -> 查询 PostgreSQL -> 结果写入 Redis

为什么需要在 PostgreSQL 数据库前面加一层 Redis 缓存呢?如果请求量不大,PostgreSQL 数据库性能足够支撑,那么就不需要 Redis 缓存了,当请求量变大,PostgreSQL 数据库性能不足以支撑,那么就需要 Redis 缓存了,加了一层缓存,为 PostgreSQL 削减了请求压力。

Redis 作为缓存,其 QPS 必须要比 PostgreSQL 高,否则 Redis 缓存没有意义。

Redis 应用场景

Redis 有着非常多的应用场景,比如:

  • 缓存
  • 计数器
  • 消息队列
  • 延时队列
  • 分布式锁