1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/TicsmycL-t-rpc-framework

Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

T_RPC_Framework

Это фреймворк для удалённого вызова процедур (RPC).

Использование

См. проект: T_RPC_Framework_Demo

Шаги (на основе версии 3.8):

  1. Запустите Nacos.

  2. Импортируйте зависимости:

    <dependency>
        <groupId>fun.ticsmyc.rpc</groupId>
        <artifactId>t-rpc-all</artifactId>
        <version>3.8</version>
    </dependency>
  3. Поместите файл конфигурации trpc.properties в каталог resources (необязательно):

    port=8999  # номер порта для сервера (по умолчанию — 8888)
    loadbalancer=round  # механизм балансировки нагрузки для клиента, можно выбрать random (случайный) или round (циклический), по умолчанию — случайный
    serializer=json # сериализатор для запросов клиента, можно выбрать kryo (по умолчанию), json, protobuf или hessian
    networkIO=netty #можно использовать netty и socket. Обнаружено, что netty работает быстрее
    nameServiceAddress=127.0.0.1:8848 #адрес реестра
  4. В классе конфигурации объявите:

    @EnableTRPC
  5. Предоставьте услуги:

    В интерфейсе сервиса объявите:

    @TRPCInterface //этот шаг предназначен для решения проблемы, когда один класс реализации реализует несколько интерфейсов.

    В классе реализации сервиса объявите:

    @TRPCService(group="t")   // атрибут group используется для обработки ситуации, когда один интерфейс хочет зарегистрировать несколько классов реализации.
  6. Используйте услуги:

    Через аннотацию @RpcClient можно внедрить службу RPC.

    @RpcClient(group = "t")
    private HelloService helloService;

Принцип работы

image-20201109152554001

Каталог

└─src
    └─main
        ├─java
        │  └─fun
        │      └─ticsmyc
        │          └─rpc
        │              ├─client :клиент
        │              │  ├─annotation:аннотации для использования клиентом
        │              │  ├─proxy :динамический прокси
        │              │  └─transport :сетевая передача
        │              │      ├─bio  :основан на socket
        │              │      └─netty :основан на netty
        │              │          ├─codec  :кодирование и декодирование
        │              │          └─handler :пользовательский обработчик
        │              │  └─util:инструменты для клиентов
        │              ├─common :общий
        │              │  ├─entity :объекты для сетевого взаимодействия
        │              │  ├─enumeration :перечисления
        │              │  ├─exception :исключения
        │              │  ├─factory :фабрики
        │              │  └─serializer :сериализация
        │              │      └─impl
        │              ├─nacos
        │              │  ├─loadbalance 负载均衡器
        │              │  │  └─impl
        │              │  └─registry 注册中心
        │              │      └─impl
        │              ├─server :сервер
        │              │  ├─annotation:аннотации для клиентов
        │              │  ├─handler :бизнес-логика: вызов соответствующего сервиса на основе полученной информации
        │              │  ├─provider:локальные сервисы сервера
        │              │  │  └─impl
        │              │  └─transport :сетевая передача
        │              │      ├─bio
        │              │      └─netty
        │              │          ├─codec
        │              │          └─handler
        │              └─test
        └─resources

Последние версии

Другие версии см. в каталоге [Historical version record.md](./wiki/Historical version record.md)

v3.8

  • Оптимизация структуры кода.
  • Исправление: ошибка двойного суждения в SingletonFactory.
  • Исправление: неправильное использование одноэлементного режима в потоконебезопасном Kryo сериализаторе.
  • Оптимизация: более грубая блокировка при отмене сервиса.
  • Новое: добавлен механизм кэширования списка сервисов Nacos.
  • Новое: добавлена конфигурация адреса реестра Nacos в файле конфигурации.
  • Оптимизация: порядок статической инициализации.
  • Оптимизация: время запуска Netty на сервере RPC настроено на запуск после полного запуска IOC контейнера через Listener.
  • Исправление: проблема с получением IP-адреса при наличии нескольких сетевых карт.
  • Исправление: после создания jar-файла невозможно прочитать файл конфигурации.

v3.7

  • Оптимизация структуры кода.
  • Использование Runtime.getRuntime().addShutdownHook для обеспечения нормального завершения работы системы и удаления сервисов из Nacos с помощью DisposableBean.
  • Механизм регистрации и внедрения сервисов оптимизирован: пользователям нужно только объявить @EnableTRPC в классе конфигурации, чтобы автоматически регистрировать и публиковать сервисы и автоматически внедрять реализации интерфейса.
  • Можно использовать файл конфигурации trpc.properties для настройки порта, замены балансировщика нагрузки и сериализатора.
  • Исправление: синхронизация потоков в bootstrap.connect().sync() приводит к повторному подключению и, наконец, к созданию нескольких соединений с одним и тем же сервером.
  • Исправление: проблемы с нулевым указателем при сериализации пакета пульсации с использованием сериализатора json.
  • Исправление: когда Spring контейнер использует cglib для генерации прокси для bean, RpcClient не может быть нормально внедрён.
  • Исправление: когда Spring контейнер использует jdk динамический прокси для генерации прокси для bean, RpcClient не может быть нормально внедрён.
  • Оптимизация: найти исходный объект через прокси-объект и внедрить RpcClient.
  • Исправление: переполнение int в алгоритме циклического распределения нагрузки.
  • Исправление: вызовы методов объекта прокси или методов, специфичных для прокси, не запускают логику RPC. Поэтому необходимо найти класс, который был проксирован, а затем прочитать аннотации на прокси-классе с помощью рефлексии, чтобы получить группу этого сервиса.

Конкретная реализация: Существует три ситуации, которые можно определить с помощью методов класса AopUtils.

  1. Не было проксировано: можно сразу прочитать аннотацию с помощью рефлексии.
  2. Используется JDK динамический прокси: можно найти внутренний проксируемый класс в соответствии с правилами генерации и прочитать аннотации из проксируемого класса.
  3. Используется прокси Cglib: аналогично предыдущему пункту.

Этот метод не всегда надёжен. Обычно при написании динамического прокси внутренний класс сохраняется, но Spring динамический прокси сохраняет тип TargetSource. Если это SingletonTargetSource, то будет сохранён один экземпляр проксируемого класса, но если это другой тип, структура проксируемого класса может измениться, и этот метод поиска проксируемого класса перестанет работать.

Почему нельзя использовать IOC-контейнер для внедрения зависимостей так же, как MyBatis, и нужно создавать собственные аннотации и внедрять их вручную?

MyBatis использует ImportBeanDefinitionRegistrar (обрабатывается BeanFactoryPostProcessor), чтобы получить метаданные конфигурации (путь к базовому пакету). Интеграция Spring с Mybatis работает следующим образом:

  1. Косвенно используется BeanFactoryPostProcessor, после завершения первого этапа инициализации IOC-контейнера (регистрация BeanDefinition) выполняется расширение:
    1. Сканируются все Mapper и получается BeanDefinition. Идентификатор этого BeanDefinition соответствует Mapper (может быть внедрён).
    2. Модифицируется сохранённый объектный тип BeanDefinition, устанавливается FactoryBean, определённый в MyBatis.
      • Этот FactoryBean переопределяет метод getObject, вызывая метод SqlSession getMapper.
    3. При внедрении интерфейса Mapper фактически вызывается метод getObject FactoryBean для получения соответствующего Mapper.

Нетрудно заметить, что каждый интерфейс Mapper имеет только одну определённую реализацию, поэтому для каждого интерфейса Mapper можно создать FactoryBean для создания Bean.

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

Механизм повторного подключения при сбое клиента

Способ отправки запроса клиентом: сначала определяется метод запроса и группа, затем из nacos получается IP и порт поставщика услуг. Затем используется netty для инициирования сетевого запроса.

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

Во время тестирования было обнаружено, что из-за задержки nacos информация о поставщике услуг часто обновляется, что приводит к тому, что IP и порты, полученные клиентом, становятся устаревшими, и многократные попытки подключения всё равно не будут успешными.

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

Проблемы с десериализацией при сериализации JSON

JSON — это текстовый сериализатор, и при десериализации, если исходный тип неизвестен, это может привести к сбою десериализации.

Если использовать объектный тип для приёма десериализованного объекта, невозможно определить исходный тип, и он станет String или другим странным типом. Например, объект Date будет десериализован в Long, вложенный объект RpcRequest будет десериализован в LinkedHashMap и т.д.

Поэтому можно только добавить параметр Class в тело запроса, а после десериализации определить, была ли десериализация успешной. Если десериализация не удалась, она будет сериализована в двоичный формат, а исходный тип будет повторно десериализован.

В этой ситуации использование двоичного сериализатора лучше, ниже приводится сравнение нескольких двоичных сериализаторов.

  1. Сериализатор Protobuf:
    • Способ использования:
      • Можно написать файл .proto (каждый класс должен соответствовать файлу .proto), а затем использовать предоставленный генератор кода для генерации схемы (также файла .java), добавить его в проект, производительность выше, но процесс сложный, и изменение структуры класса требует переписать файл .proto.
      • Ввести protobuf-runtime, во время выполнения, согласно переданному классу, использовать технологию байт-кода для динамической генерации схемы.
        • На местном уровне используется параллельное хранение в карте с отложенной загрузкой и двойным механизмом проверки для хранения сгенерированной схемы. Избегайте многократной повторной генерации схемы. Производительность низкая при первом использовании, но удобна в использовании.
  2. Сериализатор Kryo:
    • Может использоваться только в Java. Производительность хорошая. Только Google Protobuf более эффективен, чем Kryo.
    • Сам по себе не является потокобезопасным, поэтому должен существовать в ThreadLocal. Нельзя сделать его одноэлементным.
      • Возникла проблема утечки памяти ThreadLocal!
  3. Сериализатор Hession:
    • Кодированный результат короткий, но производительность низкая.
    • Не очень понятно.

Ошибка синхронизации соединения клиента, приводящая к нескольким пакетам сердцебиения на стороне сервера

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

bootstrap.connect(xxxx).addListener( ()->{
    //Код 1
    this.channel = sync.channel();
}).sync();
//Код 2

После установления соединения код 1 и код 2 выполняются синхронно в двух потоках, и невозможно гарантировать порядок выполнения кода 1 и кода 2.

Когда код 1 присваивает значение каналу, операция может произойти позже, чем код 2, что приведёт к ошибке синхронизации. Следует дождаться завершения кода 1, прежде чем выполнять код 2.

Если код 2 выполняется раньше кода 1, канал ещё не назначен, проверка вернёт null, что вызовет операцию повторного подключения. В конечном итоге система будет поддерживать несколько соединений с этим сервером, что приведёт к отправке нескольких пакетов сердцебиения каждый раз.

BeanPostProcessor вызывает сбой @Value

Сцена: хотите преобразовать конфигурацию серверной части из static в @Component. Используйте файл свойств для написания конфигурации и используйте @Value для внедрения.

Используйте InitializingBean для присвоения значений. Обнаружено, что атрибут всё ещё находится в стадии ссылки на файл конфигурации («${}» и т. д.), и значение не было заменено содержимым файла конфигурации.

  • Сценарии, в которых @Value не может получить значение:
    • Используйте @Value("${}") для внедрения свойств в класс RpcProperties.
    • Внедрить RpcProperties в другой класс. Когда внедряется Config, можно получить только строку ${}, а не другие классы.
    • Наконец, обнаружено, что BeanPostProcessor приводит к сбою @Value. Когда Bean, использующий @Value, напрямую или косвенно внедряется в BeanPostProcessor, это приведёт к сбою @Value. Даже если BeanPostProcessor вообще не использует значения @Value.

Причина: экземпляры BeanPostProcesser создаются в порядке приоритета, и экземпляры с высоким приоритетом создаются перед экземплярами с низким приоритетом. Во время создания экземпляра внутренние зависимые Bean также будут созданы. Эти Bean, созданные слишком рано, не могут воспользоваться обработкой BeanPostProcesser того же уровня приоритета или более низкого уровня, поэтому @Value не будет заменён.

@Value обрабатывается AutowiredAnnotationBeanPostProcessor, и этот BeanPostProcessor также имеет уровень приоритета PriorityOrdered.

Подробности можно найти в комментарии к интерфейсу PriorityOrdered:

 * <p>Note: {@code PriorityOrdered} post-processor beans are initialized in
 * a special phase, ahead of other post-processor beans. This subtly
 * affects their autowiring behavior: they will only be autowired against
 * beans which do not require eager initialization for type matching.

Если вам нужно зарегистрировать службу через BeanPostProcessor во время запуска Bean, вы должны убедиться, что содержимое файла конфигурации было прочитано до инициализации Bean-контейнера, поэтому статическое использование по-прежнему подходит, но использование статического метода также легко вызвать nullptr.

Проблема автоматического вызова toString в idea при отладке

Idea автоматически вызывает toString для классов в процессе отладки и отображает результаты в интерфейсе. Если не сделать специальную обработку, то при вызове метода toString для класса-прокси, созданного на основе интерфейса rpc сервиса клиента, также будет вызвана логика rpc, что приведёт к отправке запроса на вызов java.lang.Object_t. Это естественно вызовет ошибку запроса.

Решение: добавить логику короткого замыкания в метод invoke динамического прокси. Если вызывается метод класса Object или специфический метод класса-прокси, выполнять локальный вызов, не задействуя логику rpc. (Решение в MyBatis для MapperProxy.)

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Это RPC-фреймворк, основанный на Netty и реализованный на Java. Он интегрируется с Spring и предоставляет Spring Boot Starter. Фреймворк работает на транспортном уровне, не требует дополнительной настройки и готов к использованию сразу после распаковки. В нём сочетаются высокая производительность Dubbo и простота использования OpenFeign. Это тре... Развернуть Свернуть
MulanPSL-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/TicsmycL-t-rpc-framework.git
git@api.gitlife.ru:oschina-mirror/TicsmycL-t-rpc-framework.git
oschina-mirror
TicsmycL-t-rpc-framework
TicsmycL-t-rpc-framework
master