Azure Service Bus Relay - включение сжатия - программирование

Azure Service Bus Relay - включение сжатия

У нас возникают проблемы со скоростью при использовании Azure Service Bus Relay с netTcpRelayBinding и basicHttpRelayBinding. При небольших размерах сообщений (10 КБ) реле работает с низкой задержкой (100 мс), но по мере увеличения размера сообщения (100 КБ) мы наблюдаем кажущееся случайное время отклика (600 мс-1000 мс). Мы хотели бы улучшить стоимость задержки для больших сообщений.

Использует сжатие сообщения (gzip, protobuf-net и т.д.), поддерживаемых через реле служебной шины? Кто-нибудь имел успех с включением как сжатия запроса/ответа через реле? тривиально поддерживать сжатие ответов через IIS, но мы хотели бы поддержать сжатие запросов для улучшения наших латентных затрат. Поскольку мы не можем профилировать реле с помощью Fiddler, как мы знаем, что сообщение все еще сжато, когда оно проходит через реле?


Интересным моментом, который мы обнаружили, является то, что введение задержки между последующими сообщениями (2 с) мы получаем более высокую производительность (100 КБ - 200 мс). Может ли быть, что большие сообщения автоматически затухают? Было бы неплохо узнать об уменьшении размера сообщения, которое вызывает условие дросселирования.

Для нашего теста - мы просто отправляем случайную строку сообщения в сервисное реле и эхо возвращаем строку запроса с сервера. Мы пробовали этот клиент/сервер из нескольких географических местоположений (чтобы исключить проблемы с файерволом/веб-фильтром) и испытывали такое же латентное поведение.

Сторона сервера

public class ServiceRelayProfiler : IServiceRelayProfiler
{
    public string HelloProfiler(string name)
    {
        return string.Format("Hello {0}", name);
    }
}

Сторона клиента

ChannelFactory<IServiceRelayProfiler> channelFactory = new ChannelFactory<IServiceRelayProfiler>("helloProfilerTcp");
IServiceRelayProfiler channel = channelFactory.CreateChannel();
string message = RandomString(100000); // 100K
for (int i = 0; i < 100; i++)
{
    DateTime start = DateTime.Now;
    string response = channel.HelloProfiler(message);
    DateTime end = DateTime.Now;
    TimeSpan duration = end - start;
    Console.WriteLine("Response is: {0} at {1}\tDuration: {2}ms", response.Substring(0, 20) + "....", end, duration.Milliseconds);
    //Thread.Sleep(2000); // delay makes response times more consistent
}
4b9b3361

Ответ 1

Это не полный ответ, но на стороне сервера вы можете добавить его в global.asax.cs, чтобы разрешить декомпрессию запроса:

public class MvcApplication : System.Web.HttpApplication
{    
    protected void Application_BeginRequest(Object sender, EventArgs e)
    {
        //Activate request decompression
        string contentEncoding = Request.Headers["Content-Encoding"];
        if (contentEncoding != null && contentEncoding.Equals("gzip", StringComparison.CurrentCultureIgnoreCase))
        {
            Request.Filter = new GZipStream(Request.Filter, CompressionMode.Decompress, true);
        }
    }
}

Ответ 2

Одна вещь, которую вы могли бы попробовать - сжать сообщения через System.IO.Compression(см. ниже - вы можете просто добавить в качестве расширения).

Но в вопросе очереди/дросселирования. Большие сообщения всегда будут выделять сеть больше, чем меньшие/оптимизированные куски. Если вы можете разбить данные менее 50 тыс. Или меньше или использовать udp, он может передавать быстрее с меньшим количеством очередей и валидацией, чем на tcp.

Абсолютное ограничение размера пакета TCP составляет 64 Кбайт (65535 байт), но по практичности это намного больше размера любого пакета, который вы увидите, поскольку более низкие уровни (например, ethernet) имеют более низкие размеры пакетов.

MTU (Maximum Transmission Unit) для Ethernet, например, составляет 1500 байтов. Некоторые типы сетей (например, Token Ring) имеют более крупные MTU, а некоторые типы имеют меньшие MTU, но значения фиксируются для каждой физической технологии.

отсюда: максимальный размер пакета для TCP-соединения

Все будет идти гладко, когда вы разобьетесь, многопользовательские игры сделают это, и они также используют udp больше, чтобы ограничить дополнительную проверку (внедрение надежного udp для проверки отдельных сообщений и заказа при необходимости только).

Класс CompressionUtil, вставлять и вставлять сообщения перед отправкой/после получения: https://gist.github.com/drawcode/8948293

public static class CompressUtil {

    public static string ToCompressed(this string val) {
        if (!IsStringCompressed(val)) {
            return CompressString(val);
        }
        return val;
    }

    public static string ToDecompressed(this string val) {
        if (IsStringCompressed(val)) {
            return DecompressString(val);
        }
        return val;
    }

    public static string CompressString(string text) {
        byte[] buffer = Encoding.UTF8.GetBytes(text);
        var memoryStream = new MemoryStream();
        using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) {
            gZipStream.Write(buffer, 0, buffer.Length);
        }

        memoryStream.Position = 0;

        var compressedData = new byte[memoryStream.Length];
        memoryStream.Read(compressedData, 0, compressedData.Length);

        var gZipBuffer = new byte[compressedData.Length + 4];
        Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length);
        Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4);
        return Convert.ToBase64String(gZipBuffer);
    }

    public static string DecompressString(string compressedText) {
        byte[] gZipBuffer = Convert.FromBase64String(compressedText);
        using (var memoryStream = new MemoryStream()) {
            int dataLength = BitConverter.ToInt32(gZipBuffer, 0);
            memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4);

            var buffer = new byte[dataLength];

            memoryStream.Position = 0;
            using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) {
                gZipStream.Read(buffer, 0, buffer.Length);
            }

            return Encoding.UTF8.GetString(buffer);
        }
    }

    public static bool IsStringCompressed(string data) {
        if (IsStringCompressedGZip(data) || IsStringCompressedPKZip(data)) {
            return true;
        }
        return false;
    }

    public static bool IsStringCompressedGZip(string data) {
        return CheckSignatureString(data, 3, "1F-8B-08");
    }

    public static bool IsStringCompressedPKZip(string data) {
        return CheckSignatureString(data, 4, "50-4B-03-04");
    }

    public static bool CheckSignatureFile(string filepath, int signatureSize, string expectedSignature) {
        if (String.IsNullOrEmpty(filepath))
            throw new ArgumentException("Must specify a filepath");
        if (String.IsNullOrEmpty(expectedSignature))
            throw new ArgumentException("Must specify a value for the expected file signature");
        using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
            if (fs.Length < signatureSize)
                return false;
            byte[] signature = new byte[signatureSize];
            int bytesRequired = signatureSize;
            int index = 0;
            while (bytesRequired > 0) {
                int bytesRead = fs.Read(signature, index, bytesRequired);
                bytesRequired -= bytesRead;
                index += bytesRead;
            }
            string actualSignature = BitConverter.ToString(signature);
            if (actualSignature == expectedSignature)
                return true;
            else
                return false;
        }
    }

    public static bool CheckSignatureString(string data, int signatureSize, string expectedSignature) {

        byte[] datas = Encoding.ASCII.GetBytes(data);
        using (MemoryStream ms = new MemoryStream(datas)) {
            if (ms.Length < signatureSize)
                return false;
            byte[] signature = new byte[signatureSize];
            int bytesRequired = signatureSize;
            int index = 0;
            while (bytesRequired > 0) {
                int bytesRead = ms.Read(signature, index, bytesRequired);
                bytesRequired -= bytesRead;
                index += bytesRead;
            }
            string actualSignature = BitConverter.ToString(signature);
            if (actualSignature == expectedSignature)
                return true;
            else
                return false;
        }
    }
}