предисловие
существуетпредыдущий постСначала мы перечислили общие требования и определили протокол сообщений. На этот раз мы решили создать базовую структуру RPC, сначала для реализации базовой функции вызова метода.
особенности дизайна
Первым шагом вызова RPC является определение метода, который будет отображаться на стороне сервера.В grpc или thrift на этом этапе нам нужно написать независимый от языка файл idl, а затем сгенерировать соответствующий языковой код через idl. файл. В нашем фреймворке для простоты мы не используем idl, а определяем интерфейсы и методы прямо в коде. Здесь оговаривается, что внешний метод должен соответствовать следующим условиям:
- Выставленный внешнему миру метод, его тип и сам должен быть внешне видимым (Exported), то есть первая буква должна быть заглавной
- Параметров метода должно быть три, и первый из них должен быть типа context.Context.
- Третий параметр метода должен быть указателем.
- Возвращаемое значение метода должно быть типа error
- Клиенты обращаются к методам службы в форме «Тип.Метод», где Тип — это метод.Класс реализацииПолное имя класса, Method — это имя метода.
Почему существуют эти правила?Конкретная причина заключается в следующем: поскольку динамический прокси, используемый полем RPC framework в java, не поддерживается в языке go, нам нужно явно определить унифицированный формат метода, чтобы в поле Only в рамках RPC различные методы могут обрабатываться единообразно. Итак, мы указываем формат метода:
- Первый параметр метода фиксируется как Context, который используется для передачи контекстной информации.
- Второй параметр - это фактический параметр метода.
- Третий параметр представляет собой возвращаемое значение метода, после завершения вызова его значение будет изменено на результат, выполненный сервером.
- Возвращаемое значение метода фиксируется на типе ошибки, указывающем, что ошибка произошла во время вызова метода.
Здесь нам нужно обратить внимание на то, что поставщику услуг не нужно быть открытым в виде интерфейса, когда он открыт для внешнего мира, если у поставщика услуг есть метод, соответствующий правилам; и клиент указывает конкретного поставщика услуг при вызове метода type, имя интерфейса не может быть указано, даже если поставщик услуг реализует интерфейс.
contet.Context
contextЭто абстракция контекста запроса, предоставляемая языком go. Он несет информацию о крайнем сроке запроса и сигналах отмены, а также может передавать некоторую контекстную информацию. Он очень подходит в качестве контекста запроса RPC. Мы можем установить время ожидания в контексте, а также может установить некоторые независимые от параметра метаданные, которые передаются на сервер через контекст.
На самом деле, фиксированный формат метода и использование Call и Go для представления синхронных и асинхронных вызовов — все это правила в rpc, который идет вместе с go, но к параметру добавляется context.Context. Я должен сказать, что дизайн rpc, поставляемый с go, действительно превосходен, и его стоит изучить и понять.
Определение интерфейса
клиент и сервер
Во-первых, это интерфейсы клиента и сервера в среде RPC, обращенной к потребителю:
type RPCServer interface {
//注册服务实例,rcvr是receiver的意思,它是我们对外暴露的方法的实现者,metaData是注册服务时携带的额外的元数据,它描述了rcvr的其他信息
Register(rcvr interface{}, metaData map[string]string) error
//开始对外提供服务
Serve(network string, addr string) error
}
type RPCClient interface {
//Go表示异步调用
Go(ctx context.Context, serviceMethod string, arg interface{}, reply interface{}, done chan *Call) *Call
//Call表示异步调用
Call(ctx context.Context, serviceMethod string, arg interface{}, reply interface{}) error
Close() error
}
type Call struct {
ServiceMethod string // 服务名.方法名
Args interface{} // 参数
Reply interface{} // 返回值(指针类型)
Error error // 错误信息
Done chan *Call // 在调用结束时激活
}
селектор и реестр
На этот раз часть вызова RPC реализуется первой, а два уровня временно игнорируются и реализуются позже.
codec
Далее нам нужно выбрать протокол сериализации, здесь мы выбираем тот, который мы использовали ранееmessagepack. Ранее разработанный коммуникационный протокол разделен на две части: головную и основную части, обе из которых необходимо сериализовать и десериализовать. Головная часть — это метаданные, которые могут быть сериализованы непосредственно messagepack, а основная часть — это параметр или ответ метода, сериализация которого определяется SerializeType в заголовке, преимуществом этого является удобство последующего расширения. В настоящее время для сериализации также используется пакет сообщений, который можно сериализовать и другими способами.
Логика сериализации также определяется как интерфейс:
type Codec interface {
Encode(value interface{}) ([]byte, error)
Decode(data []byte, value interface{}) error
}
protocol
После определения протокола сериализации мы можем определить интерфейс, связанный с протоколом сообщений. Дизайн протокола отсылает к предыдущей статье:Внедрение инфраструктуры RPC с нуля (ноль)
Далее идет определение интерфейса протокола:
//Messagge表示一个消息体
type Message struct {
*Header //head部分, Header的定义参考上一篇文章
Data []byte //body部分
}
//Protocol定义了如何构造和序列化一个完整的消息体
type Protocol interface {
NewMessage() *Message
DecodeMessage(r io.Reader) (*Message, error)
EncodeMessage(message *Message) []byte
}
По предыдущему дизайну все взаимодействия осуществляются через интерфейсы, что удобно для расширения и замены.
transport
После определения интерфейса протокола следующим шагом является определение сетевого транспортного уровня:
//传输层的定义,用于读取数据
type Transport interface {
Dial(network, addr string) error
//这里直接内嵌了ReadWriteCloser接口,包含Read、Write和Close方法
io.ReadWriteCloser
RemoteAddr() net.Addr
LocalAddr() net.Addr
}
//服务端监听器定义,用于监听端口和建立连接
type Listener interface {
Listen(network, addr string) error
Accept() (Transport, error)
//这里直接内嵌了Closer接口,包含Close方法
io.Closer
}
Выполнение
После того, как интерфейсы каждого уровня определены, вы можете начать создавать базовую структуру. Конкретный код здесь не прилагается. Конкретный код см.ссылка на гитхаб, вот общее описание идей реализации каждой части.
Client
Функция клиента относительно проста, то есть после сериализации параметров они собираются в полное тело сообщения и отправляются. При отправке запроса незавершенный запрос кэшируется, и каждый полученный ответ сопоставляется с незавершенным запросом.
Суть отправки запросаGo
иsend
метод,Go
Функция является параметром сборки,send
Метод заключается в сериализации параметров и отправке их через интерфейс транспортного уровня при кэшировании запроса наpendingCalls
середина. иCall
метод вызывается напрямуюGo
метод и блокируется до тех пор, пока он не вернется или не истечет время ожидания.
Ядром получения ответа являетсяinput
метод,input
Метод передается, когда инициализация клиента завершена.go input()
воплощать в жизнь.input
Способ включает в себя бесконечный цикл, чтение данных, передачу слоя в бесконечном цикле и десериализацию, а ответ соответствует полученному десериализованному запросу кэша.
Примечание:send
иinput
Имя метода также можно узнать из rpc, поставляемого с go.
Server
Когда сервер примет регистрацию, он будет фильтровать различные методы поставщика услуг и кэшировать законные методы.
Основная логика сервераserveTransport
метод, который получаетTransport
объект, затем в бесконечном цикле изTransport
Прочитать данные и десериализовать их в запрос, найти метод собственного кеша по указанному в запросе методу, найти соответствующий метод и выполнить соответствующую реализацию через отражение и возврат. После завершения выполнения оно собирается в полное сообщение в соответствии с возвращенным результатом или исключением, возникающим при выполнении.Transport
отправлять.
Когда сервер выполняет метод путем отражения, ему необходимо взять средство реализации в качестве первого параметра выполнения, поэтому параметр на один больше, чем параметр в определении метода.
кодек и протокол
Эти две части относительно просты: кодек в основном использует пакет сообщений для реализации соответствующего интерфейса, реализация протокола заключается в анализе в соответствии с протоколом, который мы определяем.
резьбовая модель
В процессе выполнения в дополнение к пользовательскому потоку клиента и сервисному потоку, используемому сервером для выполнения метода, также добавляются соответственно поток опроса клиента и поток мониторинга сервера.Общая схема выглядит следующим образом:
Модель многопоточности здесь относительно проста. Сервер будет создавать поток (горутин) для каждого установленного соединения. Хотя горутина очень легкая, она не полностью свободна от потребления. В будущем может быть выполнена дальнейшая оптимизация, например, реверсирование читать данные.Методы сериализации и выполнения разделены на разные потоки для выполнения или объединения горутин и т. д.Эпилог
На данный момент наша структура RPC имеет прототип и может поддерживать базовые вызовы RPC. На самом деле вся структура относится к структуре rpc, поставляемой с go.Модель потоков клиента и сервера такая же, как у rpc, поставляемой с go, но она определяет сам протокол сериализации и сообщений, а также расширенный интерфейс сохраняется в процессе реализации, удобен для последующей доработки и расширения. Следующим шагом планирования является внедрение цепочек фильтров для последующей реализации функций, связанных с управлением услугами.