盤點一下Redis中常用的Java客戶端,或者我們們手寫一個?

跟著Mic學架構發表於2021-10-21

Java中那些Redis的客戶端

前面我們的所有操作都是基於redis-cli來完成的,那麼我們要在Java中操作Redis,怎麼做呢?首先我們先來了解一下Redis Serialization Protocol(Redis序列化協議),這個是Redis提供的一種,客戶端和Redis服務端通訊傳輸的編碼協議,服務端收到罅隙ihou,會基於這個約定編碼進行解碼。

  • 開啟Wireshark工具,對VMnet8這個網路進行抓包

  • 增加過濾條件

    ip.dst_host==192.168.221.128 and tcp.port in {6379}
    
  • 使用RDM工具連線到Redis Server進行key-value操作,比如執行 set name mic

  • 通過Wireshark工具監控資料包內容,如圖3-3所示,可以看到實際發出的資料包是:

    *3\r\n$3\r\nSET\r\n$4\r\nname\r\n$3\r\nmic
    
    • 其中*3*代表引數個數,set name mic, 表示三個引數。

    • $3表示屬性長度,$表示包含3個字元。

    客戶端和伺服器傳送的命令或資料一律以 \r\n (CRLF回車+換行)結尾。

    image-20210703173900083

    圖3-3

基於這樣一個特性,我們可以自己實現一個Java客戶端。

自定義Redis客戶端

下面我們通過抓包相關的命令,瞭解Redis客戶端的工作機制。

定義常量池。

public class CommandConstant {
    /**
     * 開始符
     */
    public static final String START = "*";
    /**
     * 指令長度符
     */
    public static final String LENGTH = "$";
    /**
     * 換行符
     */
    public static final String LINE = "\r\n";
    public enum CommandEnum {
        SET,
        GET,
        INCR
    }
}

CustomClientSocket

CustomClientSocket用來建立網路通訊連線,並且傳送資料指定到RedisServer。

public class CustomClientSocket {
    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;

    public CustomClientSocket(String ip,int port) {
        try {
            socket=new Socket(ip,port);
            inputStream=socket.getInputStream();
            outputStream=socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void send(String cmd){
        try {
            outputStream.write(cmd.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public String read(){
        byte[] bytes = new byte[1024];
        int count = 0;
        try {
            count = inputStream.read(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new String(bytes, 0, count);
    }
}

封裝客戶端

public class CustomRedisClient {

    private CustomClientSocket customClientSocket;

    public CustomRedisClient(String host,int port) {
        customClientSocket=new CustomClientSocket(host,port);
    }
    public String set(String key, String value) {
        customClientSocket.send(convertToCommand(CommandConstant.CommandEnum.SET, key.getBytes(), value.getBytes()));
        return customClientSocket.read();
    }

    public String get(String key) {
        customClientSocket.send(convertToCommand(CommandConstant.CommandEnum.GET, key.getBytes()));
        return customClientSocket.read();
    }

    public static String convertToCommand(CommandConstant.CommandEnum command, byte[]... bytes) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(CommandConstant.START).append(bytes.length + 1).append(CommandConstant.LINE);
        stringBuilder.append(CommandConstant.LENGTH).append(command.toString().length()).append(CommandConstant.LINE);
        stringBuilder.append(command.toString()).append(CommandConstant.LINE);

        for (byte[] aByte : bytes) {
            stringBuilder.append(CommandConstant.LENGTH).append(aByte.length).append(CommandConstant.LINE);
            stringBuilder.append(new String(aByte)).append(CommandConstant.LINE);
        }
        return stringBuilder.toString();
    }
}

測試方法

public static void main(String[] args) {
    CustomRedisClient redisClient=new CustomRedisClient("192.168.221.128",6379);
    System.out.println(redisClient.set("name","mic"));
    System.out.println(redisClient.get("name"));
}

總結

你看,理解了原理之後,自己去實現起來發現並不難。

但是實際開發過程中,我們難倒也需要開發自己開發客戶端嗎?當然不用,官方推薦了以下三種客戶端

配置 作用
Jedis A blazingly small and sane redis java client
lettuce Advanced Redis client for thread-safe sync, async, and reactive usage. Supports Cluster, Sentinel, Pipelining, and codecs.
Redisson distributed and scalable Java data structures on top of Redis server

Jedis

Jedis是我們最熟悉和最常用的客戶端。輕量,簡潔,便於整合和改造。

簡單使用方法

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.6.1</version>
</dependency>
public static void main(String[] args) {
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    jedis.set("qingshan", "2673");
    System.out.println(jedis.get("qingshan"));
    jedis.close();
}

一般來說,我們不會使用單個Jedis連線,而是會使用連線池,Jedis提供了連線池的功能。

public static void main(String[] args) {
    JedisPool pool = new JedisPool(ip, port);
    Jedis jedis = jedisPool.getResource();
}

Luttece

Lettuce是一個RedisJava驅動包,大家常用的spring-boot-starter-data-redis中預設就採用的Lettuce。Lettuce是一個高效能基於Java編寫的Redis驅動框架,底層整合了Project Reactor提供天然的反應式程式設計,通訊框架整合了Netty使用了非阻塞IO5.x版本之後融合了JDK1.8的非同步程式設計特性,在保證高效能的同時提供了十分豐富易用的API

簡單使用方法

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>5.1.8.RELEASE</version>
</dependency>

Lettuce使用的時候依賴於四個主要元件:

  • RedisURI:連線資訊。
  • RedisClientRedis客戶端,特殊地,叢集連線有一個定製的RedisClusterClient
  • ConnectionRedis連線,主要是StatefulConnection或者StatefulRedisConnection的子類,連線的型別主要由連線的具體方式(單機、哨兵、叢集、訂閱釋出等等)選定,比較重要。
  • RedisCommandsRedis命令API介面,基本上覆蓋了Redis發行版本的所有命令,提供了同步(sync)、非同步(async)、反應式(reative)的呼叫方式,對於使用者而言,會經常跟RedisCommands系列介面打交道。
public static void main(String[] args) {
        RedisURI redisUri = RedisURI.builder()                    // <1> 建立單機連線的連線資訊
                .withHost("192.168.221.128")
                .withPort(6379)
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        RedisClient redisClient = RedisClient.create(redisUri);   // <2> 建立客戶端
        StatefulRedisConnection<String, String> connection = redisClient.connect();     // <3> 建立執行緒安全的連線
        RedisCommands<String, String> redisCommands = connection.sync();                // <4> 建立同步命令
        SetArgs setArgs = SetArgs.Builder.nx().ex(5);
        String result = redisCommands.set("name", "throwable", setArgs);
        System.out.println(result);
        result = redisCommands.get("name");
        System.out.println(result);
        // ... 其他操作
        connection.close();   // <5> 關閉連線
        redisClient.shutdown();  // <6> 關閉客戶端
    }

和Spring Boot整合使用

Lettuce是Spring Boot 2.x 預設的客戶端,替換了Jedis。整合之後我們不需要單獨使用它,直接呼叫Spring的RedisTemplate操作,連線和建立和關閉也不需要我們操心。

引入依賴jar包

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml配置檔案如下

redis:
    port: 6379
    host: 192.168.221.128
    lettuce:
      pool:
        max-active: -1
        max-idle: 2000
        max-wait: -1
        min-idle: 1
        time-between-eviction-runs: 5000

使用方法

@RestController
@RequestMapping("/")
public class LutteceController {
    @Autowired
    RedisTemplate redisTemplate;

    @GetMapping
    public ResponseEntity get(){
        String name=(String)redisTemplate.opsForValue().get("name");
        return ResponseEntity.ok(name);
    }
}

Redisson

https://redisson.org/

https://github.com/redisson/redisson/wiki/目錄

Redisson是一個在Redis的基礎上實現的Java駐記憶體資料網格(In-Memory Data Grid)。它不僅提供了一系列的分散式的Java常用物件,還提供了許多分散式服務。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最簡單和最便捷的方法。Redisson的宗旨是促進使用者對Redis的關注分離(Separation of Concern),從而讓使用者能夠將精力更集中地放在處理業務邏輯上。

簡單使用方法

  • 引入依賴Jar包

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.16.0</version>
    </dependency>
    
  • 時間單節點連線和操作

    public static void main(String[] args) {
        Config config=new Config();
        config.useSingleServer().setAddress("redis://192.168.221.128:6379");
        RedissonClient redissonClient= Redisson.create(config);
        redissonClient.getBucket("test").set("mic");
        System.out.println(redissonClient.getBucket("test").get());
    }
    

和Spring Boot整合

Spring Boot的整合方式。

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.0</version>
</dependency>

application.yml中的配置。

spring:
  redis:
    timeout: 2000
    host: 192.168.221.128
    port: 6379

使用方法。

@RestController
public class RedissonController {
    @Autowired
    RedissonClient redissonClient;

    @GetMapping("/")
    public String get(){
        return redissonClient.getBucket("test").get().toString();
    }
}

另外一種配置方式如下

  • 修改application.yml

    spring:
      redis:
        redisson:
          file: classpath:redisson.yml
    
  • 建立一個redisson.yml檔案,內容如下

    singleServerConfig:
      address: redis://192.168.221.128:6379
      #---------------------------------------------
      # 連線空閒超時,單位:毫秒
      idleConnectionTimeout: 10000
      # 連線超時,單位:毫秒
      connectTimeout: 10000
      # 命令等待超時,單位:毫秒
      timeout: 3000
      # 命令失敗重試次數,如果嘗試達到 retryAttempts(命令失敗重試次數) 仍然不能將命令傳送至某個指定的節點時,將丟擲錯誤。
      # 如果嘗試在此限制之內傳送成功,則開始啟用 timeout(命令等待超時) 計時。
      retryAttempts: 3
      # 命令重試傳送時間間隔,單位:毫秒
      retryInterval: 1500
    

關注[跟著Mic學架構]公眾號,獲取更多精品原創

相關文章