问题描述

在Azure Blob的官方示例中,都是对文件进行上传到Blob操作,没有实现对已创建的Blob进行追加的操作。如果想要实现对一个文件的多次追加操作,每一次写入的时候,只传入新的内容?

问题解答

Azure Storage Blob 有三种类型: Block Blob, Append Blob 和 Page Blob。其中,只有Append Blob类型支持追加(Append)操作。并且Blob类型在创建时就已经确定,无法后期修改。

在查看Java Storage SDK后,发现可以使用AppendBlobClient来实现。

    /**
* Creates a new {@link AppendBlobClient} associated with this blob.
*
* @return A {@link AppendBlobClient} associated with this blob.
*/
public AppendBlobClient getAppendBlobClient() {
return new SpecializedBlobClientBuilder()
.blobClient(this)
.buildAppendBlobClient();
}

在 AppendBlobClient 类,有 appendBlock 和 appendBlockWithResponse 等多种方法来实现追加。方法定义源码如下:

    /**
* Commits a new block of data to the end of the existing append blob.
* <p>
* Note that the data passed must be replayable if retries are enabled (the default). In other words, the
* {@code Flux} must produce the same data each time it is subscribed to.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.appendBlock#InputStream-long}
*
* @param data The data to write to the blob. The data must be markable. This is in order to support retries. If
* the data is not markable, consider using {@link #getBlobOutputStream()} and writing to the returned OutputStream.
* Alternatively, consider wrapping your data source in a {@link java.io.BufferedInputStream} to add mark support.
* @param length The exact length of the data. It is important that this value match precisely the length of the
* data emitted by the {@code Flux}.
* @return The information of the append blob operation.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public AppendBlobItem appendBlock(InputStream data, long length) {
return appendBlockWithResponse(data, length, null, null, null, Context.NONE).getValue();
} /**
* Commits a new block of data to the end of the existing append blob.
* <p>
* Note that the data passed must be replayable if retries are enabled (the default). In other words, the
* {@code Flux} must produce the same data each time it is subscribed to.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.appendBlockWithResponse#InputStream-long-byte-AppendBlobRequestConditions-Duration-Context}
*
* @param data The data to write to the blob. The data must be markable. This is in order to support retries. If
* the data is not markable, consider using {@link #getBlobOutputStream()} and writing to the returned OutputStream.
* Alternatively, consider wrapping your data source in a {@link java.io.BufferedInputStream} to add mark support.
* @param length The exact length of the data. It is important that this value match precisely the length of the
* data emitted by the {@code Flux}.
* @param contentMd5 An MD5 hash of the block content. This hash is used to verify the integrity of the block during
* transport. When this header is specified, the storage service compares the hash of the content that has arrived
* with this header value. Note that this MD5 hash is not stored with the blob. If the two hashes do not match, the
* operation will fail.
* @param appendBlobRequestConditions {@link AppendBlobRequestConditions}
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return A {@link Response} whose {@link Response#getValue() value} contains the append blob operation.
* @throws UnexpectedLengthException when the length of data does not match the input {@code length}.
* @throws NullPointerException if the input data is null.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Response<AppendBlobItem> appendBlockWithResponse(InputStream data, long length, byte[] contentMd5,
AppendBlobRequestConditions appendBlobRequestConditions, Duration timeout, Context context) {
Objects.requireNonNull(data, "'data' cannot be null.");
Flux<ByteBuffer> fbb = Utility.convertStreamToByteBuffer(data, length, MAX_APPEND_BLOCK_BYTES, true);
Mono<Response<AppendBlobItem>> response = appendBlobAsyncClient.appendBlockWithResponse(
fbb.subscribeOn(Schedulers.elastic()), length, contentMd5, appendBlobRequestConditions, context);
return StorageImplUtils.blockWithOptionalTimeout(response, timeout);
}

代码实现

第一步: 在Java项目 pom.xml 中引入Azure Storage Blob依赖

    <dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
<version>12.13.0</version>
</dependency>

第二步: 引入必要的 Storage 类

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalTime;
import com.azure.core.http.rest.Response;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.AppendBlobItem;
import com.azure.storage.blob.models.AppendBlobRequestConditions;
import com.azure.storage.blob.specialized.AppendBlobClient;

第三步:创建 AppendBlobClient 对象,使用 BlobServiceClient 及连接字符串(Connection String)

        String storageConnectionString = "DefaultEndpointsProtocol=https;AccountName=*****;AccountKey=*******;EndpointSuffix=core.chinacloudapi.cn";

                String containerName = "appendblob";
String fileName = "test.txt";
// Create a BlobServiceClient object which will be used to create a container
System.out.println("\nCreate a BlobServiceClient Object to Connect Storage Account");
BlobServiceClient blobServiceClient = new BlobServiceClientBuilder()
.connectionString(storageConnectionString)
.buildClient(); BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(containerName);
if (!containerClient.exists())
containerClient.create(); // Get a reference to a blob
AppendBlobClient appendBlobClient = containerClient.getBlobClient(fileName).getAppendBlobClient();

第四步:调用 appendBlockWithResponse 方法追加内容,并根据返回状态码判断是否追加成功

                boolean overwrite = true; // Default value
if (!appendBlobClient.exists())
System.out.printf("Created AppendBlob at %s%n",
appendBlobClient.create(overwrite).getLastModified());


String data = "Test to append new content into exists blob! by blogs lu bian liang zhan deng @"
+ LocalTime.now().toString() + "\n";
InputStream inputStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
byte[] md5 = MessageDigest.getInstance("MD5").digest(data.getBytes(StandardCharsets.UTF_8));
AppendBlobRequestConditions requestConditions = new AppendBlobRequestConditions(); // Context context = new Context("key", "value");
long length = data.getBytes().length; Response<AppendBlobItem> rsp = appendBlobClient.appendBlockWithResponse(inputStream, length, md5,
requestConditions, null, null); if (rsp.getStatusCode() == 201) {
System.out.println("append content successful........");
}

运行结果展示

但如果操作的Blob类型不是Append Blob,就会遇见错误 Status code 409 ---- The blob type is invalid for this operation 错误

Exception in thread "main" com.azure.storage.blob.models.BlobStorageException: Status code 409, "
<?xml version="1.0" encoding="utf-8"?><Error>><Code>InvalidBlobType</Code>
<Message>The blob type is invalid for this operation.
RequestId:501ee0b9-301e-0003-4f7b-829ca6000000
Time:2023-05-09T13:37:17.7509942Z</Message></Error>"

at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:484)
at com.azure.core.http.rest.RestProxy.instantiateUnexpectedException(RestProxy.java:343)
at com.azure.core.http.rest.RestProxy.lambda$ensureExpectedStatus$5(RestProxy.java:382)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815)
at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.signalCached(MonoCacheTime.java:337)
at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onNext(MonoCacheTime.java:354)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2397)
at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onSubscribe(MonoCacheTime.java:293)
at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:192)
at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:57)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at reactor.core.publisher.MonoCacheTime.subscribeOrReturn(MonoCacheTime.java:143)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:57)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:157)
at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:130)
at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:118)
at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:220)
at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:130)
at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:184)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815)
at reactor.core.publisher.MonoCollectList$MonoCollectListSubscriber.onComplete(MonoCollectList.java:128)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:259)
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:401)
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:416)
at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:470)
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:685)
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:1589)
Suppressed: java.lang.Exception: #block terminated with an error
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)
at reactor.core.publisher.Mono.block(Mono.java:1703)
at com.azure.storage.common.implementation.StorageImplUtils.blockWithOptionalTimeout(StorageImplUtils.java:128)
at com.azure.storage.blob.specialized.AppendBlobClient.appendBlockWithResponse(AppendBlobClient.java:259)
at test.App.AppendBlobContent(App.java:68)
at test.App.main(App.java:31)

参考资料

appendBlockWithResponse : https://learn.microsoft.com/en-us/java/api/com.azure.storage.blob.specialized.appendblobclient?view=azure-java-stable#com-azure-storage-blob-specialized-appendblobclient-appendblockwithresponse(java-io-inputstream-long-byte()-com-azure-storage-blob-models-appendblobrequestconditions-java-time-duration-com-azure-core-util-context)

Blob(对象)存储简介  : https://docs.azure.cn/zh-cn/storage/blobs/storage-blobs-introduction

【Azure 存储服务】使用 AppendBlobClient 对象实现对Blob进行追加内容操作的更多相关文章

  1. 【Azure 存储服务】代码版 Azure Storage Blob 生成 SAS (Shared Access Signature: 共享访问签名)

    问题描述 在使用Azure存储服务,为了有效的保护Storage的Access Keys.可以使用另一种授权方式访问资源(Shared Access Signature: 共享访问签名), 它的好处可 ...

  2. 解读 Windows Azure 存储服务的账单 – 带宽、事务数量,以及容量

    经常有人询问我们,如何估算 Windows Azure 存储服务的成本,以便了解如何更好地构建一个经济有效的应用程序.本文我们将从带宽.事务数量,以及容量这三种存储成本的角度探讨这一问题. 在使用 W ...

  3. 玩转Windows Azure存储服务——网盘

    存储服务是除了计算服务之外最重要的云服务之一.说到云存储,大家可以想到很多产品,例如:AWS S3,Google Drive,百度云盘...而在Windows Azure中,存储服务却是在默默无闻的工 ...

  4. 【Azure 存储服务】.NET7.0 示例代码之上传大文件到Azure Storage Blob

    问题描述 在使用Azure的存储服务时候,如果上传的文件大于了100MB, 1GB的情况下,如何上传呢? 问题解答 使用Azure存储服务时,如果要上传文件到Azure Blob,有很多种工具可以实现 ...

  5. 【JAVA使用XPath、DOM4J解析XML文件,实现对XML文件的CRUD操作】

    一.简介 1.使用XPath可以快速精确定位指定的节点,以实现对XML文件的CRUD操作. 2.去网上下载一个“XPath帮助文档”,以便于查看语法等详细信息,最好是那种有很多实例的那种. 3.学习X ...

  6. 玩转Windows Azure存储服务——高级存储

    在上一篇我们把Windows Azure的存储服务用作网盘,本篇我们继续挖掘Windows Azure的存储服务——高级存储.高级存储自然要比普通存储高大上的,因为高级存储是SSD存储!其吞吐量和IO ...

  7. 【Azure 存储服务】Python模块(azure.cosmosdb.table)直接对表存储(Storage Account Table)做操作示例

    什么是表存储 Azure 表存储是一项用于在云中存储结构化 NoSQL 数据的服务,通过无结构化的设计提供键/属性存储. 因为表存储无固定的数据结构要求,因此可以很容易地随着应用程序需求的发展使数据适 ...

  8. 【Azure 存储服务】Java Azure Storage SDK V12使用Endpoint连接Blob Service遇见 The Azure Storage endpoint url is malformed

    问题描述 使用Azure Storage Account的共享访问签名(Share Access Signature) 生成的终结点,连接时遇见  The Azure Storage endpoint ...

  9. 【Azure 存储服务】如何把开启NFS 3.0协议的Azure Blob挂载在Linux VM中呢?(NFS: Network File System 网络文件系统)

    问题描述 如何把开启NFS协议的Azure Blob挂载到Linux虚拟机中呢? [答案]:可以使用 NFS 3.0 协议从基于 Linux 的 Azure 虚拟机 (VM) 或在本地运行的 Linu ...

  10. 【Azure 存储服务】Hadoop集群中使用ADLS(Azure Data Lake Storage)过程中遇见执行PUT操作报错

    问题描述 在Hadoop集中中,使用ADLS 作为数据源,在执行PUT操作(上传文件到ADLS中),遇见 400错误[put: Operation failed: "An HTTP head ...

随机推荐

  1. 贪心算法(Java)

    贪心算法 文章目录 贪心算法 0.写在前面 1.贪心算法的基本要素 1.1 贪心选择性质 1.2 最优子结构性质 1.3 贪心算法与动态规划算法的差异 2.贪心算法的特点 3.贪心法的正确性证明 4. ...

  2. dotNetCore创建Windows服务程序并安装服务

    一.创建控制台程序 二.在项目中添加新建项,选择Windows服务类型. 此时会出现一个错误提示,这是因为尚未添加windows服务控制引用造成的. 三.添加Nuget包,System.Service ...

  3. Python Type Hint中Optional[str]=None和str=None的区别

    Python Type Hint中Optional[str]=None和str=None的区别 1 问题来源 在读到Fluent Python, 2ed Edition, P260时产生了一些疑问: ...

  4. 基于Quartz.Net通过反射进行任务调度

    通过反射加载任务调度 需求: 因为有些任务需要进行各种定时操作,因此将 Quartz.Net 简单封装了一下使用: 希望通过上传 dll 来进行每个任务的调度,所以写了个反射调度示例: Program ...

  5. 什么是RPA?

    RPA是Robotic Process Automation(机器人流程自动化)的简称,是以软件机器人及人工智能为基础的业务过程自动化科技.它让软件机器人自动处理大量重复的.基于规则的工作流程任务,能 ...

  6. Java Swing的练习感悟

    感悟心得 这还是我第一次使用Java Swing写代码呢,直接就是趣味性拉满! 在相关的界面代码的基础上,在必要的位置嵌入Java代码,就可以很好的实现啦! 简单的嘞! (有兴趣的各位可以选择去浅浅地 ...

  7. scrcpy软件的使用

    一.scrcpy软件介绍: scrcpy是通过adb调试的方式来将手机屏幕投到电脑上,并可以通过电脑控制您的Android设备.它可以通过USB连接,也可以通过Wifi连接(类似于隔空投屏),而且不需 ...

  8. springboot--配置格式文件

    修改端口号的三种方法 1.server.port = 80 2.新建application.yml文件. 3.新建application.yaml文件. 配置文件加载顺序: 当三个文件都存在时prop ...

  9. Redis事件机制(未写完)

    Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件: 文件事件:Redis通过套接字与客户端连接,文件事件是服务器对套接字操作的抽象. 时间事件:Redis服务器中的一些操作需要给定的时间 ...

  10. 【深入浅出 Yarn 架构与实现】5-2 Yarn 三种调度器

    本篇文章将深入介绍 Yarn 三种调度器.Yarn 本身作为资源管理和调度服务,其中的资源调度模块更是重中之重.下面将介绍 Yarn 中实现的调度器功能,以及内部执行逻辑. 一.简介 Yarn 最主要 ...