Redis-基础

前言

Redis是一个开源(BSD许可)的,基于内存的数据存储系统,它可以用作数据库、缓存和消息中间件。

安装、启动

环境:CentOS 7.6 (1810)

  1. 下载最新的稳定版本redis
1
wget https://download.redis.io/releases/redis-6.0.9.tar.gz
  1. 编译

由于gcc版本较低,直接编译会报错,因此,先更新gcc

1
2
3
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc
scl enable devtoolset-9 bash

解压编译

1
2
3
4
5
tar -xzvf redis-6.0.9.tar.gz
mv redis-6.0.9 redis
cd redis
make
make install
  1. 测试
1
redis-server -v

结果

1
Redis server v=6.0.9 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=4b37fcaf3ca1b943
  1. 启动redis-server
1
redis-server ./redis.conf

其中 ./redis.conf 是配置文件路径,如果不指定配置文件,则使用默认参数启动。

在下载并解压后的文件夹根目录中有配置模板 redis.conf,其中的注释详细的说明了每个参数的意义。

客户端使用

redis中自带了一个客户端工具 redis-cli,通过类shell的交互方式与redis server连接、交互。

redis-cli 中对于命令,大小写不敏感,对于变量名,大小写敏感。

启动客户端

1
reids-cli -h 127.0.0.1 -p 6379 -n 0

其中:

-h 和 -p 参数 指定 redis server 的 host 和 端口号,不指定该参数,默认连接127.0.0.1:6379

-n参数指定使用第几个数据库,redis默认有16个库,各库中的数据相互独立。不使用-n参数时,默认使用数据库0

如果redis server配置了密码,还需要使用 -a 参数配置密码

查看帮助

redis的help指令提供了对每条指令的帮助信息。

  • 按组查看帮助:help @<group> ,如help @string,会展示所有string相关的指令格式和简介。共分为15个组,可以使用TAB键切换和自动补全。
  • 查看某条指令的帮助: help <cmd>

数据类型

redis以key-value键值对的形式存储数据,所有的key都是String类型,value有5钟数据类型,分别是String、List、Set、SortedSet、Hash,每种数据类型都有其支持的操作命令。

字符串 String

字符串是最常用的数据类型。

基本操作

命令 意义
SET key val [EX sec | PX ms | KEEPTTL] [NX | XX] 设置字符串内容
可选项[EX sec | PX ms | KEEPTTL]:设置过期时间,如果不配置,默认-1,即不过期
EX 后面跟的数字表示秒,PX后面跟的数字表示毫秒,KEEPTTL表示保留原有的过期时间
如:SET k1 “abc” EX 10,表示10秒后过期
可选项[NX | XX]表示命令执行的条件
NX:key不存在,才能set成功,即只能新建,XX:key存在,才能set成功,即只能更新
GET key 获取字符串内容
MSET key1 val1 key2 val2 … 设置多个字符串内容
MGET key1 key2 … 获取多个字符串内容
GETSET key value 设置新值并返回旧值

字符串操作

命令 意义
APPEND key val 字符串拼接
GETRANGE key start end 字符串截取,start、end可取正负索引
SETRANGE key offset val 从offset位置开始覆盖
STRLEN key 字符串长度

数值操作

命令 意义
INCR/DECR key 加/减1
常用于秒杀、点赞数、评论数等
原子操作,避免高并发下的数据库事务操作
INCRBY/DECRBY key val 加/减指定数
INCRBYFLOAT/DECRBYFLOAT key val 加/减小数

注:数值操作需要保证字符串的值能够被解析成十进制数字,否则会报错,如:

1
2
3
4
127.0.0.1:6380> GET k1
"2a"
127.0.0.1:6380> INCR k1
(error) ERR value is not an integer or out of range

Bitmap操作

命令 意义
SETBIT key bit val 将第bit为设置成0或1
bitpos key bit [start] [end] 找到bit第一次出现的位置
bit只能是0或1
start和end是字节索引
返回结果是位索引
bitcount key [start end] 计算1的数量
BITOP OP dest key1 key2… 对多个bitmap执行位操作,并将结果存储到dest中
OP可取:AND、OR、NOT、XOR

应用场景

  • 统计用户的登录天数,统计窗口随机
    • 数据结构:key为用户,bit为天数
    • 登录:SETBIT username day 1,如Joy在今年第5天登录,就 SETBIT Joy 5 1
    • 查询登录天数:BITCOUNT username start end,如统计JOY最近7天的登录天数,BITCOUNT Joy -7 -1
  • 活跃统计:统计某段时间内登陆过的用户
    • 数据结构:key为日期,bit用户id
    • 登录:SET date userid 1,如2020.10.1这一天5号用户登录,就SETBIT 20201001 5 1
    • 查询某段时间的活跃用户:1.将这些天的记录取OR或操作,2. BITCOUNT统计登录数

列表 List

List类型常用于实现队列、栈等数据结构。

特性

从源码中对list和list节点的定义可以看出:

  • 双向链表
  • 有头尾指针
  • 可以方便的实现正负索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl;
unsigned int sz; /* ziplist size in bytes */
unsigned int count : 16; /* count of items in ziplist */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;

typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; /* total count of all entries in all ziplists */
unsigned long len; /* number of quicklistNodes */
int fill : 16; /* fill factor for individual nodes */
unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
} quicklist;

操作

命令 意义
LPUSH/RPUSH key val1 val2 … 从头(左)/尾(右)添加一个或多个元素
LPOP/RPOP key val1 val2 … 从头(左)/尾(右)弹出一个或多个元素
LRANGE key start stop 取list中指定位置的数据
start和stop可以取正负索引,0表示第1个元素,-1表示倒数第一个元素
如:LRANGE 0 -1表示取所有元素
LINDEX key index 根据索引取元素
LSET key index val 根据元素设置元素值
LREM key count val 从list中删除count个值为val的元素
若count为正,从头(左)删除;若count为负,从尾(右)删除
BLPOP/BRPOP key 阻塞的弹出值
LTRIM key start stop 删除start左侧和stop右侧的所有元素
例:LTRIM 0 -1 不删任何一元素

实现常用的数据结构

  • 队列:先进先出,使用反向命令,即 lpush + rpop 或 rpush + lpop
  • 栈:后进先出,使用同向命令,即 lpush + lpop 或 rpush + rpop
  • 阻塞的单播队列:blpop + rpush 或 brpop + lpush

集合 Set

特性

  • 去重
  • 集合内元素值为String类型
  • 存储顺序与插入顺序无关

基本操作

命令 意义
SADD key val1 val2 … 向集合中添加一个或多个元素
SREM key val1 val2 … 从集合中删除一个或多个元素
SMEMBERS key 获取集合所有元素
SISMEMBER key val 判断是否在集合内
SCARD key 集合内元素数量

集合的交、并、差操作

命令 意义
SINTER key1 key2 … 求多个集合的交集
SINTERSTORE dest key1 key2 … 求多个集合的交集并将结果存储到dest中
SUNION key1 key2 … 求多个集合的并集
SUNIONSTORE dest key1 key2 … 求多个集合的并集并将结果存储到dest中
SDIFF key1 key2 … 求一个集合对其余集合的差集
SDIFFSTORE dest key1 key2 … 求一个集合对其余集合的差集并存储到dest中

随机操作

SRANDMEMBER

  • 用法:SRANDMEMBER key count
  • count值可以为正数或负数

  • 若count为正数,随机取出一个去重的结果集,结果集内元素数量不超过原集合内的元素数量

  • 若count为负数,随机取出一个结果集,结果集内的元素可能重复,结果集内元素的数量一定为|count|

SPOP

  • 用法:SPOP key [count]
  • count值必须为正数,如果不传,默认为1
  • 每次随机从集合中取出count个元素,并从集合中去除该元素

应用:抽奖

  1. 集合中放入所有候选人
  2. 多轮抽奖
    • 单个轮次中获奖人一定不同,但一个人可以在多个轮次中奖:每轮抽奖使用 SRANDMEMBER key count,其中count是正数
    • 单个轮次中一个人也可以获多个奖(适用于奖品数大于候选人数的情况):每轮抽奖用 SRANDMEMBER key count,其中count是负数
    • 单个轮次中获奖人一定不同,且一个人一旦中奖,在后面轮次中就不再参与抽奖:每轮抽奖用 POP key count

有序集合 SortedSet

有序集合常用于榜单排序。

特性

  • 去重
  • 集合内元素为String类型
  • 有序,集合内每个元素有float类型的分数score,根据元素的score排序

基本操作

命令 意义
ZADD key score1 member1 score2 member2… 向有序集合中添加一个或多个元素
ZCARD key 获取有序集合的元素数量
ZCOUNT key min max 获取有序集合中指定分数区间内的元素个数
ZINCRBY key increment member 对有序集合内的指定元素的分数增加increment
ZRANK key member 返回有序集合中某元素的排名(从小到大,从0开始)
ZREM key member1 member2… 删除一个或多个元素

集合的交、并、差操作

ZINTERSTORE/ZUNIONSTORE/ZDIFFSTORE dest numKeys key1 key2 … [WEIGHTS w1 w2 …] [AGGREGATE SUM|MIN|MAX]

dest:存储操作结果的key

numKeys:参与操作的集合数量

WIGHTS w1 w2…:权重,权重的数量需要和numKeys对应,执行操作时,会先将各集合中元素乘以该集合对应的权重。不填默认为1

AGGREGATE:对score的处理方法,不填默认为SUM。可选SUM,MIN,MAX

散列 Hash

Hash适合存储对象,将对象的属性以键值对的形式存储到一个key中,如:

存储一个个人信息对象 {"name":"Joy", "age":21, "height":165, "weight":50}

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6380> HMSET person1 name Joy age 21 height 165 weight 50 
OK
127.0.0.1:6380> HGETALL person1
1) "name"
2) "Joy"
3) "age"
4) "21"
5) "height"
6) "165"
7) "weight"
8) "50"

Hash的基本操作命令如下:

命令 意义
HSET key field val 设置哈希表的某个字段
HMSET key field1 val1 field2 val2 … 设置哈希表的多个字段
HGET key field 获取哈希表的某个字段
HMGET key field1 field2 … 获取哈希表的多个字段
HKEYS key 获取哈希表的所有字段名
HVALS key 获取哈希表的所有字段值
HGETALL key 获取哈希表的所有字段名和值
HDEL key field1 field2 … 删除哈希表中的一个或多个字段
HINCRBY/HINCRBYFLOAT key field val 为哈希表的指定字段的整数值/浮点数值增加val

redis作为缓存

缓存的特点

  • 缓存数据不重要(和数据库相比)
  • 不是全量数据,而是热点数据
  • 会随着访问而变化

问题

内存的大小是有限的,需要redis中的数据随着业务访问而变化,只保留热数据。

相关特性

  • 虚拟内存:2.6版本之前有此功能,开启了该功能(vm-enabled ture)后,若使用内存达到vm-max-memory,redis会将访问率不高的冷数据交换到磁盘中去,这些数据被访问时,又会加载到内存中。2.6之后的版本已移除该功能。

  • key的有效期

    • 设置有效期的方法:
      • SET 命令的 EX、PX、KEEPTTL参数
      • EXPIRE、PEXPIRE命令:设置过期剩余时间
      • EXPIREAT、PEXPIREAT命令:设置过期时间戳
    • 读命令不会延长过期时间
    • 写命令,如果不加EX、PX或KEEPTTL参数,会剔除过期时间,即该key不会过期
    • 过期判定机制
      • 被动判定:访问该key时,判断是否过期
      • 主动判定:周期性轮询判定:1. 每10秒判定20个key 2. 清除过期的key 3. 如果过期key占比超过25%,重复第1步
  • 内存有限,淘汰冷数据,在 redis.cnf 中配置相关参数:
    • maxmemory : 最大内存,当redis使用的内存超过改参数指定的内存大小,则按照配置的策略尝试删除key。如果无法删除或淘汰策略配置为了noeviction,则当执行需要增加内存的指令(如SET, LPUSH等)时,会返回错误。
    • maxmemory-policy :达到最大内存时的淘汰策略。
      • noeviction:不淘汰
      • volatile-lru:对设置了有效期的key采用lru淘汰
      • allkeys-lru:对所有key采用lru淘汰
      • volatile-lfu:对设置了有效期的key采用lfu淘汰
      • allkeys-lfu:对所有key采用lfu淘汰
      • volatile-random:对设置了有效期的key采用随机淘汰
      • allkeys-random:对所有key采用随机淘汰
      • volatile-ttl:淘汰过期剩余时间最少的key
    • maxmemory-samples:采样数量。redis的LRU、LFU和TTL淘汰都是近似的算法,考虑到性能问题,并没有对全量数据采样。该参数默认值是5,表示采样5个key并从中选一个淘汰。redis认为5已经能够达到足够的精度,10已经非常接近精确算法的结果,但会消耗更多的cpu资源。
  • 若没有配置maxmemory,当使用内存达到物理内存上线后,会使用操作系统的虚拟内存机制,会导致redis性能急剧下降。

注:

  • lru - least recently used,最近最少使用,淘汰最长时间未被使用的条目

  • lfu - least frequently used,最不经常使用,淘汰一段时间内使用次数最少的条目

redis与memcached的对比

redis常常被拿来与memcached做比较。

首先看看各自官网对自己的定义:

redis(redis.io):Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker.

memcached(memcached.org):Free & open source, high-performance, distributed memory object caching system

从其各自官网定义可以看出来:

相同点:两者都是开源的(事实上都是BSD开源协议);两者都是基于内存的;

不同点:memcached只是定位于缓存,而redis除了缓存,还可用于数据库、消息中间件

除此之外:

对比项 redis memcached
性能 核心业务单线程,只能利用一个cpu核,但是省去了锁、上下文切换等方面的开销
根据附录redis作者antirez在Stack Overflow上的回答,redis在约100k以上的大数据存取上性能略逊于memcached,但无论使用哪个,基本上都不用担心性能问题成为系统瓶颈。
多线程,主线程listen,多个worker线程
持久化 支持 不支持,一旦重启,数据就丢失
支持的数据类型和操作 支持字符串、列表、集合、有序集合、哈希
且操作也非常丰富
计算向数据移动的思想
只支持字符串
分布式高可用集群 官方提供Sentinel、主从复制、cluster等机制实现高可用集群 官方本身不支持
分布式集群的服务器本身是不通讯的,其实是伪分布式,是靠客户端通过分片算法把数据存到不同的memcached服务器中
高可用需要第三方软件(如:repcached,memagent、memcached-ha等)或自己设计实现

附录:antirez在Stack Overflow上的回答

https://stackoverflow.com/questions/2873249/is-memcached-a-dinosaur-in-comparison-to-redis

  • You should not care too much about performances. Redis is faster per core with small values, but memcached is able to use multiple cores with a single executable and TCP port without help from the client. Also memcached is faster with big values in the order of 100k. Redis recently improved a lot about big values (unstable branch) but still memcached is faster in this use case. The point here is: nor one or the other will likely going to be your bottleneck for the query-per-second they can deliver.
  • You should care about memory usage. For simple key-value pairs memcached is more memory efficient. If you use Redis hashes, Redis is more memory efficient. Depends on the use case.
  • You should care about persistence and replication, two features only available in Redis. Even if your goal is to build a cache it helps that after an upgrade or a reboot your data are still there.
  • You should care about the kind of operations you need. In Redis there are a lot of complex operations, even just considering the caching use case, you often can do a lot more in a single operation, without requiring data to be processed client side (a lot of I/O is sometimes needed). This operations are often as fast as plain GET and SET. So if you don’t need just GET/SET but more complex things Redis can help a lot (think at timeline caching).

译文参考 https://blog.csdn.net/weiwangchao_/article/details/50620301

  • 没有必要过多的关心性能,因为二者的性能都已经足够高了。由于Redis只使用单核,而Memcached可以使用多核,所以在比较上,平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。说了这么多,结论是,无论你使用哪一个,每秒处理请求的次数都不会成为瓶颈。
  • 如果要说内存使用效率,使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。当然,这和你的应用场景和数据特性有关。
  • 如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcached都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。

  • 当然,最后还得说到你的具体应用需求。Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果你需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。