基本信息

Redis (Remote Dictionary Server)是一个开源(BSD 许可)的、基于内存的、多数据结构、键值对型的数据存储系统,常被用作数据库、缓存和消息中间件;
它可以使用客户端分片来扩展写性能,内置了:复制 (replication)、LUA 脚本(Lua scripting)、LRU 驱动事件(LRU eviction)、事务(transactions)和不同级别的磁盘持久化(persistence), 并通过 Redis 哨兵(Sentinel)和自动分区(Cluster)实现高可用(high availability)集群方案。

服务特性

  • 完全基于内存操作,性能极高,读写速度快
  • 支持多种数据类型 - string、Hash、list、set、sorted set
  • 支持多种数据淘汰策略

    volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
    volatile-ttl :从已设置过期时间的数据集中挑选将要过期的数据淘汰
    volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
    allkeys-lru:从所有数据集中挑选最近最少使用的数据淘汰
    allkeys-random:从所有数据集中任意选择数据进行淘汰
    noeviction :禁止驱逐数据

  • 提供多种持久化操作:RDB 和 AOF 以及混合模式

  • 支持主从模式,支持读写分离与分布式,能通过 Redis Cluster 提供集群模式

    性能优势

  • 完全基于内存
  • 数据结构简单,操作方便,并且不同数据结构能够应对于不同场景
  • 采用单线程(网络请求模块使用单线程,其他模块仍用了多线程),避免了不必要的上下文切换和竞争条件,也不存在多进程或多线程切换导致 CPU 消耗,不需要考虑各种锁的问题。
  • 使用多路 I/O 复用模型,为非阻塞 I/O
  • Redis 本身设定了 VM 机制,没有使用 OS 的 Swap,可以实现冷热数据分离,避免因为内存不足而造成访问速度下降的问题

    数据淘汰

    Redis 有两种方式实现缓存淘汰:

  • 消极方式:访问 Redis Key 时,发现失效就删除

  • 积极方式:周期性从设置了失效时间的 Key 中,根据淘汰策略,删除一部分失效的 Key。

    存储分区

    Redis Cluster 集群包含 16384 个虚拟 Hash 槽,它通过一个高效的算法来计算 Key 属于哪个 Hash 槽;
    Redis Cluster 支持请求分发 - 节点在接到一个命令请求时,会先检测这个命令请求要处理的键所在的槽是否由自己负责,如果不是的话,节点将向客户端返回一个 MOVED 错误,MOVED 错误携带的信息可以指引客户端将请求重定向至正在负责相关槽的节点。

    主从复制

    Redis 2.8 后支持异步复制,有两种模式:
  • 完整重同步(full resychronization):用于初次复制,执行步骤与 SYNC 命令基本一致。
  • 部分重同步(partial resychronization):用于断线后重复制,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只需接收并执行这些写命令,即可将主从服务器的数据库状态保持一致。

集群中每个节点都会定期向集群中的其他节点发送 PING 消息,以此来检测对方是否在线;
如果一个主节点被认为下线,则在其从节点中,根据 Raft 算法,选举出一个节点,升级为主节点。

数据一致

Redis 不保证强一致性,因为这会使得集群性能大大降低,它是通过异步复制来实现最终一致性

数据结构

Redis 存储的所有数据都是以唯一的 key 字符串作为名称,以及与之相应的 Value 数据;它有五种基础数据结构,分别为:string(字符串)、list(列表)、hash(字典)、set(合集)和 zset(有序合集)

数据类型 可存储的值 操作
STRING 字符串、整数或者浮点数 对整个字符串或者字符串的其中一部分执行操作对整数和浮点数执行自增或者自减操作
LIST 列表 从两端压入或者弹出元素对单个或者多个元素进行修剪只保留一个范围内的元素
SET 无序集合 添加、获取、移除单个元素检查一个元素是否存在于集合中计算交集、并集、差集从集合里面随机获取元素
HASH 包含键值对的无序散列表 添加、获取、移除单个键值对获取所有键值对检查某个键是否存在
ZSET 有序集合 添加、获取、删除元素根据分值范围或者成员来获取元素计算一个键的排名

string

字符串是 Redis 最简单的数据结构,它的内部就是一个字符串数组;用途非常广泛,常见的如缓存用户信息
Redis 的字符串是动态字符串,可以修改,采用预分配冗余空间的方式来减少内存的频繁分配;一般分配的空间会高于实际字符串长度(容量) len。
当字符串长度(容量)小于 1MB 时,扩容都是加倍现有的空间;如果长度(容量)超过 1MB,扩容时一次只会多扩 1MB。
字符串最大长度(容量)为 512MB。

1
2
3
4
5
6
7
8
9
10
11
12
#设置
set name junyu
#获取
get name
#校验
exists name
#删除
del name
#批量设置
mset name junyu age 30
#批量获取
mget name age

list

Redis 的列表是链表不是数组,意味着 list 的插入和删除操作非常快,时间复杂度为 O (1);但索引定位很慢,时间复杂度为 O (n)。
列表中的每一个元素都可以使用双向指针顺序,串起来可以同时支持向前或向后遍历;当列表弹出最后一个元素之后,该数据结构被自动删除,内存被回收。
Redis 的列表常用来做异步队列使用,将需要延后处理的任务结构体序列化成字符串,塞进 Redis 列表中,另一个线程从这个列表中轮询数据进行处理。
队列:先进先出的数据结构,常用于消息队列异步逻辑处理,确保元素的访问顺序性

1
2
3
4
5
6
#创建,入操作
rpush l A B C
#统计长度
llen
#出操作
lpop

栈:先进后出的数据结构,跟队列正好相反(业务场景)
1
2
3
4
#创建,入操作
rpush l A B C
#出操作
rpop

hash

Redis 的字典是无序字典,内部存储了很多键值对;都是 “数组 + 链表”的二维结构,采用了渐进式 rehash 的策略。
渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务或操作指令中,渐进的将旧的 Hash 内容一点点移动到新的 Hash 结构中。当移动完成,就会使用新的 hash 结构代替。
当 hash 移除最后一个元素之后,该数据结构会被自动删除,内存被回收。
hash 结构也可以用于存储用户信息,并且不需要序列化,可以直接对用户的每个字段单独存储。当我们需要获取用户信息时可以部分获取,而以字符串存储用户信息只能全部读取,这样会浪费网络流量。
hash 也有缺点,hash 结构的存储消耗要远高于单个字符串,到底该使用 hash 还是字符串,应该根据实际情况再三权衡。

1
2
3
hset
hlen
hget

set

Redis 的集合内部键值对是无序的,唯一的;相当于一个特殊的字典,字典中所有的 Value 值都是 NULL。
因为有去重功能,set 可以用来存储活动中奖的用户 ID,可以确保同一用户不会多次中将。
当集合中最后一个元素被移除后,数据结构被自动删除,内存被回收。

1
2
3
4
5
6
7
8
#添加条目
sadd
#查看集合(无序)
smembers
#获取长度
scard
#出操作
spop

zset

zset 可能是 Redis 中提供的最具有特色的数据结构,叫“跳跃列表”;一方面它是一个 set 保证了内部 Value 唯一性,另一方面它可以给每一个内部 Value 赋予一个 score,代表这个 value 的排位权重。

  • 存储粉丝列表,value 存粉丝 ID,score 为关注时间,按照关注时间进行排序
  • 存储学生成绩,value 存学生 ID,score 为考试成绩,按照分数排名得到名次

zset 中的最后一个 value 被移除后,数据结构被自动删除,内存被回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#插入数据
zadd demo 1 "aaa"
zadd demo 2 "bbb"
zadd demo 3 "ccc"
#按 score 顺序列出
zrange demo 0 -1
#按 score 逆序列出
zrevrange demo 0 -1
#计数
zcard demo
#获取指定 value 的 score 值
zscore demo "ccc"
#获取 value 的排名(从 0 开始)
zrank demo "ccc"
# 删除 value
zrem demo "ccc"

通用规则

list、set、hash、set 这四种数据结构是容器型的数据结构,它们共享下面两条通用的规则:

  • create if not exists:容器不存在,那就创建一个,在进行操作
  • drop if no elements:容器为空,立即自动删除容器,释放缓存

    生命周期

    Redis 的所有数据都可以已对象为单位设置过期时间,时间一过自动删除相应的对象;都已经存在的对象使用了 set 方法过期时间会取消。
    1
    2
    3
    4
    #设置过期时间
    expire demo 5
    #查看剩余时间
    ttl demo

    性能测试

    1
    2
    redis-benchmark -n 10000  -q
    redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -n 10000 -q

    编译安装

    1
    2
    3
    4
    5
    6
    7
    8
    cd /app/software
    #解压并编译安装
    tar -zxvf redis-5.0.14.tar.gz
    cd redis-5.0.14
    #make && make install
    make && make install PREFIX=/app/midware/redis
    mkdir -pv /app/data/redis/conf && mkdir -pv /app/logs/redis
    cp redis.conf /app/data/redis/conf/6379.conf
    其他工具
  • redis-benchmark:性能测试工具
  • redis-check-aof:AOF 文件修复工具
  • redis-check-rdb:RDB 文件修复工具
  • redis-cli:命令行客户端
  • redis.conf:配置文件
  • redis-server:服务进程
  • redis-sentinel:哨兵

    配置文件

    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
    vi /app/data/redis/conf/6379.conf

    #修改如下
    #任何主机均可访问 redis
    bind 0.0.0.0
    port 6379
    #开启守护进程(即后台运行)
    daemonize yes
    pidfile /app/data/redis/redis_6379.pid
    #日志目录
    logfile "/app/logs/redis/redis_6379.log"
    #开启 RDB 持久化
    stop-writes-on-bgsave-error yes
    dbfilename dump_6379.rdb
    #数据目录
    dir /app/data/redis/
    #开启 AOF 持久化
    appendonly yes
    appendfilename "appendonly_6379.aof"
    #集群设置
    cluster-enabled yes
    cluster-config-file et.conf
    cluster-node-timeout 15000
    #集群中的所有 slot(16384)全部覆盖,才能提供服务
    cluster-require-full-coverage no

    相关命令

    1
    2
    3
    ln -s /app/midware/redis/bin/* /usr/bin
    #拷贝官方脚本(root 权限)
    cp /app/software/redis-5.0.14/utils/redis_init_script /etc/init.d/redis
    修改启动脚本
    1
    2
    3
    4
    5
    6
    7
    vi /etc/init.d/redis

    # chkconfig: 2345 80 90
    EXEC=/usr/bin/redis-server
    CLIEXEC=/usr/bin/redis-cli
    PIDFILE=/app/data/redis/redis.pid
    CONF="/app/data/redis/conf/6379.conf"
    1
    2
    #给 /etc/init.d/redis 增加可执行权限
    chmod +x /etc/init.d/redis
    1
    2
    3
    4
    5
    6
    7
    8
    #启用 redis 开机启动
    chkconfig redis on
    #禁用 redis 开机启动
    chkconfig redis off
    #启动 redis
    /etc/init.d/redis start
    #关闭 redis
    redis-cli shutdown
    同节点多服务启动
    1
    2
    redis-server /app/data/redis/conf/6379.conf
    redis-server /app/data/redis/conf/6479.conf
    MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk
    1
    2
    config get stop-writes-on-bgsave-error
    config set stop-writes-on-bgsave-error no
    1
    2
    3
    vi /etc/sysctl.conf 
    vm.overcommit_memory=1
    sysctl -p /etc/sysctl.conf

    集群部署

    Redis 集群至少需要 6 个实例(三主三从),按上述方式设置 6 个单节点(可以三台主机每台主机两个端口),然后创建集群
    1
    redis-cli --cluster create --cluster-replicas 1 xxx.xxx.xxx.xxx:6379 xxx.xxx.xxx.xxx:6379 xxx.xxx.xxx.xxx:6379 xxx.xxx.xxx.xxx:6379 xxx.xxx.xxx.xxx:6379 xxx.xxx.xxx.xxx:6379
    验证
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    redis-cli -p 6379
    cluster info
    cluster nodes
    cluster slots
    #添加节点
    cluster MEET <ip> <port>
    #删除节点
    cluster forget <node_id>
    cluster failover
    #将当前节点设置为 node_id 的从节点
    cluster replicate <node_id>
    #保存配置
    cluster saveconfig

    修改密码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #本地登录 Redis
    redis-cli -c -h xxx -p 6379
    #设置集群密码(单机部署忽略)
    config set masterauth CL@gK6@sn
    #设置集群密码
    config set requirepass CL@gK6@sn
    #密码认证
    auth CL@gK6@sn
    #写入配置文件
    config rewrite

    管理节点

    1
    2
    3
    4
    5
    6
    7
    # 新增节点
    redis-cli --cluster add-node ip:port
    # 检查集群
    redis-cli --cluster check ip:port
    # 删除节点
    redis-cli --cluster del-node ip:port id
    redis-cli --cluster saveconfig