总结
我在成长过程中也是一路摸爬滚打,没有任何人的指点,所以走的很艰难。例如在大三的时候,如果有个学长可以阶段性的指点一二,如果有已经工作的师兄可以告诉我工作上需要什么,我应该前面的三年可以缩短一半;后来去面试bat,失败了有5、6次,每次也不知道具体是什么原因,都是靠面试回忆去猜测可能是哪方面的问题,回来学习和完善,当你真正去招人的时候,你就会知道面试记录是多么重要,面试官可以从面试记录里看到你的成长,总是去面试,总是没有成长,就会被定义为缺乏潜力。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
}
} 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);
if (written > 0) {
con.netOutBytes += written;
con.processor.addNetOutBytes(written);
con.lastWriteTime = TimeUtil.currentTimeMillis();
} else {
break;
}
}
if (buffer.hasRemaining()) {
con.writeAttempts++;
return false;
} else {
con.writeBuffer = null;
con.recycle(buffer);
}
}
while ((buffer = con.writeQueue.poll()) != null) { //@2
if (buffer.limit() == 0) {
con.recycle(buffer);
con.close(“quit send”);
return true;
}
buffer.flip();
try {
while (buffer.hasRemaining()) {
written = channel.write(buffer);// java.io.IOException:
// Connection reset by peer
if (written > 0) {
con.lastWriteTime = TimeUtil.currentTimeMillis();
con.netOutBytes += written;
con.processor.addNetOutBytes(written);
con.lastWriteTime = TimeUtil.currentTimeMillis();
} else {
break;
}
}
} catch (IOException e) {
con.recycle(buffer);
throw e;
}
if (buffer.hasRemaining()) {
con.writeBuffer = buffer;
con.writeAttempts++;
return false;
} else {
con.recycle(buffer);
}
}
return true;
}
经典的写处理操作,while( buffer.hasRemaining()),在循环中,调用通道的write方法,然后判断写入的字节数,如果大于0,则继续写,否则跳出,然后再次检测待写缓存区是否有剩余空间,如果没有,则回收该ByteBuffer,如果没有,待下次继续写入(直接结束本次写入操作)。如果成功将AbstractConnection的writeBuffer写入后,继续处理写任务队列中的ByteBuffer,如果全部写完,则返回true,表示没有更多数据,否则返回false。写事件的处理就分析到到,既然有任务缓存区,我们肯定也要关注一下,缓存区中的待写ByteBuffer是从哪来的。关注一下AbstractConnection:
protected volatile ByteBuffer writeBuffer;
protected final ConcurrentLinkedQueue writeQueue = new ConcurrentLinkedQueue();//写任务队列
private final void writeNotSend(ByteBuffer buffer) { // 放入写任务队列,但不立即写出
if (isSupportCompress()) {
ByteBuffer newBuffer = CompressUtil.compressMysqlPacket(buffer, this, compressUnfinishedDataQueue);
writeQueue.offer(newBuffer);
} else {
writeQueue.offer(buffer);
}
}
Vue 面试题
1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?
如果你觉得对你有帮助,可以戳这里获取:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
offer(buffer);
}
}
Vue 面试题
1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?
[外链图片转存中…(img-el4W9pgJ-1714999815808)]