高性能缓存服务—HANBO(2)

HANBO剖析

hanbo是一个高性能、高可用、低延迟的内存数据库。

协议概览

协议描述

同redis协议,通过\r\n解析。

解码

public class RedisCommandDecoder extends ReplayingDecoder<Void> {
    private byte[][] bytes;

    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (bytes != null) {
            int numArgs = bytes.length;
            for (int i = 0; i < numArgs; i++) {
                if (in.readByte() == '$') {
                    long l = readLong(in);
                    if (l > Integer.MAX_VALUE) {
                        throw new IllegalArgumentException("Java only supports arrays up to " + Integer.MAX_VALUE + " in size");
                    }
                    int size = (int) l;
                    bytes[i] = new byte[size];
                    in.readBytes(bytes[i]);
                    if (in.bytesBefore((byte) '\r') != 0) {
                        throw new Exception("Argument doesn't end in CRLF");
                    }
                    in.skipBytes(2);
                    checkpoint();
                } else {
                    throw new IOException("Unexpected character");
                }
            }
            try {
                out.add(new Command(bytes));
            } finally {
                bytes = null;
            }
        } else if (in.readByte() == '*') {
            long l = readLong(in);
            if (l > Integer.MAX_VALUE) {
                throw new IllegalArgumentException("Java only supports arrays up to " + Integer.MAX_VALUE + " in size");
            }
            int numArgs = (int) l;
            if (numArgs < 0) {
                throw new Exception("Invalid size: " + numArgs);
            }
            bytes = new byte[numArgs][];
            checkpoint();
            decode(ctx, in, out);
        } else {
            // Go backwards one
            in.readerIndex(in.readerIndex() - 1);
            // Read command -- can't be interrupted
            byte[][] b = new byte[1][];
            b[0] = in.readBytes(in.bytesBefore((byte) '\r')).array();
            in.skipBytes(2);
            out.add(new Command(b, true));
        }
    }

}

编码,BulkReply

  public void write(ByteBuf os) throws IOException {
    os.writeByte(MARKER);
    os.writeBytes(numToBytes(capacity, true));
    if (capacity > 0) {
      os.writeBytes(bytes);
      os.writeBytes(CRLF);
    }
  }

系统概览

数据流

核心类图

指令模块存储模块

指令入口

    protected void channelRead0(ChannelHandlerContext ctx, Command msg) throws Exception {
        Reply reply = invoker.handlerEvent(ctx, msg);
        if (reply == QUIT) {
            ctx.close();
        } else {
            if (msg.isInline()) {
                if (reply == null) {
                    reply = new InlineReply(null);
                } else {
                    reply = new InlineReply(reply.data());
                }
            }
            if (reply == null) {
                reply = NYI_REPLY;
            }
            if (reply instanceof MultiBulkReply) {
                MultiBulkReply multiBulkReply = (MultiBulkReply) reply;
                if (multiBulkReply == MultiBulkReply.BLOCKING_QUEUE) {
                    return;
                }
            }
            ctx.write(reply);
        }
    }

请求处理

多db实现,类似于threadlocal,只不过这里是会话级别(channel),不是线程

    public StatusReply select(byte[] index0) throws RedisException {
        DatabaseRouter.RedisDB store = databaseRouter.select(Integer.parseInt(new String(index0)));
        Attribute attribute = channelHandlerContext.channel().attr(session);
        if (null == store) {
            attribute.set(null);
            throw new RedisException();
        }
        attribute.set(store);
        return StatusReply.OK;
    }

事务控制,基于会话控制,如果有multi指令,则后续指令入队列,等待exec/discard指令执行/取消

    public boolean hasOpenTx() {
        return getTxAttribute().get() != null;
    }

    public Attribute getTxAttribute() {
        return channelHandlerContext.channel().attr(transaction);
    }


    public Reply handlerTxOp(Command command) throws RedisException {
        if (new String(command.getName()).equals("exec")) {
            return exec();
        }
        if (new String(command.getName()).equals("discard")) {
            return discard();
        }
        Queue queue = (Queue) getTxAttribute().get();
        //declare internal event for command
        command.setEventType(1);
        queue.add(command);
        return StatusReply.QUEUED;
    }

数据存储

映射文件到堆外内存

    public BaseMedia(int db, String fileName, int memSize) throws Exception {
        if (db == 0)
            f = new File(defaultFile.getAbsolutePath() + File.separator + fileName);
        else
            f = new File(defaultFile.getParentFile().getAbsolutePath() + File.separator + db + File.separator + fileName);
        if (!f.exists())
            f.createNewFile();
        fileChannel = new RandomAccessFile(f, "rw").getChannel();
        long fileSize = Math.max(memSize * size, fileChannel.size());
        buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize == 0 ? memSize * size : fileSize);
    }

基于堆外内存的CRUD

    public DataHelper add(ByteBuffer b) throws Exception {
        int pos;
        if ((pos = buffer.getInt()) != 0)
            buffer.position(pos);
        else
            buffer.position(4);
        resize(pos);
        buffer.put(b);
        buffer.putChar(NORMAL);
        DataHelper dh = new DataHelper();
        dh.pos = pos == 0 ? 4 + 4 : pos + 4;
        int curPos = buffer.position();
        buffer.position(0);
        buffer.putInt(curPos);//head 4 byte in last postion
        buffer.rewind();
        return dh;
    }

    public byte[] get(DataHelper dh) {
        buffer.position(dh.pos);
        byte[] data = new byte[dh.length];
        buffer.get(data);
        if (buffer.getChar() == DELETE)
            return null;
        buffer.rewind();
        return data;
    }

    public void remove(DataHelper dh) {
        buffer.position(dh.pos + dh.length);
        buffer.putChar(DELETE);
        buffer.rewind();
    }

    public DataHelper update(DataHelper dh, byte[] newBuf) {
        buffer.position(dh.pos - 4);
        int length = newBuf.length;
        if (length > maxUnit)
            throw new RuntimeException("exceed max storage limited exception");
        else {
            buffer.putInt(length);
            buffer.put(newBuf);
            dh.length = length;
            buffer.rewind();
            return dh;
        }
    }

set/get,对应write和read

    public boolean write(String key, String value) {
        try {
            if (super.write(key, value)) {
                DataHelper dataHelper = (DataHelper) indexHelper.type(key);
                if (dataHelper != null) {
                    dataHelper = dataMedia.update(dataHelper, value.getBytes(Charsets.UTF_8));
                    indexHelper.updateIndex(dataHelper);
                    return true;
                } else {
                    ByteBuffer b = ByteBuffer.allocateDirect(128);
                    int length = value.getBytes().length;
                    b.putInt(length);
                    b.put(value.getBytes(Charsets.UTF_8));
                    b.flip();
                    DataHelper dh = dataMedia.add(b);
                    dh.setKey(key);
                    dh.setLength(length);
                    indexHelper.add(dh);
                    return true;
                }
            }
        } catch (Exception e) {
            log.error("write data error", e);
        }
        return false;
    }

    public byte[] read(String key) throws RedisException {
        try {
            if (!checkKeyType(key)) {
                throw new RedisException("Operation against a key holding the wrong kind of value");
            }
            if (super.isExpire(key)) {
                return null;
            }
            long start = System.currentTimeMillis();
            DataHelper idx = (DataHelper) indexHelper.type(key);
            if (idx == null) {
                return null;
            }
            byte[] data = dataMedia.get(idx);
            String resp = new String(data, Charsets.UTF_8);
            log.debug("key={},value={} cost={}ms", key, resp, (System.currentTimeMillis() - start));
            return data;
        } catch (Exception e) {
            log.error("read data error", e);
            throw e;
        }
    }

高性能集合库-fastUtil

protected Map<String, Object> keyMap = new Object2ObjectAVLTreeMap<>();

自动扩容

    public void reAllocate() throws Exception {
        System.err.println("reAllocate file begin");
        String dir = f.getParentFile().getParentFile().getAbsolutePath();

        File newFile = new File(dir + File.separator + accessIndex + File.separator + f.getName() + "_tmp");
        com.google.common.io.Files.copy(f, newFile);

        File newFile_ = new File(f.getAbsolutePath());
        clean();
        com.google.common.io.Files.copy(newFile, newFile_);
        newFile.delete();

        fileChannel = new RandomAccessFile(newFile_, "rw").getChannel();
        buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, buffer.capacity() * 2);
        System.err.println("reAllocate file end");
    }

    public void resize(int pos) throws Exception {
        //reallocate buffer
        if (buffer.remaining() - pos < 4) {
            reAllocate();
            if ((pos = buffer.getInt()) != 0)
                buffer.position(pos);
            else
                buffer.position(4);
        }
    }

运行

java -jar hanboServer.jar

单节点

配置

#服务地址
server.host=127.0.0.1
#服务端口
server.port=16379
#内存大小
memorySize=32
#db数量
dbSize=8
logging.level.root=error

主从配置

只需要加入如下配置项

主配置

replication.mode=master

从配置

replication.mode=slave
slaver.of=127.0.0.1:16379

项目地址:

https://github.com/3kuai/jredis

https://gitee.com/lmx_007/jredis

欢迎更多伙伴们一起来建议和完善它

公众号

公众号

微信

微信

转载于:https://my.oschina.net/u/4177567/blog/3082682

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值