一.Redis之分布式缓存
Redis最常见的用法就是作为分布式缓存中间件,一般在一个完整的请求链路中作为数据库前面的缓冲地带,防止在高并发情况下数据库性能扛不住而使整个服务崩溃。
数据库性能规格列表:
二.缓存的适用场景-读多写少
缓存本质上就是将数据从速度慢的介质加载到速度快的介质。(典型的用法如硬盘数据加载到内存)
缓存又可以分为如下几类:
- 单机JVM缓存:HashMap,谷歌的Guava包…等
- 分布式缓存中间件:Redis,MemCached…等
- K/V型的Nosql内存数据库
三.Redis底层通讯原理-RESP
Redis是一个c/s模式的TCP Server,像Web开发中,浏览器与服务端交互的协议通常是HTTP协议一样,那么Redis Client和Redis Server之间是如何交互的呢?是否也遵循着某种协议?
Redis 定义了 RESP(Redis Serialization Protocol,Redis 序列化协议)实现客户端与服务端的通信,协议本身很简洁,请求格式如下:
*参数个数 \r\n
$第一个参数长度 \r\n
第一个参数值 \r\n
*$第N个参数长度 \r\n
第N个参数值 \r\n
响应格式如下:
状态回复:第一个字节为“+”
错误回复:第一个字节为“-”
整数回复:第一个字节为“:”
字符串回复:第一个字节为“$”
多条字符串回复:第一个字节为“*”
优点:
- 内容简单,解析快
- 网络传输快
- 网卡压力小
- 单位时间传输的命令次数多
四.模拟Redis客户端与服务端交互
既然知道了通讯协议,那我们自己也可以按照协议约定的格式模拟客户端与服务端的交互。
package com.hong.service;
import java.io.IOException;
import java.net.Socket;
/**
* 模拟Redis客户端与服务端交互的原理
*/
public class RedisClient {
Socket connection;
public RedisClient() throws IOException {
connection = new Socket("127.0.0.1",6379);
}
/**
* 模拟客户端 set key value操作
* @param key
* @param value
*/
public void set(String key,String value) throws IOException {
/**
* 协议约定如下:
* *参数个数 \r\n
* $第一个参数长度 \r\n
* 第一个参数值 \r\n
* $第N个参数长度 \r\n
* 第N个参数值 \r\n
*
* 补充说明:很多高性能的框架都是自定义协议的,为什么不采用通用的HTTP协议呢?
* 效率。HTTP协议本身并不是高效的,它包含请求头 请求行 cookie等一大堆数据,
* 而Redis为了追求高效的读写,采取自定义的简单协议。
*/
// 1.协议组装,注意一定要getBytes(),否则传输中文会报错,因为网络传输时以字节为单位的
StringBuilder sb = new StringBuilder();
sb.append("*3").append("\r\n")
.append("$3").append("\r\n")
.append("SET\r\n")
.append("$").append(key.getBytes().length).append("\r\n")
.append(key).append("\r\n")
.append("$").append(value.getBytes().length).append("\r\n")
.append(value).append("\r\n");
/**
*3
$3
SET
$5
hello
$5
redis
sb的输出将会被记录在aof文件中
*/
System.out.println(sb);
//2.将按协议组装好的数据发送给redis server
connection.getOutputStream().write(sb.toString().getBytes());
//3.读取redis server的响应
byte[] response = new byte[1024];
connection.getInputStream().read(response);
System.out.println(new String(response));//+OK
}
public static void main(String[] args) throws IOException {
// 推导法--用已知推理未知
// 网络交互 -socket-BIO/NIO
/* Socket socket = new Socket("127.0.0.1",6379);
// 要想与redis server通信,就要遵循redis通信协议RESP
socket.getOutputStream().write("hello redis\r\n".getBytes());
byte[] response = new byte[1024];
socket.getInputStream().read(response);
System.out.println(new String(response));*/
RedisClient redisClient = new RedisClient();
redisClient.set("hello","redis");
}
}
打印结果如下:
*3
$3
SET
$5
hello
$5
redis
+OK
管道机制:pipeline
/**
* 管道机制批量操作
*
* @param key
* @param value
* @throws IOException
*/
public void pipelineSet(String key, String value) throws IOException {
StringBuilder command = new StringBuilder();
command.append("*3").append("\r\n")
.append("$3").append("\r\n")
.append("SET\r\n")
.append("$").append(key.getBytes().length).append("\r\n")
.append(key).append("\r\n")
.append("$").append(value.getBytes().length).append("\r\n")
.append(value).append("\r\n");
// 不断发送数据
connection.getOutputStream().write(command.toString().getBytes());
}
/**
* 获取管道执行的结果
*
* @throws IOException
*/
public void pipelineResponse() throws IOException {
byte[] response = new byte[1024];
connection.getInputStream().read(response);
System.out.println("pipelineSet接收到响应:" + new String(response));
}
public static void main(String[] args) throws IOException {
RedisClient redisClient = new RedisClient();
/**
* 对比使用常规的set操作和pipelineSet操作10000条数据耗时
*/
long s0 = System.currentTimeMillis();
/* for (int i=0;i<10000;i++){
redisClient.set("hello_" + i, "redis");
}
System.out.println("set操作耗时:" + (System.currentTimeMillis() - s0) + "ms"); // 970ms
*/
for (int i=0;i<10000;i++){
redisClient.pipelineSet("hello_" + i, "redis");
}
System.out.println("pipelineSet操作耗时:" + (System.currentTimeMillis() - s0) + "ms"); // 280ms
}
检测数据是否set成功:
五.技术的互通性
六.Redis中蕴含的分布式开发技术
- 客户端与服务端之间的RPC技术
- 集群中一致性Hash算法的应用
- 分布式一致性协议Raft协议在哨兵选举中的应用
- 高可用分布式系统的设计思路