基础导论

redis需求的产生

基本的应用服务一般如下图:

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

流程: 客户端发送请求到服务器端,服务器端查询数据库然后做相应到业务处理,最终返回给客户端。

问题:一旦涉及到互联网的高并发问题,比如秒杀的库存扣减,APP的访问流量高峰等,每一次服务器都要通过IO流去查询数据库,速度特别慢并且很容易把数据库打崩,所以引入了缓存中间件,我们可以将数据存储在内存中,访问数据时候直接在内存中读取,内存读取性能比IO读取提高百倍。比较常用的缓存中间件有Redis 和 Memcached ,本次主要写Redis,服务器端获取数据首先在redis中获得,如获得则直接将结果返回,如没获得再从mysql中读取数据返回,并且会将mysql中数据缓存到Redis中。

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

Redis简介

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API

Redis特性:速度快(QPS性能可达10W/s),键值对的数据结构服务器,丰富的功能,简单稳定,持久化,主从复制,高可用和分布式转移, Redis 是​​单线程的​​,客户端API支持语言较多,

Redis支持执行​​Lua​​脚本,可自动实现原子性操作。

Redis底层数据类型

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

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 的手动实现_jedis_04在查找时,从上层指针开始查找,找到对应的区间之后再到下一层去查找。下图演示了查找 22 的过程。

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

与红黑树等平衡树相比,跳跃表具有以下优点:


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


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_06

参考

redis性能测试9W+/服务器14W+

RESP通讯协议