"Указанный список заблокирован недопустим" при параллельной загрузке BLOB-объектов

У меня есть (довольно большое) приложение Azure, которое загружает (довольно большие) файлы параллельно с хранилищем BLOB-объектов Azure.

В нескольких процентах загрузок я получаю исключение:

The specified block list is invalid.

System.Net.WebException: The remote server returned an error: (400) Bad Request.

Это когда мы запускаем довольно безобидный фрагмент кода для загрузки большого двоичного объекта параллельно хранилищу Azure:

    public static void UploadBlobBlocksInParallel(this CloudBlockBlob blob, FileInfo file) 
    {
        blob.DeleteIfExists();
        blob.Properties.ContentType = file.GetContentType();
        blob.Metadata["Extension"] = file.Extension;

        byte[] data = File.ReadAllBytes(file.FullName);

        int numberOfBlocks = (data.Length / BlockLength) + 1;
        string[] blockIds = new string[numberOfBlocks];

        Parallel.For(
            0, 
            numberOfBlocks, 
            x =>
        {
            string blockId = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
            int currentLength = Math.Min(BlockLength, data.Length - (x * BlockLength));

            using (var memStream = new MemoryStream(data, x * BlockLength, currentLength))
            {
                var blockData = memStream.ToArray();
                var md5Check = System.Security.Cryptography.MD5.Create();
                var md5Hash = md5Check.ComputeHash(blockData, 0, blockData.Length);

                blob.PutBlock(blockId, memStream, Convert.ToBase64String(md5Hash));
            }

            blockIds[x] = blockId;
        });

        byte[] fileHash  = _md5Check.ComputeHash(data, 0, data.Length);
        blob.Metadata["Checksum"] = BitConverter.ToString(fileHash).Replace("-", string.Empty);
        blob.Properties.ContentMD5 = Convert.ToBase64String(fileHash);

        data = null;
        blob.PutBlockList(blockIds);
        blob.SetMetadata();
        blob.SetProperties();
    }

Все очень загадочно; Я думаю, что алгоритм, который мы используем для расчета списка блоков, должен создавать строки одинаковой длины...

1 ответ

Мы столкнулись с подобной проблемой, однако мы не указывали ни одного идентификатора блока и даже не использовали его нигде. В нашем случае мы использовали:

using (CloudBlobStream stream = blob.OpenWrite(condition))
{
   //// [write data to stream]

   stream.Flush();
   stream.Commit();
}

Это вызвало бы The specified block list is invalid. ошибки при распараллеленной нагрузке. Переключение этого кода для использования UploadFromStream(…) Метод при буферизации данных в памяти исправил проблему:

using (MemoryStream stream = new MemoryStream())
{
   //// [write data to stream]

   stream.Seek(0, SeekOrigin.Begin);
   blob.UploadFromStream(stream, condition);
}

Очевидно, что это может иметь отрицательные последствия для памяти, если слишком много данных буферизуется в памяти, но это упрощение. Стоит отметить, что UploadFromStream(...) использования Commit() в некоторых случаях, но проверяет дополнительные условия, чтобы определить лучший метод для использования.

Это исключение также может произойти, когда несколько потоков открывают поток в большой двоичный объект с одним и тем же именем файла и пытаются одновременно выполнить запись в этот большой двоичный объект.

ПРИМЕЧАНИЕ: это решение основано на коде Azure JDK, но я думаю, мы можем с уверенностью предположить, что чистая версия REST будет иметь тот же эффект (как и любой другой язык на самом деле).

Поскольку я потратил весь рабочий день на борьбу с этой проблемой, даже если это на самом деле угловой случай, я оставлю здесь записку, может быть, это кому-то поможет.

Я все сделал правильно. У меня были идентификаторы блоков в правильном порядке, у меня были идентификаторы блоков одинаковой длины, у меня был чистый контейнер без остатков некоторых предыдущих блоков (эти три причины - единственные, которые я смог найти через Google).

Была одна загвоздка: я создавал свой черный список для фиксации через

CloudBlockBlob.commitBlockList(Iterable<BlockEntry> blockList)

с использованием этого конструктора:

BlockEntry(String id, BlockSearchMode searchMode)

прохождение

BlockSearchMode.COMMITTED

во втором аргументе. И ЭТО оказалось основной причиной. Как только я изменил его на

BlockSearchMode.UNCOMMITTED

и в конечном итоге приземлился на конструктор с одним параметром

BlockEntry(String id)

который по умолчанию использует UNCOMMITED, фиксация списка блокировки сработала, и blob был успешно сохранен.

Другие вопросы по тегам