前言
Redis是一个开源(BSD许可)的,基于内存的数据存储系统,它可以用作数据库、缓存和消息中间件。
安装、启动
环境:CentOS 7.6 (1810)
- 下载最新的稳定版本redis
1 | wget https://download.redis.io/releases/redis-6.0.9.tar.gz |
- 编译
由于gcc版本较低,直接编译会报错,因此,先更新gcc
1 | yum -y install centos-release-scl |
解压编译
1 | tar -xzvf redis-6.0.9.tar.gz |
- 测试
1 | redis-server -v |
结果
1 | Redis server v=6.0.9 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=4b37fcaf3ca1b943 |
- 启动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 | 127.0.0.1:6380> GET k1 |
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 | typedef struct quicklistNode { |
操作
命令 | 意义 |
---|---|
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个元素,并从集合中去除该元素
应用:抽奖
- 集合中放入所有候选人
- 多轮抽奖
- 单个轮次中获奖人一定不同,但一个人可以在多个轮次中奖:每轮抽奖使用 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 | 127.0.0.1:6380> HMSET person1 name Joy age 21 height 165 weight 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会是不错的选择。