redis(2)-api使用

在正式介绍5种数据结构之前, 了解一下Redis的一些全局命令、 数据结构和内部编码、 单线程命令处理机制是十分有必要的, 它们能为后面内容的学习打下一个好的基础。Redis不是万金油, 有些数据结构和命令必须在特定场景下使用, 一旦使用不当可能对Redis本身或者应用本身造成致命伤害。
全局命令
1.查看所有的键
keys *
keys*命令会将所有的键输出。

2.键总数
dbsize
dbsize命令会返回当前数据库中键的总数。 例如当前数据库有4个键,分别是hello、 java、python、 mylist, 所以dbsize的结果是4。

注意:dbsize命令在计算键总数时不会遍历所有键, 而是直接获取Redis内置的键总数变量, 所以dbsize命令的时间复杂度是O(1) 。 而keys命令会遍历所有键, 所以它的时间复杂度是O(n) , 当Redis保存了大量键时, 线上环境禁止使用。

3.检查键是否存在
exists key
如果键存在则返回1, 不存在则返回0。

4.删除键
del key [key …]
del是一个通用命令, 无论值是什么数据结构类型, del命令都可以将其删除。
返回结果为成功删除键的个数, 假设删除一个不存在的键, 就会返回0。

5.键过期
expire key seconds
Redis支持对键添加过期时间, 当超过过期时间后, 会自动删除键。

ttl命令会返回键的剩余过期时间, 它有3种返回值:
·大于等于0的整数: 键剩余的过期时间。
·-1: 键没设置过期时间。
·-2: 键不存在
可以通过ttl命令观察键hello的剩余过期时间
ttl key

6.键的数据结构类型
type key
例如键hello是字符串类型, 返回结果为string。 键mylist是列表类型, 返回结果为list。如果键不存在, 则返回none.

7.键重命名
rename key newkey
如果在rename之前, 键已经存在, 那么它的值也将被覆盖。为了防止被强行rename, Redis提供了renamenx命令, 确保只有newKey不存在时候才被覆盖 返回为0时就是说已经存在newKey,rename失败。

8.随机返回一个键
randomkey
有啥用?不太懂

9.键过期
·expire key seconds: 键在seconds秒后过期
expireat key timestamp: 键在秒级时间戳timestamp后过期
ttl命令和pttl都可以查询键的剩余过期时间, 但是pttl精度更高可以达到毫秒级别。返回值与ttl一样。
pexpire key milliseconds: 键在milliseconds毫秒后过期
pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期

在使用Redis相关过期命令时, 需要注意以下几点
1) 如果expire key的键不存在, 返回结果为0
2) 如果过期时间为负值, 键会立即被删除, 犹如使用del命令一样
3) persist命令可以将键的过期时间清除
4) 对于字符串类型键, 执行set命令会去掉过期时间, 这个问题很容易在开发中被忽视
5) Redis不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
6) setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间。

迁移键
迁移键功能非常重要, 因为有时候我们只想把部分数据由一个Redis迁移到另一个Redis(例如从生产环境迁移到测试环境) , Redis发展历程中提供了move、 dump+restore、 migrate三组迁移键的方法, 它们的实现方式以及使用的场景不太相同, 下面分别介绍。
(1) move
move key db
move命令用于在Redis内部进行数据迁移, Redis内部可以有多个数据库彼此在数据上是相互隔离的, move key db就是把指定的键从源数据库移动到目标数据库中, 但笔者认为多数据库功能不建议在生产环境使用, 所以这个命令读者知道即可。
(2) dump+restore
dump key
restore key ttl value
dump+restore可以实现在不同的Redis实例之间进行数据迁移的功能, 整个迁移的过程分为两步:
1) 在源Redis上, dump命令会将键值序列化, 格式采用的是RDB格式。
2) 在目标Redis上, restore命令将上面序列化的值进行复原, 其中ttl参数代表过期时间, 如果ttl=0代表没有过期时间。
整个迁移过程并非原子性的, 而是通过客户端分步完成的。迁移过程是开启了两个客户端连接, 所以dump的结果不是在源Redis和目标Redis之间进行传输。
(3) migrate
migrate命令也是用于在Redis实例间进行数据迁移的, 实际上migrate命令就是将dump、 restore、 del三个命令进行组合, 从而简化了操作流程。migrate命令具有原子性, 而且从Redis3.0.6版本以后已经支持迁移多个键的功能, 有效地提高了迁移效率, migrate水平扩容中起到重要作用。
第一, 整个过程是原子执行的, 不需要在多个Redis实例上开启客户端的, 只需要在源Redis上执行migrate命令即可。
第二, migrate命令的数据传输直接在源Redis和目标Redis上完成的。
第三, 目标Redis完成restore后会发送OK给源Redis, 源Redis接收后会根据migrate对应的选项来决定是否在源Redis上删除对应的键。

migrate target_ip target_port key destination_db timeout
·host: 目标Redis的IP地址。
·port: 目标Redis的端口。
·key|"":当前需要迁移多个键, 此处为空字符串""
·destination-db: 目标Redis的数据库索引, 例如要迁移到0号数据库, 这里就写0。
·timeout: 迁移的超时时间(单位为毫秒) 。
·[copy]: 如果添加此选项, 迁移后并不删除源键。
·[replace]: 如果添加此选项, migrate不管目标Redis是否存在该键都会正常迁移进行数据覆盖
·[keys key[key…]]: 迁移多个键, 例如要迁移key1、 key2、 key3, 此处填写“keys key1 key2 key3”
遍历键
Redis提供了两个命令遍历所有的键, 分别是keys和scan
1.全量遍历键
keys pattern
*代表匹配任意字符
·代表匹配一个字符
[]代表匹配部分字符, 例如[1, 3]代表匹配1, 3, [1-10]代表匹配1到10的任意数字
\x用来做转义, 例如要匹配星号、 问号需要进行转义。

keys *很有可能导致redis的阻塞,如果实在是要用在下面三种情况下使用
1.在一个不对外提供服务的Redis从节点上执行, 这样不会阻塞到客户端的请求, 但是会影响到主从复制。
2.如果确认键值总数确实比较少, 可以执行该命令
3.使用下面要介绍的scan命令渐进式的遍历所有键, 可以有效防止阻塞

2.渐进式遍历
scan能有效的解决keys命令存在的问题。 和keys命令执行时会遍历所有键不同, scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题, 每次scan命令的时间复杂度是O(1) , 但是要真正实现keys的功能, 需要执行多次scan。
每次执行scan, 可以想象成只扫描一个字典中的一部分键, 直到将字典中的所有键遍历完毕。
scan cursor [match pattern] [count number]
1.cursor是必需参数, 实际上cursor是一个游标, 第一次遍历从0开始, 每次scan遍历完都会返回当前游标的值, 直到游标值为0, 表示遍历结束
2.match pattern是可选参数, 它的作用的是做模式的匹配, 这点和keys的模式匹配很像
3.count number是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是10, 此参数可以适当增大
除了scan以外, Redis提供了面向哈希类型、 集合类型、 有序集合的扫描遍历命令, 解决诸如hgetall、 smembers、 zrange可能产生的阻塞问题, 对应的命令分别是hscan、 sscan、 zscan, 它们的用法和scan基本类似。
scan遍历并不锁住对应对象,也就是说可能我们scan过程中发生变化,比如删除,新增,修改,那么新增的键没有遍历到 或者遍历到重读的键。

数据库管理
Redis提供了几个面向Redis数据库的操作, 它们分别是dbsize、 select、flushdb/flushall命令。
1.切换数据库
select dbIndex
前面说到一个redis进程可以分为多个数据库,多个数据库互不干扰。Redis默认配置中是有16个数据库,也就是0-15的index,不同的数据中可以存在同样的键。反正这里是建议别用多个数据库实现,而是起多个redis来实现。

2.flushdb/flushall
flushdb/flushall命令用于清除数据库, 两者的区别的是flushdb只清除当前数据库, flushall会清除所有数据库。
注意了 这是一个删库的操作,想跑路的同学可以学习一哈。而且如果数据库kv比较多,很有可能导致数据库阻塞。

实际上每种数据结构都有自己底层的内部编码实现, 而且是多种实现,这样Redis会在合适的场景选择合适的内部编码可以看到每种数据结构都有两种以上的内部编码实现, 例如list数据结构包含了linkedlist和ziplist两种内部编码。 同时有些内部编码, 例如ziplist,可以作为多种外部数据结构的内部实现, 可以通过object encoding命令查询内部编码.
redis(2)-api使用
Redis这样设计有两个好处: 第一, 可以改进内部编码, 而对外的数据结构和命令没有影响, 这样一旦开发出更优秀的内部编码, 无需改动外部数据结构和命令, 例如Redis3.2提供了quicklist, 结合了ziplist和linkedlist两者的优势, 为列表类型提供了一种更为优秀的内部编码实现, 而对外部用户来说基本感知不到。 第二, 多种内部编码实现可以在不同场景下发挥各自的优势, 例如ziplist比较节省内存, 但是在列表元素比较多的情况下, 性能会有所下降, 这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist。
redis单线程架构
Redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库服务。想想也是,也只有io多路复用才能实现线程来处理多个客户端的请求消息了。如果是我来实现一个基于java的redis,我会用多路复用器注册服务端的channel注册onConnect onRead等事件,然后单线程while true轮询这个selector,返回的事件如果是onConnect,则把返回的channel也注册到selector中,然后订阅onRead事件。如果是onRead事件则获取客户端传入的命令信息,加入到一个任务队列里面。当整个一次遍历结束时,再去执行工作队列里面的任务,然后把结果返回至对应的channel中。
为什么单线程还能这么快
通常来讲, 单线程处理能力要比多线程差。那么为什么Redis使用单线程模型会达到每秒万级别的处理能力呢? 可以将其归结为三点。
第一, 纯内存访问, Redis将所有数据放在内存中, 内存的响应时长大约为100纳秒, 这是Redis达到每秒万级别访问的重要基础。
第二, 非阻塞I/O, Redis使用epoll作为I/O多路复用技术的实现, 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间。
第三, 单线程避免了线程切换和竞态产生的消耗。单线程可以简化数据结构和算法的实现。 如果对高级编程语言熟悉的读者应该了解并发数据结构实现不但困难而且开发测试比较麻烦。单线程避免了线程切换和竞态产生的消耗,对于服务端开发来说, 锁和线程切换通常是性能杀手。但是对于每个命令的执行时间是有要求的。 如果某个命令执行过长, 会造成其他命令的阻塞, 对于Redis这种高性能的服务来说是致命的, 所以Redis是面向快速执行场景的数据库。