• 【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支持执行Lua脚本,可自动实现原子性操作。

    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("
    ");
            command.append("$3").append("
    ");
            command.append("SET").append("
    ");
            command.append("$").append(k.getBytes().length).append("
    ");
            command.append(k).append("
    ");
            command.append("$").append(v.getBytes().length).append("
    ");
            command.append(v).append("
    ");
    
            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("
    ");
            command.append("$3").append("
    ");
            command.append("GET").append("
    ");
            command.append("$").append(k.getBytes().length).append("
    ");
            command.append(k).append("
    ");
    
            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通讯协议

  • 相关阅读:
    期权标的概率密度函数
    Girsanov Theorem
    拉马努金恒等式
    波动率的三类模型
    stack(栈) and heap(堆)
    covar of lognormal variables
    BS 相关的一些近似公式
    布朗运动的一些特殊性质
    排序算法
    Mac node.js
  • 原文地址:https://www.cnblogs.com/sowhat1412/p/12734094.html
Copyright © 2020-2023  润新知