全部產品
Search
文件中心

ApsaraDB for MongoDB:MongoDB SSL加密串連對執行個體效能影響測試

更新時間:Oct 31, 2024

本文介紹如何測試SSL加密對ApsaraDB for MongoDB執行個體的效能影響。

測試環境

建立ECS執行個體和ApsaraDB for MongoDB複本集執行個體。如何建立,請參見棄置站台集執行個體通過控制台使用ECS執行個體(快捷版)

本次測試的執行個體配置如下:

  • MongoDB執行個體

    • 執行個體架構:複本集

    • 主備節點數:三節點

    • 唯讀節點數:一隻讀節點

    • 執行個體規格:1核2 GB(通用型)

    • 執行個體版本:MongoDB 4.2

  • ECS執行個體

    • 執行個體規格:ecs.e-c1m2.large(2核4 GB)

測試方法

匯入測試資料

本文使用YCSB匯入1000000條資料,資料預設匯入到ycsb資料庫裡的usertable集合中。匯入資料的命令如下。

./bin/ycsb load mongodb -s -p workload=site.ycsb.workloads.CoreWorkload -p recordcount=1000000 -p mongodb.url="mongodb://root:******@dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717/admin?readPreference=secondary&replicaSet=mgset-82894753" -threads 8

YCSB產生的集合結構如下。

{
  "_id": "user1352498093671118016",
  "field1": "BinData(0,\"L1s/KS8+MlYhISUuIz8qNVx9PTRuNiooNlolPVMzJCE0ODNuP15zLjB6NSU4LV0pPC98LkovOFttOyQkL1chNV8tOlxrKlstPkp3IzkiLTcuLU93ITRqOzEwPScsIEx5L1o5JQ==\")",
  "field0": "BinData(0,\"I0I/NDAmNUkrLFA3PEA5IEI3KC4qKEJ1LCc6JV8rJlo7KUR3MDRqMk95KjhoIlUjLy1qMls9OFU7PTpuKTZsNlUpMVYvNSI4KDlwK1trIUpnMlInKSAmNDg8LSpmOl85PkozMA==\")",
  "field7": "BinData(0,\"NzxyPjUkNiYsOEp1MUIpIDA0M1U/JE1nMjMmODhkIko/LEh1NSRqKl1jKDBqJjMqMiw8PSc6KjgsNEwrLFkpKFh9OzQ+MS86LiB4P1oxLVxjIiQgMCdsM0AhO0QzNTFqI1dpMw==\")",
  "field6": "BinData(0,\"MVhpIj1uI0RlJ1AtP0t/LkAxMVp9MlU3KFJpK0Z3M0M/OE55IFBhPyA6Njp+L1tnJjh4Iic4K1F/Oi9gKz44PFFpPE5jNydqKUJrPyV4KDY2LDd0Nic6PC9wPDhgNykwOj10Iw==\")",
  "field9": "BinData(0,\"OEA/NDRwLilwLzVoMz5oIzVuLStoPlF9NVU5MVd3PDUkPV0nPl07OU05LkYnKDQ8L0VrIkwlJDg+NCRuIitoNjV4PkM9NCt0LyE4PCFkKUI/LVwxKjdsMkgnICsgKFQrJit8Lg==\")",
  "field8": "BinData(0,\"P0J/P0ozMCBwIjJwP0IpJUMlPkhhIkI3MU91OjUmMFM9NEBhJD8+OEdpJDh2OyxmMCQkKUgnJjYoKC0kKVMnKTQuODp6JjVwKSRwI1FhJy88PVAhPiE2OCMsKlYvJF1xOEUrJA==\")",
  "field3": "BinData(0,\"Mjc6KE4zJTEuJCJ4P18lNCZ4NkAtKTAwLFUvMDEsOzUkIEs3MVEzKT4mL1Q5IVF9N0N3O1snIUZ1L0k9IUY7IV8nNiQ+KTJ0I097Llg1MjE0PCQ4KDV2KzYsIDtwNzhuKkQrMA==\")",
  "field2": "BinData(0,\"Jit6L0olLTByJzpuPygyKSE8Li9iK00xIig4PVQhKTBqLkI/O0N9Izs+NVk/ISVmIy14Nko3NyxoI1Z1Pyo4JVt/MylsK1t9PS9yP1kzJFMjPF43P1l5P1xtPDAqLF57IiAsPQ==\")",
  "field5": "BinData(0,\"NSd2NiskPzcmITU4Oz58NzliP0ZpOz5wKiI0OVhlNDx0PlV1OllnNzJiOyMmOF45IEg7OD90KUZnP0g5OUJzK0M/OyN4O15lPzA6LTUgNUw9NDVoL0QrOTssISQ2KCw6KUJlKg==\")",
  "field4": "BinData(0,\"NDNkKjFuPyxsJzEmNzMqLVJzL0YpMTNwMjs8MTRiNEYtLE05P0I9NEU1LS4yNEFpNjUsOUs9JEs1IU5jOF5vIjpiNTMuKSY6K0s1LDp2JEp9KyFiIS1+J1U/LkoxMEp5OzJiIw==\")"
}

運行測試指令碼

由於YCSB不支援在SSL串連方式下進行壓測,因此本文中將採用測試指令碼進行壓測。指令碼具體內容如下。

import time
import pymongo
import datetime
import logging as log
from functools import wraps

log.basicConfig(
    level=log.INFO, format='%(asctime)s - [line %(lineno)d] : %(levelname)s - %(message)s')
console = log.StreamHandler()
console.setLevel(log.INFO)
formatter = log.Formatter(
    '%(asctime)s - [line %(lineno)d] : %(levelname)s - %(message)s')
console.setFormatter(formatter)


def time_recorder():
    def inner(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            elapsed_time = (end_time - start_time) * 1000

            print_latency = kwargs.get("print_latency", False)
            return_latency = kwargs.get("return_latency", False)

            if print_latency:
                log.info(
                    f"Function {func.__name__} took {elapsed_time:.4f} millseconds to execute.")

            if return_latency:
                return result, elapsed_time
            return result
        return wrapper
    return inner


@time_recorder()
def build_client_without_ssl(**kwargs):
    client = pymongo.MongoClient(
        "mongodb://root:******@dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717/admin?readPreference=secondary&replicaSet=mgset-82894753")
    return client


@time_recorder()
def build_client_with_ssl(**kwargs):
    client = pymongo.MongoClient(
        "mongodb://root:******@dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717/admin?readPreference=secondary&replicaSet=mgset-82894753&tls=true&tlsAllowInvalidHostnames=true&tlsCAFile=/root/ApsaraDB-CA-Chain.pem")
    return client


@time_recorder()
def find_one(client, **kwargs):
    client["ycsb"]["usertable"].find_one()


def test_connection():
    client = build_client_without_ssl(print_latency=True)
    find_one(client)

    client = build_client_with_ssl(print_latency=True)
    find_one(client)


test_connection()


@time_recorder()
def test(with_ssl=False, repeat=5000, **kwargs):
    log.info(f"test, with_ssl: {with_ssl}, repeat: {repeat}")
    ssl_suffix = "without-ssl"
    if with_ssl:
        ssl_suffix = "with-ssl"
    suffix = f"{repeat}-{ssl_suffix}.log"
    only_connection_file = f"only-connection-{suffix}"
    first_query_file = f"first-query-{suffix}"
    connection_and_first_query_file = f"connection-and-first-query-{suffix}"

    only_connection_f = open(only_connection_file, 'w')
    first_query_f = open(first_query_file, 'w')
    connection_and_first_query_f = open(connection_and_first_query_file, 'w')

    for _ in range(repeat):
        client = None
        connection_latency = 0

        if with_ssl:
            client, connection_latency = build_client_with_ssl(
                return_latency=True)
        else:
            client, connection_latency = build_client_without_ssl(
                return_latency=True)

        _, query_latency = find_one(client, return_latency=True)

        only_connection_f.write(f"{connection_latency}\n")
        first_query_f.write(f"{query_latency}\n")
        connection_and_first_query_f.write(f"{connection_latency + query_latency}\n")

    only_connection_f.close()
    first_query_f.close()
    connection_and_first_query_f.close()

    log.info(f"test end, write to {only_connection_file}, {first_query_file} and {connection_and_first_query_file}")


test(False, 5000, print_latency=True)
test(True, 5000, print_latency=True)


@time_recorder()
def run(client, log_file, repeat=5000, **kwargs):
    start = time.time()
    log.info(f"start to query, repeat: {repeat}")

    with open(log_file, 'w') as f:
        for _ in range(repeat):
            latency = find_one(client, return_latency=True)
            f.write(f"{latency}\n")

    end = time.time()
    total = (end - start) * 1000
    avg = total / repeat
    log.info(
        f"end query, repeat: {repeat}, total: {total} millseconds, avg: {avg} millseconds")


client = build_client_without_ssl()
run(client, "without-ssl.log")

client = build_client_with_ssl()
run(client, "with-ssl.log")

測試時,您可以根據需求修改以下參數:

  • client:請將pymongo.MongoClient()參數括弧內的串連串、使用者名稱和密碼替換為執行個體的真實資訊。

  • repeat:運行次數,本次測試將重複運行5000次,您可以根據實際需求修改運行次數。

指令碼的輸出資訊如下:

  • only-connection:記錄建立串連的耗時。

  • first-query:記錄建立串連以後第一次查詢的耗時,查詢為簡單的find_oneclient["ycsb"]["usertable"].find_one()

  • connection-and-first-query:該項為only-connection與first-query兩項之和,記錄建立串連到第一次查詢完成的總耗時。

  • latency:記錄複用Client情況下,5000次查詢的平均耗時。

測試結果

以下資料為5000次測試下的平均值。

測試項

未使用SSL串連的耗時(單位:ms)

使用SSL串連的耗時(單位:ms)

建立串連的耗時

3.7009

19.8515

建立串連後的第一次查詢的耗時

20.7456

43.5884

建立串連到第一次查詢完成的總耗時

24.4465

63.4399

複用Client情況下查詢的平均耗時

0.7506

0.7643

經測試,使用SSL串連和未使用SSL串連的耗時主要差距在於建立串連步驟。如果複用已經建立好的串連,則使用SSL串連和不使用SSL串連方式下查詢的平均耗時差距不大。造成該結果的原因主要包括以下兩個方面:

  • TLS(Transport Layer Security,傳輸層安全性協議)雙方建立串連多出來的RTT(Round-Trip Time,往返時延)。

    使用串連池(Connection Pooling)的情況下,建立串連多出來的RTT開銷會被均攤,您在使用SSL串連時盡量複用已經建立好的Client,能夠有效避免每次建立串連產生的TLS RTT時間。

  • 建立串連後,訊息體使用加密解密演算法的額外開銷。

    現在CPU對訊息體的加密解密速度已足夠快,查詢耗時可能更多體現在系統的其他方面(例如磁碟IO、隊列等待等),加密解密對整體效能的影響相對來說就會變小。