1. 程式人生 > 資料庫 >redis基礎學習跟Jedis的手動實現

redis基礎學習跟Jedis的手動實現

文章目錄

基礎導論

redis需求的產生

基本的應用服務一般如下圖:
在這裡插入圖片描述
流程: 客戶端傳送請求到伺服器端,伺服器端查詢資料庫然後做相應到業務處理,最終返回給客戶端。
問題:一旦涉及到網際網路的高併發問題,比如秒殺的庫存扣減,APP的訪問流量高峰等,每一次伺服器都要通過IO流去查詢資料庫,速度特別慢並且很容易把資料庫打崩,所以引入了快取中介軟體,我們可以將資料儲存在記憶體中,訪問資料時候直接在記憶體中讀取,記憶體讀取效能比IO讀取提高百倍。比較常用的快取中介軟體有Redis 和 Memcached ,本次主要寫Redis,伺服器端獲取資料首先在redis中獲得,如獲得則直接將結果返回,如沒獲得再從mysql中讀取資料返回,並且會將mysql中資料快取到Redis中。

在這裡插入圖片描述

Redis簡介

Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API
Redis特性:速度快(QPS效能可達10W/s),鍵值對的資料結構伺服器,豐富的功能,簡單穩定,持久化,主從複製,高可用和分散式轉移, Redis 是單執行緒的,客戶端API支援語言較多,

Redis底層資料型別

在這裡插入圖片描述

1. String

底層資料格式 C語言中字串用char[],redis對其封裝了一成SDS(這個也是redis儲存的最小單元)。然後再SDS基礎上又封裝了一層 -> RedisObject,裡面可以指定物種資料型別,當我們 set name blog 時,redis其實會建立兩個RedisObject物件, 鍵的RedisObject 和 值的RedisOjbect 其中它們的type=REDIS_STRING。原始碼在sds.h

2. List

底層資料格式為雙向連結串列,可用來實現簡單的任務佇列等,原始碼是在adlist.c

3. Hash

底層資料格式涉及到hashtable, 在redis的這個層面,它永遠只有一個鍵,一個值,這個鍵永遠都是字串物件,而value 就是若干等k-v 屬性。底層還會涉及到rehash。

4. Set

底層資料格式底層跟Hash其實類似,我們可以認為Set 儲存到是Hash中到ke,value全部為空,跟Java中HashMap和HashSet 原理類似。

5.ZSet

底層資料格式跳躍表,範圍查詢的天敵就是有序集合,跳躍表是有序集合的底層實現之一。跳躍表是基於多指標有序連結串列實現的,可以看成多個

有序連結串列。
在這裡插入圖片描述在查詢時,從上層指標開始查詢,找到對應的區間之後再到下一層去查詢。下圖演示了查詢 22 的過程。
在這裡插入圖片描述
與紅黑樹等平衡樹相比,跳躍表具有以下優點:

插入速度非常快速,因為不需要進行旋轉等操作來維護平衡性;
更容易實現;
支援無鎖操作。

Redis使用場景 :

  1. 快取資料庫:
  2. 排行榜:
  3. 計數器應用:
  4. 社交網路
  5. 淘寶購物車:
  6. 訊息佇列:
  7. 其他場景等:自我聯想ing

Redis 指令

關於安裝跟配置百度即可,關於指令熟悉跟使用推薦查詢官方API

Jedis的手動實現

Java操作Redis使用的Jedis,它的 通訊協議採用的是RESP,它是基於TCP的應用層協議 RESP(REdis Serialization Protocol);RESP底層採用的是TCP的連線方式,通過tcp進行資料傳輸,然後根據解析規則解析相應資訊,該資料傳輸規則簡單明瞭,我們可以根據RESP協議以及Jedis底層原始碼實現自己到客戶端:

獲取Jedis傳送資料

倒入Jedis依賴

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.7.2</version>
		</dependency>
  1. jedis測試
    public static final void main(String[] args) {

        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println(jedis.set("name","sowhat"));
    }
  1. 看jedis.set底層
  public String set(final String key,String value) {
    checkIsInMulti();
    client.set(key,value);// 深入
    return client.getStatusCodeReply();
  }
  public void set(final String key,final String value) {
    set(SafeEncoder.encode(key),SafeEncoder.encode(value));
    //對資料進行了編碼,再進入
  }
 public void set(final byte[] key,final byte[] value) {
    sendCommand(Command.SET,key,value);//再進入
  } 
  protected Connection sendCommand(final ProtocolCommand cmd,final byte[]... args) {
    try {
      connect();
       // 劃重點  獲得一個連結 在進入
      Protocol.sendCommand(outputStream,cmd,args);
       //劃重點如何 傳送一個指令
      pipelinedCommands++;
      return this;
    } catch (JedisConnectionException ex) {
      // Any other exceptions related to connection?
      broken = true;
      throw ex;
    }
  }
public void connect() {
    if (!isConnected()) {
      try {
        socket = new Socket();
        // ->@wjw_add
        socket.setReuseAddress(true);
        socket.setKeepAlive(true); // Will monitor the TCP connection is
        // valid
        socket.setTcpNoDelay(true); // Socket buffer Whetherclosed,to
        // ensure timely delivery of data
        socket.setSoLinger(true,0); // Control calls close () method,// the underlying socket is closed
        // immediately
        // <-@wjw_add

        socket.connect(new InetSocketAddress(host,port),connectionTimeout);
        socket.setSoTimeout(soTimeout);
        outputStream = new RedisOutputStream(socket.getOutputStream());
        inputStream = new RedisInputStream(socket.getInputStream());
      } catch (IOException ex) {
        broken = true;
        throw new JedisConnectionException(ex);
      }
    }
  }

結論: 可以看到 傳輸資料對時候底層用的是Socket傳輸。這樣到話我們可以模擬一個redis伺服器端看jedis是如何加工資料的。
模擬服務端

package com.james.cache.socket;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(6378);
        // 程式碼阻塞等待客戶端連結
        Socket socket = serverSocket.accept();
        // 把訊息讀到byte陣列中
        InputStream reader = socket.getInputStream();
        byte[] request = new byte[1024];
        reader.read(request);
        //資料轉化為String 輸出
        String req = new String(request);
        System.out.println(req);
        serverSocket.close();
    }
}

jedis客戶端

package com.james.cache.socket;

import redis.clients.jedis.Jedis;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        Jedis jedis = new Jedis("127.0.0.1",6378);
        jedis.set("name","sowhat");
        jedis.close();
    }
}

*3
$3
SET
$4
name
$6
sowhat

可以看到伺服器端接收到的資料格式如上,這就是RESP的通訊資料格式
MyJedis 的手動實現

package com.james.cache.socket;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class MyJedis {
    Socket socket;
    InputStream reader;
    OutputStream writer;

    public MyJedis() throws Exception {
        socket = new Socket("127.0.0.1",6379);
        reader = socket.getInputStream();
        writer = socket.getOutputStream();
    }

    public String set(String k,String v) throws Exception {
        StringBuffer command = new StringBuffer();
        command.append("*3").append("\r\n");
        command.append("$3").append("\r\n");
        command.append("SET").append("\r\n");
        command.append("$").append(k.getBytes().length).append("\r\n");
        command.append(k).append("\r\n");
        command.append("$").append(v.getBytes().length).append("\r\n");
        command.append(v).append("\r\n");

        writer.write(command.toString().getBytes());

        byte[] reponse = new byte[1024];
        reader.read(reponse);
        return new String(reponse);
    }

    public String get(String k) throws Exception {
        StringBuffer command = new StringBuffer();
        command.append("*2").append("\r\n");
        command.append("$3").append("\r\n");
        command.append("GET").append("\r\n");
        command.append("$").append(k.getBytes().length).append("\r\n");
        command.append(k).append("\r\n");

        writer.write(command.toString().getBytes());
        byte[] reponse = new byte[1024];
        reader.read(reponse);
        return new String(reponse);
    }
}

testCode

    @Test
    public void testMyJedis() throws  Exception
    {
        MyJedis myJedis = new MyJedis();
        System.out.println(myJedis.set("name","sowhat1412"));
        System.out.println( myJedis.get("name"));
    }

結果: 可以發現我們自己的Jedis以及可以成功跟Redis客戶端通訊了。
在這裡插入圖片描述

參考

redis效能測試9W+/伺服器14W+
RESP通訊協議