clj-xmemcached

An opensource memcached client for clojure wraps xmemcached. Xmemcached is an opensource high performance memcached client for java.

Leiningen Usage

To use clj-xmemcached,add:

    [clj-xmemcached "0.3.0"]

to your project.clj.

API docs

clj-xmemcached APIs

Usage

Client

    (require '[clj-xmemcached.core :as xm])
    (def client (xm/memcached "host:port"))
    (def client (xm/memcached "host1:port1 host2:port2" :protocol :binary))

We create a memcached client using text protocol by default,but we can create a client using binary protocol.

Also,we can create a client using consistent hash and binary protocol:

    (def client (xm/memcached "host1:port1 host2:port2" :protocol :binary :hash :consistent))

All valid options:

 :protocol  Protocol to talk with memcached,a keyword in :text,:binary or :kestrel,default is text.
 :hash  Hash algorithm,a keyword in  :consistent, :standard or :php, default is standard hash.
 :pool  Connection pool size,default is 1,it's a recommended value.
 session-locator memcached connection locator,default is created based on :hash algorithm value.
 :sanitize-keys  Whether to sanitize keys before operation,default is false.
 :reconnect  Whether to reconnect when connections are disconnected,default is true.
 :heartbeat  Whether to do heartbeating when connections are idle,default is true.
 :timeout  Operation timeout in milliseconds,default is five seconds.
 :transcoder Transcoder to encode/decode data. For example, clj-json-transcoder.
 :name  A name to define a memcached client instance"

Store items

    (xm/with-client client
        (xm/set "key1" "dennis")
        (xm/set "key2" "dennis" 1)
        (xm/add "key3" "dennis")
        ;;touch the key3 expire timeout to 3 seconds.
        (xm/touch "key3" 2))

Use with-client macro to bind the client for following operations. The value 100 in set is the expire timeout for the item in seconds.Storing item functions include set,add,replace,touch,append and prepend.

Unless you need the added flexibility of specifying the client for each request,you can save some typing with a little macro:

    (defmacro wxm
        [& body]
        `(xm/with-client client ~@body))

If you have only one client in your application, you can set the global client by:

    (xm/set-client! client)

Then all the following requests will use the global client by default,except you bind another client using with-client macro.

Get items

    ;;get k1 k2 k3...
    (wxm
        (xm/get "key1")
        (xm/get "key1" "key2" "key3")
        (xm/gets "key1"))

Using get to retrieve items from memcached.You can retrieve many items at one time by bulk getting,but it's result is a java.util.HashMap. gets returns a clojure map contains cas and value,for example:

      {:value "hello,dennis zhuang", :cas 396}

Increase/Decrease numbers

    ;;incr/decr key delta init-value
    (wxm
        (xm/incr "num" 1)
        (xm/decr "num" 1)
        (xm/incr "num" 1 0)

Above codes try to increase/decrease a number in memcached with key "num",and if the item is not exists,then set it to zero.

Delete items

    (xm/delete "num")
    ;;delete with CAS in binary protocol.
    (xm/delete "num" (:cas (gets "num")))

Compare and set

    (xm/cas "key" inc)

We use inc function to increase the current value in memcached and try to compare and set it at most Integer.MAX_VALUE times. cas can be called in:

     (xm/cas key cas-fn max-times)

The cas-fn is a function to return a new value,set item's new value to:

    (cas-fn current-value)

Distribution Lock

Use memcached as a lightweight distribution lock:

    (def counter (atom 0))
    (future (try-lock "lock" 5 (do (Thread/sleep 3000)
                                   (swap! counter inc))
                                (println "else1")))
    (future (try-lock "lock" 5 (do (Thread/sleep 3000)
                                   (swap! counter inc))
                                (println "else2")))
    (future (try-lock "lock" 5 (do (Thread/sleep 3000)
                                   (swap! counter inc))
                                (println "else3")))

    (Thread/sleep 4000)
    (is (nil? (get "lock")))
    (is (= 1 @counter))

through macro

    (through uid (get-user-from-databse uid))

Equals to:

    (if-let [rt (get uid)]
        rt
        (let [rt (get-user-from-database uid)]
            (when rt
                (add uid rt 0))
            rt))

Shutdown

    (xm/shutdown)

Flush

    (xm/flush-all)

Statistics

    (xm/stats)

Get the raw XmemcachedClient instance

Because memcached function returns a delayed object,so if you want to get the raw XmemcachedClient instance,you have to deref it:

    @client
    (xm/shutdown @client)

Transcoders

We use SerializationTranscoder by default,it will encode/decode values using java serialization. But since 0.2.2, we provide a new transcoder clj-json-transcoder to encode/decode values using clojure.data.json.It is suitable to integrate with other systems written in other languages.

And we add nippy-transcoder in 0.2.3, it use nippy for serialization.

Example

Please see the example code in example/demo.clj

Performance:

Benchmark on my machine:

CPU 2.3 GHz Intel Core i5
Memory 8G 1333 MHz DDR3
JVM options: default

Start memcached by:

memcached -m 4096 -d

Benchmark result using text protocol and 1 NIO connection:

Benchmark set: threads=50,repeats=10000,total=500000
Elapsed time: 10990.256 msecs
Benchmark get: threads=50,repeats=10000,total=500000
Elapsed time: 7768.998 msecs
Benchmark set & get: threads=50,repeats=10000,total=500000
Elapsed time: 18445.409 msecs

That it is:

45495 sets/secs
64350 gets/secs
27114 tps (both set and get an item in one round)

License

Copyright (C) 2011-2020 dennis zhuang[killme2008@gmail.com]

Distributed under the Eclipse Public License, the same as Clojure.