一、Redis简介
1.1 Redis数据库简介
Redis(Remote Dictionary Server),即远程字典服务,是一个开源的(基于BSD协议)、使用ANSI C语言编写、基于内存的且支持持久化、高性能的 NOSQL数据库(Key-Value
数据库);
Redis支持的数据结构类型很丰富,如:字符串(Strings、包括字符 和 数字)、列表(Lists)、集合(Sets)、有序集合(Sorted Sets)以及范围查询、散列(Hashes)、Bitmaps(位图)、HyperLogLog (超级日志)和 地理空间(Geospatial)索引半径查询等等。
- String: 二进制安全的字符串;
- Lists: 按插入顺序排序的字符串元素的集合。它们基本上就是链表(linked lists)。
- Sets: 不重复且无序的字符串元素的集合。
- Sorted Sets: 类似Sets,但是每个字符串元素都关联到一个叫score浮动数值(floating number value)。里面的元素总是通过score进行着排序,所以不同的是,它是可以检索的一系列元素。(例如你可能会问:给我前面10个或者后面10个元素)。
- Hashes: 由field和关联的value组成的map。field和value都是字符串的。这和Ruby、Python的Hashes很像。
- Bit arrays (或者说 simply Bitmaps): 通过特殊的命令,可以将 String 值当作一系列 bits 处理,可以设置和清除单独的 bits,数出所有设为 1 的 bits 的数量,找到最前面的被设为 1 或 0 的 bit等等。
- HyperLogLog: 这是被用于估计一个 set 中元素数量的概率性的数据结构。
- Streams: 5.0版本新增,append-only collections of map-like entries that provide an abstract log data type. 可以用来做持久化的消息队列。
Redis 所有的数据结构都是以唯一的 key (一般为字符串)作为名称(索引),然后通过这个唯一 key 值来操作(设置、修改、查询、删除等)相应的 value 数据,不同类型的数据结构的差异就在于 value 的结构不一样。
Redis官网链接: https://redis.io/
Redis 的写入和查询速度(性能)非常快,支持每秒10w次查询请求。
Redis 内置复制、Lua脚本、LRU驱逐、事务 和 不同级别的磁盘持久化,并通过 Redis Sentinel 和 Redis Cluster自动分区 等提供高可用性。
Redis 的常见应用场景:
- 缓存:数据库之前加缓存,降低数据库读写压力;
- 排行榜:按照热度排名、按照发布时间排名;
- 计数器:播放数、浏览数;
- 社交网络:赞、踩、粉丝、下拉刷新;
- 消息队列:发布订阅;
Redis有着丰富的主流开发语言客户端支持,包括C/C++、Python、Erlang、R、C#、Java、PHP、Objective-C、Perl、Ruby、Scala、Go、JavaScript等等。
Redis 默认有 16 个数据库,类似数组下标从 0 号库 开始 到 15号库 结束,默认使用 0 号库,使用 select dbid
命令来切换数据库,如: select 8
切换到 8 号数据库。
Redis 对 CPU 的要求并不高,反而是对内存和磁盘的要求很高,因为 Redis 大部分时候都在做读写操作,使用更多的内存和更快的磁盘,对 Redis 性能的提高非常有帮助。
1.2 常见的非关系型数据库NOSQL分类
NOSQL类型 | 主要数据库产品 | 类型特色 |
---|---|---|
K-V 键值对存储类型 | Redis、Memcached | 使用key可以快速的查询到value,Memcached可以支持String类型的值value;Redis支持的数据类型很多如:String、list、set、sort set、Hashes、Bitmaps、HyperLogLog、geo等等 |
文档存储类型 | MongoDB、elasticsearch、CouchDB | 使用JSON或类JSON的BSON数据结构,存储的内容为文档型,能够实现部分关系型数据库的功能 |
列存储类型 | HBase、Cassandra | 按照列进行数据存储,该类型便于存储结构化和半结构化的数据,可以方便做数据压缩和针对某一列或者某几列的数据查询 |
图存储类型 | Neo4J、FlockDB | 以图形关系存储数据,能够很好的弥补关系型数据库在图形存储时的不足 |
对象存储类型 | Db4o、Versant | 该存储类型的数据库通过类似面向对象的方式操作数据库,通过对象的方式存取数据 |
XML存储类型 | Berkeley DB XML、BaseX | 该类型数据库可以高效的存储XML数据,并且支持XML的内部查询语法,例如;XQuery、 |
1.3 Redis 发展历史
2009年4月10日,Redis 初始版本发布。 2013年5月之前,其开发由 VMware 赞助。 2013年5月至2015年6月期间,其开发由 Pivotal 赞助。 2015年4月1日,Redis 3.0.0 版本正式发布,最重要的特征是对 Redis 集群的支持(Redis Cluster)。 2015年6月开始,Redis 的开发由 Redis Labs 赞助。 2017年7月14日,Redis 4.0.0 版本正式发布,其中最大变化就是加入了模块系统。 2018年10月17日,Redis 5.0 GA 正式版发布,其中比较大的变化是增加了新的流数据类型。
1.4 Redis 配置文件详解
- https://www.knowledgedict.com/tutorial/redis-config.html
- https://redis.io/docs/management/config
- https://redis.io/docs/management/config-file
二、Redis的基本命令
Tips:推荐网站
2.1 基础命令
1、redis-cli 连接redis服务器:
|
|
2、select 选取数据库号
|
|
dbid:数据库编号,redis有 16 个数据库,使用0-15进行编号,类似数组下标从 0 号库 开始 到 15号库 结束(登录复位后默认使用 0 号库);
3、Help帮助命令:
|
|
4、INFO 查看Redis服务器的各种信息和统计数值 INFO 命令以一种易于理解 和 阅读的格式,返回关于Redis服务器的各种信息和统计数值。
|
|
通过给定可选的参数 section
,可以让命令只返回某一部分的信息:
- server: Redis服务器的一般信息
- clients: 客户端的连接部分
- memory: 内存消耗相关信息
- persistence: RDB和AOF相关信息
- stats: 一般统计
- replication: 主/从复制信息
- cpu: 统计CPU的消耗
- commandstats: Redis命令统计
- cluster: Redis集群信息
- keyspace: 数据库的相关统计
它也可以采取以下值:
- all: 返回所有信息
- default: 值返回默认设置的信息
如果没有使用任何参数时,默认为default。
命令返回值
- bulk-string-reply: 文本行的合集
每一行包含了包含一种信息或者属性(从#字符开始)。 所有的属性都是以 字段:值(field:value)的形式,以 \r\n
结尾。
2.2 Redis 键(key) 命令
Redis 键(keys)命令用于管理 Redis 的键。
命令 | 描述 |
---|---|
TYPE | 返回 key 所储存的值的类型。 |
KEYS | 查找所有符合给定模式( pattern)的 key 。 |
EXISTS | 检查给定 key 是否存在。 |
EXPIRE | 为给定 key 设置过期时间(多少秒),单位为秒,当 key 过期时会被自动删除。 |
PEXPIRE | 为给定 key 设置过期时间(多少毫秒),单位为毫秒,当 key 过期时会被自动删除。 |
EXPIREAT | 作用和 EXPIRE 类似,都用于为 key 设置过期时间。不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp),单位为秒。 |
PEXPIREAT | 作用和 PEXPIRE 类似,都用于为 key 设置过期时间。不同在于 PEXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp),单位为毫秒(milliseconds-timestamp)。 |
TTL | 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
PTTL | 以毫秒为单位返回 key 的剩余的过期时间。 |
PERSIST | 移除 key 的过期时间,将这个 key 从易失的状态转换成持久的状态。 |
RENAME | 修改 key 的名称 |
RENAMENX | 仅当 newkey 不存在时,将 key 改名为 newkey 。 |
Move | 将当前数据库的 key 移动到给定的数据库 db 当中。 |
MIGRATE | 将 key 原子性地从当前实例传送到目标实例的指定数据库上。 |
RANDOMKEY | 从当前数据库中随机返回一个 key 。 |
Dump | 序列化给定 key ,并返回被序列化的值。 |
RESTORE | 将反序列化指定的序列化值,并将它和指定的 key 关联。 |
DEL | 删除指定的一个或多个 key。 |
UNLINK | 异步删除指定的 key,Redis 4.0.0 版本开始支持。 |
TOUCH | 改变指定 key 的最后访问时间。 |
WAIT | 阻塞当前客户端,直到所有以前的写命令都成功的传输和指定的 slaves 确认。 |
SCAN | 用于迭代当前数据库中的数据库键。 |
SORT | 返回或保存指定列表、集合、有序集合 key 中经过排序的元素。 |
OBJECT | 从内部查看指定 key 的 Redis 对象信息。 |
1、查找键格式:
|
|
pattern的取值:
*
任意长度字符(一个或多个);
?
任意一个字符;
[]
字符集合,表示该位置可以是集合中的任意一个字符;
示例:
|
|
2、查看键的 Redis 数据类型
|
|
Redis Type 命令用于返回 key 所储存的值的类型。返回 key 的数据类型,当key不存在时返回none,数据类型有:
- none (key不存在)
- String (字符串)
- list (列表)
- set (集合)
- zset (有序集)
- Hashes (哈希表)
3、查看 key 的 Redis 对象信息 Redis OBJECT 命令允许从内部查看指定 key 的 Redis 对象信息。它通常用在调试,或者了解相关 key 是否使用特殊编码的数据类型来节省空间等。此外,当 Redis 作为缓存时,你也可以通过 OBJECT 命令中的信息,决定 key 的淘汰策略。
|
|
OBJECT 命令支持多个子命令:
- OBJECT REFCOUNT
:返回指定 key 引用所储存的值的次数。此命令主要用于调试。 - OBJECT ENCODING
:返回指定 key 所储存的值所使用的内部表现形式(或者叫编码情况)。 - OBJECT IDLETIME
:返回指定 key 自储存以来的空闲时间(没有被读取也没有被写入),以秒为单位。虽然该命令返回以秒为单位,但是实际的时间粒度是 10 秒,以后会对实现有所变化。该子命令对淘汰策略设置 LRU 或者没有设置时有用。 - OBJECT FREQ
:返回指定 key 所存储的值的访问频率对数。该子命令对淘汰策略设置 LFU 时有用。 - OBJECT HELP:返回帮助文档。
4、判断键是否存在
|
|
5、查看当前数据库的 key 的数量
|
|
6、给键重命名
|
|
7、DEl删除键(阻塞)
|
|
其中,key是要删除的键名。可以指定多个键名,删除多个键。如果指定的键不存在,则会被忽略。
del命令会直接删除指定的键以及与之相关联的值。如果键不存在,则不执行任何操作。
del命令删除key后不会释放已经分配的内存。
del命令是一种同步命令,即它会阻塞客户端,直到所有指定的键都被删除为止。在删除大量键的情况下,del命令可能会导致Redis服务器阻塞一段时间。
del命令返回被删除键的数量。
8、UNLINK删除键(非阻塞) 在删除大量键时,应该考虑使用异步删除方式,以避免阻塞客户端。
unlink命令是Redis提供的另一种删除键的命令。它的语法与del命令类似:
|
|
其中,key是要删除的键名。可以指定多个键名,删除多个键。如果指定的键不存在,则会被忽略。
unlink命令会异步地删除指定的键以及与之相关联的值。即,它会将要删除的键添加到一个待删除的列表中,并立即返回,不会阻塞客户端。Redis服务器会在后台异步地删除待删除列表中的键。
unlink命令会释放已经分配的内存。在删除大量键时,使用unlink命令可以减少内存使用量。
使用unlink命令的好处是可以减少删除操作的阻塞时间。在删除大量键的情况下,unlink命令可以使Redis服务器更快地响应客户端请求。
unlink命令不会返回被删除键的数量。这是因为unlink命令是异步执行的,Redis无法立即知道已经删除的键的数量。
Tips: del命令和unlink命令都会影响Redis的持久化操作。在Redis进行持久化操作时,所有待删除的键都会被删除,无论是使用del命令还是unlink命令删除的。
2.3 清除库数据命令
|
|
三、Redis的键(key)
3.1 Redis的键(key)简介
Redis 所有的数据结构都是以唯一的 key (一般为字符串)作为名称(索引),然后通过这个唯一 key 值来操作(设置、修改、查询、删除等)相应的 value 数据,不同类型的数据结构的差异就在于 value 的结构不一样。
Redis的 key 值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如 foo
的简单字符串到一个JPEG文件的内容都可以,空字符串也是有效key值。
在Redis中对数据进行操作时,通常是对 key
来进行操作,只有设置了 key
,才能对 key
进行相应的赋值,修改,删除等操作。
Key取值原则:
- 键值不需要太长(建议不超过44字节),太长会消耗内存,且在数据中查找这类键值的计算成本较高;
- 键值不宜过短,过短则可读性较差;
- 最好坚持一种模式,例如:
object-type:id:field
就是个不错的注意,像这样user:1000:password
;
Tips: Key是String类型,底层编码包括
int
、embstr
和row
三种,在小于44字节时使用embstr
,该编码类型采用连续的内存空间,内存占用小;
3.2 Redis的大key问题讨论及解决方案
所谓的bigkey 主要是指以下以下场景:
- key本身的数据量过长(建议key的长度不超过44字节)
- 单个简单的key存储的value很大,(推荐单个key的value 不超过 10KB)
- Hashes,set,zset,list 中存储过多的元素(建议集合类型的key中存放的元素小于1000,且元素的值[即占用字节数]不要太大)
- key的数量过多,一个redis集群存储了上亿的key
BigKey的危害:
- 导致网络阻塞:对 BigKey执行读/写请求时,少量的QPS就可能导致带宽被占满,导致redis服务实例甚至服务器OS性能下降;
- 导致数据倾斜: BigKey所在的Redis实例内存使用率远超其它实例,无法使数据分片的内存资源达到均衡;
- 导致Redis阻塞: 对元素较多的Hashes、list、zset等做运算会耗时较久,使主线程被阻塞;
- 导致 CPU压力: 对BigKey的数据序列化和反序列化会导致CPU的使用率飙升,影响Redis实例和本机其它应用;
3.3 查找BigKey
1、使用redis-cli的bigkeys参数查找BigKey
|
|
利用redis客户端提供的参数,可以遍历分析所有的key,并返回key的整体统计信息与每个数据类型的Top1的 BigKey。
Tips: 每种数据类型的Top1可能是bigKey也可能不是,因为可能这种数据类型的key使用的数量少,而一些使用频率高的数据类型可能Top2也是BigKey,因此统计的并不完整。
2、利用scan 命令扫描Redis中的所有key,利用strlen、hlen等命令判断key的长度 scan 是对所有key分成多个部分进行扫描,避免在千万数量级key下 扫描所有key 影响主线程性能
3、第三发工具查找 利用第三方工具,如 Redis-Rdb-Tools 分析 RDB快照文件,全面分析内存使用情况。
4、 网络监控 自定义工具,监控Redis的网络数据,超出预警值时主动告警 (一般使用云服务中的工具分析如:阿里云)。
3.4 删除Bigkey
1、使用 delete 命令删除key 这种方法可能导致服务卡顿或阻塞
2、 使用 unlick 异步删除(Redis 4.0及更高版本支持)
3.5 对于Bigkey常用的解决办法
1、单个简单的key存储的value很大
-
对象需要每次都整存整取
- 可以尝试将对象分拆成几个key-value, 使用 multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的IO影响;
-
该对象每次只需要存取部分数据
- 可以像第一种做法一样,分拆成几个key-value,也可以将这个存储在一个Hashes中,每个field代表一个具体的属性,使用hget,hmget来获取部分的value,使用hset,hmset来更新部分属性;
2、Hashes,set,zset,list 中存储过多的元素
- 可以对存储元素按一定规则进行分类,分散存储到多个redis实例中;
- 对于一些榜单类的场景,用户一般只会访问前几百及后几百条数据,可以只缓存前几百条以及后几百条,即对用户经常访问的数据做缓存(正序倒序的前几页),而不是全部都做,对于获取中间的数据则可以直接从数据库获取;
3、一个集群存储了上亿的key
- 如果key的个数过多会带来更多的内存空间占用,
- key本身的占用。
- 集群模式中,服务端有时需要建立一些slot2key的映射关系,这其中的指针占用在key多的情况下也是浪费巨大空间。
所以减少key的个数可以减少内存消耗,可以参考的方案是转Hashes结构存储,即原先是直接使用Redis String 的结构存储,现在将多个key存储在一个Hashes结构中
- 对缓存操作的改善可以利用pipeline管道 拆分之后可以考虑采用pipeline去取,由于redis是单线程的,一次只能执行一个命令,这里采用Pipeline模式,一次发送多个命令,无需等待服务端返回。这样就大大的减少了网络往返时间,提高了系统性能。