Redis的Java客戶端比較:絕地武士與生菜

Yujiaao發表於2022-05-31

絕地武士與生菜:探索

作者: Guy Royse

我是一個真正的探索者,所以當我必須做出技術決定時——比如選擇一個 Redis 客戶端——我會去探險。這是我對 Java 客戶端押韻組合的探索: JedisLettuce

我的計劃很簡單:

  • 在程式碼中嘗試一些簡單的事情
  • 在程式碼中嘗試一些高階的東西
  • 達到某種選擇標準
  • 利潤!

利潤的格言目標,就像內褲一樣,始終存在。 但您可以從中受益的部分是選擇標準。這將使我們能夠決定何時 Jedis 是正確的選擇,而 Lettuce 何時才是正確的選擇。這一點非常重要,因為我們都知道 選擇工具時任何問題的答案都是“視情況而定”。

一種簡單的程式碼

讓我們比較一下所有練習中最簡單的一些程式碼:從 Redis 的單個例項中設定和獲取值。

首先,我們使用 Jedis 執行此操作:

import redis.clients.jedis.Jedis;

public class JedisSetGet {
    private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0";

    public static void main(String[] args) {
        Jedis jedis = new Jedis(YOUR_CONNECTION_STRING);
        jedis.set("foo", "bar");
        String result = jedis.get("foo");
        jedis.close();
        System.out.println(result); // "bar"
    }
}

檢視託管的 原始JedisSetGet.java

通過 GitHub

看程式碼,這很簡單。建立連線。用它。關閉它。

接下來我們將使用生菜來做:

import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class LettuceSetGet {
    private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0";
    public static void main(String[] args) {
        RedisClient redisClient = RedisClient.create(YOUR_CONNECTION_STRING);
        StatefulRedisConnection<String, String> connection = redisClient.connect();
        RedisCommands<String, String> sync = connection.sync();
        sync.set("foo", "bar");
        String result = sync.get("foo");
        connection.close();
        redisClient.shutdown();
        System.out.println(result); // "bar"
    }
}

通過 GitHub 檢視託管的 原始LettuceSetGet.java

這看起來有點複雜。有一個客戶端、一個連線和一個命令物件。它們的名稱和模板性質表明它們可能有多種變體。也許除了 StatefulRedisConnection<String, String> 型別之外,我們還有一個接受 byte[] 的無狀態變體?(劇透: 叢集和主/副本配置有多種連線型別,但不是無狀態的)。

但是,一旦您完成了設定和拆卸,這兩個客戶端中的基本程式碼都是相同的:建立連線。用它。關閉它。

現在,就這麼簡單的事情,Jedis 看起來更輕鬆。這是有道理的,因為它的程式碼更少。但我確信生菜擁有所有這些東西是有原因的——可能是為了處理更高階的場景。

管道、同步和非同步

Jedis 都是同步的,除了管道。管道允許非同步使用 Redis,但不幸的是,它不能與叢集一起使用。但是,管道很容易使用:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
import redis.clients.jedis.Tuple;
import java.util.Set;
import java.util.stream.Collectors;
public class JedisPipelining {
    private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0";
    public static void main(String[] args) {
        Jedis jedis = new Jedis(YOUR_CONNECTION_STRING);
        Pipeline p = jedis.pipelined();
        p.set("foo", "bar");
        Response<String> get = p.get("foo");
        p.zadd("baz", 13, "alpha");
        p.zadd("baz", 23, "bravo");
        p.zadd("baz", 42, "charlie");
        Response<Set<Tuple>> range = p.zrangeWithScores("baz", 0, -1);
        p.sync();
        jedis.close();
        System.out.println(get.get()); // "bar"
        System.out.println(range.get().stream()
                .map(Object::toString)
                .collect(Collectors.joining(" "))); // [alpha,13.0] [bravo,23.0] [charlie,42.0]
    }
}

通過 GitHub 檢視託管的 原始 JedisPipelining.java

如果你喜歡這種東西(我就是這樣),Lettuce 支援同步、非同步甚至反應式介面。然而,這些都只是 Lettuce 的多執行緒、基於事件的模型之上的語法糖層,理所當然地使用流水線。即使你同步使用它,它在下面也是非同步的。

我們已經通過我們超級複雜的 set and get 示例看到了同步介面的實際作用。但是讓我們看一下非同步的:

import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;
public class LettuceAsync {
    private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0";
    public static void main(String[] args) {
        RedisClient redisClient = RedisClient.create(YOUR_CONNECTION_STRING);
        StatefulRedisConnection<String, String> connection = redisClient.connect();
        RedisAsyncCommands<String, String> async = connection.async();
        final String[] result = new String[1];
        async.set("foo", "bar")
                .thenComposeAsync(ok -> async.get("foo"))
                .thenAccept(s -> result[0] = s)
                .toCompletableFuture()
                .join();
        connection.close();
        redisClient.shutdown();
        System.out.println(result[0]); // "bar"
    }
}

通過 GitHub 檢視託管的 原始LettuceAsync.java

這設定和獲取,就像同步示例一樣,但顯然這是更多涉及的程式碼。它也是多執行緒的。

Jedis 和多執行緒程式碼

Jedis 可以很好地處理多執行緒應用程式,但是 Jedis 連線不是執行緒安全的。所以不要線上程間共享它們。如果你跨執行緒共享 Jedis 連線,Redis 會脫口而出各種協議錯誤,例如:

expected '$' but got ' '

為了解決這類問題,請使用 JedisPool——一個執行緒安全的物件,它分配執行緒不安全的 Jedis 物件。使用它很簡單,就像其他絕地武士一樣。完成後,只需請求一個執行緒並通過 .close() 將其返回到池中。這是在行動:

import redis.clients.jedis.*;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class JedisMultithreaded {
    private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0";
    public static void main(String[] args) {
        JedisPool pool = new JedisPool(YOUR_CONNECTION_STRING);
        List<String> allResults = IntStream.rangeClosed(1, 5)
                .parallel()
                .mapToObj(n -> {
                    Jedis jedis = pool.getResource();
                    jedis.set("foo" + n, "bar" + n);
                    String result = jedis.get("foo" + n);
                    jedis.close();
                    return result;
                })
                .collect(Collectors.toList());
        pool.close();
        System.out.println(allResults); // "bar1, bar2, bar3, bar4, bar5"
    }
}

通過 GitHub 檢視託管的 原始JedisMultithreaded.java

這些 Jedis 物件中的每一個都封裝了到 Redis 的單個連線,因此根據池的大小,可能存在阻塞或空閒連線。此外,這些連線是同步的,因此總是存在一定程度的空閒。

絕地武士、生菜和叢集

我覺得我應該談談叢集,但沒什麼可說的——至少在比較方面。有很多功能可以討論,但兩個庫都支援它。不出所料,Jedis 更易於使用,但只能同步使用叢集。Lettuce 更難使用,但能夠與叢集進行同步、非同步和反應式互動。

這是反覆出現的主題。這應該不足為奇。它自己承認 “Jedis 被認為易於使用”。Lettuce 在其主頁上宣告“Lettuce 是一個可擴充套件的 Redis 客戶端,用於構建非阻塞反應式應用程式” 。

當然,如果您使用的是 Redis Enterprise,則不必擔心叢集,因為它是在伺服器端處理的。只需使用 Jedis 或 Lettuce 的非叢集 API,管理您的金鑰,以便將它們分配到正確的分片中,您就可以開始了。

做出決定

那麼,絕地武士還是生菜?這得看情況。(看,我告訴過你我們會在這裡結束!)這是程式碼複雜性和應用程式可擴充套件性之間的經典權衡。

如果您需要高度可擴充套件的東西,請使用生菜。其更復雜的抽象提供了更輕鬆地製作可擴充套件產品的能力。Lettuce 是一個強大的解決方案,可讓您使用 Redis 的全套功能。

如果您需要快速構建一些東西並且可擴充套件性不是並且可能不會成為問題,請使用 Jedis。它簡單易用,讓您更容易專注於應用程式和資料,而不是資料儲存機制。

如果您仍然無法決定,您可以隨時使用 Spring Data Redis,它將抽象出 Jedis 和 Lettuce,以便您將來改變主意。當然,這伴隨著它自己的一系列權衡。但這是未來部落格文章的主題!

RedisLabs 贊助

相關文章