实现一个 Java 版的 Redis (1)----百行代码解析Redis 协议.
最近看了 Redis 的代码,感觉还是挺简单的.有冲动想用其它语言实现(抄)一个.原来想用 Python 实现来着.后来想想试试 Netty.原因有二
第一:Java 的NIO 和Netty 的 EventLoop 配合起来和 Redis 的网络模型很接近.都是 Ractor 模型.甚至 Redis的模型更简单--只有一个 EventLoop 线程.写(抄)起来更方便
第二:Netty 架构挺不错.借这个机会学习一下.
如果我们从一个很抽象(简单)的角度看 Redis Server.就是一个监听在6379的程序, 本质上是一个处理单线线请求的 Hashtable. 而 Redis 的协议也是非常非常的简单.比 http 协议可简单多了.
以下是这个协议的一般形式:
*<参数数量> CR LF $<参数 1 的字节数量> CR LF <参数 1 的数据> CR LF ... $<参数 N 的字节数量> CR LF <参数 N 的数据> CR LF
这基本就是一个很简单的有限状态机.
所以我给我们的命令解析器设置3个状态.
public enum State {NUMBER_OF_ARGS,NUMBER_BYTE_OF_ARGS,ARGS_DATA }
我们将初始状态设置NUMBER_OF_ARGS 也就是开始那个绿色的状态.当有数据到达时.我们不停的判断程序的状态.是哪个状态,我们做啥.
while(true){switch (state()){case NUMBER_OF_ARGS://从当前数据中读取参数个数break;case NUMBER_BYTE_OF_ARGS://从数据中读取参数长度break;case ARGS_DATA://按参数长度读取参数//判断参数个数.如果到了最后一个.则跳出,否则状态转回NUMBER_BYTE_OF_ARGSbreak;} }
下面我们按着我们上面思路实现一下.
package me.yunanw.redisinjava;import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.ReplayingDecoder;import java.util.List;/*** Created by yunanw on 2016/10/15.*/ public class CommandDecoder extends ReplayingDecoder<CommandDecoder.State> {public enum State {NUMBER_OF_ARGS,NUMBER_BYTE_OF_ARGS,ARGS_DATA}static final char CR = '\r';static final char LF = '\n';public CommandDecoder(){state(State.NUMBER_OF_ARGS);}protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {RedisFrame frame = doDecode(channelHandlerContext,byteBuf,list);if (frame != null){list.add(frame);}}private RedisFrame doDecode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {RedisFrame frame = null;int currentArgsLen = 0;int argsCount = 0;while(true){switch (state()){case NUMBER_OF_ARGS:if (byteBuf.readByte() != '*'){throw new DecoderException("can not found *");}argsCount = parseRedisNumber(byteBuf);frame = new RedisFrame(argsCount);checkpoint(State.NUMBER_BYTE_OF_ARGS);break;case NUMBER_BYTE_OF_ARGS:if (byteBuf.readByte() != '$'){throw new DecoderException("can not found $");}currentArgsLen = parseRedisNumber(byteBuf);checkpoint(State.ARGS_DATA);;break;case ARGS_DATA:frame.AppendArgs(byteBuf.readBytes(currentArgsLen).array());if (byteBuf.readByte() != CR || byteBuf.readByte() != LF)throw new DecoderException("can not found CR OR LF");if ((--argsCount) <=0)return frame;else {checkpoint(State.NUMBER_BYTE_OF_ARGS);}break;default:throw new DecoderException("");}}}private int parseRedisNumber(ByteBuf byteBuf) {byte readByte = byteBuf.readByte();boolean negative = readByte == '-';if (negative) {readByte = byteBuf.readByte();}int result = 0;do {int digit = readByte - '0';if (digit >= 0 && digit < 10) {result = (result * 10) + digit;} else {throw new DecoderException("Invalid character in integer");}} while ((readByte = byteBuf.readByte()) != CR);if ((readByte = byteBuf.readByte()) != LF){throw new DecoderException("can not found LF");}return (negative? -result:result);}}
写到这里有一个小问题,如果你上面代码看懂了,你就会发现一个小问题.如果由于网络原因,有时数据可以并没有接收完全.而我们的代码完全没有做这方面的考虑? 而 Checkpoint 这是又什么鬼?
第一个问题:
事实上我们有考虑这个问题.所以我们继承了一个相对比较特别Decoder--ReplayingDecoder.我们看一下ReplayingDecoder的 CallDecode 方法.(这个名字起的非常的直白.你一定明白他是干啥的)
try {decode(ctx, replayable, out);//省略 } catch (Signal replay) {replay.expect(REPLAY);//省略// Return to the checkpoint (or oldPosition) and retry.int checkpoint = this.checkpoint;if (checkpoint >= 0) {in.readerIndex(checkpoint);} else {// Called by cleanup() - no need to maintain the readerIndex// anymore because the buffer has been released already.}break; }
Signal replay 是 Netty 中定义的一个错误.当我们读取错误时,Netty 会再等到下次有数据到达时,再试一次Decode 方法.看看能再解析成功.所以我们就可以假设置我们要的数据都已经读取了.
但是要注意: replaydecoder 的 decode 方法会被反复调用..所以我们的代码中要做好这样的准备.
二: CheckPoint 就是为了防止如果每次反复调用 Decode 时从头执行,而设置的一个状态.让我们这个 decode 方法有状态.
好了.现在我们创建监部分的代码.这都是套数,直接抄下来就行了
ServerBootstrap bootstrap = new ServerBootstrap(); final DefaultEventExecutorGroup group = new DefaultEventExecutorGroup(1); try {bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100).localAddress(port).childOption(ChannelOption.TCP_NODELAY, true).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();p.addLast(new CommandDecoder());p.addLast(new RedisServerHandler());}});// Start the server.ChannelFuture f = bootstrap.bind().sync();// Wait until the server socket is closed.f.channel().closeFuture().sync(); } finally {// Shut down all event loops to terminate all threads.group.shutdownGracefully(); }
我们把 Redis 的协议解析为RedisFrame 类
package me.yunanw.redisinjava;import java.util.ArrayList; import java.util.List;/*** Created by yunanw on 2016/10/17.*/ public class RedisFrame {private int argsCount = 0;List<String> ArgsData = null;public RedisFrame(int argsCount){this.argsCount = argsCount;this.ArgsData = new ArrayList<String>(argsCount);}public void AppendArgs(byte[] args){this.ArgsData.add(new String(args));}public int getCommandCount(){return ArgsData.size();}public String GetFristCommand(){if (ArgsData.size() > 0){return ArgsData.get(0);}return null;}public String GetCommand(int index){if (ArgsData.size() > index){return ArgsData.get(index);}return null;} }
好了.这时你打开 Redis-cli 试试是不是可以连上我们的 "假Redis" Server.有意的是---你打开 Redis-cli.他会自动发一个 "Command" 命令.而你不管回复什么,它都认为连上了.
转载于:https://my.oschina.net/wynwyy/blog/761230
实现一个 Java 版的 Redis (1)----百行代码解析Redis 协议.相关推荐
- 百行代码打造一个DI容器(支持瞬时生命周期、单利生命周期、构造函数自动注入、属性自动注入、字段自动注入)...
DI注入在.Net平台是非常流行的, 很多项目都用到了,很多开发人员或多或少也用到DI容器了,感觉DI容器很神奇很厉害.本文将通过百行代码展示DI容器的内部核心代码(包括组件的瞬时生命周期.单利生命周 ...
- 基于Ganos百行代码实现亿级矢量空间数据在线可视化
简介: 本文介绍如何使用RDS PG或PolarDB(兼容PG版或Oracle版)的Ganos时空引擎提供的数据库快显技术,仅用百行代码实现亿级海量几何空间数据的在线快速显示和流畅地图交互,且无需关注 ...
- 鱼佬:百行代码入手数据挖掘赛!
↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:鱼佬,武汉大学,Datawhale成员 本实践以科大讯飞xData ...
- 猜猜乐游戏php源码,C/C++百行代码实现热门游戏消消乐功能的示例代码
游戏设计 首先我们需要使用第三方框架,这里我使用的是sfml,不会使用sfml在我的上几篇文章当中-扫雷(上)有详细的开发环境搭建介绍 首先准备图片资源 一张背景图片,一张宝石图片 窗口初始化加载图片 ...
- Kaggle—So Easy!百行代码实现排名Top 5%的图像分类比赛
北京 上海巡回站 | NVIDIA DLI深度学习培训 2018年1月26/1月12日 NVIDIA 深度学习学院 带你快速进入火热的DL领域 阅读全文 ...
- python图像分类代码_Kaggle—So Easy!百行代码实现排名Top 5%的图像分类比赛
Kaggle-So Easy!百行代码实现排名Top 5%的图像分类比赛 作者:七月在线彭老师 说明:本文最初由彭老师授权翟惠良发布在公众号"七月在线实验室"上,现再由July重新 ...
- Python百日百行代码挑战-day8,day9,day10,游戏实战系列-五子棋
Python百日百行代码挑战-day8,day9,day10,游戏实战系列-五子棋 写在前面 需要用到的工具包和参考 游戏设定 初始化 切换下棋方 判断五子连珠(核心) 鼠标点击流程事件 成品展示 打 ...
- python写百行代码可运行_56 岁潘石屹学俩月 Python ,写下百行代码
原标题:56 岁潘石屹学俩月 Python ,写下百行代码 By 超神经 内容导读:跨界王潘石屹在近期迷上了编程,不仅高调宣布学习 Python,拜老师,还隔三差五晒出自己的「编程课作业」,和网友进行 ...
- php消消乐代码,C/C++百行代码实现热门游戏消消乐功能的示例代码
游戏设计 首先我们需要使用第三方框架,这里我使用的是sfml,不会使用sfml在我的上几篇文章当中-扫雷(上)有详细的开发环境搭建介绍 首先准备图片资源 一张背景图片,一张宝石图片 窗口初始化加载图片 ...
- js 监听 安卓事件_百行代码实现js事件监听实现跨页面数据传输
百行代码实现js事件监听实现跨页面数据传输 使用场景 类似消息队列的使用场景,支持同页面和跨页面通信,发送消息和接收消息 技术原理 跨页面通信: 基于事件监听,通过监听 storage事件监听回调机制 ...
最新文章
- centos下pg_dump的服务器版本不匹配问题
- html中open打开新网页,JavaScript-打开新窗口(window.open)
- 创建cordova项目
- 磁头号和起始扇区的计算方法------软盘结构
- P2619 [国家集训队]Tree I(WQS二分/带权二分/最小生成树)
- jQuery框架学习第四天:使用jQuery操作元素的属性与样式
- gitlab mr wip 怎么弄成_基于GitLab的工作流程设计
- 库克看下!华为MatePad 11月25日发布:剑指苹果
- java二维码小试牛刀
- 【数据库/数据挖掘/内容检索】 2019年-中国计算机学会推荐国际学术会议和期刊目录(五)
- matlab将日线编成周线,通达信公式日线10周线调用,请教通达信日线数据上引用周线数据需要用到的函数和方法...
- 机器视觉培训教程-镜头的基本知识
- 关于LCR表和万用表测电容容值不一样的怪事
- 【视频】离职创业感悟
- HDOJ 2010 水仙花数
- PyQtGraph库的部分踩坑记录
- WIN7中任务栏,菜单栏和工具栏的基本设置
- d3dcompiler_47.dll缺失怎么修复?D3DCompiler_47.dll修复工具下载
- #3 GPA计算(python)
- 图灵奖今日出炉,“龙书” 作者、编程语言大佬 Alfred Aho 和 Jeffrey Ullman 获奖
热门文章
- firefox浏览器和IE
- opencv2+ glob循环读入图片,将文件夹中的图片统一格式
- python监控进程状态,python psutil监控进程实例
- idea创建springcloud项目_新手向,十分钟快速创建 Spring Cloud 项目
- mysql 序列 语句_MySQL 序列使用
- Android——ImageView报错:不是可绘制对象(颜色或路径)
- SQL Sever — 查询前10%的数据、查询并参与计算显示出数据、查询前10行数据、多重升序降序查询、去掉重复值、替换列名显示
- JavaWeb — 获取复选框的值时,要注意不是得到显示的值,而是得到value值
- 织梦自定义表单限制IP24小时只能提交一次方法
- vue.js 源代码学习笔记 ----- instance inject