Redis基础学习 跟 Jedis 的手动实现

基础导论

redis需求的产生

基本的应用服务一般如下图:
Redis基础学习 跟 Jedis 的手动实现
流程: 客户端发送请求到服务器端,服务器端查询数据库然后做相应到业务处理,最终返回给客户端。
问题:一旦涉及到互联网的高并发问题,比如秒杀的库存扣减,APP的访问流量高峰等,每一次服务器都要通过IO流去查询数据库,速度特别慢并且很容易把数据库打崩,所以引入了缓存中间件,我们可以将数据存储在内存中,访问数据时候直接在内存中读取,内存读取性能比IO读取提高百倍。比较常用的缓存中间件有Redis 和 Memcached ,本次主要写Redis,服务器端获取数据首先在redis中获得,如获得则直接将结果返回,如没获得再从mysql中读取数据返回,并且会将mysql中数据缓存到Redis中。
Redis基础学习 跟 Jedis 的手动实现

Redis简介

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
Redis特性:速度快(QPS性能可达10W/s),键值对的数据结构服务器,丰富的功能,简单稳定,持久化,主从复制,高可用和分布式转移, Redis 是单线程的,客户端API支持语言较多,
Redis支持执行Lua脚本,可自动实现原子性操作。

Redis底层数据类型

Redis基础学习 跟 Jedis 的手动实现

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

底层数据格式跳跃表,范围查找的天敌就是有序集合,跳跃表是有序集合的底层实现之一。跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。
Redis基础学习 跟 Jedis 的手动实现在查找时,从上层指针开始查找,找到对应的区间之后再到下一层去查找。下图演示了查找 22 的过程。
Redis基础学习 跟 Jedis 的手动实现
与红黑树等平衡树相比,跳跃表具有以下优点:

插入速度非常快速,因为不需要进行旋转等操作来维护平衡性;
更容易实现;
支持无锁操作。

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基础学习 跟 Jedis 的手动实现

参考

redis性能测试9W+/服务器14W+
RESP通讯协议

上一篇:JedisPool 工具类(DCL 思想)


下一篇:Redis