Redis是一款数据跑在内存上的数据库
项目源码:我的github链接,仅供参考哦!
一、认识Redis
我们常用的是MySQL或者SqlServer数据库。接入层(http服务器)直接操作数据库,整体服务效率较低,因为数据库的内容主要在磁盘上。
我们引入Redis的理由也就是希望能够更快的访问到数据,提高效率。就像以下场景一样:
热加载:把热点数据提前加载到内存中,提升整体的访问速度。
缓存模型:把热点数据提前加载到读写速度更快的介质中。
我们在有缓存的前提下,为什么要引入redis或者memcached呢?
可以使用缓存,但是没必要。自己做缓存成本较高。
Tips:Redis和memcached是分布式缓存
1. Redis vs Memcached
- redis支持的数据结构丰富,memcached只支持key-value型的数据结构
- redis支持的社区更友好
- redis效率很高,Redis重启后数据还在,本身自带持久化。
- 是属于IT界的跟风???
2. Redis支持的类型
类型 | 返回值 | Java中的类型 | 标记字节 | 格式说明 |
---|---|---|---|---|
Simple String | 返回OK | String | ‘+’ | ‘+OK\r\n’ |
Error | 通知出错 | Exception | ‘-’ | '-ERR unknown command ‘put’\r\n |
Integer | 整数 | long | ‘:’ | ':0\r\n |
Bulk String | 字节流 | byte[] | ‘$’ | '$5\r\nhello\r\n |
Array | 数组 | List | ‘*’ | '*2\r\n:0\r\n$5\r\nhello\r\n |
Redis和Java语言的类型对应起来就需要"协议解析"
如将‘+OK\r\n’变为'OK'的过程,其他类型也是一样的道理。
二、协议解析
服务器接收到客户端发来的输入流,经过了协议解析,收到的实际上是带标记字节的字符串列表,因此我们可以根据标记字节解析出客户端输入的是哪一类型的Redis指令。
一般来说Redis命令传过来都是数组类型,即Java中的List;以*标记字节开头的输入流。
如“+OK\r\n”,判断为String类型。最后的返回结果应该为OK。
每个字符串的结尾都为\r\n,因此在readLine()方法中,一旦我们读到最后的\r\n,就将\r\n前(除了标记字节)的字符串返回。
所以,如果不是末尾的\r,就要加上转义符(\\r),否则\r返回到行首,就会将前面的字符串覆盖。
这里涉及到状态转换机:
其他字符-> \r -> \n->结束。
如果其他字符遇到\r,则判断\r的下一个字符是否为\n。如果下一个为\n,则结束;如果为\r,则继续判断当前\r的下一个字符是否为\n;如果是其他字符,拼接字符串即可,继续判断其他字符的下一个字符。
ProtocolInputStream 类
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ProtocolInputStream extends FilterInputStream {
public ProtocolInputStream(InputStream in) {
super(in);
}
public String readLine() throws IOException {
boolean needRead = true;
StringBuilder sb = new StringBuilder();
int b = -1;
while (true) {
if (needRead) {
b= in.read();
if (b == -1) {
throw new RuntimeException("不应该读到结尾的");
}
}else {
needRead = true;
}
// +OK\r3\r\n -> 结果:
// 3
//
// +OK\\r3\r\n -> 结果:
// OK\r3
//
//如果末尾\r\n前存在\r,需要加上转义符\。
//否则,\r后面的字符会覆盖前面的字符,产生数据覆盖的情况。
if (b == '\r') {
int c = in.read();
if (c == -1) {
throw new RuntimeException("不应该读到结尾的");
}
if (c == '\n') {
break;
}
if (c == '\r') {
sb.append((char)b);
b = c;
needRead = false;
}else {
sb.append((char)b);
sb.append((char)c);
}
}else {
sb.append((char)b);
}
}
return sb.toString();
}
public long readInteger() throws IOException {
/*
如果读到的第二个字符为-,则为负数,标记isNegative为true。
如果不是-,就拼接字符b
继续读剩下的字符,如果是\r\n,则跳出循环,结束。
如果不是\r,就拼接字符b。继续循环,直到符合跳出条件(到达末尾的\r\n)。
最后将读到的数字字符串转为long型整数。如果是负数,v = -v。
最终返回v即可。
* */
boolean isNegative = false;
StringBuilder sb = new StringBuilder();
int b = in.read();
if (b == -