High-performance cache service - HANBO (2)

HANBO autopsy

hanbo is a high-performance, high-availability, low-latency in-memory database.

Protocol overview

Protocol description

Same as redis protocol, parsed by \r\n.

decoding

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));
        }
    }

}

encoding, 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);
    }
  }

System overview

data flow

Core class diagram

command module storage module

command entry

    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);
        }
    }

request processing

Multi-db implementation, similar to threadlocal, but here is the session level (channel), not the thread

    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;
    }

Transaction control, based on session control, if there is a multi command, the subsequent commands are queued, waiting for the execution/cancellation of the exec/discard command

    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;
    }

data storage

Map files to off-heap memory

    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 based on off-heap memory

    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, corresponding to write and 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;
        }
    }

High performance collection library - fastUtil

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

Automatic expansion

    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);
        }
    }

run

java -jar hanboServer.jar

single node

configure

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

master-slave configuration

Just add the following configuration items

Main placement

replication.mode=master

from config

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

project address:

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

https://gitee.com/lmx_007/jredis

Welcome more partners to suggest and improve it

No public

No public

WeChat

WeChat

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324125332&siteId=291194637