混合 NoSQL:键值用例

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

我一直在谈论 NoSQL 数据库供应商如何尝试支持来自其他类型的 NoSQL 数据库的更多数据模型。我称这些为混合 NoSQL 数据库。在混合 NoSQL 系列的这篇文章中,我讨论了其他类型的 NoSQL 数据库如何处理键值用例。

什么是键值存储?

KV 存储是一种相对简单的数据模型,但它允许快速的数据操作。因为速度至关重要,所以通常不支持更高级的功能。

大多数东西都可以是键值存储。笔记本电脑上的文件系统是键值存储。键是文件名,值是文件内容。您的笔记本电脑(通常)对文件一无所知或对其进行特殊处理——它只允许您打开、阅读、修改和保存内容。完全没有花哨的操作。

KV商店能做什么?

以下是所有 KV 存储可以执行的操作:-

  • 使用此密钥标识符存储此二进制值
  • 获取此密钥标识符的二进制值
  • 跨多个服务器分片我的数据,均匀分布我的 KV 对,以确保性能

以下是一些不太常见的功能:-

  • 增加/减少这个键的值(Redis DECR、DECRBY、INCR、INCRBY、INCRBYFLOAT)
  • 检查一个键是否存在(Redis EXISTS)
  • 将此数据附加到此键的值中保存的列表(或集合)(Redis APPEND)
  • 堆栈操作(Redis LPUSH 和 LPOP)
  • 哈希字段操作(以 H* 开头的 Redis 命令——有很多!)
  • 地理空间领域操作(Beta,在测试中,Redis 与 GEOADD、GEODIS、GEOHASH、GEORADIUS、GEOPOS 的功能)
  • 发布/订阅(Redis 允许客户端订阅关于键的通知!有点酷!)

为什么我要在另一种类型的 NoSQL 数据库中执行这些操作?

可能是因为您已经拥有一个,或者您还需要在另一个应用程序中使用文档存储或列存储——并且想知道您是否可以重复使用它。当然,它永远不会像纯 KV 存储那样快得让人眼花缭乱——但也许你不需要每台服务器每秒处理 400 000 个事务。

我能做什么和不能做什么?

我将使用 MarkLogic Server 作为比较。 MarkLogic 是一个混合 NoSQL 数据库,支持文档、搜索和三重存储操作。它还可以用于存储任意二进制数据以及它最初构建的文本、json 和 XML。许多源文件都是二进制的——比如 PDF。这意味着它具有存储和检索任意数据的核心功能,就像 KV 存储一样。

存储和获取

MarkLogic 可用于通过简单地使用它的 REST API 来存储二进制数据。 对 /v1/documents?uri=/some/doc/uri.bin 执行一个 POST ,并使用 Content-Type: 设置为适合您需要的 MIME 类型。使用 GET /v1/documents?uri=whatever 以其原始内容类型获取它。

MarkLogic 中的 URI 是我们的关键标识符。你可以自己分配一个,如上所述,或者要求服务器生成一个随机的。在这种情况下,URI 在 POST 响应中返回。

分片

MarkLogic 大型二进制文件(默认情况下,任何超过 1 MB 的文件,尽管可以配置)按原样存储在文件系统上。更改被记录下来,并且出于 HA 原因在事务边界内复制数据——它符合 ACID。数据库复制和备份也适用于二进制数据,所以不用担心。

接收新文档(或值)的机器将文档随机放置在集群中服务器上的碎片(称为森林)中。 MarkLogic Server 还有一个称为自动重新平衡的功能,因此如果您不小心将所有文档添加到一台服务器,MarkLogic 会自动为您在集群中重新平衡它们。所有这些都没有任何重复或数据不可用。

递增/递减

MarkLogic 可用于存储 XML 和 JSON 数据。这些文档可以根据您的喜好简单或复杂。如果你愿意,它们可以是单个值,就像 KV 存储一样,但这不是很有效(就像 KV 存储!)。每个 MarkLogic 文档都有一点开销——大约 750 字节。所以存储一个 2 字节的值意味着开销很大。

最好创建一个包含许多值的文档,然后更改该文档的一部分。例如,不是每个产品的每个属性都有一个键——比如 PRODUCT1234-QuantityInStock——而是有一个 Product1234 的文档,其中包含库存数量、产品名称、标题、条形码和所有其他元数据。 (这就是 KV 存储也支持哈希的原因,我将在本文后面介绍……)

但是如何管理这些数据呢? MarkLogic 的 REST API 支持 PATCH 操作 。这可以完成几件事。在我们的场景中,它可用于将值递增或递减 1 或任何其他数量。使用 替换补丁机制 ,通过操作 ml.add – 但您也可以进行乘法、除法、正则表达式搜索和替换、连接字符串或执行子字符串操作。

检查密钥是否存在

如果您在 MarkLogic 中的文档包含整个键值,那么您只需 对 /v1/documents?uri=whatever 执行 HEAD 操作。如果您收到 404 响应,则该文档不存在。

但是,如果您想检查 XML 或 JSON 文档中单个元素中的值,则有点棘手。

MarkLogic 索引文档中的内容及其结构以及存在的元素。人们通常不会检查某个元素是否存在于文档中——他们通常会撤回整个内容并自行检查。但是您可以通过搜索检查通用索引来检查特定文档是否有密钥。

实际上,您构建了一个仅限于一个文档的搜索——您关心的 URI。您进一步使用元素查询(现在称为容器查询)来检查元素是否存在。如果您的搜索结果返回一个 URI——您的文档——那么它就存在。否则,它不会。

您可以使用 POST /v1/search 构建它。我没有测试下面的内容,但是 POST 正文中的类似内容应该可以工作:-


 {
  "search": {
    "query" : {
      "queries": [{
        "and-query": {
          "queries": [
            {"document-query": {"uri": [ "/my/doc/uri.bin" ]},      // does my doc exist?
            {"container-query": {"json-property": "mypropertykey"}} // does my property exist?
          ]
        }
      }]
    }, 
    "options" : { "return-results" : false, "return-metrics" : false } 
  } 
}


(注意 return-results:false 意味着不会返回任何结果数据,但您仍然会得到一个可以检查的计数——它将是 0 或 1。)

将数据附加到列表

MarkLogic Patch 操作还支持 插入模式 。有了它,您可以将一个或多个元素附加到现有容器元素。因此,您可以使用位置设置为“之后”的插入轻松实现列表。

堆栈操作

Patch 的插入模式允许您将位置指定为“之前”或“之后”,从而允许将项目放置在列表的前端和末尾。使用 Patch 的删除模式,您还可以删除列表的第一个 (/list/parent/child/fn:first()) 或最后一个 (/list/parent/child/fn:last()) 元素。

您还可以获取特定元素而不是完整文档。为此,您创建一个非常简单的 转换 (XQuery 或 XSLT 或服务器端 JavaScript)并将其与 GET /v1/documents ?transform=mytransform 端点一起使用。因此,您可以为“列表中的第一项”保存一个,为“列表中的最后一项”保存一个,如果键值文档中的结构复杂,甚至可以为“获取整个列表”保存一个。

因此,您不仅可以实现堆栈(推送和弹出等同于“after”和“fn:last”),还可以实现 FIFO 和 FILO 队列!

哈希运算

Redis 中的 Hash 就像 Java 中的 HashMap 一样工作。一个值包含不同属性的列表,每个属性的基数只是一个。然后,您可以对散列中的这些键执行操作。

在文档存储中,您改为在文档中创建元素,如下所示:-


 {
  "search": {
    "query" : {
      "queries": [{
        "and-query": {
          "queries": [
            {"document-query": {"uri": [ "/my/doc/uri.bin" ]},      // does my doc exist?
            {"container-query": {"json-property": "mypropertykey"}} // does my property exist?
          ]
        }
      }]
    }, 
    "options" : { "return-results" : false, "return-metrics" : false } 
  } 
}


与在 KV 存储中相比,这些数据结构在文档存储中作为聚合更容易在单个命令中进行管理。如果您将聚合存储在 KV 存储中并编写大量函数来更新它们,请考虑改用文档存储。

获取和更新聚合是一个简单的案例,或者在文档存储中存储或检索文档。使用补丁,您还可以只修改文档的一部分。

地理空间外勤业务

许多文档存储支持“二级索引”。这是您指示 NoSQL 数据库文档中的一个或多个元素需要特别索引的地方。通常这用于范围(小于、大于)查询,但这也可以用于地理空间查询。

通过简单地在 MarkLogic 中配置地理空间索引,列出包含经度和纬度的字段,足以支持地理查询的配置。每次添加文档时,无需向数据库指示键值是地理值。这是一次性的配置。

MarkLogic 有很多很多高级地理空间功能,包括在文档中搜索位于边界框、点半径(圆)或多边形中的点(文档可以包含很多点)。您甚至可以返回按距圆或多边形中心的反向距离排序的搜索结果! (就像典型的酒店搜索应用程序一样)。

同样,您还可以检查存在两个字段(地理值的经度和纬度)的文档——这样您也可以只找到包含地理数据的文档。

MarkLogic 拥有太多地理功能,无法在此处记录。我建议你去看看,因为它很棒。

发布/订阅

MarkLogic 服务器包括一个“反向查询”索引。这使您能够进行任何搜索——包括对单个文档或文档的任何部分进行任何更改的搜索——并在创建或更改文档并满足所有搜索条件(可以是任意复杂的你喜欢)。

这些警报操作可以是执行任何操作的 XQuery 或 JavaScript 代码。他们可以向电子邮件服务器或网络服务发送消息。我个人使用 Node.js 创建了一个 W3C WebSockets 服务器,该服务器保持与客户端的连接打开。 Node.js 服务器还有一个 Web 服务端点。触发警报时,MarkLogic 会向此端点发送一条消息,Node.js 使用 WebSockets 将此消息传递给 Web 应用程序客户端。

与 Redis 相比,这不是最简单的设置——因为 MarkLogic 出于 ACID 的原因不支持在事务边界之外保持连接打开,所以不支持 WebSockets 标准——但只用几行就可以做到Node.js 中的代码。

总之

你能成功地将文档存储用作 KV 存储吗?是的。对于所有操作也是如此吗?是的。是的,它会更慢,但速度是相对的。

例如,我的带有 VMWare 和 CentOS 的笔记本电脑,MarkLogic 只配置了一个林(分片),每秒可以进行 3000-4000 次写入或更新。通常对于 8 核机器,您将拥有大约 6 个分片。所以你可以在一台机器上轻松地进入数万个更新,更不用说最小的 3 节点集群了。 (HA 的最低要求)。

如果您需要极快的速度,请使用真正的 KV 存储。 Redis(和 Redis Cloud)是很棒的开源选项。 Aerospike 是我最喜欢的商业选择(它非常擅长本地管理闪存)——但如果您不需要每秒超过 100,000 次更新,那么您可以使用像 MarkLogic 这样的文档存储。 (如果您禁用不需要的索引,它们可能会工作得更快)。

其他文档存储可能能够支持较少的功能——尤其是围绕元素存在的查询和文档修补操作(递增/递减)。

有 MarkLogic 吗?需要 KV 存储?每台服务器每秒不需要超过 100 000 次更新?然后随意使用 MarkLogic。

文章最初发布在我的博客上:https: //adamfowlerml.wordpress.com/2015/08/14/hybrid-nosql-key-value-use-cases/