carmine

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介:
Taoensso open-source

CHANGELOG | API | current Break Version:

[com.taoensso/carmine "2.17.0"] ; See CHANGELOG for details

Please consider helping to support my continued open-source Clojure/Script work?

Even small contributions can add up + make a big difference to help sustain my time writing, maintaining, and supporting Carmine and other Clojure/Script libraries. Thank you!

- Peter Taoussanis

Carmine

A Clojure Redis client & message queue

Redis is awesome and it's getting more awesome every day. It deserves a great Clojure client.

Features

  • Small, simple all-Clojure library
  • Fully documented, API with full support for the latest Redis versions
  • Great performance
  • Industrial strength connection pooling
  • Composable, first-class command functions
  • Flexible, high-performance binary-safe serialization using Nippy
  • Full support for Lua scriptingPub/Sub, etc.
  • Full support for custom reply parsing
  • Command helpers (atomicluasort*, etc.)
  • Ring session-store
  • Simple, high-performance message queue (v2+, Redis 2.6+)
  • Simple, high-performance distributed lock (v2+, Redis 2.6+)
  • Pluggable compression and encryption support (v2+)
  • Includes Tundra, an API for replicating data to an additional datastore (v2+, Redis 2.6+)

3rd-party tools, etc.

Link Description
@lantiga/redlock-clj Distributed locks for uncoordinated Redis clusters
@danielsz/system PubSub component for system
Your link here? PR's welcome!

Getting started

Add the necessary dependency to your project:

[com.taoensso/carmine "2.17.0"]

And setup your namespace imports:

(ns my-app
  (:require [taoensso.carmine :as car :refer (wcar)]))

Connections

You'll usually want to define a single connection pool, and one connection spec for each of your Redis servers.

(def server1-conn {:pool {<opts>} :spec {<opts>}}) ; See `wcar` docstring for opts
(defmacro wcar* [& body] `(car/wcar server1-conn ~@body))

A simple example spec with no pool and one server using redistogo.com would be:

(def server1-conn {:pool {} :spec {:uri \"redis://redistogo:pass@panga.redistogo.com:9475/\"}})

Basic commands

Executing commands is easy:

(wcar* (car/ping)) ; => "PONG" (1 command -> 1 reply)

(wcar*
  (car/ping)
  (car/set "foo" "bar")
  (car/get "foo")) ; => ["PONG" "OK" "bar"] (3 commands -> 3 replies)

Note that executing multiple Redis commands in a single wcar request uses efficient Redis pipelining under the hood, and returns a pipeline reply (vector) for easy destructuring, etc.

If the number of commands you'll be calling might vary, it's possible to request that Carmine always return a destructurable pipeline-style reply:

(wcar* :as-pipeline (car/ping)) ; => ["PONG"] ; Note the pipeline-style reply

If the server responds with an error, an exception is thrown:

(wcar* (car/spop "foo"))
=> Exception ERR Operation against a key holding the wrong kind of value

But what if we're pipelining?

(wcar* (car/set  "foo" "bar")
       (car/spop "foo")
       (car/get  "foo"))
=> ["OK" #<Exception ERR Operation against ...> "bar"]

Serialization

The only value type known to Redis internally is the byte string. But Carmine uses Nippy under the hood and understands all of Clojure's rich datatypes, letting you use them with Redis painlessly:

(wcar* (car/set "clj-key" {:bigint (bigint 31415926535897932384626433832795)
                           :vec    (vec (range 5))
                           :set    #{true false :a :b :c :d}
                           :bytes  (byte-array 5)
                           ;; ...
                           })
       (car/get "clj-key"))
=> ["OK" {:bigint 31415926535897932384626433832795N
          :vec    [0 1 2 3 4]
          :set    #{true false :a :c :b :d}
          :bytes  #<byte [] [B@4d66ea88>}]

Types are handled as follows:

Clojure type Redis type
Strings Redis strings
Keywords Redis strings (v2+)
Simple numbers Redis strings
Everything else Auto de/serialized with Nippy

You can force automatic de/serialization for an argument of any type by wrapping it with car/serialize.

Documentation and command coverage

Like labs-redis-clojure, Carmine uses the official Redis command reference to generate its own command API. Which means that not only is Carmine's command coverage always complete, but it's also fully documented:

(use 'clojure.repl)
(doc car/sort)
=> "SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]

Sort the elements in a list, set or sorted set.

Available since: 1.0.0.

Time complexity: O(N+M*log(M)) where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is currently O(N) as there is a copy step that will be avoided in next releases."

Lua

Redis 2.6 introduced a remarkably powerful feature: server-side Lua scripting! As an example, let's write our own version of the set command:

(defn my-set
  [key value]
  (car/lua "return redis.call('set', _:my-key, 'lua '.. _:my-val)"
                  {:my-key key}   ; Named key variables and their values
                  {:my-val value} ; Named non-key variables and their values
                  ))

(wcar* (my-set "foo" "bar")
       (car/get "foo"))
=> ["OK" "lua bar"]

Script primitives are also provided: evaleval-shaeval*eval-sha*. See the Lua scripting docs for more info.

Helpers

The lua command above is a good example of a Carmine helper.

Carmine will never surprise you by interfering with the standard Redis command API. But there are times when it might want to offer you a helping hand (if you want it). Compare:

(wcar* (car/zunionstore "dest-key" 3 "zset1" "zset2" "zset3" "WEIGHTS" 2 3 5))
(wcar* (car/zunionstore* "dest-key" ["zset1" "zset2" "zset3"] "WEIGHTS" 2 3 5))

Both of these calls are equivalent but the latter counted the keys for us. zunionstore* is another helper: a slightly more convenient version of a standard command, suffixed with a * to indicate that it's non-standard.

Helpers currently include: atomiceval*evalsha*info*luasort*zinterstore*, and zunionstore*. See their docstrings for more info.

Commands are (just) functions

In Carmine, Redis commands are real functions. Which means you can use them like real functions:

(wcar* (doall (repeatedly 5 car/ping)))
=> ["PONG" "PONG" "PONG" "PONG" "PONG"]

(let [first-names ["Salvatore"  "Rich"]
      surnames    ["Sanfilippo" "Hickey"]]
  (wcar* (mapv #(car/set %1 %2) first-names surnames)
         (mapv car/get first-names)))
=> ["OK" "OK" "Sanfilippo" "Hickey"]

(wcar* (mapv #(car/set (str "key-" %) (rand-int 10)) (range 3))
       (mapv #(car/get (str "key-" %)) (range 3)))
=> ["OK" "OK" "OK" "OK" "0" "6" "6" "2"]

And since real functions can compose, so can Carmine's. By nesting wcar calls, you can fully control how composition and pipelining interact:

(let [hash-key "awesome-people"]
  (wcar* (car/hmset hash-key "Rich" "Hickey" "Salvatore" "Sanfilippo")
         (mapv (partial car/hget hash-key)
               ;; Execute with own connection & pipeline then return result
               ;; for composition:
               (wcar* (car/hkeys hash-key)))))
=> ["OK" "Sanfilippo" "Hickey"]

Listeners & Pub/Sub

Carmine has a flexible Listener API to support persistent-connection features like monitoring and Redis's fantastic Publish/Subscribe facility:

(def listener
  (car/with-new-pubsub-listener (:spec server1-conn)
    {"foobar" (fn f1 [msg] (println "Channel match: " msg))
     "foo*"   (fn f2 [msg] (println "Pattern match: " msg))}
   (car/subscribe  "foobar" "foobaz")
   (car/psubscribe "foo*")))

Note the map of message handlers. f1 will trigger when a message is published to channel foobarf2 will trigger when a message is published to foobarfoobazfoo Abraham Lincoln, etc.

Publish messages:

(wcar* (car/publish "foobar" "Hello to foobar!"))

Which will trigger:

(f1 '("message" "foobar" "Hello to foobar!"))
;; AND ALSO
(f2 '("pmessage" "foo*" "foobar" "Hello to foobar!"))

You can adjust subscriptions and/or handlers:

(with-open-listener listener
  (car/unsubscribe) ; Unsubscribe from every channel (leave patterns alone)
  (car/psubscribe "an-extra-channel"))

(swap! (:state listener) assoc "*extra*" (fn [x] (println "EXTRA: " x)))

Remember to close the listener when you're done with it:

(car/close-listener listener)

Note that subscriptions are connection-local: you can have three different listeners each listening for different messages, using different handlers. This is great stuff.

Reply parsing

Want a little more control over how server replies are parsed? You have all the control you need:

(wcar* (car/ping)
       (car/with-parser clojure.string/lower-case (car/ping) (car/ping))
       (car/ping))
=> ["PONG" "pong" "pong" "PONG"]

Binary data

Carmine's serializer has no problem handling arbitrary byte[] data. But the serializer involves overhead that may not always be desireable. So for maximum flexibility Carmine gives you automatic, zero-overhead read and write facilities for raw binary data:

(wcar* (car/set "bin-key" (byte-array 50))
       (car/get "bin-key"))
=> ["OK" #<byte[] [B@7c3ab3b4>]

Message queue

Redis makes a great message queue server:

(:require [taoensso.carmine.message-queue :as car-mq]) ; Add to `ns` macro

(def my-worker
  (car-mq/worker {:pool {<opts>} :spec {<opts>}} "my-queue"
   {:handler (fn [{:keys [message attempt]}]
               (println "Received" message)
               {:status :success})}))

(wcar* (car-mq/enqueue "my-queue" "my message!"))
%> Received my message!

(car-mq/stop my-worker)

Guarantees:

  • Messages are persistent (durable) as per Redis config
  • Each message will be handled once and only once
  • Handling is fault-tolerant: a message cannot be lost due to handler crash
  • Message de-duplication can be requested on an ad hoc (per message) basis. In these cases, the same message cannot ever be entered into the queue more than once simultaneously or within a (per message) specifiable post-handling backoff period.

See the relevant API docs for details.

Distributed locks

(:require [taoensso.carmine.locks :as locks]) ; Add to `ns` macro

(locks/with-lock "my-lock"
  {:pool {<opts>} :spec {<opts>}} ; Connection details
  1000 ; Time to hold lock
  500  ; Time to wait (block) for lock acquisition
  (println "This was printed under lock!"))

Again: simple, distributed, fault-tolerant, and fast.

See the relevant API docs for details.

Tundra (beta)

Redis is a beautifully designed datastore that makes some explicit engineering tradeoffs. Probably the most important: your data must fit in memory. Tundra helps relax this limitation: only your hot data need fit in memory. How does it work?

  1. Use Tundra's dirty command any time you modify/create evictable keys
  2. Use worker to create a threaded worker that'll automatically replicate dirty keys to your secondary datastore
  3. When a dirty key hasn't been used in a specified TTL, it will be automatically evicted from Redis (eviction is optional if you just want to use Tundra as a backup/just-in-case mechanism)
  4. Use ensure-ks any time you want to use evictable keys - this'll extend their TTL or fetch them from your datastore as necessary

That's it: two Redis commands, and a worker! Tundra uses Redis' own dump/restore mechanism for replication, and Carmine's own message queue to coordinate the replication worker.

Tundra can be very easily extended to any K/V-capable datastore. Implementations are provided out-the-box for: DiskAmazon S3 and DynamoDB.

(:require [taoensso.carmine.tundra :as tundra :refer (ensure-ks dirty)]
          [taoensso.carmine.tundra.s3]) ; Add to ns

(def my-tundra-store
  (tundra/tundra-store
    ;; A datastore that implements the necessary (easily-extendable) protocol:
    (taoensso.carmine.tundra.s3/s3-datastore {:access-key "" :secret-key ""}
      "my-bucket/my-folder")))

;; Now we have access to the Tundra API:
(comment
 (worker    my-tundra-store {} {}) ; Create a replication worker
 (dirty     my-tundra-store "foo:bar1" "foo:bar2" ...) ; Queue for replication
 (ensure-ks my-tundra-store "foo:bar1" "foo:bar2" ...) ; Fetch from replica when necessary
)

Note that this API makes it convenient to use several different datastores simultaneously (perhaps for different purposes with different latency requirements).

See the relevant API docs for details.

Performance

Redis is probably most famous for being fast. Carmine hold up its end and usu. performs w/in ~10% of the official C redis-benchmark utility despite offering features like command composition, reply parsing, etc.

Thanks to Navicat

Navicat-logo

Carmine's SQL-interop features (forthcoming) were developed with the help of Navicat, kindly sponsored by PremiumSoft CyberTech Ltd.

Thanks to YourKit

Carmine was developed with the help of the YourKit Java Profiler. YourKit, LLC kindly supports open source projects by offering an open source license.

This project supports the ClojureWerkz-logo goals

  • ClojureWerkz is a growing collection of open-source, batteries-included Clojure libraries that emphasise modern targets, great documentation, and thorough testing.

Contacting me / contributions

Please use the project's GitHub issues page for all questions, ideas, etc. Pull requests welcome. See the project's GitHub contributors page for a list of contributors.

Otherwise, you can reach me at Taoensso.com. Happy hacking!

Peter Taoussanis

License

Distributed under the EPL v1.0 (same as Clojure).
Copyright © 2012-2016 Peter Taoussanis.






本文作者:陈群
本文来自云栖社区合作伙伴rediscn,了解相关信息可以关注redis.cn网站。
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
机器学习/深度学习 前端开发 API
一年的打磨,MNN正式版发布!
MNN 的诞生源于淘系技术部的一群对技术充满热情的同学,在充分的行业调研后认为当时的推理引擎如 TFLite 不足以满足手机淘宝这样一个亿级用户与日活的超级 App 。
3069 0
一年的打磨,MNN正式版发布!
|
人工智能 数据挖掘 算法
《中国人工智能学会通讯》——11.25 单目视频下运动物体建模及分析
本节书摘来自CCAI《中国人工智能学会通讯》一书中的第11章,第11.25节, 更多章节内容可以访问云栖社区“CCAI”公众号查看。
1439 0
|
安全 数据库 数据安全/隐私保护