目录

redis-单机场景

我把单机和集群分开总结,这篇主要是单机下的基础,优化方案等

概要

有些细节直接看书就行,就不再重复了,一些常用的命令也不说了。重点说使用场景,注意事项,性能分析调优等。 先简单介绍下最基本的数据结构:

typedef struct redisObject {
    // 数据类型 string list set zset hset
    unsigned type:4; 

    // 编码类型
    unsigned encoding:4;

    // 开启策略后 内存不足进行指定回收
    unsigned lru:LRU_BITS; 

    // 内存回收相关,redis使用引用计数回收内存,当refcount为0后,回收内存
    int refcount;
    
    //指向具体的用户数据
    void *ptr;
} robj;

redis存储的数据都是以上述redisObject对象方式存储,主要是为了几个功能: 节省内存、增加内存回收机制、对外的api掩盖了编码不同的复杂

基础命令

编码方式在什么情况下转换细节不说了,具体可以看《redis设计与实现》

string

  • 字符串类型数据会有3种编码方式: int、embstr、raw;
  • 使用场景:
    • 最常用的缓存功能: key是关键字,value是缓存信息
    • 限速: 比如验证码每隔5秒才能重新请求一次,设定一个key超时时间,获取不到key时才能重新请求验证码

hset

  • 字典类型有2种编码方式: ziplist、hashtable
  • 使用场景:
    • 整合string: 如果同类数据都单独kv存储,键过多浪费内存,在业务上也不直观,这时候用hset内聚
    • 字符串序列化: 直接将序列化数据一键保存,坏处是数据量大的情况下要全部取出,修改后再更新,并且序列化有一定的开销。如果用字典取代则第一次存储会麻烦一点,但之后可以指定具体key进行修改,不过hset的整体内存消耗也会大于一个简单的k/v。

list

列表结构对插入,查询是有顺序的

  • 列表类型有2种编码方式: ziplist、quicklist、linkedlist
  • 使用场景:
    • 队列: lpush + rpop
    • 栈: lpush + lpop
    • 消息队列: 命令增加b则可以阻塞,用lpush+brpop 就可以当成一个简单的消息队列使用

set

set不允许数据重复,set之间可以进行交并差集操作

  • 列表类型有2种编码方式: intset(所有元素都是整数)、hashtable
  • 使用场景:
    • 标签: 每个用户有一个爱好集合,多个用户直接查看共同爱好只需要进行交集操作 sinter user1 user2
    • 抽奖: 生成n个随机数写入set,然后每个用户都能进行 spop key 获得一个数字

zset

有序集合根据分值进行排序,增加一个元素都会设置一个分值,之所以用skiplist好处是范围查询。

  • 列表类型有2种编码方式: ziplist、skiplist
  • 使用场景:
    • 排行榜: 根据游戏玩家战斗积分进行排名

有用的小功能

这里介绍一些不太常用,但比较有用的redis操作

Lua & 事务

一组动作要么全部执行成功,要么全部执行失败,这就是事务。 redis中使用事务很简单:

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set a 11
QUEUED
127.0.0.1:6379(TX)> set b aa
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
127.0.0.1:6379>

redis不支持事务回滚,如果写错了某个命令, 最终执行成功后,是无法回滚的。lua脚本可以提供更强大的功能。

命令:

EVAL(命令的关键字) "luascript(Lua脚本)" numkeys(指定的Lua脚本需要处理键的数量) key1 key2.. arg1 arg2...

举例:

127.0.0.1:6379> set a hello
OK
127.0.0.1:6379> eval "return redis.call('GET',KEYS[1])..ARGV[1]" 1 a ee
"helloee"

也可以将脚本加载到redis中,达到复用

127.0.0.1:6379> script load "return redis.call('GET',KEYS[1])..ARGV[1]"
"2395484f3580116c01aecdda33849d4b42b2d5c2"
127.0.0.1:6379> evalsha 2395484f3580116c01aecdda33849d4b42b2d5c2 1 a ee
"helloee"

那么事务能做的,用lua脚本也可以实现,并且lua还能更简单的实现复杂的带业务逻辑的事务比如: 需要对某个有序集合范围内的数据进行分值统一修改,

Bitmap

比如有这样一个场景: 游戏服务搞活动,连续7天登录可以获得奖励。 如果用redis实现,那我们要分别记录7条用户id的集合,最后做交集,计算出符合要求的用户,进行奖励发放。 如果用户量很大,每天上线的用户很多,每个集合会很大,这时候用bitmap可以很方便解决。

一个位只能表示0或1,一个byte则能表示8个元素的状态,如果有8个id为1,2,3,4,5,6,7,8的用户,每个用户登录则修改所占位为1,假设第一天前四个用户登录了,那么一个byte则为11110000,这就是bitmap的原理。随着用户的数量增大,就比其他数据结构节约更大内存。

Hyperloglog

hyperloglog使用基数估算算法,可以大幅度降低内存占用,用来估算一批数据中不重复的元素数量,比hashmapbitmap内存占用还要小。 但不精准,redis的失误率是0.81%,这种使用场景主要是数据量大或对精确度不敏感的场景,比如查看google.com每天的访问总量,这种上亿访问量的站点,即便有几十万,几百万的不精准也不重要。

常用命令:

  1. PFADD key element [element …] : 添加指定元素到 HyperLogLog 中。
  2. PFCOUNT key [key …] : 返回给定 HyperLogLog 的基数估算值。
  3. PFMERGE destkey sourcekey [sourcekey …] : 将多个 HyperLogLog 合并为一个 HyperLogLog

GEO

录入地址的经纬度,就可以获取两个地址的距离,也可以根据经纬度判断是否在某个地址范围

Pipeline 减少网络rtt

多个命令一起发送给redis服务器,redis将结果集统一返回给客户端,减少了多个单一命令的往返时间,多次往返时间变成了一次。 所以在网络延迟大的情况下,pipeline效果更明显。需要注意的是pipeline不是原子操作。

其他命令

  1. append
127.0.0.1:6379> set a hello
OK
127.0.0.1:6379> append a world
(integer) 10
127.0.0.1:6379> get a
"helloworld"

127.0.0.1:6379> exists b
(integer) 0
127.0.0.1:6379> append b hello
(integer) 5
127.0.0.1:6379> get b
"hello"

若有内容则追加,否则和set相同,这个命令的好处是可以追加,也就是无需get后再重新set。 需要注意的是无论原本的编码格式是什么 embstrint,无论追加什么内容都会改变为 raw编码

127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> object encoding a
"int"
127.0.0.1:6379> append a 2
(integer) 2
127.0.0.1:6379> get a
"12"
127.0.0.1:6379> object encoding a
"raw"
  1. getset 返回老值并赋予一个新值,当没有此值的时候返回空
127.0.0.1:6379> getset a hello
(nil)
127.0.0.1:6379> getset a world
"hello"
127.0.0.1:6379> get a
"world"

可以理解为 get + set 命令集,且是原子性的

  1. stream 主要用于消息队列,缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。

持久化

redis是内存数据库,内存在程序退出或异常退出后都会丢失,redis提供了两种持久化的方法

rdb

rdb触发条件有两种

  1. 手动: 手动触发比较简单:使用save命令,会阻塞主线程,在完成之前无法使用其他命令, bgsave会fork子进程异步执行save。

  2. 自动: 一般配置文件里会有 save m n相关配置,表示m秒内数据进行n次修改时,自动触发bgsave。

rdb是全量复制,好处是备份方便,备份文件拷贝到其他机器做灾难恢复,也可以对备份后的rdb做分析检查redis内存性能问题。坏处就是需要fork子进程,频繁操作成本高。 无法实时的持久化。

aof

aof提供了命令级别的持久化,会把写入相关命令同步到aof文件中,因为一条写入命令就立刻落地到文件中,会影响redis的高性能。所以官方提供了三种持久化策略进行选择:

  1. AOF_FSYNC_NO : redis不做任何同步操作,保存时机由系统决定;因为调用的系统命令sync

  2. AOF_FSYNC_EVERYSEC :每一秒钟保存一次

write会写入命令,但fsync命令才会最终执行同步到文件中,所以redis有一个单独的线程来每秒进行fsync操作,理论上只有在系统突然宕机的情况下丢失1秒的数据。(严格 来说最多丢失1秒数据是不准确的)

  1. AOF_FSYNC_ALWAYS :每执行一个命令保存一次

每次有写入命令,都会进行同步到aof文件操作,虽然保证了实时性(最多丢失一个命令),但如果同步过程阻塞,则会影响整个redis的命令读写效率。 当然,也提供了手动执行的方式: 命令为 bgrewriteaof

aof是追加式的写入,这样的缺点就是会有重复,比如:

set a hello
set a world

两条命令,其实可以合并为一条set a world,这以点redis提供了重写机制,用来将已有的aof文件进行整合缩减。 重写条件在配置文件中,比如当aof文件增长比达到 n% 后就会进行重写,会后台启动一个子线程进行重写操作。

内存回收

redis主要有两种回收策略,设置了过期的key,过期后回收。内存达到上限后有限制的回收。这些都有配置进行策略性的回收

过期策略

redis使用了两种方式进行过期键的回收

  1. 惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存

  1. 定期过期

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

两者结合来提高内存的释放效率,节省cpu资源

lazy free

若删除一个大的key,因为慢而阻塞redis,所以4.0开始加入了惰性删除。使用命令unlink key 可以主动的使用惰性删除某个key。或根据下面配置,进行被动的删除。

## 在内存到达最大内存需要逐出数据时使用
## 建议关闭,避免内存未及时释放
lazyfree-lazy-eviction no
## 在KEY过期时使用
## 建议开启
lazyfree-lazy-expire no
## 隐式删除服务器数据时,如RENAME操作
## 建议开启
lazyfree-lazy-server-del no

无论是主动还是被动他们的流程都是一样的:

  1. 删除的时候计算Lazy Free方式释放对象的成本,只有超过特定阈值,才会采用Lazy Free方式
  2. Lazy Free方式会调用bioCreateBackgroundJob函数来使用BIO线程后台异步释放对象。
  3. 当Redis对象执行UNLINK操作后,对应的KEY会被立即删除,不会被后续命令访问到,对应的VALUE采用异步方式来清理。

若对过期不敏感,可以考虑多个key分散过期时间,防止key都在一个时间内过期造成性能影响。

数据删除

redis作为缓存服务时可以利用下面策略来降低内存, 具体策略受maxmemory-policy参数控制,Redis支持6种策略

  1. noeviction:不删除任何key,,新写入操作会报错。
  2. allkeys-lru:在键空间中,移除最近最少使用的key。
  3. allkeys-random:在键空间中,随机移除某个key。
  4. volatile-lru:在设置了过期时间的键空间中,移除最近最少使用的key。
  5. volatile-random:在设置了过期时间的键空间中,随机移除某个key。
  6. volatile-ttl:在设置了过期时间的键空间中,有更早过期时间的key优先移除。

内存优化

基础对象的编码调整

不同的数据有不同的编码方式(encoding)主要是用来节省内存空间,比如使用hset数据类型时,在元素数量小于配置值的时候,同时所有值都小于hash-max-ziplist-value配置时,使用ziplist结构当字典使用,更加节省内存。当超出条件后检索效率会降低,所以会改为hashtable。

每种redis数据结构都有2种或以上的编码方式来实现效率和空间的平衡,元素数量极少的时候即便是0(n²)也可以满足性能需求

对象共享

相同数据多个key会指向同一个对象,这时refcount 会增加引用数量,不过这种节省内存的方式redis只提供了数字上的对象复用,因为判断数字是否一致时间复杂度为0(1),而字符串需要0(n),其他类型可能更复杂,如果复用就导致了时间换空间,对于高性能的redis并不合适。一个数字类型占用空间很小,比一个redisObject对象小的多,再加上判断快,所以对于数字对象的内存共享是很有意义的。

redis 只支持10000以内的数字对象复用,并且不可配置写死在代码中的。

server.h
#define OBJ_SHARED_INTEGERS 10000

server.c

void createSharedObjects(void) {
    //...
    for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
        shared.integers[j] =
            makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
        shared.integers[j]->encoding = OBJ_ENCODING_INT;
    }
    //...
}

需要注意的是如果开启 maxmemoryLRU淘汰策略后对象池就无效了。因为共享一个redisObject后也会共享lru字段:

typedef struct redisObject {
    // 数据类型 string list set zset hset
    unsigned type:4; 

    // 编码类型
    unsigned encoding:4;

    // 开启策略后 内存不足进行指定回收
    unsigned lru:LRU_BITS; 

    // 内存回收相关,redis使用引用计数回收内存,当refcount为0后,回收内存
    int refcount;
    
    //指向具体的用户数据
    void *ptr;
} robj;

导致无法对每个对象的最后访问时间进行分别记录。

内存碎片

什么是内存碎片?网上看到一个例子非常贴切: 坐高铁,假设一个车厢60个位置,目前空位有3个,但这三个都是独立的位置,不是连续的。这时候如果有3个朋友想坐在一起,就无法满足,只能选其他车厢,也就是整体内存是足够的,但无法提供服务

造成内存碎片主要原因是:

  1. 为了方便的做内存管理,内存分配器不会完全按照申请的大小做分配,比如jemalloc分配器,我们申请15字节,jemalloc会给我们20字节内存,这样好处是如果继续写入5字节内容,就减少了一次分配次数,但多出来的5字节就是碎片。
  2. 正常的业务都会对kv内容进行修改删除造成内存扩大或释放

如何判断内存碎片情况:

使用命令:info memory

# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G

# mem_fragmentation_ratio 大于1但小于1.5。这种情况是合理的。
# mem_fragmentation_ratio 大于 1.5 。这表明内存碎片率已经超过了50%。一般情况下,这个时候,我们就需要采取一些措施来降低内存碎片率了
mem_fragmentation_ratio:1.86

解决方法:

  1. 重启redis: 重启后数据重新加载,之前非连续的内存就能连续了
  2. 配置设置: 此配置可以控制redis自动清理
  activedefrag true #  是否开启自动内存清理
  active-defrag-ignore-bytes 100mb # 表示内存碎片的字节数达到 100MB 时,开始 清理;
  active-defrag-threshold-lower 10 #  表示内存碎片空间占操作系统分配给 Redis 的 总空间比例达到 10% 时,开始清理。
  active-defrag-cycle-min 25  #  表示自动清理过程所用 CPU 时间的比例不低于 25%,保证清理能正常开展;
  active-defrag-cycle-max 75  # 表示自动清理过程所用 CPU 时间的比例不高于 75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致 响应延迟升高。

一些性能问题

慢查询日志

slowlog-log-slower-than 10000  记录超过10000微妙的命令
slowlog-max-len 128  最多存储128条慢查询日志

使用命令 slowlog get 就能查询出所有符合上述条件的命令

 1) 1) (integer) 10466    日志id
    2) (integer) 1650529643   命令执行时间
    3) (integer) 377462  执行耗时(微妙)
    命令和命令参数
    4) 1) "LRANGE"
       2) "1611a5de-c05f-11ec-9c1a-0050569ae574_20220421_160652_421424"
       3) "0"
       4) "-1"

    5) "127.0.0.1:45984"  执行命令的客户端
    6) ""

.....

bigkey

利用命令redis-cli -h ip -p port -a pwd --bigkeys 可以查看bigkeys信息。

redis的命令读写是单线程的,操作大的key会直接影响整个阻塞整个服务。redis4.0 前 删除bigkey会阻塞住,4.0之后支持了异步删除。建议不用redis存,或者将bigkey进行拆分

redis与系统

redis与cpu

  • 先简单说说cpu
  1. cpu: 中央处理器,一个cpu不等于物理核,也不等于逻辑核。
  2. 物理核: cpu真正的运行单元,有独立的运作能力(能独自运行指令、有独立缓存)
  3. 逻辑核: 物理核中逻辑层面的核,一个物理核可以有多个逻辑核,物理核通过高速运算,让应用层误以为有多个cpu在运算

奔腾处理器时代,计算机想要提高运算性能,可以使用多个cpu,插入到主板上。但主板上的多个cpu之间进行通信效率非常低,因为通过系统总线完成,所以无法做到1+1=2的效果。既然多个cpu之间通信效率低,于是又在单cpu上进行了研究,之后英特尔开发了超线程(Hyper-threading)技术,它可以复制cpu内部组件,便于线程之间共享信息,这样的好处是加快了多个计算过程,更高效的利用cpu。假设只有一个物理核的cpu,利用超线程,操作系统误以为有2个物理核,需要注意的是这是提高了cpu的利用率,但并没有真正达到2倍的cpu处理能力。超线程提高了性能, 但并没有达到真正意义上的并行处理,之后多核架构的出现,一个cpu内有多个物理核心,达到了真正意义上的并行处理,多个物理核直接不在靠系统总线传输,而是通过共享芯片的内部总线。 最后多个物理核+超线程,就有了现在的 双核4线程/八核16线程的cpu。

  • 对于cpu调用程序
  1. 软亲和性:进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他CPU。Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity) 的特性,因此linux通过这种软的亲和性试图使某进程尽可能在同一个CPU上运行
  2. 硬亲和性:将进程或者线程绑定到某一个指定的cpu核运行,虽然Linux尽力通过一种软的亲和性试图使进程尽量在同一个处理器上运行,但它也允许用户强制指定进程无论如何都必须在指定的处理器上运行。

目前我们的cpu架构是numa架构(非统一内存访问架构(Non-uniform Memory Access,简称NUMA架构),这意味着物理核之间如果处于不同的numa节点,那么内存是分离的,a核心(socket 1)访问b核心(socket 2)内存数据是需要经过总线的,会增加延迟。 linux使用lscpu看cpu情况:

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                8
On-line CPU(s) list:   0-7
Thread(s) per core:    1
Core(s) per socket:    1
座:                 8
NUMA 节点:         1
厂商 ID:           GenuineIntel
CPU 系列:          6
型号:              79
型号名称:        Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz
步进:              1
CPU MHz:             2199.998
BogoMIPS:            4399.99
超管理器厂商:  VMware
虚拟化类型:     完全
L1d 缓存:          32K
L1i 缓存:          32K
L2 缓存:           256K
L3 缓存:           25600K
NUMA 节点0 CPU:    0-7

3种方式

  1. 指定某个进程绑定到cpu: taskset -pc cpuid 进程id
  2. 启动进程的时候进行绑定: taskset -c cpuid 程序启动项
  3. 使用系统调用:
#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <sched.h>

/* 设置进程号为pid的进程运行在mask所设定的CPU上
 * 第二个参数cpusetsize是mask所指定的数的长度
 * 通常设定为sizeof(cpu_set_t)

 * 如果pid的值为0,则表示指定的是当前进程 
 */
int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);/* 获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中 */

redis的持久化,还有个别命令都是在子进程或子线程执行的,也就是说对于redis绑定一个物理核还是有可能阻塞的。另外redis网络用的是io多路复用,监听的是io事件,在使用numa架构的时候我们应该防止redis在绑定cpu时跨节点。

redis 与linux hugepage

  • 写时复制

fork是系统命令,主进程执行后,会将内存数据完全的拷贝在子进程中,相当于创建了一个快照。redis用fork的好处是方便,且不影响主进程工作,因为是完全拷贝了主进程的内存,但当redis内存数据非常大的时候,fork会非常慢,若使用了10g内存,fork之后总体就占了20G内存

linux提供了fork的写时复制(copy-on-write),主要作用就是将拷贝推迟到写操作真正发生时,这也就避免了大量无意义的拷贝操作。

也就是在redis进行 rdb save的时候,fork是很快的,因为fork只是子进程指向了与主进程同样的物理内存中,并没有发生内存复制操作。类似应用代码中创建了个指针,两个指针指向的是同一个数据。只有当主进程的数据做了修改,才会开始复制,并且只会复制修改所在内存页的数据,也就是复制的效率取决于redis在save过程中的写命令是否频繁,内存页的大小。但因为redis总体是读多写少。也就是说假设在fork后进行备份过程中,redis并没有任何写入行为,那么fork子进程进行持久化操作是不会产生额外的使用内存。

刚才说内存页也影响了rdb效率,是因为linux hugepage(大内存页),hugepage可以增加命中率减少页数量的,这对数据库来讲是个好处。同样的内存需求情况下内存页大了意味着页表项的减少,这样就可以提高快表的命中率了,linux系统是支持内存大页机制的 默认是2mb: grep Huge /proc/meminfo,但对于redis进行rdb时利用写时复制,内存页大导致主进程写入操作会复制更大的内存空间和数据,所以如果开启了hugepage redis会有下面log:

WARNING you have Transparent Huge Pages (THP) support enabled in your kernel.
This will create latency and memory usage issues with Redis. To fix this issue run the command
'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, 
and add it to your /etc/rc.local in order to retain the setting after a reboot.
 Redis must be restarted after THP is disabled (set to 'madvise' or 'never').

如果有rdb需求则可以考虑关闭linux hugepage。

redis 与 vm

redis3.0 之前自己开发了vm,之后就去掉了,主要原因可能是代码复杂,重启慢等。3.0开始使用了/proc/sys/vm/overcommit_memory系统相关的vm,若没设置,会有下面的log

 WARNING overcommit_memory is set to 0! Background save may fail under low memory condition.
  To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

linux系统配置/proc/sys/vm/overcommit_memory 有三种策略:

# 设置为2,禁用overcommit,会降低内存的使用效率,浪费内存资源。但是不会发生OOM。
# 设置为1,内核假装总是有足够的内存,直到它实际耗尽
# 设置为0,默认值,适度超发内存,但也有OOM风险。(这也是数据库经常发生OOM的原因)
vm.overcommit_memory=1

配置


# -------- 持久化相关 -------------
# rdb相关持久化 
# save <seconds> <changes>
# Redis 默认配置文件中提供了三个条件:
# 90 0 秒(15 分钟)内有 1 个更改
save 900 1
# 300 秒(5 分钟)内有 10 个更改
save 300 10
# 60 秒内有 10000 个更改
​save 60 10000
# 指定存储至本地数据库时是否压缩数据,默认为 yes,Redis 采用 LZF 压缩,如果为了节省 CPU 时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes

# 存储路径
dir /var/lib/redis/

# aof持久化
# 是否启动aof
appendonly no

# 三种aof策略
# no:表示等操作系统进行数据缓存同步到磁盘(快)  
# always:表示每次更新操作后手动调用 fsync() 将数据写到磁盘(慢,安全) 
# everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec

# -------- 内存管理策略 -------------
# volatile-lru:  对设置了过期时间的keys适用LRU淘汰策略
# allkeys-lru:  对所有keys适用LRU淘汰策略
# volatile-lfu:  对设置了过期时间的keys适用LFU淘汰策略
# allkeys-lfu:  对所有keys适用LFU淘汰策略
# volatile-random:  对设置了过期时间的keys适用随机淘汰策略
# allkeys-random:  对所有keys适用随机淘汰策略
# volatile-ttl:  淘汰离过期时间最近的keys
# noeviction:  不淘汰任何key,仅对写入操作返回一个错误
maxmemory-policy noeviction # 默认是noeviction

# -------- 惰性删除 -------------
## 在内存到达最大内存需要逐出数据时使用
## 建议关闭,避免内存未及时释放
lazyfree-lazy-eviction no
## 在KEY过期时使用
lazyfree-lazy-expire no
## 隐式删除服务器数据时,如RENAME操作
lazyfree-lazy-server-del no

# -------- 数据结构高级配置 -------------
# ziplist最大条目数
hash-max-ziplist-entries 512
# ziplist单个条目value的最大字节数
hash-max-ziplist-value 64
# ziplist列表最大值,默认存在五项:
# -5:最大大小:64 Kb <——不建议用于正常工作负载
# -4:最大大小:32 Kb <——不推荐
# -3:最大大小:16 Kb <——可能不推荐
# -2:最大大小:8 Kb<——很好
# -1:最大大小:4 Kb <——好
list-max-ziplist-size -2
# 一个quicklist两端不被压缩的节点个数
# 0: 表示都不压缩。这是Redis的默认值
# 1: 表示quicklist两端各有1个节点不压缩,中间的节点压缩
# 3: 表示quicklist两端各有3个节点不压缩,中间的节点压缩。
list-compress-depth 0
# 当集合中的元素全是整数,且长度不超过set-max-intset-entries(默认为512个)时,
# redis会选用intset作为内部编码,大于512用set。
set-max-intset-entries 512
# 当有序集合的元素小于zset-max-ziplist-entries配置(默认是128个),同时每个元素
# 的值都小于zset-max-ziplist-value(默认是64字节)时,Redis会用ziplist来作为有
# 序集合的内部编码实现,ziplist可以有效的减少内存的使用。
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
# value大小 小于等于hll-sparse-max-bytes使用稀疏数据结构(sparse),大于hll-sparse-max-bytes使用稠密的数据结构(dense)
hll-sparse-max-bytes 3000
# Streams单个节点的字节数,以及切换到新节点之前可能包含的最大项目数。
stream-node-max-bytes 4096
stream-node-max-entries 100
# 主动重新散列每100毫秒CPU时间使用1毫秒,以帮助重新散列主Redis散列表(将顶级键映射到值)
activerehashing yes

# -------- 内存碎片  -------------
# 是否启用碎片整理,默认是no
activedefrag no
# 最小的碎片空间浪费量
active-defrag-ignore-bytes 100mb
# 最小的碎片百分比阈值
active-defrag-threshold-lower 10
# 最大的碎片百分比阈值
active-defrag-threshold-upper 100
# 碎片整理周期CPU消耗最小百分比
active-defrag-cycle-min 1
# 碎片整理周期CPU消耗最大百分比
active-defrag-cycle-max 25
# redis5.0之后的配置 从set / hash / zset / list 扫描的最大字段数
active-defrag-max-scan-fields 1000
# redis6.0之后的配置 默认情况下,用于清除的Jemalloc后台线程是启用的。
jemalloc-bg-thread yes

# -------- cpu绑定(redis6.0) -------------
# 设置redis服务器的IO线程组的CPU绑定:0,2,4,6
server_cpulist 0-7:2
# 设置BIO线程的CPU绑定为:1,3:
bio_cpulist 1,3
# 设置AOF子进程的CPU绑定为:8,9,10,11
aof_rewrite_cpulist 8-11
# 设置bgsave的CPU绑定为:1,10-11
bgsave_cpulist 1,10-11

# -------- 其他 -------------
# 执行大于多少微妙,才存入慢查询队列
slowlog-log-slower-than 10000
# 慢查询最多保存多少日志
slowlog-max-len 128
# 一个Lua脚本最长的执行时间,单位为毫秒,如果为0或负数表示无限执行时间,默认为5000
lua-time-limit 5000
  • 6379来源: 是手机按键的 MERZ,原因是redis作者Antirez在看一个广告,意大利广告女郎「Alessia Merz」在电视节目上说了一堆愚蠢的话。

参考

  1. 《redis设计与实现》
  2. 《redis开发与运维》
  3. http://cenalulu.github.io/linux/numa/
  4. https://cloud.tencent.com/developer/article/1465603