2024年最新源码分析mycat1(1),头条面试题

最后:

总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

lastReadTime = TimeUtil.currentTimeMillis();

if (got < 0) {

this.close(“stream closed”);

return;

} else if (got == 0

&& !this.channel.isOpen()) {

this.close(“socket closed”);

return;

} // @2

netInBytes += got;

processor.addNetInBytes(got); //@3

// 循环处理字节信息

int offset = readBufferOffset, length = 0, position = readBuffer.position(); // @4

for (;😉 {

length = getPacketLength(readBuffer, offset); //@5

if (length == -1) { //@6

if (offset != 0) {

this.readBuffer = compactReadBuffer(readBuffer, offset);

} else if (readBuffer != null && !readBuffer.hasRemaining()) {

throw new RuntimeException( "invalid readbuffer capacity ,too little buffer size "

  • readBuffer.capacity());

}

break;

}

if (position >= offset + length && readBuffer != null) { // @7

// handle this package

readBuffer.position(offset);

byte[] data = new byte[length];

readBuffer.get(data, 0, length);

handle(data); // @8

// maybe handle stmt_close

if(isClosed()) { // @9

return ;

}

// offset to next position

offset += length;

// reached end

if (position == offset) { // @10

// if cur buffer is temper none direct byte buffer and not

// received large message in recent 30 seconds

// then change to direct buffer for performance

if (readBuffer != null && !readBuffer.isDirect()

&& lastLargeMessageTime < lastReadTime - 30 * 1000L) { // used temp heap

if (LOGGER.isDebugEnabled()) {

LOGGER.debug(“change to direct con read buffer ,cur temp buf size :” + readBuffer.capacity());

}

recycle(readBuffer);

readBuffer = processor.getBufferPool().allocate(processor.getBufferPool().getConReadBuferChunk());

} else {

if (readBuffer != null) {

readBuffer.clear();

}

}

// no more data ,break

readBufferOffset = 0;

break;

} else { // @11

// try next package parse

readBufferOffset = offset;

if(readBuffer != null) {

readBuffer.position(position);

}

continue;

}

} else { // @12

// not read whole message package ,so check if buffer enough and

// compact readbuffer

if (!readBuffer.hasRemaining()) {

readBuffer = ensureFreeSpaceOfReadBuffer(readBuffer, offset, length);

}

break;

}

}

}

首先,mycat处理读事件的接收缓存区为readBuffer,每个Connection只有一个。接下来,主要的思路是如果接收缓存区中包含一个完整的数据包,则对数据包进行处理,如果没有,确保接收缓存区能够容纳一个包的大小,然后等更多数据到达。整体浏览了该方法的实现,有些异议,本文先按照作者的思路进行分析,然后提出自己的优化建议供大家交流讨论。

代码@1,参数got,就是本次SocketChannel读入的字节数据。

代码@2,如果读取的数据为-1,或为0,并且通道已经关闭了,直接返回。

代码@3,NIOProcessor的 netInBytes 主要是用来统计信息的。

代码@4,offset,上次读到readerBuffer的偏移量。length,当前mysql请求包的数据长度(包括协议头),position,readerBuf当前可写,可读的位置,当前的reader  ByteBuffer处于可写状态。

代码@5,开始循环解析数据包。从当前reader buffer中获取数据包的长度。

protected int getPacketLength(ByteBuffer buffer, int offset) {

int headerSize = getPacketHeaderSize();

if ( isSupportCompress() ) {

headerSize = 7;

} //@51

if (buffer.position() < offset + headerSize) { // @52

return -1;

} else { // @53

int length = buffer.get(offset) & 0xff;

length |= (buffer.get(++offset) & 0xff) << 8;

length |= (buffer.get(++offset) & 0xff) << 16;

return length + headerSize;

}

}

代码@51,首先mysql协议包长度,如果没有启用压缩,协议头部长度固定为4字节,如果启用了压缩,则为7个字节。

代码@52,判断该readerBuffer中数据是否有一个完整的数据包头部,当前position为第一个可写的位置,readerBuffer中第一个有效数据为offset。如果不够一个完整的数据包,则返回-1。

代码@53,读取头部的前3个字节,表示数据包(报文体)的长度,由于使用了小端序列。然后返回加上头部长度,得出数据包的最终长度。

代码@6,如果readerBuffer中没有包含一个完整的数据包,并且offset不为0,则压缩该read buffer,节省空间。

代码@7、@8,如果该readerBuf中包含一个完整的mysql数据包。准备从readerbuf中读取一个完整的数据包,这里没有使用flip方法,而是手动改变position的值。

首先设置position的值为offset,然后在堆里创建一个与待解析数据包相同大小的byte[],然后就数据读入到该数组中。在这里我觉得这样做不妥,既然是用的堆外内存,在处理数的时候,为什么需要将数据从堆外内存拷贝到堆内呢?关于优化点先放到文章的末尾。然后将一个完整的数据包交给NIOHandler进行处理。

代码@9,处理完一个完整的数据包后,再次检查连接是否已经关闭。

代码@10,如果ReaderBuffer读取完毕,进行一次优化,如果使用的Reader ByteBuffer是一个堆内Buffer,则使用直接内存进行替换。

代码@11,尝试继续解析下一个数据包。

代码@12,如果readBuffer中不包含一个完整的数据包,则判断是否需要扩容当前的ByteBuffer,如果需要,则扩容,否则结束本次读任务,等待更多数据到达。

onReadData中,每解析一个数据包,将转发给NIOHandler进行处理(单线程中)。

压缩readBuffer的实现:

private ByteBuffer compactReadBuffer(ByteBuffer buffer, int offset) {

if(buffer == null) {

return null;

}

buffer.limit(buffer.position());

buffer.position(offset);

buffer = buffer.compact();

readBufferOffset = 0;

return buffer;

}

readBuf扩容的实现:

private ByteBuffer ensureFreeSpaceOfReadBuffer(ByteBuffer buffer,

int offset, final int pkgLength) {

// need a large buffer to hold the package

if (pkgLength > maxPacketSize) {

throw new IllegalArgumentException(“Packet size over the limit.”);

} else if (buffer.capacity() < pkgLength) {

ByteBuffer newBuffer = processor.getBufferPool().allocate(pkgLength);

lastLargeMessageTime = TimeUtil.currentTimeMillis();

buffer.position(offset);

newBuffer.put(buffer);

readBuffer = newBuffer;

recycle(buffer);

readBufferOffset = 0;

return newBuffer;

} else {

if (offset != 0) {

// compact bytebuffer only

return compactReadBuffer(buffer, offset);

} else {

throw new RuntimeException(" not enough space");

}

}

}

至此,mycat读事件的解决就分析到这里了,提出如下4个性能优化点:

性能优化点:

1、首先 该类的 readBufferOffset 属性其实完成可以不需要,依然能够合理的解析出数据包。readBufferOffset是voliate类型的字段,有一定的性能损坏。

2、第二个重点,在解析数据包的时候:

if (position >= offset + length && readBuffer != null) {

// handle this package

readBuffer.position(offset);

byte[] data = new byte[length];

readBuffer.get(data, 0, length);

handle(data);

// 其他代码省略

readBuffer是 堆外内存,现在在处理数据的时候,又从堆外内存,拷贝一次到堆内存(byte[]),这里多了一本从堆内存拷贝到堆内存的步骤,抵消了直接内存的优势;是否可 以实现一个从ReadBuffer slice(int startIndex, int posistion),使用readerBuffer的内存,但用SliceByteBuffer进行后面的数据包解析等等。

这样的理解是否合理。

3、当Reader Buffer中没有一个足够的mysql数据包时,此时的扩容条件,可以进一步优化为

if (!readBuffer.hasRemaining()   ||  (readBuffer.limit - offset + 1  ) < length  ) {

readBuffer = ensureFreeSpaceOfReadBuffer(readBuffer, offset, length);

// readBuffer.limit - offset + 1 表示readBuffer可以写入的总长度,如果可以写入的总长度小于数据包的长度,则需要扩容

}

4、readBuffer只有扩容,没有容量压缩,这里不同于上面compactReadBuffer的实现。举个例子,默认readBuffer的容量为4K,突然一个数据包,用了16M,,但以后每个包的容量又只需要4K,但该连接的readBuffer始终占有16M的空间,导致内存空间的浪费。

2、mycat前端写事件处理

==============

入口:

AbstractConnection的doNextWriteCheck()

public void doNextWriteCheck() throws IOException {

this.socketWR.doNextWriteCheck();

}

在讲解写事件之前,我们不妨再看看NIOSocketWR的相关属性,我们从前文已经知道NIOSocketWR与AbstractConnection一一对应。NIOSocketWR是负责Connection网络的读写。

private SelectionKey processKey;

private static final int OP_NOT_READ = ~SelectionKey.OP_READ;

private static final int OP_NOT_WRITE = ~SelectionKey.OP_WRITE;

private final AbstractConnection con;

private final SocketChannel channel;

private final AtomicBoolean writing = new AtomicBoolean(false);    //@1通道是否正在处理写事件,默认为false。

接下来重点关注doNextWrite

public void doNextWriteCheck() {

if (!writing.compareAndSet(false, true)) { // @1

return;

}

try {

boolean noMoreData = write0(); //@2

writing.set(false);

if (noMoreData && con.writeQueue.isEmpty()) { //@3

if ((processKey.isValid() && (processKey.interestOps() & SelectionKey.OP_WRITE) != 0)) {

disableWrite();

}

} else { // @4

if ((processKey.isValid() && (processKey.interestOps() & SelectionKey.OP_WRITE) == 0)) {

enableWrite(false);

}

}

} catch (IOException e) {

if (AbstractConnection.LOGGER.isDebugEnabled()) {

AbstractConnection.LOGGER.debug(“caught err:”, e);

}

con.close(“err:” + e);

}

}

代码@1,如果有写操作正在进行,则直接退出。

代码@2,具体的通道写,稍后查看。

代码@3,如果没有数据待写,并且写任务队列为空,并且关注了写事件,则取消写事件。noMoreData为true,表示没有更多数据。

代码@4,如果有更多数据待写,并且没有关注写事件,重新关注写事件。

接下来重点关注写操作的具体执行逻辑:

private boolean write0() throws IOException {

int written = 0;

ByteBuffer buffer = con.writeBuffer;

if (buffer != null) { //@1

while (buffer.hasRemaining()) {

written = channel.write(buffer);

基础学习:

前端最基础的就是 HTML , CSS 和 JavaScript 。

网页设计:HTML和CSS基础知识的学习

HTML是网页内容的载体。内容就是网页制作者放在页面上想要让用户浏览的信息,可以包含文字、图片、视频等。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

CSS样式是表现。就像网页的外衣。比如,标题字体、颜色变化,或为标题加入背景图片、边框等。所有这些用来改变内容外观的东西称之为表现。

动态交互:JavaScript基础的学习

JavaScript是用来实现网页上的特效效果。如:鼠标滑过弹出下拉菜单。或鼠标滑过表格的背景颜色改变。还有焦点新闻(新闻图片)的轮换。可以这么理解,有动画的,有交互的一般都是用JavaScript来实现的。

hasRemaining()) {

written = channel.write(buffer);

基础学习:

前端最基础的就是 HTML , CSS 和 JavaScript 。

网页设计:HTML和CSS基础知识的学习

HTML是网页内容的载体。内容就是网页制作者放在页面上想要让用户浏览的信息,可以包含文字、图片、视频等。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

[外链图片转存中…(img-93KgDyVW-1715754927142)]

CSS样式是表现。就像网页的外衣。比如,标题字体、颜色变化,或为标题加入背景图片、边框等。所有这些用来改变内容外观的东西称之为表现。

[外链图片转存中…(img-KdeGd8Sb-1715754927142)]

动态交互:JavaScript基础的学习

JavaScript是用来实现网页上的特效效果。如:鼠标滑过弹出下拉菜单。或鼠标滑过表格的背景颜色改变。还有焦点新闻(新闻图片)的轮换。可以这么理解,有动画的,有交互的一般都是用JavaScript来实现的。

[外链图片转存中…(img-mPN3oIod-1715754927143)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值