redis基础 跟Jedis的手动实现

原创
2020/03/31 00:00
阅读数 513

基础导论

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依赖

 1        <dependency>
2            <groupId>junit</groupId>
3            <artifactId>junit</artifactId>
4            <version>4.12</version>
5            <scope>test</scope>
6        </dependency>
7
8        <dependency>
9            <groupId>redis.clients</groupId>
10            <artifactId>jedis</artifactId>
11            <version>2.7.2</version>
12        </dependency>
  1. jedis测试

1    public static final void main(String[] args) {
2
3        Jedis jedis = new Jedis("127.0.0.1"6379);
4        System.out.println(jedis.set("name""sowhat"));
5    }
  1. 看jedis.set底层

1  public String set(final String key, String value) {
2    checkIsInMulti();
3    client.set(key, value);// 深入
4    return client.getStatusCodeReply();
5  }
1  public void set(final String key, final String value) {
2    set(SafeEncoder.encode(key), SafeEncoder.encode(value));
3    //对数据进行了编码,再进入
4  }
1 public void set(final byte[] key, final byte[] value) {
2    sendCommand(Command.SET, key, value);//再进入
3  } 
 1  protected Connection sendCommand(final ProtocolCommand cmd, final byte[]... args) {
2    try {
3      connect();
4       // 划重点  获得一个链接 在进入
5      Protocol.sendCommand(outputStream, cmd, args);
6       //划重点如何 发送一个指令
7      pipelinedCommands++;
8      return this;
9    } catch (JedisConnectionException ex) {
10      // Any other exceptions related to connection?
11      broken = true;
12      throw ex;
13    }
14  }
 1public void connect() {
2    if (!isConnected()) {
3      try {
4        socket = new Socket();
5        // ->@wjw_add
6        socket.setReuseAddress(true);
7        socket.setKeepAlive(true); // Will monitor the TCP connection is
8        // valid
9        socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
10        // ensure timely delivery of data
11        socket.setSoLinger(true0); // Control calls close () method,
12        // the underlying socket is closed
13        // immediately
14        // <-@wjw_add
15
16        socket.connect(new InetSocketAddress(host, port), connectionTimeout);
17        socket.setSoTimeout(soTimeout);
18        outputStream = new RedisOutputStream(socket.getOutputStream());
19        inputStream = new RedisInputStream(socket.getInputStream());
20      } catch (IOException ex) {
21        broken = true;
22        throw new JedisConnectionException(ex);
23      }
24    }
25  }

结论:可以看到 传输数据对时候底层用的是Socket传输。这样到话我们可以==模拟一个redis服务器端==看jedis是如何加工数据的。
模拟服务端

 1package com.james.cache.socket;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.net.ServerSocket;
6import java.net.Socket;
7
8public class Server {
9    public static void main(String[] args) throws IOException {
10        ServerSocket serverSocket = new ServerSocket(6378);
11        // 代码阻塞等待客户端链接
12        Socket socket = serverSocket.accept();
13        // 把消息读到byte数组中
14        InputStream reader = socket.getInputStream();
15        byte[] request = new byte[1024];
16        reader.read(request);
17        //数据转化为String 输出
18        String req = new String(request);
19        System.out.println(req);
20        serverSocket.close();
21    }
22}

jedis客户端

 1package com.james.cache.socket;
2
3import redis.clients.jedis.Jedis;
4
5import java.io.IOException;
6import java.io.OutputStream;
7import java.net.Socket;
8
9public class Client {
10    public static void main(String[] args) throws IOException {
11        Jedis jedis = new Jedis("127.0.0.1"6378);
12        jedis.set("name","sowhat");
13        jedis.close();
14    }
15}

*3
4
name
$6
sowhat

可以看到服务器端接收到的数据格式如上,这就是RESP的通讯数据格式
==MyJedis== 的手动实现

 1package com.james.cache.socket;
2
3import java.io.InputStream;
4import java.io.OutputStream;
5import java.net.Socket;
6
7public class MyJedis {
8    Socket socket;
9    InputStream reader;
10    OutputStream writer;
11
12    public MyJedis() throws Exception {
13        socket = new Socket("127.0.0.1"6379);
14        reader = socket.getInputStream();
15        writer = socket.getOutputStream();
16    }
17
18    public String set(String k, String v) throws Exception {
19        StringBuffer command = new StringBuffer();
20        command.append("*3").append("\r\n");
21        command.append("$3").append("\r\n");
22        command.append("SET").append("\r\n");
23        command.append("$").append(k.getBytes().length).append("\r\n");
24        command.append(k).append("\r\n");
25        command.append("$").append(v.getBytes().length).append("\r\n");
26        command.append(v).append("\r\n");
27
28        writer.write(command.toString().getBytes());
29
30        byte[] reponse = new byte[1024];
31        reader.read(reponse);
32        return new String(reponse);
33    }
34
35    public String get(String k) throws Exception {
36        StringBuffer command = new StringBuffer();
37        command.append("*2").append("\r\n");
38        command.append("$3").append("\r\n");
39        command.append("GET").append("\r\n");
40        command.append("$").append(k.getBytes().length).append("\r\n");
41        command.append(k).append("\r\n");
42
43        writer.write(command.toString().getBytes());
44        byte[] reponse = new byte[1024];
45        reader.read(reponse);
46        return new String(reponse);
47    }
48}

testCode

1    @Test
2    public void testMyJedis() throws  Exception
3    
{
4        MyJedis myJedis = new MyJedis();
5        System.out.println(myJedis.set("name","sowhat1412"));
6        System.out.println( myJedis.get("name"));
7    }

结果: 可以发现我们自己的Jedis以及可以成功跟Redis客户端通讯了。

在这里插入图片描述

参考

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


本文分享自微信公众号 - sowhat1412(sowhat9094)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部