CL-REDIS:Redis 的 Common Lisp 冒险之旅 🗺️

CL-REDIS 就像一个探险家,带着你深入 Redis 的世界,探索数据存储的奥秘。它是一个快速、可靠的 Common Lisp 客户端,让你可以轻松地与 Redis 服务器进行交互。想象一下,它就像一个经验丰富的向导,带着你穿越 Redis 的广阔领域,为你提供所有你需要探索和管理数据的工具。 🧭

快速入门:准备出发 🥾

在你开始你的 Redis 冒险之旅之前,你需要准备一些必需品。首先,确保你有一台正在运行的 Redis 服务器。然后,使用 ql:quickload 'cl-redis 加载 CL-REDIS 库。就像在你的背包里装满地图和指南针一样。

接下来,你需要连接到 Redis 服务器。你可以使用 (redis:connect :host <host> :port <port>) 函数来建立连接。默认情况下,host127.0.0.1port6379。就像找到你探险的起点一样。

现在,你可以使用 red 包中的 Redis 命令来与服务器进行交互了。例如,你可以使用 (red:ping) 命令测试连接。就像向你的向导打招呼一样。

完成你的探险之后,你可以使用 (redis:disconnect) 函数断开连接。或者,你可以使用 with-connection 宏,它会自动为你打开和关闭连接。就像在你的探险结束后,回到你的出发点一样。

可用命令:你的探险工具箱 🧰

CL-REDIS 提供了大量的 Redis 命令,让你可以执行各种操作,包括:

  • 字符串操作: SETGETAPPENDINCRDECR 等。就像在你的探险中,记录你的发现和修改你的笔记一样。
  • 哈希操作: HSETHGETHDELHGETALL 等。就像在你的探险中,收集和整理各种信息一样。
  • 列表操作: LPUSHRPUSHLRANGELREM 等。就像在你的探险中,收集和整理各种信息一样。
  • 集合操作: SADDSMEMBERSSISMEMBERSREM 等。就像在你的探险中,收集和整理各种信息一样。
  • 排序集操作: ZADDZRANGEZSCOREZREM 等。就像在你的探险中,收集和整理各种信息一样。
  • 事务操作: MULTIEXECDISCARD 等。就像在你的探险中,执行一系列操作,并确保它们按顺序完成一样。
  • 发布订阅操作: PUBLISHSUBSCRIBEUNSUBSCRIBE 等。就像在你的探险中,与其他探险者进行交流一样。

代码组织:你的探险地图 🗺️

CL-REDIS 提供了两个包:REDISRED。所有功能都可以在 REDIS 包中使用。为了避免符号冲突,Redis 命令在这个包中定义时,会加上一个前缀(默认情况下为 red-,在编译时设置)。 RED 包是语法糖,它只是提供了没有前缀的 Redis 命令。因此,它不建议导入,以避免与 COMMON-LISP 包发生符号冲突。你只需要使用包限定的符号名称即可。例如,同一个 Redis 命令(例如 GET)可以调用为 RED-GET(如果你导入了 REDIS 包)或 RED:GET

安装:准备你的装备 🎒

CL-REDIS 可通过 quicklisp 获取。它依赖于以下几个库:

调试和错误恢复:你的探险指南 🧭

如果 *echo-p*T,所有客户端-服务器通信将被回显到 *echo-stream* 流中,默认情况下为 *standard-output*

错误处理模仿了 Postmodern。特别是,当发生错误导致通信流中断时,会发出 redis-connection-error 类型的条件,提供一个 :reconnect 重启。如果选择它,整个 Redis 命令将被重新发送,如果重新连接尝试成功。此外,connect 检查是否已经建立了与 Redis 的连接,如果已经建立,则提供两个重启(:leave:replace)。

当服务器响应错误回复(即以 - 开头的回复)时,会发出 redis-error-reply 类型的条件。

还有一个高级的 with-persistent-connection 宏,它会尽力做到正确的事情™(即在连接断开后自动重新打开连接一次)。

高级用法:你的探险技巧 🧗‍♀️

发布订阅

由于没有专门的命令通过发布订阅从 Redis 接收消息,你可以使用以下方法:

(bt:make-thread (lambda ()
                  (with-connection ()
                    (red:subscribe "foo")
                    (loop :for msg := (expect :anything) :do
                      (print msg))))
                "pubsub-listener")

要发布消息,可以使用以下方法:

(with-connection ()
  (red:publish "foo" "test"))

管道

为了提高性能,Redis 允许对命令进行管道化,并延迟接收结果,直到最后再批量处理。CL-REDIS 提供了 with-pipelining 宏来支持管道化。比较以下示例中的执行时间(使用管道和不使用管道):6.567 秒 vs. 2023.924 秒!

(let ((names (let (acc)
               (dotimes (i 1000 (nreverse acc))
                 (push (format nil "n~a" i) acc))))
      (vals  (let (big-acc)
               (dotimes (i 1000 (nreverse big-acc))
                 (let (acc)
                   (dotimes (i (random 100))
                     (push (list (random 10) (format nil "n~a" i)) acc))
                   (push (nreverse acc) big-acc))))))
  (time (redis:with-connection ()
          (redis:with-pipelining
            (loop :for k :in names :for val :in vals :do
              (dolist (v val)
                (apply #'red:zadd k v)))
            (red:zunionstore "result" (length names) names)
            (red:zrange "result" 0 -1))))

  ;; Evaluation took:
  ;;  6.567 seconds of real time
  ;;  3.900243 seconds of total run time (3.200200 user, 0.700043 system)

  (time (redis:with-connection ()
          (loop :for k :in names :for val :in vals :do
            (dolist (v val)
              (apply #'red:zadd k v)))
          (red:zunionstore "result" (length names) names)
          (red:zrange "result" 0 -1))))

  ;; Evaluation took:
  ;; 2023.924 seconds of real time
  ;; 3.560222 seconds of total run time (2.976186 user, 0.584036 system)

请注意,with-pipelining 调用理论上可以嵌套,但结果只对最高级别的管道可用,所有嵌套的管道将返回 :PIPELINED。因此,在这种情况下会发出警告。

内部机制:你的探险指南 🗺️

通用函数 tellexpect 根据 规范 实现 Redis 协议。tell 指定了 Redis 请求的格式,expect 指定了响应的处理方式。实现 expect 上另一种方法的最佳方式通常是使用 def-expect-method,它会安排从套接字读取数据,并提供一个 reply 变量,该变量保存从服务器解码的回复数据,并删除了初始字符。例如:

(def-expect-method :ok
  (assert (string= reply "OK"))
  reply)

Redis 操作通过 def-cmd 定义为普通函数,只需要提供参数和返回类型。def-cmd 将所有定义的函数名称加上 *cmd-prefix* 前缀,默认情况下为 'red。(请注意,设置 *cmd-prefix* 将在编译时生效)。它还将它们从 REDIS 包导出,并从 RED 包导出,但不带前缀。

下面是一个命令定义的示例:

(def-cmd KEYS (pattern) :multi
  "Return all the keys matching the given pattern.")

请参阅 commands.lisp 查看所有定义的命令。

未实现的功能:你的探险计划 🗺️

  • 以下命令未实现,因为它们不打算在客户端使用:MONITORDEBUG OBJECTDEBUG SEGFAULT
  • Unix 域套接字支持 - 已计划
  • 一致性哈希 未内置。实际上,这种东西与该库的功能是正交的,可能应该在另一个库中实现。
  • 连接池也没有实现,因为在存在 with-persistent-connection 的情况下,它实际上并不那么需要。持久连接对于专用线程来说更简单、更高效,而且错误更少。但是,连接池还有其他用例,因此它可能会在将来的版本中实现。

致谢:你的探险伙伴 🤝

该库由 Vsevolod Dyomkin vseloved@gmail.com 开发和维护。

在最初阶段,Alexandr Manzyuk manzyuk@googlemail.com 开发了连接处理代码,遵循了 Postmodern 中的实现。后来,它被部分重写,以适应更高级的连接处理策略,例如持久连接。

许可证:你的探险指南 🧭

MIT(有关详细信息,请参阅 LICENSE 文件)。

https://github.com/vseloved/cl-redis/raw/refs/heads/master/README.md

0 0 投票数
Article Rating
订阅评论
提醒
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x