虽然Redis提供了非常丰富的数据结构以及强大的功能,如流水线、事务,特别是Lua脚本的引入,让用户可以很方便的扩展Redis的功能,但是这些或多或少都有各自的缺陷。例如,使用Lua脚本来扩展Redis,就需要对熟悉Lua脚本的语法以及Lua数据类型与Redis数据类型直接的转换。为了解决进一步解决Redis功能扩展问题,Redis在4.0版本添加了一个重要功能——模块
Redis的模块
Redis的模块功能允许开发者通过Redis开放的一簇API,将Redis用作网络服务和数据平台,通过C语言在Redis上构建任意复杂的、全新的数据结构、功能和应用。对于开发者来说,Redis模块为他们提供了一个可以按需扩展和定制Redis的机会。
Redis在官方网站(https://redis.io/modules)上罗列了目前可用的模块,这些模块通常是由C语言或者能够和C语言进行交互的语言实现的。用户只需要下载模块的源码,编译它们,并在Redis里面载入编译好的模块,就可以在像使用普通Redis命令一样使用模块实现的功能了。
模块的管理
在了解模块之前,我们必须学习如何编译模块,载入模块以及卸载模块。
编译模块
虽然不同模块的编译方式不同,但是绝大多数都会在文档中详细说明自身的编译方法,用户只需按照文档里的提示方法进行编译即可。
编译测试模块
Redis自身提供了几个用于测试的模块,这些模块位于源码的 /src/modules 目录中:
Makefile helloacl.xo hellocluster.c hellodict.so hellohook.xo hellotype.c helloworld.so testmodule.xo
gendoc.rb helloblock.c hellocluster.so hellodict.xo hellotimer.c hellotype.so helloworld.xo
helloacl.c helloblock.so hellocluster.xo hellohook.c hellotimer.so hellotype.xo testmodule.c
helloacl.so helloblock.xo hellodict.c hellohook.so hellotimer.xo helloworld.c testmodule.so
其中以 .c 结尾的文件就是模块的源码,而编译后的模块则以 .so 结尾。一般情况下,这些模块会随着Redis服务器一起被编译,如果没有,则可以使用 make命令自行手动编译。
载入模块
为了使用指定的模块,用户可以在服务器启动时,通过给定的 loadmodule 选项来指定想要载入的模块:
loadmodule <module_path>
loadmodule 选项接受模块的具体文件路径作为参数。
loadmodule /usr/local/redis/src/modules/helloworld.so
除了可以在服务器启动的时候使用 loadmodule 选项载入模块之外,还可以在Redis服务器运行期间,执行 MODULE LOAD 命令在线载入模块,被载入的模块立即生效:
MODULE LOAD <module_path>
127.0.0.1:6379> module load /usr/local/redis/src/modules/helloworld.so
注意:一条loadmodule指令或一条module load命令只能载入一个模块
列出已载入的模块
使用MODULE LIST命令可以查看目前已经被载入的所有模块:
MODULE LIST
卸载模块
当用户不再需要某个模块的时候,可以执行 MODULE UNLOAD 命令来卸载指定的模块,被卸载的模块将立即失效并不可使用:
MODULE UNLOAD module_name
注意:这里使用的是模块的名字,而不是模块文件的路径
ReJSON模块
JSON作为一种非常常见的数据交换格式,在Web领域应用非常广泛。Redis虽然支持文本存储和二进制数据,但是并没有提供对JSON数据的原生支持,无法直接通过命令操作存储在数据库中的JSON数据。为了解决这个问题,RedisLabs公司开发出了ReJSON模块。
ReJSON模块(https://github.com/RedisJSON/RedisJSON) 可以在Redis上实现对JSON数据结构的原生支持:可以直接在Redis数据库中存储、更新和获取JSON数据,就像操作其他Redis数据类型一样。
编译和载入
编译过程详见之前的文章Redis模块之RedisJSON,下面主要说明ReJSON模块的使用方式。
- 设置和获取JSON字符串以及数字值:
# 创建JSON字符串
127.0.0.1:6379> JSON.SET module_name . '"ReJSON"'
OK
# 取值
127.0.0.1:6379> JSON.GET module_name
"\"ReJSON\""
# 查看字符串的长度
127.0.0.1:6379> JSON.STRLEN module_name
(integer) 6
# 查看该JSON键的类型
127.0.0.1:6379> JSON.TYPE module_name
string
# 设置JSON数字值
127.0.0.1:6379> JSON.SET num . 10086
OK
# 取值
127.0.0.1:6379> JSON.GET num
"10086"
# 执行加法
127.0.0.1:6379> JSON.NUMINCRBY num . 1
"10087"
- 设置和获取JSON数组:
# 设置JSON数组
127.0.0.1:6379> JSON.SET arr . '["abc","def",123,456,true,null]'
OK
# 取值
127.0.0.1:6379> JSON.GET arr
"[\"abc\",\"def\",123,456,true,null]"
# 获取数组在索引0上的元素
127.0.0.1:6379> JSON.GET arr [0]
"\"abc\""
# 获取索引1上的元素
127.0.0.1:6379> JSON.GET arr [1]
"\"def\""
# 获取索引-1上的元素
127.0.0.1:6379> JSON.GET arr [-1]
"null"
- 设置和获取JSON对象
# 创建JSON对象
127.0.0.1:6379> JSON.SET doc . '{"name":"peter","age":25,"fav_foods":["cake","rice","noodles"]}'
OK
# 获取对象包含的键
127.0.0.1:6379> JSON.OBJKEYS doc
1) "name"
2) "age"
3) "fav_foods"
# 取出整个对象
127.0.0.1:6379> JSON.GET doc
"{\"name\":\"peter\",\"age\":25,\"fav_foods\":[\"cake\",\"rice\",\"noodles\"]}"
# 获取指定键的值
127.0.0.1:6379> JSON.GET doc name
"\"peter\""
127.0.0.1:6379> JSON.GET doc age
"25"
127.0.0.1:6379> JSON.GET doc fav_foods
"[\"cake\",\"rice\",\"noodles\"]"
ReJSON路径
ReJSON使用树状结构存储JSON数据,并允许用户通过ReJSON自行定义的路径语法来获取JSON数据的指定部分。
一个ReJSON值总是从根(root)开始,而ReJSON值包含的各个元素则是这棵树上的子节点。ReJSON值的根可以用 . 来表示,除此之外,子节点中的对象元素可以通过 foo.bar
或者 foo["bar"]
的语法进行访问,而子节点中的数组元素可以通过下标的方式访问。
127.0.0.1:6379> JSON.SET word . '"hello"'
OK
127.0.0.1:6379> JSON.GET word .
"\"hello\""
127.0.0.1:6379> JSON.SET arr . '[1,2,[3,[4]]]'
OK
127.0.0.1:6379> JSON.GET arr [0]
"1"
127.0.0.1:6379> JSON.GET arr [1]
"2"
127.0.0.1:6379> JSON.GET arr [2][0]
"3"
127.0.0.1:6379> JSON.GET arr [2][1][0]
"4"
127.0.0.1:6379> JSON.SET doc . '{"name":"peter","age":25,"fav_fruits":["apple","banana","cherry"]}"
OK
127.0.0.1:6379> JSON.GET doc .name
"\"peter\""
127.0.0.1:6379> JSON.GET doc .age
"25"
127.0.0.1:6379> JSON.GET doc .fav_fruits[0]
"\"apple\""
JSON.SET命令
JSON.SET 命令接受一个键、一个路径以及一个JSON值作为参数,并将指定路径上的JSON数据设置为给定的值:
JSON.SET <key> <path> <json>
在使用JSON.SET命令创建新的Redis键时,路径必须是树的根。
与Redis内置的命令一样,JSON.SET 命令也支持可选项 NX 和 XX:
JSON.SET <key> <path> <json> [NX | XX]
NX 只会在给定的Redis键不存在的时候进行设置,而 XX 只会在给定的Redis键存在的情况下进行设置。
JSON.GET命令
JSON.GET命令接受一个Redis键以及任意多个路径作为参数,然后从给定的键中获取指定路径上存储的数据:
JSON.GET <key> [path path ... ]
如果用户在执行命令时没有给定任何参数,那么命令将使用树的根作为默认路径并返回键中存储的所有JSON数据。
JSON.DEL命令
JSON.DEL命令接受一个或两个参数作为输入,当用户只给定Redis键作为参数时,ReJSON将直接删除该键及其包含的所有JSON数据:
JSON.DEL <key>
以这种形式调用的JSON.DEL命令总是会返回0作为结果。
如果用户在执行JSON.DEL命令时给定了可选的路径参数,那么ReJSON将只删除给定路径上的数据:
JSON.DEL <key> [path]
接收两个参数的JSON.DEL命令在执行之后将返回被删除值的数量作为结果。
JSON.MGET命令
JSON.MGET命令接受任意多个Redis键以及一个路径作为参数,然后从给定的键中获取位于指定路径上的JSON数据:
JSON.MGET <key> [key ... ] <path>
JSON.MGET命令将返回多个值作为结果。如果给定的Redis键或路径不存在,那么JSON.MGET将在结果对应的位置返回 nil 。
JSON.TYPE命令
JSON.TYPE命令接受一个键以及一个可选的路径作为参数,然后返回给定路径上的值的类型作为结果:
JSON.TYPE <key> [path]
如果用户没有显式的指定路径,将以根路径为默认路径。
RediSQL模块
RediSQL通过Redis的模块机制嵌入了一个完整的SQLite实现,用户可以通过这个实现在Redis服务器中使用 SQLite的全部功能。并且,通过将数据库文件存放在Redis数据库中而不是硬盘文件中,RediSQL获得了内存级别的数据读取速度,也就是说,通过使用 RediSQL,用户将能够同时享受到关系数据库的强大功能以及内存数据库的快速存取优势。
编译模块
首先需要下载RediSQL模块的源码:
git clone git@github.com:RedBeardLab/rediSQL.git
在获得源码之后,我们需要进入源码目录,并使用以下命令编译RediSQL模块:
cargo build --release
因为RediSQL使用Rust语言编写,编译它需要用到Rust语言附带的包管理工具cargo,所以如果你的计算机上尚未安装Rust语言环境,可以通过以下命令进行安装 curl https://sh.rustup.rs -sSf | sh
编译完成后在源码文件夹的**/target/release** 文件夹中,可以找到 libredis_sql.dylib,只要将这个文件载入到Redis即可开始使用 RediSQL。
如果编译失败,也可以直接在GitHub上下载发布版:
https://github.com/RedBeardLab/rediSQL/releases
使用示例
要在关系数据库中存储数据,首先得创建相应的数据库。在RediSQL中,这一步通过执行 CREATE_DB命令来完成:
127.0.0.1:6379> redisql.create_db mydb
OK
然后,我们还需要在数据库中创建出相应的表用于存储数据,这一步可以通过 RediSQL的EXEC命令配合SQLite的CREATE TABLE命令来完成,前者用于在RediSQL中执行SQL命令,后者用于在关系数据库中创建表格:
127.0.0.1:6379> redisql.exec mydb "create table users (Name TEXT,Age INT);"
1) DONE
2) (integer) 0
之后,再次执行EXEC命令和SQLite的INSERT命令,即可将相应的数据插入表格当中:
127.0.0.1:6379> redisql.exec mydb "insert into users values('peter',25);"
1) DONE
2) (integer) 1
127.0.0.1:6379> redisql.exec mydb "insert into users values('jack',28);"
1) DONE
2) (integer) 1
127.0.0.1:6379> redisql.exec mydb "insert into users values('mary',23);"
1) DONE
2) (integer) 1
然后执行SELECT命令来查询刚才插入的数据:
127.0.0.1:6379> redisql.exec mydb "select * from users"
1) 1) "peter"
2) (integer) 25
2) 1) "jack"
2) (integer) 28
3) 1) "mary"
2) (integer) 23
API介绍
创建数据库
CREATE_DB命令用于创建数据库,它接受一个数据库名字和一个可选的路径参数:
REDISQL.CREATE_DB name [path]
如果用户没有指定 path 参数,那么RediSQL将使用特殊字符串:**memory:**作为path参数的值,意味着数据将被存储在Redis中;如果用户想要把数据存储到硬盘中,那么只需要在 path 参数中指定数据库文件的具体路径即可。
删除数据库
RediSQL没有直接删除数据库的命令,不过由于RediSQL创建的每个数据库都对应一个Redis键,所以我们可以直接使用Redis内置的 DEL 命令来移除 RediSQL 创建的数据库:
DEL db [db ... ]
执行语句
RediSQL的 EXEC 命令接受一个数据库名和一个 SQLite 语句作为参数,然后在数据库执行语句:
REDISQL.EXEC db "statement"
执行查询
如果用户想要执行的 SQLite 语句是不需要修改数据库的只读语句,那么可以使用 RediSQL 的 QUERY 命令代替 EXEC 命令,以此来获得更为安全的语句执行环境以及潜在的性能优化:
REDISQL.QUERY db "statement"
使用QUERY命令执行写操作语句,将引发错误。
复制数据库
用户可以通过执行 COPY 命令,将源数据库的所有数据复制到目标数据库:
REDISQL.COPY source destination
如果给定的目标数据库为非空的,那么将会先清空目标数据库,然后再进行复制。
关于RediSQL的详细使用方法请参见 https://redisql.redbeardlab.com/rediSQL/blog/tutorial/
RediSearch模块
RediSearch是基于Redis的模块机制在顶层实现的全文搜索引擎,这个全文搜索引擎功能非常强大,它能够为多种语言的文档构建索引,并通过高效的反向索引数据结构快速的进行检索操作。
下载与编译
首先下载RediSearch模块的源码:
git clone git@github.com:RediSearch/RediSearch.git
接着使用 make命令来编译RediSearch模块:
cd RediSearch/
make
编译RediSearch需要用到Cmake,因此首先要保证计算机中安装了CMake
编译完成后,在源码包中的**/build**文件夹下就可以找到 rediSearch.so ,将其载入到Redis中即可。
使用示例
RediSearch支持非常丰富的功能,但是它最重要的功能还是实施全文搜索,使用 FT.CREATE 命令可以来构建索引:
127.0.0.1:6379> ft.create database schema title text description text
OK
上述命令,我们构建了一个名为 database的索引,该索引可以存储包含 title 和 description 两个字段的文档。
在建立索引之后,可以通过 FT.ADD 命令将文档添加至索引:
127.0.0.1:6379> ft.add database doc1 1.0 language "chinese" fields title "Redis" description "Redis是一个使用ANSI C编写的开 源、支持网络、基于内存、可持久性的键值对存储数据库。"
OK
127.0.0.1:6379> ft.add database doc2 1.0 language "chinese" fields title "MySQL" description "MySQl是一个开放源代码的关系数 据库管理系统。"
OK
127.0.0.1:6379> ft.add database doc3 1.0 language "chinese" fields title "PostgreSQL" description "PostgreSQL是自由对象-关系数据库服务器。"
OK
接下来就可以检索索引中的文档了。
127.0.0.1:6379> ft.search database "Redis" language "chinese"
1
doc1
description
Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可持久性的键值对存储数据库。
title
Redis
127.0.0.1:6379> ft.search database "关系数据库" language "chinese"
2
doc3
title
PostgreSQL
description
PostgreSQL是自由对象-关系数据库服务器。
doc2
title
MySQL
description
MySQl是一个开放源代码的关系数据库管理系统。
API简介
创建索引
通过FT.CREATE命令创建具有指定名称的索引,每个索引可以包含任意多个字段,字段的类型可以是文本、数字、地理位置或标签:
FT.CREATE name SCHEMA [field [WEIGHT n] [TEXT | NUMERIC | GEO | TAG] [SORTABLE]] [field ... ]
可选的 WEIGHT 用于为每个字段设置权重,如果不设置权重,则默认使用 1.0 作为权重。
TEXT 表示文本类型,NUMERIC表示数字类型,GEO表示地理位置,TAG 表示标签。
SORTABLE 可以将字段设置为可排序字段,带有该属性的字段可以在实施检索时通过 SORTBY 选项排序。
添加文档至索引
在创建索引之后,通过执行 FT.ADD 命令将给定的文档添加到索引中:
FT.ADD index docId source FIELDs field value [field value]
最基本的FT.ADD命令接受index、docId、sorce以及任意多个field-value键值对作为参数,其中:
- index用于指定文档所属的索引,docId用于指定文档ID,sorce用于指定文档分值。文档分值可以介于 0.0 至于 1.0 之间,如果没有特殊要求则一般设置为 1.0
- FIELDS选项之后的任意多个field-value键值对则用于指定文档包含的键值对。
设置文档的语言
通过可选项 LANGUAGE 为文档设置相应的语言,以便RediSearch根据语言对文档做相应的处理:
FT.ADD index docId score [LANGUAGE lang] FIELDS filed value [field value ... ]
必须根据文档内容设置正确的语言,不正确的语言设置将导致文档无法被检索。
只索引而不存储文档
如果用户在执行 FT.ADD 命令时给定了可选项 NOSAVE ,那么命令只会为文档建立索引,并不会存储文档本身:
FT.ADD index docId score [NOSAVE] FIELDS field value [field value ... ]
单纯被索引而未被存储的文档在检索的时候只会返回文档的ID而不会返回文档本身。
使用新文档代替旧文档
如果用户在执行 FT.ADD 命令时给定了可选的 REPLACE 选项,并且索引中存在于给定的ID相同的文档,那么命令将使用新文档代替旧文档:
FT.ADD index docId score [REPLACE] FIELDS field value [field value ... ]
部分更新文档
在一些情况下,我们可能只是想要更新已有文档中的某些字段,而不是替换整个文档,这时就需要用到 PARTIAL 选项:
FT.ADD index docId score [REPLACE PARTIAL] FIELDS field value [field value ... ]
在默认情况下,用户在定义索引的时候设定文档有多少个字段,添加文档的时候就需要提供多少个字段,但是在启用了 PARTIAL可选项并且旧文档已经存在的情况下,用户只需要给定想要更新的字段及其新值即可。
添加散列至索引
为了让用户可以快速的将存储在Redis散列中的文档添加至索引,RediSearch提供了 FT.ADDHASH命令:
FT.ADDHASH index hash score [LANGUAGE lang] [REPLACE]
这个命令接受的参数与FT.ADD命令基本相同,唯一的不同之处在于,用户只需要在 hash 参数中提供散列的键名,命令就会把散列中包含的字段和值作为文档添加到索引中。
查看索引信息
在创建索引之后,用户可以通过执行FT.INFO命令查看索引的相关信息,比如索引包含的文档数量、索引包含的唯一字段数量、索引在内存中的分布信息等。
FT.INFO index
检索文档
通过执行 FT.SEARCH命令,用户可以根据给定的文本在索引中查找与之匹配的文档:
FT.SEARCH index query [LANGUAGE lang]
其中 index为被检索索引的名字,query指定查找的文本, 如果默认不是英语,则需要使用 LANGUAGE 指定语言。
只返回匹配的文档ID
如果用户在检索时只想获取被匹配文档的ID而不是文档本身,那么只需要在执行命令的时候提供 NOCONTENT 选项即可:
FT.SEARCH index query [NOCONTENT]
只返回指定的字段
在默认情况下,FT.SEARCH命令将返回文档包含的所有字段,但用户也可以通过 RETURN 选项让命令只返回指定的字段:
FT.SEARCH index query [RETURN num field ... ]
用户需要通过 num 参数指定自己想要返回的字段数量,并在之后给出具体的字段名。
只返回自定数值区间内的文档
使用 FILTER 选项,可以让 FT.SEARCH命令只返回结果文档中,指定数值字段符合给定区间的文档:
FT.SEARCH index query [FILTER numeric_field min max]
FILTER字段接受numeric_field 、min 和 max这3个参数作为输入,其中numeric_field参数用于指定文档中数值字段,而 min 和 max 参数则用于指定具体的数值区间。这个数值区间可以像 ZRANGE 之类的命令一样,使用 -inf 和 +inf 来指定无限值,或者使用左括号 ( 和 右括号 )。
根据指定规则排序
通过 SORTBY 可选项,用户可以让 FT.SEARCH 命令根据指定字段的大小按顺序或逆序返回结果:
FT.SEARCH index query [SORTBY field [ASC | DESC]
默认采用 ASC[升序] 方式排序。
使用SORTBY排序的字段必须在创建索引的时候使用SORTABLE选项进行声明,否则SORTBY将得出错误的结果。
限制命令返回的结果数量
通过 LIMIT 选项,可以限制命令返回的结果数量:
FT.SEARCH index query [LIMIT offset num]
从索引中获取文档
用户可以通过FT.GET命令或者FT.MGET命令,从指定的索引中获取一个或多个文档:
FT.GET index docId
FT.MGET index docId [docId ... ]
从索引中移除文档
当不再需要索引中的某个文档时,可以通过以下命令将其移除:
FT.DEL index docId
注意:单纯的执行FT.DEL只会将文档移除出索引,但是并不会删除被移除的文档。因此,还是可以使用FT.GET命令来获取被移除的文档。
如果想要在移除索引文档的同时将文档一并删除,就需要在执行命令的时候使用 DD (Delete Document)可选项:
FT.DEL index docId [DD]
移除索引
当用户不再需要某个索引的时候,可以通过以下命令将其移除:
FT.DROP index [KEEPDOCS]
在默认情况下,FT.DROP命令在移除索引的同时也会删除索引中的所有文档,如果用户想要保留被删除的索引,那么只需要在执行命令的时候使用 KEEPDOCS 可选项即可。
关于RediSearch模块的详细使用方法,请参见 https://oss.redislabs.com/redisearch/