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

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

从前几天就看到的 《僧丫 》的实名举报助学金名额被挤占,这几天一直在关注,直到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);
相关推荐
小扳4 分钟前
微服务篇-深入了解使用 RestTemplate 远程调用、Nacos 注册中心基本原理与使用、OpenFeign 的基本使用
java·运维·分布式·后端·spring·微服务·架构
ᝰꫝꪉꪯꫀ36110 分钟前
JavaWeb——SpringBoot原理
java·开发语言·后端·springboot
LLLibra14611 分钟前
如何使用Postman优雅地进行接口自动加密与解密
后端
LightOfNight31 分钟前
Redis设计与实现第14章 -- 服务器 总结(命令执行器 serverCron函数 初始化)
服务器·数据库·redis·分布式·后端·缓存·中间件
刽子手发艺40 分钟前
云服务器部署springboot项目、云服务器配置JDK、Tomcat
java·后端·部署
White graces1 小时前
Spring MVC练习(前后端分离开发实例)
java·开发语言·前端·后端·spring·java-ee·mvc
kingwebo'sZone4 小时前
ASP.net WebAPI 上传图片实例(保存显示随机文件名)
后端·asp.net
桑榆肖物4 小时前
一个简单的ASP.NET 一致性返回工具库
后端·asp.net
组态软件7 小时前
web组态软件
前端·后端·物联网·编辑器·html
Peter_chq7 小时前
【计算机网络】多路转接之select
linux·c语言·开发语言·网络·c++·后端·select