Python — подробное объяснение gRPC и практическая схема предотвращения ям (Часть 2)

Python

предисловие

В предыдущей статье была описана концепция службы RPC, базовое использование gRPC и использование синтаксиса прототипа. Однако, когда мы фактически развернем сервис gRPC в производственной среде, мы столкнемся со многими проблемами.Первое, что нужно учитывать, это проблема аутентификации данных протокола.Во-вторых, gRPC также поддерживает режим потоковой связи.Эта статья сделает введение иллюстрировать.

RPC-аутентификация

Службы RPC обычно используются во внутренней сети службы, но есть также службы RPC, которые существуют во внешней сети, но независимо от того, какая это служба RPC, будь то http2.Для механизма аутентификации с шифрованием личности клиенту и серверу требуется для выполнения доверенной аутентификации для обеспечения безопасности данных.

Общий метод проверки подлинности с шифрованием для службы RPC

Шифрование канала на основе SSL/TLSОбычно механизм аутентификации заключается в шифровании канала передачи через TLS/SSL, чтобы предотвратить утечку конфиденциальных данных в сообщениях запроса и ответа. Используются три основных сценария:

  • Внутренние микросервисы напрямую открыты для конечной стороны, например мобильное приложение, ТВ, мультиэкран и т. д. Не существует единого шлюза API/SLB для безопасного доступа и аутентификации;
  • Внутренние микросервисы напрямую открыты для порталов управления или эксплуатации и обслуживания, развернутых в демилитаризованной зоне;
  • Серверные микросервисы напрямую открыты для сторонних партнеров/каналов. В дополнение к вышеупомянутым широко используемым межсетевым сценариям, для некоторых бизнес-сценариев с высокими требованиями к уровню безопасности, даже для связи в интрасети, если требуется связь между узлами/VM/контейнерами, обязательно шифрование канала передачи. В этом сценарии, даже если есть только вызовы RPC из каждого модуля интрасети, все равно необходимо выполнить SSL/TLS.

Типичный сценарий использования SSL/TLS выглядит следующим образом:

在这里插入图片描述

В настоящее время наиболее широко используемым инструментом/библиотекой классов SSL/TLS является OpenSSL, который представляет собой протокол безопасности, обеспечивающий безопасность и целостность данных для сетевого взаимодействия, включая основные криптографические алгоритмы, часто используемые функции управления инкапсуляцией ключей и сертификатов и протокол SSL.

Большинство веб-сайтов с шифрованием SSL используют пакет программного обеспечения с открытым исходным кодом под названием OpenSSL, который широко используется важными веб-сайтами, такими как онлайн-банкинг, онлайн-платежи, веб-сайты электронной коммерции, порталы и электронная почта, поскольку это также наиболее широко используемый метод безопасной передачи. в интернете.

Отдельное шифрование для конфиденциальных данныхНекоторые вызовы RPC не связаны с передачей конфиденциальных данных или доля конфиденциальных полей невелика.Чтобы максимизировать пропускную способность и уменьшить задержку вызова, конфиденциальные поля HTTP/TCP + обычно шифруются отдельно, что не только обеспечивает конфиденциальную информацию. снижает потери производительности, вызванные использованием зашифрованных каналов SSL/TLS.Для собственной библиотеки классов SSL JDK это улучшение производительности особенно очевидно.

Это работает следующим образом:

在这里插入图片描述

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

У этой схемы есть два основных недостатка:

  • Идентификация конфиденциальной информации может быть предвзятой, ее легко упустить или чрезмерно защитить.Необходимо интерпретировать законы и положения о защите данных и конфиденциальности, а в разных странах используются разные определения конфиденциальных данных, что создает много трудностей для идентификации;
  • Легко пропустить, когда интерфейс обновляется, например, при разработке новых полей и забывании определить, являются ли это конфиденциальными данными.

метод проверки подлинности с шифрованием gRPC

Для gRPC протокол SSL/TLS также является основным методом шифрования и аутентификации. Протокол SSL/TLS использует метод шифрования с открытым ключом. Клиент запрашивает у сервера открытый ключ, а затем использует открытый ключ для шифрования информации. После того, как сервер получит зашифрованный текст, он использует свой собственный ключ для расшифровки закрытого ключа.

SSL/TLSSSL/TLS делится на одностороннюю аутентификацию и двустороннюю аутентификацию.В реальном бизнесе широко используется односторонняя аутентификация, то есть клиент аутентифицирует сервер, а сервер не аутентифицирует клиента. Процесс сертификации выглядит следующим образом:

  • Клиент передает номер версии клиентского SSL-протокола, поддерживаемый тип алгоритма шифрования, сгенерированное случайное число и другую необязательную информацию на сервер;
  • Сервер возвращает ответ рукопожатия и передает номер версии, тип алгоритма шифрования, случайное число и другую связанную информацию клиенту для подтверждения протокола SSL;
  • Сервер отправляет клиенту свой открытый ключ;
  • Клиент проверяет сертификат сервера, и проверка действительности сервера включает: истек ли срок действия сертификата, надежен ли ЦС, выдавший сертификат сервера, и может ли открытый ключ сертификата эмитента правильно разблокировать «сертификат эмитента». цифровая подпись» сертификата сервера. , соответствует ли доменное имя в сертификате сервера фактическому доменному имени сервера и т. д.;
  • Клиент случайным образом генерирует «симметричный пароль» для последующей связи, затем шифрует его с помощью открытого ключа сервера и передает зашифрованный «пре-мастер-пароль» на сервер;
  • Сервер расшифрует зашифрованный «предварительный мастер-пароль» с помощью собственного закрытого ключа, а затем выполнит ряд шагов для создания мастер-пароля;
  • Клиент отправляет серверу сообщение, указывающее, что последующая передача данных будет использовать мастер-пароль в качестве симметричного ключа, и в то же время информирует сервер о том, что процесс рукопожатия клиента завершен;
  • Сервер отправляет клиенту сообщение, указывающее, что последующая передача данных будет использовать мастер-пароль в качестве симметричного ключа, и в то же время информирует клиента о том, что процесс рукопожатия на стороне сервера завершен;
  • Часть рукопожатия SSL заканчивается, безопасный канал SSL устанавливается, и клиент и сервер начинают шифровать данные одним и тем же симметричным ключом, а затем передавать их через Socket.

Практикуйте аутентификацию TLS gRPC

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

создать сертификат

openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 3650 -out server.crt

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

Мы можем определить общее имя какrpc_service, все возвраты каретки будут генерироваться после заполненияserver.keyа такжеserver.crtдокумент

Развернуть службу gRPC

Продолжите определение файла proto из предыдущей статьи (blog.CSDN.net/dream_Су Ченг сторона…) Реализуйте серверный код server.py:

from concurrent import futures
import time
import grpc
import test_pb2
import test_pb2_grpc

# 实现 proto 文件中定义的 SearchService
class RequestRpc(test_pb2_grpc.SearchService):
    # 实现 proto 文件中定义的 rpc 调用
    def doRequest(self, request, context):
        return test_pb2.Search(query = 'hello {msg}'.format(msg = request.name)) # return的数据是符合定义的SearchResponse格式

def serve():
    # 启动 rpc 服务,这里可定义最大接收和发送大小(单位M),默认只有4M
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), options=[
        ('grpc.max_send_message_length', 100 * 1024 * 1024),
        ('grpc.max_receive_message_length', 100 * 1024 * 1024)])
    
    test_pb2_grpc.add_SearchServiceServicer_to_server(RequestRpc(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    try:
        while True:
            time.sleep(60*60*24) # one day in seconds
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()

Код клиента client.py:

import grpc
import helloworld_pb2
import helloworld_pb2_grpc

def run():
    # 连接 rpc 服务器
    channel = grpc.insecure_channel('localhost:50051')
    # 调用 rpc 服务
    stub = test_pb2_grpc.SearchServiceStub(channel)
    response = stub.doRequest(test_pb2.SearchRequest(query='henry'))
    print("client received: ", response)

if __name__ == '__main__':
    run()

Добавить аутентификацию TLS

будет генерироватьserver.keyа такжеserver.crtФайл помещается в каталог проекта server.py, и если client.py находится в другом проекте,server.crtФайл также необходимо поместить в каталог проекта client.py, Мы добавляем логику аутентификации TLS на сервер:

from concurrent import futures
import time
import grpc
import test_pb2
import test_pb2_grpc

# 实现 proto 文件中定义的 SearchService
class RequestRpc(test_pb2_grpc.SearchService):
    # 实现 proto 文件中定义的 rpc 调用
    def doRequest(self, request, context):
        return test_pb2.Search(query = 'hello {msg}'.format(msg = request.name)) # return的数据是符合定义的SearchResponse格式

def serve():
    # 启动 rpc 服务,这里可定义最大接收和发送大小(单位M),默认只有4M
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), options=[
        ('grpc.max_send_message_length', 100 * 1024 * 1024),
        ('grpc.max_receive_message_length', 100 * 1024 * 1024)])
    
    test_pb2_grpc.add_SearchServiceServicer_to_server(RequestRpc(), server)
    
    server.add_insecure_port('[::]:50051') # 注释掉这句不安全的启动服务的方法,加入以下写法:
    # read in key and certificate
        with open('server.key', 'rb') as f:
            private_key = f.read()
        with open('server.crt', 'rb') as f:
            certificate_chain = f.read()

        # create server credentials
        server_credentials = grpc.ssl_server_credentials(
            ((private_key, certificate_chain,),))
        server.add_secure_port('[::]:50051', server_credentials)
    
    server.start()
    try:
        while True:
            time.sleep(60*60*24) # one day in seconds
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()

Соответствующая клиентская логика:

import grpc
import helloworld_pb2
import helloworld_pb2_grpc

def run():
    # 读取证书
    with open('server.crt', 'rb') as f:
        trusted_certs = f.read()
    credentials = grpc.ssl_channel_credentials(
        root_certificates=trusted_certs)
        
    # 连接 rpc 服务器,这里填入证书的COMMON NAME,我们定义的是rpc_service
    channel = grpc.secure_channel("{}:{}".format('localhost', 50051), credentials,
        options=(('grpc.ssl_target_name_override', "rpc_service",),
                 ('grpc.max_send_message_length', 100 * 1024 * 1024),
                 ('grpc.max_receive_message_length', 100 * 1024 * 1024)))    
    # 调用 rpc 服务
    stub = test_pb2_grpc.SearchServiceStub(channel)
    response = stub.doRequest(test_pb2.SearchRequest(query='henry'))
    print("client received: ", response)

if __name__ == '__main__':
    run()

Вышеуказанное может реализовать службу gRPC с аутентификацией TLS.

Потоковая связь с gRPC

способ потоковой передачи

Как и http-связь, grpc также является связью, основанной на режиме «запрос-ответ». В соответствии с различными бизнес-сценариями его можно разделить на:

  1. Клиент делает один запрос, и сервер отвечает один раз:
  2. Клиент делает запрос, а сервер отвечает потоком (фактически эквивалентно возврату клиенту нескольких фрагментов данных).
  3. Клиентский поток запрашивает, сервер отвечает один раз
  4. Запрос потоковой передачи клиента, ответ потоковой передачи сервера Подобно установлению соединения через сокет и установлению сеанса чата, сервер также может отправлять много данных клиенту.Данные, синхронизированные с клиентом, передаются клиенту в потоковом режиме.
    在这里插入图片描述

Конкретная реализация потоковой связи

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

def ListFeatures(self, request, context):
  left = min(request.lo.longitude, request.hi.longitude)
  right = max(request.lo.longitude, request.hi.longitude)
  top = max(request.lo.latitude, request.hi.latitude)
  bottom = min(request.lo.latitude, request.hi.latitude)
  for feature in self.db:
    if (feature.location.longitude >= left and
        feature.location.longitude <= right and
        feature.location.latitude >= bottom and
        feature.location.latitude <= top):
      yield feature

В python генератор реализован через ключевое слово yield, которое отвечает в потоке. Клиент может постепенно получать потоковый ответ от сервера через цикл for или генератор:

for feature in stub.ListFeatures(rectangle):

Когда использовать потоковую RPC

  1. Массивный пакет
  2. сцена в реальном времени

Обработка исключений для gRPC

С бурным развитием Интернета интернет-сервисы перестали быть монолитными приложениями, а стали микросервисами, состоящими из нескольких модулей.Каждый модуль можно самостоятельно расширять, уменьшать, развертывать самостоятельно и т. д. Подключаться к сети. Наше приложение должно изящно обрабатывать сетевые ошибки. Например, возникает сетевой джиттер, и взаимодействующая одноранговая машина просто снова подключается к сети.

Типы исключений для gRPC

gRPC имеет собственный набор кодов ошибок, похожих на коды состояния HTTP, каждый код ошибки представляет собой строку, например ВНУТРЕННЯЯ, ПРЕВРАЩЕНО, НЕДОСТУПНО. Мы часто сталкиваемся с такими ошибками, как "StatusCode=Unreachable, Detail="не удалось подключиться ко всем адресам"Анализ причин

  1. Запрос клиента RPC не достигает сервера, например, дрожание при синтаксическом анализе сети, изменение IP-адреса облачного хоста и т. д.
  2. Возникла проблема с экземпляром, инициализированным сервером, и он не может обработать запрос клиента.
  3. Соединение между клиентом RPC и сервером разорвано или соединение не установлено

Для описанной выше ситуации нам необходимо добавить механизм повторного подключения и механизм повторной передачи для обеспечения надежности обслуживания.

механизм пересоединения

Мы можем добавить механизм try exclude для захвата исключений при подключении к службам gRPC.Как только будет найдено исключение соединения, мы можем попытаться повторить соединение.Если в среде flask или структуре tornado, мы можем написать метод соединения как синглтон, а затем инкапсулировать запрос в фреймворк Добавить переподключение к механизму обработки исключений.Например, фляга может определить:

@app.errorhandler(Exception)
def handle_exception(e):
	# 此处添加e的类型判断,如果是RPC连接出错,执行重新连接的代码

Если это торнадо, вы можете определить:

    def log_exception(self, typ, value, tb):
        if issubclass(typ, RpcConnectError):
        		# 此处添加重新连接RPC的代码

Таким образом, вы можете убедиться, что исключения подключения RPC в вашей системе переподключены для соответствия требованиям надежности.

механизм повторной попытки

Канал gRPC может указать параметр options при его инициализации. Ранее мы указали максимальное количество отправленных и полученных байтов. Здесь также можно указать конфигурацию автоматического повтора. Подробную информацию см. по адресу:GitHub.com/PersonalPC/предложения…Прозрачный повторпрозрачная повторная попытка автоматически повторяется, когда логика приложения на сервере не получает запрос. Прозрачная повторная попытка может решить первый и второй пункт при анализе причин обращения, а также мы можем настроить повторную попытку самостоятельно, настроив параметр retryPolicy конфига сервиса.

在这里插入图片描述

Случай реализации:

options = [('grpc.max_send_message_length', 100 * 1024 * 1024),
                       ('grpc.max_receive_message_length', 100 * 1024 * 1024),
                       ('grpc.enable_retries', 1),
                       ('grpc.service_config', '{ "retryPolicy":{ "maxAttempts": 4, "initialBackoff": "0.1s", "maxBackoff": "1s", "backoffMutiplier": 2, "retryableStatusCodes": [ "UNAVAILABLE" ] } }')]
            channel = grpc.insecure_channel("{}:{}".format('localhost', 50051),
                                            options=options)

Вышеприведенный код включает grpc.enable_retries, хотя он включен по умолчанию, но установка его в 0 также может отключить прозрачную повторную попытку; Кроме того, grpc.service_config — это конфигурация, мы можем настроить стратегию повторных попыток (конфигурация параметра может относиться к:PersonalPC.GitHub.IO/personalPC/core/…):

{
    "retryPolicy":{
        "maxAttempts": 4,
        "initialBackoff": "0.1s",
        "maxBackoff": "1s",
        "backoffMutiplier": 2,
        "retryableStatusCodes": [
            "UNAVAILABLE" ] 
    }
}

Для повторной попытки при таких ошибках, как НЕДОСТУПНО, вы можете указать количество повторных попыток и т. д. Значение конкретных параметров см. на официальном сайте Краткое введение:

  • maxAttempts должно быть целым числом больше 1, значения больше 5 обрабатываются как 5
  • InitialBackoff и maxBackoff должны быть указаны и должны иметь значения больше 0
  • backoffMultiplier должен быть указан и больше нуля
  • retryableStatusCodes должен быть указан как данные кода состояния, не может быть пустым, и ни один код состояния не должен быть допустимым кодом состояния gPRC, может быть целым числом и не учитывать регистр.

стратегия хеджирования

Хеджирование означает активную отправку нескольких запросов на один вызов без ожидания ответа. Если метод использует стратегию хеджирования, первый запрос будет отправлен как обычный вызов RPC. Если нет ответа в течение настроенного времени, второй запрос будет отправлен напрямую и так далее, пока не будет отправлено значение maxAttempts.

在这里插入图片描述
Когда запрос хеджирования получает коды nonFatalStatusCodes, следующий запрос хеджирования будет отправлен немедленно, независимо от задержки хеджирования. Если встречаются другие коды состояния, все невыполненные запросы на хеджирование будут отменены, а код состояния будет возвращен вызывающему абоненту.

Случай реализации

options = [('grpc.max_send_message_length', 100 * 1024 * 1024),
                       ('grpc.max_receive_message_length', 100 * 1024 * 1024),
                       ('grpc.enable_retries', 1),
                       ('grpc.service_config', '{ "hedgingPolicy":{ "maxAttempts": 4, "hedgingDelay": "0.5s", "nonFatalStatusCodes":[ "UNAVAILABLE", "INTERNAL", "ABORTED" ] } }')]
            channel = grpc.insecure_channel("{}:{}".format('localhost', 50051),
                                            options=options)

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

Повторить регулирование

Когда отношение неудач к успеху клиента превышает определенный порог, gRPC предотвратит перегрузку сервера из-за повторных попыток, отключив эти стратегии повторных попыток.Это стратегия ограничения количества повторных попыток. То же самое можно настроить в конфигурации сервиса:

"retryThrottling":{
    "maxTokens": 10,
    "tokenRatio": 0.1
}

Для каждого сервера клиент gRPC поддерживает переменную token_count, изначально установленную как maxToken, со значением в диапазоне от 0 до maxToken.

Каждый запрос RPC будет иметь следующий эффект на token_count

  • Каждый неудачный запрос RPC уменьшает значение token_count на 1.
  • Успешный RPC увеличит token_count и tokenRatio. Если token_count (maxTokens/2), возобновите повторную попытку.

Для хеджирования RPC после отправки первого запроса RPC, если значение token_count больше (maxTokens/2), будут отправлены последующие запросы хеджирования. Когда token_count

Фактическую текущую конфигурацию ограничивающих параметров по-прежнему необходимо измерять в соответствии с ресурсами производительности сервера.

использованная литература