基础导论
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使用场景 :
- 缓存数据库:
- 排行榜:
- 计数器应用:
- 社交网络
- 淘宝购物车:
- 消息队列:
- 其他场景等:自我联想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>
- jedis测试
public static final void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println(jedis.set("name", "sowhat"));
}
- 看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通讯协议