docs: update documents

pull/132/head
yanglbme 2 years ago
parent 2d4ee6f2a0
commit 4e8642240c

@ -1,25 +0,0 @@
name: Prettier
on:
pull_request:
push:
branches:
- main
jobs:
prettier:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
- name: Prettify code
uses: creyD/prettier_action@v3.3
with:
prettier_options: --write **/*.{md}
commit_message: "style: prettify code or document"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -279,10 +279,10 @@
- [RocketMQ 生产者启动流程](docs/rocketmq/rocketmq-producer-start.md) - [RocketMQ 生产者启动流程](docs/rocketmq/rocketmq-producer-start.md)
- [RocketMQ 消息发送流程](docs/rocketmq/rocketmq-send-message.md) - [RocketMQ 消息发送流程](docs/rocketmq/rocketmq-send-message.md)
- [RocketMQ 消息发送存储流程](docs/rocketmq/rocketmq-send-store.md) - [RocketMQ 消息发送存储流程](docs/rocketmq/rocketmq-send-store.md)
- [RocketMQ MappedFile内存映射文件详解](docs/rocketmq/rocketmq-mappedfile-detail.md) - [RocketMQ MappedFile 内存映射文件详解](docs/rocketmq/rocketmq-mappedfile-detail.md)
- [RocketMQ ConsumeQueue详解](docs/rocketmq/rocketmq-consumequeue.md) - [RocketMQ ConsumeQueue 详解](docs/rocketmq/rocketmq-consumequeue.md)
- [RocketMQ CommitLog详解](docs/rocketmq/rocketmq-commitlog.md) - [RocketMQ CommitLog 详解](docs/rocketmq/rocketmq-commitlog.md)
- [RocketMQ IndexFile详解](docs/rocketmq/rocketmq-indexfile.md) - [RocketMQ IndexFile 详解](docs/rocketmq/rocketmq-indexfile.md)
- [RocketMQ 消费者启动流程](docs/rocketmq/rocketmq-consumer-start.md) - [RocketMQ 消费者启动流程](docs/rocketmq/rocketmq-consumer-start.md)
- [RocketMQ 消息拉取流程](docs/rocketmq/rocketmq-pullmessage.md) - [RocketMQ 消息拉取流程](docs/rocketmq/rocketmq-pullmessage.md)
- [RocketMQ Broker 处理拉取消息请求流程](docs/rocketmq/rocketmq-pullmessage-processor.md) - [RocketMQ Broker 处理拉取消息请求流程](docs/rocketmq/rocketmq-pullmessage-processor.md)
@ -387,6 +387,10 @@ GitHub 技术社区 [Doocs](https://github.com/doocs),致力于打造一个内
</tr> </tr>
</table> </table>
<<<<<<< HEAD
关注公众号「**Doocs**」,回复 **PDF**,即可获取 [互联网 Java 工程师进阶知识完全扫盲](https://github.com/doocs/advanced-java) 项目离线 PDF 文档283 页精华),学习更加方便! 关注公众号「**Doocs**」,回复 **PDF**,即可获取 [互联网 Java 工程师进阶知识完全扫盲](https://github.com/doocs/advanced-java) 项目离线 PDF 文档283 页精华),学习更加方便!
=======
关注「**Doocs**」公众号,回复 **PDF**,即可获取 [互联网 Java 工程师进阶知识完全扫盲](https://github.com/doocs/advanced-java) 项目离线 PDF 文档283 页精华),学习更加方便!
>>>>>>> d309ac0 (docs: update documents)
![](./images/pdf.png) ![](./images/pdf.png)

@ -2,7 +2,7 @@
## Netty 中的 ByteBuf 为什么会发生内存泄漏 ## Netty 中的 ByteBuf 为什么会发生内存泄漏
在 Netty 中ByetBuf 并不是只采用可达性分析来对 ByteBuf 底层的 `byte[]` 数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。 在 Netty 中ByetBuf 并不是只采用可达性分析来对 ByteBuf 底层的 `byte[]` 数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。
在每个 ByteBuf 中都维护着一个 refCnt 用来对 ByteBuf 的被引用数进行记录,当 ByteBuf 的 `retain()` 方法被调用时,将会增加 refCnt 的计数,而其 `release()` 方法被调用时将会减少其被引用数计数。 在每个 ByteBuf 中都维护着一个 refCnt 用来对 ByteBuf 的被引用数进行记录,当 ByteBuf 的 `retain()` 方法被调用时,将会增加 refCnt 的计数,而其 `release()` 方法被调用时将会减少其被引用数计数。
@ -24,7 +24,7 @@ private boolean release0(int decrement) {
} }
``` ```
当调用了 ByteBuf 的 `release()` 方法的时候,最后在上方的 `release0()` 方法中将会为 ByteBuf 的引用计数减一,当引用计数归于 0 的时候,将会调用 `deallocate()` 方法对其对应的底层存储数组进行释放(在池化的 ByteBuf 中,在 `deallocate()` 方法里会把该 ByteBuf 的 `byte[]` 回收到底层内存池中,以确保 `byte[]` 可以重复利用)。 当调用了 ByteBuf 的 `release()` 方法的时候,最后在上方的 `release0()` 方法中将会为 ByteBuf 的引用计数减一,当引用计数归于 0 的时候,将会调用 `deallocate()` 方法对其对应的底层存储数组进行释放(在池化的 ByteBuf 中,在 `deallocate()` 方法里会把该 ByteBuf 的 `byte[]` 回收到底层内存池中,以确保 `byte[]` 可以重复利用)。
由于 Netty 中的 ByteBuf 并不是随着申请之后会马上使其引用计数归 0 而进行释放,往往在这两个操作之间还有许多操作,如果在这其中如果发生异常抛出导致引用没有及时释放,在使用池化 ByetBuffer 的情况下内存泄漏的问题就会产生。 由于 Netty 中的 ByteBuf 并不是随着申请之后会马上使其引用计数归 0 而进行释放,往往在这两个操作之间还有许多操作,如果在这其中如果发生异常抛出导致引用没有及时释放,在使用池化 ByetBuffer 的情况下内存泄漏的问题就会产生。

@ -112,7 +112,7 @@ sds sdsnewlen(const void *init, size_t initlen) {
unsigned char *fp; unsigned char *fp;
// 检查长度是否溢出 // 检查长度是否溢出
assert(initlen + hdrlen + 1 > initlen); assert(initlen + hdrlen + 1 > initlen);
// 创建字符串,+1是因为 `\0` 结束符 // 创建字符串,+1是因为 `\0` 结束符
// sh指向header首字节 // sh指向header首字节
@ -131,7 +131,7 @@ sds sdsnewlen(const void *init, size_t initlen) {
// 赋值len, alloc, flags // 赋值len, alloc, flags
... ...
// 赋值buf[] // 赋值buf[]
if (initlen && init) if (initlen && init)
@ -145,7 +145,7 @@ sds sdsnewlen(const void *init, size_t initlen) {
} }
``` ```
创建 SDS 的大致流程是这样的:首先根据字符串长度计算得到 type根据 type 计算头部所需长度然后动态分配内存空间。通过计算出指向header的指针sh指向buf的指针s对结构体各字段进行赋值。 创建 SDS 的大致流程是这样的:首先根据字符串长度计算得到 type根据 type 计算头部所需长度,然后动态分配内存空间。通过计算出指向 header 的指针 sh指向 buf 的指针 s对结构体各字段进行赋值。
注意: 注意:
@ -180,11 +180,11 @@ void sdsclear(sds s) {
} }
``` ```
### 3. 更新len ### 3. 更新 len
因为 sdsnewlen 函数返回的是 char\* 类型的 buf所以兼容了 c 语言操作字符串的函数,
那么当 `s = ['a', 'b', 'c', '\0']` 时, 再操作`s[2] = '\0'`, 这个时候`sdslen(s)`得到的结果是 3因为 len 字段没有更新,如果直接更新`'\0'`,需要调用以下函数更新 len
因为sdsnewlen函数返回的是char* 类型的buf所以兼容了c语言操作字符串的函数
那么当 `s = ['a', 'b', 'c', '\0']` 时, 再操作`s[2] = '\0'`, 这个时候`sdslen(s)`得到的结果是3因为len字段没有更新如果直接更新`'\0'`需要调用以下函数更新len
```c ```c
void sdsupdatelen(sds s) { void sdsupdatelen(sds s) {
size_t reallen = strlen(s); size_t reallen = strlen(s);
@ -256,7 +256,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
newlen = (len+addlen); newlen = (len+addlen);
// 检查长度是否溢出 // 检查长度是否溢出
assert(newlen > len); assert(newlen > len);
// 新长度<1MB2 // 新长度<1MB2
if (newlen < SDS_MAX_PREALLOC) if (newlen < SDS_MAX_PREALLOC)
@ -274,7 +274,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
hdrlen = sdsHdrSize(type); hdrlen = sdsHdrSize(type);
// 检查长度是否溢出 // 检查长度是否溢出
assert(hdrlen + newlen + 1 > len); assert(hdrlen + newlen + 1 > len);
if (oldtype==type) { if (oldtype==type) {
// 类型没变直接通过realloc扩大动态数组即可。 // 类型没变直接通过realloc扩大动态数组即可。
@ -310,4 +310,4 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
1. SDS 返回的是指向 buf 的指针,同时以`\0`结尾,所以兼容了 C 语言操作字符串的函数,读取内容时,通过 len 属性来限制读取的长度,不受 `\0` 影响,从而保证二进制安全; 1. SDS 返回的是指向 buf 的指针,同时以`\0`结尾,所以兼容了 C 语言操作字符串的函数,读取内容时,通过 len 属性来限制读取的长度,不受 `\0` 影响,从而保证二进制安全;
2. Redis 根据字符串长度的不同定义了多种数据结构包括sdshdr5/sdshdr8/sdshdr16/sdshdr32/sdshdr64。 2. Redis 根据字符串长度的不同定义了多种数据结构包括sdshdr5/sdshdr8/sdshdr16/sdshdr32/sdshdr64。
3. SDS 在设计字符串修改出会调用 `sdsMakeRoomFor` 函数进行检查,根据不同情况进行扩容。 3. SDS 在设计字符串修改出会调用 `sdsMakeRoomFor` 函数进行检查,根据不同情况进行扩容。

@ -8,7 +8,7 @@
1. 时间窗口还未建立,那么将会为此次流量的进入建立一个新的时间窗口返回,并且接下来这个时间窗口内的获取请求都将返回该窗口。 1. 时间窗口还未建立,那么将会为此次流量的进入建立一个新的时间窗口返回,并且接下来这个时间窗口内的获取请求都将返回该窗口。
2. 时间窗口已经建立的情况下,将会直接获取已经存在的符合条件的时间窗口。 2. 时间窗口已经建立的情况下,将会直接获取已经存在的符合条件的时间窗口。
3. 时间窗口可能已经存在,但是当前获取的时间窗口已经过期,需要加锁,并重置当前时间窗口。 3. 时间窗口可能已经存在,但是当前获取的时间窗口已经过期,需要加锁,并重置当前时间窗口。
4. 当前进入的时间已经远远落后当前的时间,目标时间窗口已经被 reset 更新成更新的时间窗口,那么将不会返回目标时间窗口,而是返回一个新的空的时间窗口进行统计,这个时间窗口不会再被重复利用。 4. 当前进入的时间已经远远落后当前的时间,目标时间窗口已经被 reset 更新成更新的时间窗口,那么将不会返回目标时间窗口,而是返回一个新的空的时间窗口进行统计,这个时间窗口不会再被重复利用。
其中的第四个情况表明sentinel 的滑动时间窗口是有时间范围的,这也是为了尽量减少 sentinel 的所占用的内存,默认情况下 sentinel 的采取的时间长度为 1 分钟和 1 秒钟。这里的实现与 LeapArray 类的结构非常有关系。 其中的第四个情况表明sentinel 的滑动时间窗口是有时间范围的,这也是为了尽量减少 sentinel 的所占用的内存,默认情况下 sentinel 的采取的时间长度为 1 分钟和 1 秒钟。这里的实现与 LeapArray 类的结构非常有关系。
@ -22,7 +22,7 @@ protected final AtomicReferenceArray<WindowWrap<T>> array;
private final ReentrantLock updateLock = new ReentrantLock(); private final ReentrantLock updateLock = new ReentrantLock();
``` ```
此处的 updateLock 是专门在上述的第三个情况来进行加锁的,只有成功得到锁的线程才会对过期的时间窗口进行 reset 操作,其他没有成功获取的线程将不会挂起等待,而是通过 `yield()` 方法回到就绪态,在下一次的循环尝试重新获取该位置的时间窗口。在下一次获取该锁的线程可能已经完成了,那么将会执行上述第二步,否则继续回到就绪态等待下一次循环中再次获取该时间窗口。 此处的 updateLock 是专门在上述的第三个情况来进行加锁的,只有成功得到锁的线程才会对过期的时间窗口进行 reset 操作,其他没有成功获取的线程将不会挂起等待,而是通过 `yield()` 方法回到就绪态,在下一次的循环尝试重新获取该位置的时间窗口。在下一次获取该锁的线程可能已经完成了,那么将会执行上述第二步,否则继续回到就绪态等待下一次循环中再次获取该时间窗口。
以上两个数据结构是 LeapArray 类实现时间窗口在高并发下准确获取时间窗口并更新的关键。 以上两个数据结构是 LeapArray 类实现时间窗口在高并发下准确获取时间窗口并更新的关键。

@ -378,7 +378,7 @@ class BeatTask implements Runnable {
} else { } else {
Random random = new Random(System.currentTimeMillis()); Random random = new Random(System.currentTimeMillis());
int index = random.nextInt(servers.size()); int index = random.nextInt(servers.size());
for (int i = 0; i < servers.size(); i++) { for (int i = 0; i < servers.size(); i++) {
String server = servers.get(index); String server = servers.get(index);
try { try {
@ -392,10 +392,10 @@ class BeatTask implements Runnable {
index = (index + 1) % servers.size(); index = (index + 1) % servers.size();
} }
} }
NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(), NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(),
exception.getErrMsg()); exception.getErrMsg());
throw new NacosException(exception.getErrCode(), throw new NacosException(exception.getErrCode(),
"failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage()); "failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());
``` ```

@ -18,7 +18,7 @@ if (group.length() >CHARACTER_MAX_LENGTH) {
} }
``` ```
消费组名称只能包含数字、字母、%、-、_、| 消费组名称只能包含数字、字母、%、-、\_、|
```java ```java
// regex: ^[%|a-zA-Z0-9_-]+$ // regex: ^[%|a-zA-Z0-9_-]+$
@ -129,7 +129,7 @@ private void copySubscription() throws MQClientException {
`3、初始化MqClientInstance、RebalanceImpl、PullApiWrapper` `3、初始化MqClientInstance、RebalanceImpl、PullApiWrapper`
创建`MqClientInstance` 无论在生产者端还是消费者端都是一个很重要的类, 封装了Topic信息、broker信息当然还有生产者和消费者的信息。 创建`MqClientInstance` 无论在生产者端还是消费者端都是一个很重要的类, 封装了 Topic 信息、broker 信息,当然还有生产者和消费者的信息。
```java ```java
public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) { public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {

@ -28,7 +28,7 @@ org.apache.rocketmq.store.index.IndexFile#putKey
如果当前 Index 文件未满,则根据 key 计算出哈希码,然后对槽数量取余定位到某一个哈希槽位置, 如果当前 Index 文件未满,则根据 key 计算出哈希码,然后对槽数量取余定位到某一个哈希槽位置,
哈希槽的物理偏移量 = IndexHeader 的大小(默认 40Byte + 哈希槽位置 * 每个哈希槽的大小4 字节) 哈希槽的物理偏移量 = IndexHeader 的大小(默认 40Byte + 哈希槽位置 \* 每个哈希槽的大小4 字节)
```java ```java
int keyHash = indexKeyHashMethod(key); int keyHash = indexKeyHashMethod(key);
@ -61,7 +61,7 @@ if (this.indexHeader.getBeginTimestamp() <= 0) {
} }
``` ```
新添加的消息 index 的物理偏移量 = IndexHeader 大小40Byte + Index 文件哈希槽的数量 * 哈希槽的大小4Byte + Index 文件索引数量 * 索引大小20Byte 新添加的消息 index 的物理偏移量 = IndexHeader 大小40Byte + Index 文件哈希槽的数量 _ 哈希槽的大小4Byte + Index 文件索引数量 _ 索引大小20Byte
将消息哈希码、消息物理偏移量、消息存储时间戳与 Index 文件第一条消息的时间戳的差值、当前哈希槽的值、当前 Indexfile 的索引个数存入 mappedByteBuffer 将消息哈希码、消息物理偏移量、消息存储时间戳与 Index 文件第一条消息的时间戳的差值、当前哈希槽的值、当前 Indexfile 的索引个数存入 mappedByteBuffer
@ -115,7 +115,7 @@ long end: 结束时间戳
根据 key 计算哈希码,哈希码与哈希槽的数量取余得到哈希槽的索引 根据 key 计算哈希码,哈希码与哈希槽的数量取余得到哈希槽的索引
哈希槽的物理地址 = IndexHeader40byte + 哈希槽索引 * 每个哈希槽的大小4byte 哈希槽的物理地址 = IndexHeader40byte + 哈希槽索引 \* 每个哈希槽的大小4byte
```java ```java
int keyHash = indexKeyHashMethod(key); int keyHash = indexKeyHashMethod(key);

Loading…
Cancel
Save