僧丫的事 + 如何提升存储模块的性能和可靠性 代码实战

序

最近在忙工作的事,本来每天中午都要午睡的,我媳妇 都让我冷静点,但我冷静不了,知道没什么用,但是我尽力就好,让更多人看到

从前几天就看到的 《僧丫 》的实名举报助学金名额被挤占,这几天一直在关注,直到11月2号凌晨, 《四川观察》 第一个官媒帮忙发视频。

我也是一个单亲家庭,我听他本人的视频的时候 认真的听,不争气的说(我哭了),能感觉到他的无奈,尤其那句 奶奶和他说 家里真的没钱了,我们对这种事可能早就见过 或者 听过,但是一般都是不管不顾,因为没有必要,但能感觉到 他是遇到了特别的难处

我当时家里亲戚和我孤儿寡母 的打官司,要我家房子,打了4年,从12岁打到16岁,本来能赢的,前面还老赢,后面我们没钱请律师了,终审 判给了他们,4家,一家8w块,(16年前),这个社会就这样,我早看透了,希望大家去b站,或者抖音 帮顶下

僧丫 18岁的户主

建档立卡都评不上,操

如果占用了掘金的资源,抱歉

生气的点

b站在《僧丫》视频出来几个小时 就删除了

我看到了,然后后面就看不了了

《僧丫》给出了证据,还有人说风凉话

我相信这绝不是唯一的一件,我当时的大学导员就是 奖学金,助学金都是内定的,只是人家准备了一堆理由,让你找不到把柄,明眼人谁不知道怎么回事,我相信

序

今天再看一篇文章的时候,看到一句话

确实我也感觉有点 纯理论了,我给补充点代码

问题列表

  1. rocketmq 怎么做的存储模块
  2. 操作系统怎么做到的"按照空间占用比例"、"时间周期扫描"

本节细节

  1. 怎么判断 操作系统PageCache繁忙
  2. 做一个sdk,让我们能直接使用

背景

我是一枚大专非计算机专业的 java程序员,读写文件 我可能第一印象是File的工具类,比如

ini 复制代码
long startTime = System.currentTimeMillis();
        MultipartFile multipartFile = req.getFile();
        PDDocument document;
        String doc = "";
        String originalFilename = "";
        String prefixStr;

        File file;
        try {
            originalFilename = multipartFile.getOriginalFilename();
            String[] filename = originalFilename.split("\.");

            prefixStr = String.join(".", Arrays.copyOfRange(filename, 0, filename.length - 1));

            file = File.createTempFile(VerificationCodeGenerator.generateCode(6), "." + filename[filename.length-1]);
            multipartFile.transferTo(file);
            long startTime1 = System.currentTimeMillis();
            document = PDDocument.load(file);
            long endTime1 = System.currentTimeMillis();
            System.out.println("读取pdf 花费的时间:" + (endTime1 - startTime1));
            PDFTextStripper pdfStripper = new PDFTextStripper();
            doc = pdfStripper.getText(document);
            long endTime2 = System.currentTimeMillis();
            System.out.println("读取doc内容 花费的时间:" + (endTime2 - startTime1));
//            file.deleteOnExit();
            document.close();

        } catch (IOException e) {
            log.error(e.getMessage());
            return null;
        }

大概读一个100页的pdf 耗时

复制代码
读取pdf 花费的时间:104
读取doc内容 花费的时间:264

读取pdf 花费的时间:127
读取doc内容 花费的时间:486

读取pdf 花费的时间:64
读取doc内容 花费的时间:364

读取pdf 花费的时间:98
读取doc内容 花费的时间:287

有没有办法优化下呢

分两种,因为pdf 是特殊的文件,FileChannal 读取不了pdf内容,一般都是操作的txt。

  1. 基础中间件的高性能读写
  2. 业务中 读取pdf / docx 等文件的读写

今天这篇 我们先说 基础中间的高性能读写

  1. 内存读写的效率高于硬盘读写
  2. 批量读写的效率高于单条读写
  3. 顺序读写的效率高于随机读写
  4. 数据复制次数越多,效率越低

具体说说

提升写入操作的性能

写入性能的提高主要有缓存写、批量写、顺序写三个思路,这里对比来讲。

写入内存 就返回肯定是最快的,但是还要保证高性能的同时,不丢失数据

缓存写 与批量写

在计算机理论基础中,计算机多级存储模型的层级越高,代表速度越快(同时容量也越小,价格也越贵),也就是说写入速度从快到慢分别是:寄存器 > 缓存 > 主存 > 本地存储 > 远程存储。

写入优化的主要思路之一是:将数据写入到速度更快的内存中,等积攒了一批数据,再批量刷到硬盘中。

平时我们在一些技术文章看到的"数据先写入 PageCache,再批量刷到硬盘",说的就是这个思路。PageCache 指操作系统的页缓存,简单理解就是内存,通过缓存读写数据可以避免直接对硬盘进行操作,从而提高性能。

把缓存数据刷回到硬盘,一般有"按照空间占用比例"、"时间周期扫描"和"手动强制刷新"三种策略。操作系统内核提供了前两种处理策略,不需要应用程序感知。我们具体了解一下。

按空间占用比例刷新是指当系统内存中的"脏"数据大于某个阈值时会将数据刷新到硬盘。

操作系统提供了两个配置项。

  1. "脏"数据在内存中的占比(dirty_background_ratio)
  2. "脏"数据的绝对的字节数(dirty_background_bytes)

当这两个配置超过阈值,就会触发刷新操作。如果两者同时设置,则以绝对字节数为更高优先级。

按时间周期刷新是指根据配置好的时间,周期性刷新数据到硬盘。主要通过脏页存活时间(dirty_expire_seconds) 和刷新周期(dirty_writeback_centisecs)两个参数来配置。两个配置默认都是 1/100,也就说时间间隔为每秒 100 次,根据刷新周期的配置周期性执行刷新,刷新会检查脏页的存活时间是否超过配置的最大存活时间,如果是则刷入硬盘。

同时,操作系统也提供了第三种方法程序手动强制刷新,你可以通过系统提供的 sync()/msync()/fsync() 调用来强制刷新缓存。通过操作系统的参数配置,在 Java 代码中,通过 Java.NIO 包中 FileChannel 提供的 write() 和 force() 方法,实现写缓存和强制刷新缓存。代码参考:

ini 复制代码
FileChannel channel = fin.getChannel();
file.write(buf)
file.force(true)

通过 FileChannel 提供的 write() 方法写数据时,FileChannel 把数据写入到缓存就会返回成功,然后依赖操作系统的缓存更新策略,将数据刷新到硬盘。我们也可以在代码中调用 FileChannel 提供的 force() 方法,把数据立即刷入刷盘中以免丢失。

基本所有的消息队列在写入时用的都是这个方案,比如 Kafka、RocketMQ、Pulsar 就是先写入缓存,然后依赖操作系统的策略刷新数据到硬盘。

消息队列一般会同时提供:是否同步刷盘、刷盘的时间周期、刷盘的空间比例三个配置项,让业务根据需要调整自己的刷新策略。从性能的角度看,异步刷新肯定是性能最高的,同步刷新是可靠性最高的。

rocketmq 怎么做的存储模块

Broker 怎么进行的优化呢?

先把本地Rocketmq 跑起来,可以看 Rocketmq源码(一)手把手本地调试_本地测试rocketmq_Serena0814的博客-CSDN博客

主要代码在 broker中调用的store代码

kotlin 复制代码
@Override
public CompletableFuture<PutMessageResult> asyncPutMessage(MessageExtBrokerInner msg) {
    PutMessageStatus checkStoreStatus = this.checkStoreStatus();
    if (checkStoreStatus != PutMessageStatus.PUT_OK) {
        return CompletableFuture.completedFuture(new PutMessageResult(checkStoreStatus, null));
    }

    PutMessageStatus msgCheckStatus = this.checkMessage(msg);
    if (msgCheckStatus == PutMessageStatus.MESSAGE_ILLEGAL) {
        return CompletableFuture.completedFuture(new PutMessageResult(msgCheckStatus, null));
    }

    long beginTime = this.getSystemClock().now();
    CompletableFuture<PutMessageResult> putResultFuture = this.commitLog.asyncPutMessage(msg);

    putResultFuture.thenAccept((result) -> {
        long elapsedTime = this.getSystemClock().now() - beginTime;
        if (elapsedTime > 500) {
            log.warn("putMessage not in lock elapsed time(ms)={}, bodyLength={}", elapsedTime, msg.getBody().length);
        }
        this.storeStatsService.setPutMessageEntireTimeMax(elapsedTime);

        if (null == result || !result.isOk()) {
            this.storeStatsService.getPutMessageFailedTimes().incrementAndGet();
        }
    });

    return putResultFuture;
}

检查 Store 状态

kotlin 复制代码
private PutMessageStatus checkStoreStatus() {
    //本身broker 是否关闭
    if (this.shutdown) {
        log.warn("message store has shutdown, so putMessage is forbidden");
        return PutMessageStatus.SERVICE_NOT_AVAILABLE;
    }
    //如果是broker的子节点
    if (BrokerRole.SLAVE == this.messageStoreConfig.getBrokerRole()) {
        long value = this.printTimes.getAndIncrement();
        if ((value % 50000) == 0) {
            log.warn("broke role is slave, so putMessage is forbidden");
        }
        return PutMessageStatus.SERVICE_NOT_AVAILABLE;
    }

    if (!this.runningFlags.isWriteable()) {
        long value = this.printTimes.getAndIncrement();
        //% 50000 只是为了打印日志 找问题
        if ((value % 50000) == 0) {
            log.warn("the message store is not writable. It may be caused by one of the following reasons: " +
                "the broker's disk is full, write to logic queue error, write to index file error, etc");
        }
        return PutMessageStatus.SERVICE_NOT_AVAILABLE;
    } else {
        this.printTimes.set(0);
    }

    if (this.isOSPageCacheBusy()) {
        return PutMessageStatus.OS_PAGECACHE_BUSY;
    }
    return PutMessageStatus.PUT_OK;
}

分析上述检查broker状态代码,broker拒绝写入消息有以下几种情况:

  1. broker处于停止状态。broker在停止时,会通知MessageStore将shutdown标识设置为true
  2. broker是slave角色
  3. broker不支持写入,可能是磁盘满了、写consumeQueue消息队列错误或是写IndexFile错误等原因
  4. 操作系统PageCache繁忙

怎么判断 操作系统PageCache繁忙

arduino 复制代码
public boolean isOSPageCacheBusy() {
    long begin = this.getCommitLog().getBeginTimeInLock();
    long diff = this.systemClock.now() - begin;
    //代码@2
    return diff < 10000000
        && diff > this.messageStoreConfig.getOsPageCacheBusyTimeOutMills();
}
  • begin 通俗的一点讲,就是将消息写入Commitlog文件所持有锁的时间,精确说是将消息体追加到内存映射文件(DirectByteBuffer)或pageCache(FileChannel#map)该过程中开始持有锁的时间戳,具体的代码请参考:CommitLog#putMessage。
  • diff 一次消息追加过程中持有锁的总时长,即往内存映射文件或pageCache追加一条消息所耗时间。

代码@2:如果一次消息追加过程的时间超过了Broker配置文件osPageCacheBusyTimeOutMills,则认为pageCache繁忙,osPageCacheBusyTimeOutMills默认值为1000,表示1s。

存储到store

中间就是判断消息是否正确,然后就开始存储

ini 复制代码
result = mappedFile.appendMessage(msg, this.appendMessageCallback);
相关推荐
调试人生的显微镜19 分钟前
iOS开发入门:Interface Builder、基础控件及UITextField详解
后端·ios
泡海椒21 分钟前
巡检照片与工单附件:jquick-pdf 图片嵌入的业务实战
后端
蜗牛互联网23 分钟前
Python消费Responses SSE事件:增量文本、超时与取消
java·开发语言·人工智能·后端·python
AI持续学习24 分钟前
文档权限变更如何回归测试?账号、空间、文件和分享链接用例清单
后端
谢亮_vipxieliang1 小时前
Go Worker Pool 设计——从原理到生产级实现
开发语言·后端·golang
IT_陈寒1 小时前
为什么你应该学习JavaScript?
前端·人工智能·后端
lightning_bug2 小时前
Windows系统Docker+SpringBoot+H5+Mysql+Redis打包部署流程
后端·架构
SingleShadow2 小时前
一文搞懂 CAN 总线:从入门到 STM32 应用
后端
码事漫谈2 小时前
国庆七天,AI圈没一天消停
后端
掘金酱2 小时前
[稀土掘金 × 火山引擎] AI用量周榜冲刺赛|获奖名单公示
前端·人工智能·后端