MinIO分片上传超大文件(纯服务端)
目录
- 一、MinIO快速搭建
- 1.1、拉取docker镜像
- 1.2、启动docker容器
- 二、分片上传大文件到MinIO
- 2.1、添加依赖
- 2.2、实现MinioClient
- 2.3、实现分片上传
- 2.3.0、初始化MinioClient
- 2.3.1、准备分片上传
- 2.3.2、分片并上传
- 2.3.2.1、设置分片大小
- 2.3.2.2、分片
- 2.3.3、分片合并
- 三、测试
- 3.1、完整测试代码
- 3.2、运行日志和效果
一、MinIO快速搭建
这里简单介绍一下通过docker方式快速搭建MinIO
的大体流程。
1.1、拉取docker镜像
首先直接尝试拉取:
docker pull minio/minio
如果拉不到,试图更改docker镜像源:
echo '{"registry-mirrors": ["https://4xxwxhl6.mirror.aliyuncs.com","https://mirror.iscas.ac.cn","https://docker.rainbond.cc","https://docker.nju.edu.cn","https://6kx4zyno.mirror.aliyuncs.com","https://mirror.baidubce.com","https://docker.m.daocloud.io","https://dockerproxy.com"]
}' | sudo tee /etc/docker/daemon.json > /dev/null
接着重启docker服务,使新配置生效:
sudo systemctl restart docker
最后再次拉取即可。
1.2、启动docker容器
首先创建配置和数据目录:
mkdir -p /opt/minio/config
mkdir -p /opt/minio/data
接着启动:
docker run -p 9000:9000 -p 9001:9001 --net=host --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -v /opt/minio/data:/data -v /opt/minio/config:/root/.minio minio/minio server /data --console-address ":9001" -address ":9000"
最后进入MinIO控制台http://192.168.2.195:9001
,简单做点存储桶、用户、用户组等配置即可。比如创建新用户名minioUser,密码minioUser123。
二、分片上传大文件到MinIO
2.1、添加依赖
这里需要注意minio 8.3.3必须依赖okhttp的版本不小于4.8.1。
// minio 8.3.3 Must use okhttp >= 4.8.1
implementation 'io.minio:minio:8.3.3'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
2.2、实现MinioClient
参考S3
官方文档https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html#mpu-process,大文件的分片上传,主要分三步实现:
initMultiPartUpload
创建一个大文件分片上传任务uploadMultiPart
逐个上传分片mergeMultipartUpload
合并分片
通过继承默认的MinioClient
,将一些相关的重要方法暴露出来,以便使用。
package com.szh.minio;import com.google.common.collect.Multimap;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Part;import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;public class CustomMinioClient extends MinioClient {/*** 继承父类*/public CustomMinioClient(MinioClient client) {super(client);}/*** 初始化分片上传即获取uploadId*/public String initMultiPartUpload(String bucket, String region, String object, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {CreateMultipartUploadResponse response = this.createMultipartUpload(bucket, region, object, headers, extraQueryParams);return response.result().uploadId();}/*** 上传单个分片*/public UploadPartResponse uploadMultiPart(String bucket, String region, String object, Object data,long length,String uploadId,int partNumber,Multimap<String, String> headers,Multimap<String, String> extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {return this.uploadPart(bucket, region, object, data, length, uploadId, partNumber, headers, extraQueryParams);}/*** 合并分片*/public ObjectWriteResponse mergeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws IOException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException, ServerException, InvalidKeyException {return this.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);}public void cancelMultipartUpload(String bucketName, String region, String objectName, String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, IOException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {this.abortMultipartUpload(bucketName, region, objectName, uploadId, extraHeaders, extraQueryParams);}/*** 查询当前上传后的分片信息*/public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {return this.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);}
}
2.3、实现分片上传
2.3.0、初始化MinioClient
连接到minio,并确保存储桶的存在。
static CustomMinioClient minioClient = new CustomMinioClient(MinioClient.builder().endpoint("http://192.168.2.195:9000").credentials("minioUser", "minioUser123").build());
// 测试桶
static String bucketName = "test";
static {try {boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());if (!found) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}} catch (Exception e) {throw new RuntimeException(e);}
}
2.3.1、准备分片上传
创建一个大文件分片上传任务。
String contentType = "application/octet-stream";
HashMultimap<String, String> headers = HashMultimap.create();
headers.put("Content-Type", contentType);
String uploadId = minioClient.initMultiPartUpload(bucketName, null, file.getName(), headers, null);
System.out.println("uploadId: " + uploadId);
2.3.2、分片并上传
本文是使用纯服务端进行分片和上传,而实际项目中更推荐由后端首先调用minio的接口getPresignedObjectUrl
,逐个生成每个分片的签名后的上传url,然后前端直接以此上传到minio,即可省去后端服务的网络IO开销。
📢 后者方案请见:MinIO分片上传超大文件(非纯服务端)
2.3.2.1、设置分片大小
一方面,需要注意单个分片大小最小5MB
,如果每个分片设置小于5MB,则minio或S3底层在合并时报错:code = EntityTooSmall, message = Your proposed upload is smaller than the minimum allowed object size
。
另一方面,在调整分片大小时,需要注意minio或S3底层允许的分片范围[1,10000]
。
2.3.2.2、分片
一方面,为了保证分片的效率,借助线程池的并发,以及RandomAccessFile
的文件随机访问能力,更快地完成分片的流程。当然,可控制并发数和分片大小以防止并发分片中的OOM。
另一方面,考虑到分片全部完成之后,还有最后的合并操作,所以借助CountDownLatch
来确保所有分片上传之后,再去执行合并。
2.3.3、分片合并
合并所有已上传的分片。
Part[] parts = new Part[(int) chunkCount];
// 查询上传后的分片数据。S3最大允许10000,且从1开始
ListPartsResponse partResult = minioClient.listMultipart(bucketName, null, file.getName(), 10000, 0, uploadId, null, null);
int partNumber = 1;
for (Part part : partResult.result().partList()) {parts[partNumber - 1] = new Part(partNumber, part.etag());partNumber++;
}
ObjectWriteResponse objectWriteResponse = minioClient.mergeMultipartUpload(bucketName, null, file.getName(), uploadId, parts, null, null);
三、测试
3.1、完整测试代码
package com.szh.minio;import com.google.common.collect.HashMultimap;
import io.minio.*;
import io.minio.messages.Part;
import lombok.Getter;
import lombok.Setter;import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;@Setter
@Getter
public class MinioMain {
static CustomMinioClient minioClient = new CustomMinioClient(MinioClient.builder().endpoint("http://192.168.2.195:9000").credentials("minioUser", "minioUser123").build());// 测试桶static String bucketName = "test";static {try {boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());if (!found) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}} catch (Exception e) {throw new RuntimeException(e);}}// 需要被分片上传的大文件static String filePath = "C:\\tmp\\psi_result.csv";static File file = new File(filePath);// 单个分片大小5MB,如果每个分片设置小于5MB,则minio或S3底层在合并时报错:// code = EntityTooSmall, message = Your proposed upload is smaller than the minimum allowed object size.static final long CHUNK_SIZE = 5 * 1024 * 1024;// 当前分片号,minio或S3底层允许的分片范围[1,10000]// https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html#mpu-processprivate int chunkIndex;// 用于得知所有分片都传输成功后的时刻,进而进行合并private static CountDownLatch countDownLatch;public static void main(String[] args) throws Exception {// 第一步:准备分片上传String contentType = "application/octet-stream";HashMultimap<String, String> headers = HashMultimap.create();headers.put("Content-Type", contentType);String uploadId = minioClient.initMultiPartUpload(bucketName, null, file.getName(), headers, null);System.out.println("uploadId: " + uploadId);// 第二步:分片并上传// ps:实际项目中可由后端先getPresignedObjectUrl逐个生成每个分片的签名后的上传url,前端直接以此上传到minio,即可省去后端服务的网络开销long totalLength = file.length();System.out.println("totalLength: " + totalLength + " Byte");// 计算分片数量long chunkCount = (totalLength + CHUNK_SIZE - 1) / CHUNK_SIZE;System.out.println("chunkCount: " + chunkCount);countDownLatch = new CountDownLatch((int) chunkCount);// 5个核心线程并发上传分片ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);for (long i = 0; i < chunkCount; i++) {long position = i * CHUNK_SIZE;int bytesRead = (int) Math.min(CHUNK_SIZE, totalLength - position);MinioMain minioMain = new MinioMain();// S3分片号从1开始minioMain.setChunkIndex((int) i + 1);fixedThreadPool.submit(new Runnable() {@Overridepublic void run() {try {// 上传分片minioMain.processChunk(filePath, position, bytesRead, uploadId);} catch (Exception e) {throw new RuntimeException(e);}}});}countDownLatch.await();fixedThreadPool.shutdownNow();// 第三步:合并分片System.out.println("ready to merge <" + file.getName() + " - " + uploadId + " - " + bucketName + ">");Part[] parts = new Part[(int) chunkCount];// 查询上传后的分片数据。S3最大允许10000,且从1开始ListPartsResponse partResult = minioClient.listMultipart(bucketName, null, file.getName(), 10000, 0, uploadId, null, null);int partNumber = 1;for (Part part : partResult.result().partList()) {parts[partNumber - 1] = new Part(partNumber, part.etag());partNumber++;}ObjectWriteResponse objectWriteResponse = minioClient.mergeMultipartUpload(bucketName, null, file.getName(), uploadId, parts, null, null);System.out.println("mergeMultipartUpload resp etag: " + objectWriteResponse.etag());StatObjectResponse statObjectResponse = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(file.getName()).build());System.out.println("etag: " + statObjectResponse.etag() + " size: " + statObjectResponse.size() + " lastModified: " + statObjectResponse.lastModified());}private void processChunk(String filePath, long position, int bytesRead, String uploadId) {// 可控制并发数和分片大小以防止OOMbyte[] buffer = new byte[bytesRead];RandomAccessFile raf = null;try {int chunkIndex = this.getChunkIndex();raf = new RandomAccessFile(filePath, "r");// 定位到指定位置raf.seek(position);// 读取bytesRead字节长度作为分片raf.readFully(buffer);String contentType = "application/octet-stream";HashMultimap<String, String> headers = HashMultimap.create();headers.put("Content-Type", contentType);UploadPartResponse uploadPartResponse = minioClient.uploadMultiPart(bucketName, null, file.getName(),buffer, bytesRead,uploadId, chunkIndex, headers, null);System.out.println("chunk[" + chunkIndex + "] buffer size: [" + buffer.length + " Byte] upload etag: [" + uploadPartResponse.etag() + "]");} catch (Exception e) {e.printStackTrace();} finally {if (raf != null) {try {raf.close();} catch (IOException e) {e.printStackTrace();}}countDownLatch.countDown();}}
}
3.2、运行日志和效果
运行日志如下:
uploadId: MzFiMWRmZjctMDg0Yy00YzMyLTk5NTYtMjRkZGZiMDZlYjJhLmUwZmFkNzFiLWEwZTctNDU1Yi04ZWFjLWFhODQyZjBiMmIyOXgxNzI3MzQwMjUzMTA2Njc5MTEz
totalLength: 3576974860 Byte
chunkCount: 683
chunk[1] buffer size: [5242880 Byte] upload etag: [97096e510d1dcda56646608345de08ea]
chunk[3] buffer size: [5242880 Byte] upload etag: [d8102f80f10eb79f600cdf2d378ae8fe]
chunk[4] buffer size: [5242880 Byte] upload etag: [b74f9b8fa2025580b4fc00449c66e271]
chunk[5] buffer size: [5242880 Byte] upload etag: [e77603ee49cc3f7d229f124ecd9a3f38]
chunk[2] buffer size: [5242880 Byte] upload etag: [b148b311ccd2b3fcd4777d56a8758c3d]
chunk[6] buffer size: [5242880 Byte] upload etag: [94abe5a7a2117b612d9805029398cfd9]
chunk[7] buffer size: [5242880 Byte] upload etag: [433b52aed0d1b1486df07a2259932a83]
chunk[8] buffer size: [5242880 Byte] upload etag: [2c242bd205f9b3c4546454fe2d0abef4]
...
chunk[679] buffer size: [5242880 Byte] upload etag: [8492b0573cc74ec55cb6d2a86aee0f69]
chunk[678] buffer size: [5242880 Byte] upload etag: [4aa5c01b4f7aea95952ec62d71ee9996]
chunk[681] buffer size: [5242880 Byte] upload etag: [ac0b739044bfd2644fc8da97fc03a1a9]
chunk[680] buffer size: [5242880 Byte] upload etag: [d95ee210ac774b3ca26e091941c66e20]
chunk[682] buffer size: [5242880 Byte] upload etag: [75e78df64c1fad0839ba8a1583cd93ec]
chunk[683] buffer size: [1330700 Byte] upload etag: [2f30c8d65e23d266c7f10f051854bc6a]
ready to merge <psi_result.csv - MzFiMWRmZjctMDg0Yy00YzMyLTk5NTYtMjRkZGZiMDZlYjJhLmUwZmFkNzFiLWEwZTctNDU1Yi04ZWFjLWFhODQyZjBiMmIyOXgxNzI3MzQwMjUzMTA2Njc5MTEz - test>
mergeMultipartUpload resp etag: "ff6ebd330b3cb224ade84463dd14df82-683"
etag: ff6ebd330b3cb224ade84463dd14df82-683 size: 3576974860 lastModified: 2024-09-26T09:09Z
上传后的控制台:
相关文章:

MinIO分片上传超大文件(纯服务端)
目录 一、MinIO快速搭建1.1、拉取docker镜像1.2、启动docker容器 二、分片上传大文件到MinIO2.1、添加依赖2.2、实现MinioClient2.3、实现分片上传2.3.0、初始化MinioClient2.3.1、准备分片上传2.3.2、分片并上传2.3.2.1、设置分片大小2.3.2.2、分片 2.3.3、分片合并 三、测试3…...

leetcode链表(一)-移除链表元素
题目 t. - 力扣(LeetCode) 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 例1 输入:head [1,2,6,3,4,5,6], val 6 输出:[1,2,3,4,5]…...

python的特殊方法——魔术方法
前言 __init__(self[]) 编辑 __call__(self [, ...]) __getitem__(self, key) __len__(self) __repr__(self) / __str__(self) __add__(self, other) __radd__(self, other) 参考文献 前言 官方定义好的,以两个下划线开头且以两个下划线结尾来命名的方法…...

深入浅出理解TCP三次握手与四次挥手
目录 引言1.为什么需要三次握手?2. 三次握手的过程3. 为什么需要四次挥手?4. 四次挥手的过程5. 为什么挥手需要四次,而握手只需三次?6. 三次握手与四次挥手的时序图7. TIME_WAIT状态的意义8. 总结9.面试时候问到什么是三次握手和四…...

如何在Windows和Linux查看正在监听的端口和绑定的进程
端口(Port)和进程(Process)是计算机网络和操作系统中的重要概念,它们之间有着密切的关系。以下是对这两个概念的详细介绍以及它们之间的关系(附Windows和Linux查看端口和进程的命令): 端口(Por…...

如何用深度神经网络预测潜在消费者
1. 模型架构 本项目采用的是DeepFM模型,其结构结合了FM(因子分解机)与深度神经网络(DNN),实现了低阶与高阶特征交互的有效建模。模型分为以下几层: 1.1 FM部分(因子分解机层&#…...

基于opencv答题卡识别判卷
我们是一个深度学习领域的独立工作室。团队成员有:中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等,曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝,拥有2篇国家级人工智能发明专利。 社区特色:深度实…...

ShardingSphere分库分表产品介绍
目录 一、ShardingSphere分库分表产品介绍 二、客户端分库分表与服务端分库分表 1、ShardingJDBC客户端分库分表 2、ShardingProxy服务端分库分表 3、ShardingSphere混合部署架构 三、分库分表,能不分就不分! 1、为什么要分库分表? 2、…...

Java经典面试题-多线程打印
threadsynchronized 就好像一个圆圈,A->B->C->A。。。。。 synchronized能够保证多个线程进入实,只用一个线程能进入。 /**多线程交替打印* */ public class Task {private final Object lock new Object();private int count 0;public st…...

FireFox简单设置设置
文章目录 一 设置不显示标签页1原来的样子2新的样子3操作方法 二 设置竖直标签页栏1 效果图2 设置方法 三 设置firefox不提醒更新 一 设置不显示标签页 1原来的样子 2新的样子 3操作方法 地址栏输入 about:config搜索icon,双击选项列表中browserchrome.site icons的值&#…...

Sollong手机——一站式Web3生态解决方案
从定义上讲,Web3公司也属于互联网公司,不过与传统互联网公司相比,他们有一个很明显的特征:他们不断尝试做去中心化的事,一步步将数据和金融的控制权从美联储(央行和金融机构)、苹果(…...

《重生到现代之从零开始的数据结构生活》—— 顺序表1
线性表 线性表:是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的 数据结构,常⻅的线性表有顺序表、链表、栈、队列、字符串等等 线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连…...

2本书让你轻松入门大模型!《大模型入门:技术原理与实战应用》+《自然语言处理:大模型理论与实践》
随着大模型技术的不断完善和普及,我们将进入一个由数据驱动、智能辅助的全新工作模式和生活模式。个人和企业将能够利用大模型来降本增效,并创造全新的用户体验。 人工智能是人类探索未来的重要领域之一,以GPT为代表的大模型应用一经推出在短…...

【JDK17 | 1】Java 17 深入剖析:新特性与变革
引言 Java 17 是 Oracle 发布的一个重要版本,它不仅是一个长期支持(LTS)版本,还引入了许多新的特性和改进,为开发者提供了更强大的工具和更优雅的编程体验。在本专栏的文章中,我们将对 Java 17 的新特性进行深入剖析,帮助开发者更好地理解和利用这一版本。 1. 新特性概述…...

strtok
strtok函数原型 char *strtok(conset char *str,conset char *dst); 功能: 将字符串分割成一个一个片段 当strtok()在参数str的字符串中发现参数dst中包含的分割字符时,则会将该字符改为\0字符 当连续出现…...

零信任身份安全的基本原则
零信任身份安全的核心原则就是 “持续验证,永不信任”。可以通过以下6个方面理解: 对“谁”和“什么”进行认证和授权 在零信任身份安全模型中,对用户和设备的身份进行认证和授权是至关重要的。认证是确认用户或设备是其所宣称的身份的过程…...

【AAOS】Android Automotive 9模拟器源码下载及编译
源码下载 repo init -u https://android.googlesource.com/platform/manifest -b android-9.0.0_r61 repo sync -c --no-tags --no-clone-bundle 源码编译 source build/envsetup.sh lunch aosp_car_x86_64-userdebug make -j8 运行效果 emulator Home界面 MAP All apps S…...

手动降级wsl中的numpy
下载完pytorch之后想验证一下cuda好不好使,在测试的时候发现一个warning python中报错如下 我下载的pytorch版本比较低,numpy太高,所以需要手动给numpy降级 pip install numpy\<2 降级后再进到python验证cuda就没有warning和报错了&…...

极客兔兔Gee-Cache Day7
protobuf配置: 从 Protobuf Releases 下载最先版本的发布包安装。解压后将解压路径下的 bin 目录 加入到环境变量即可。 如果能正常显示版本,则表示安装成功。 $ protoc --version libprotoc 3.11.2在Golang中使用protobuf,还需要protoc-g…...

R包:APAlyzer从RNA-seq数据计算APA表达丰度
文章目录 介绍教程实战案例数据脚本运行 介绍 今天安利APAlyzer工具,它是通过RNA-seq数据获取3′UTR APA, intronic APA等表达谱的R包。 APAlyzer将bam文件比对到PolyA-DB数据库识别APA。 Most eukaryotic genes produce alternative polyadenylation (APA) isofo…...

YOLOv11改进策略【损失函数篇】| 利用MPDIoU,加强边界框回归的准确性
一、背景 目标检测和实例分割中的关键问题: 现有的大多数边界框回归损失函数在不同的预测结果下可能具有相同的值,这降低了边界框回归的收敛速度和准确性。 现有损失函数的不足: 现有的基于 ℓ n \ell_n ℓn范数的损失函数简单但对各种尺度…...

dayu_widgets-简介
前言: 越来越多的人开始使用python来做GUI程序,市面上却很少有好的UI控件。即使有也是走的商业收费协议,不敢使用,一个不小心就收到法律传票。 一、原始开源项目: 偶然在GitHub上发现了这个博主的开源项目。https://github.com/phenom-films…...

改变数组页面重新渲染的操作/那些操作不会重新渲染页面以及解决方法
在前端开发中,当数组数据发生变化时,是否会导致页面重新渲染,以及如何进行相关操作,这取决于使用的具体框架或库(如React、Vue等)及其内部机制。以下是对这一问题的详细解答: 一、会导致页面重…...

米哈游Android面试题汇总及参考答案
Java 的内存回收机制是如何工作的? 在 Java 中,内存回收主要由垃圾回收器(Garbage Collector)来完成。 Java 的内存主要分为堆(Heap)和栈(Stack)等区域。其中,对象主要分配在堆上。当创建一个对象时,会在堆上为其分配内存空间。 垃圾回收器主要负责回收不再被使用的对…...

搜维尔科技:【应用】Xsens动作捕捉技术为奇幻电影注入活力
在英格兰古朴的小镇和连绵起伏的群山之间,坐落着一个虚构的小镇Anghenfil,在这里有一个早已被人遗忘的传说。在这部由英国电影制片人Ryan Garry自编自导的奇幻电影《Every Legend Ends》中,这个传说即将变成可怕的现实。 怪物苏醒&#…...

useradd命令:添加Linux新用户
一、命令简介 useradd 命令用于在 Linux 系统中创建新用户账号。 二、命令参数 useradd [选项] 用户名一些常用的选项包括: -c, --comment "Comment" : 为用户账号添加注释信息。-m, --create-home: 创建用户的家目录。-s, --shell /path/to/…...

Python+ffmpeg实现字幕视频合并
背景 我想给自己的视频添加字幕,但是市面上比较好的软件都不太对我口味,要么贵,要么就是学习版不给力。兜兜转转,我决定用多款开源软件分步实现,当然,也可以去白piao某些软件的字幕功能。 驱动力 ffmpeg…...

垂直分库分表、水平分库分表
垂直分库:分出来的数据库的结构完全不一样,垂直分库,更像单体项目到问服务项目过度,根据业务拆分多个模块,每个模块把数据单独抽离出来作为数据库,垂直分库就是根据不同的表业务放在不同放数据库里…...

rocksdb merge的简单记录
背景 rocksdb的merge主要是为了解决读&写需要两步的操作。例如定义一个累加器,总得先把之前的值读出来才能加。 下面给两个例子,大家可以直接用。 AboutAddMerge #include <iostream> #include <rocksdb/db.h> #include <rocksdb…...

安卓开发板_MTK联发科评估套件_安卓开发板Demo板
开发板简介: 安卓开发板采用了副板架在底板的配套方式,支持更换不同平台核心板的副板就能直接完成对某个平台核心板在客户项目需求中的技术评估,既能在研发前期节约人力和物力成本,也能更直观体现出不同平台的核心板在同一个硬件下…...