Мотивом для написания данной статьи послужил тот факт, что на habr.com участилось появление материалов маркетингового характера про Apache Kafka. А также тот факт, что из статей складывается впечатление что пишут их немного далекие от реального использования люди — это конечно же только впечатление, но почему-то в большинстве своем статьи обязательно содержат сравнение Apache Kafka с RabbitMQ, причем не в пользу последнего. Что самое интересное — читая подобные статьи управленцы без технического бэкграунда начинают тратить деньги на внутренние исследования, чтобы ведущие разработчики и технические директора выбрали одно из решений. Так как я очень жадный/домовитый, а также так как я сторонник тезиса “В споре НЕ рождается истина” предлагаю вам ознакомится с другим подходом — почти без сравнения разных брокеров.
Вообще, по правильному, я должен был сделать статью в формате Kafka+RabbitMQ+Nats+ActiveMQ+Mosquito+etc
, но мне кажется, что для Вас дорогие читатели это будет перебор, хотя обычно в моих архитектурных решениях присутствуют все вышеуказанные сервисы (и не только). И это я еще не рассказываю про AzureServiceBus/AmazonServiceBus — которые также участвуют в “гибридах” при крупных программах проектов. Поэтому пока остановимся на связке Kafka+RabbitMQ и далее вы поймете почему: по аналогии можно подключить любой сервис с его протоколом. Потому что:
сравнивая Apache Kafka и RabbitMQ вы сравниваете 2 (два) бренда, а точнее 2 коммерческие компании — Confluent и vmWare, и немножко Apache Software Foundation (но это не компания)
то есть формально при сравнении мы должны сравнивать бизнес-модели компаний которые являются основными драйверами развития наших сегодняшних подоопытных. Так как Хабр все таки не портал экономических исследований, поэтому мы для начала должны вспомнить не бренды, а те описания которые стоят за этими брендами (то как сами себя называют наши сегодняшние участники).
Я не зря третьим пунктом выделяю наработки компании Confluent — те кто собирается использовать Apache Kafka в продуктиве должны хотя бы видеть какую функциональность дополнительно добавляет Confluent к Apache Kafka. А это SchemeRegistry
, RestProxy
, kSQL
и еще несколько интересных штук, о одной из которых мы поговорим ниже, она называется Kafka-Connect
.
Но вернемся к сравнению — внимательный читатель видит, что RabbitMQ сам себя называет брокером сообщений выделяя свою главную фишку “мультипротокольность”, а товарищи из экосистемы Kafka почему-то называют себя аж платформой (завышенное самомнение оно такое).
Итак — чтобы было совсем понятно, куда я веду.
Отсюда и возникают минусы каждого из решений
Еще одна новость: действительно если почитать источник, то Сократ вообще-то в итоге пришел к тому, что нужно обеспечить диалог, а если по научному — то истина рождается в научном споре
, который формально представляет собой процесс публикация со ссылкой на источники
-> а затем научная критика оппонентов
-> истина
А значит перейдем к ссылкам — для начала их будет три. Когда 14 лет назад я совместно с коллегами начинал использовать брокеры сообщений в качестве основы для построения своих интеграционных решений, мы сразу обратили внимание, что фактически с точки зрения “клиента” (конечного приложения), под разные задачи подходят разные протоколы интеграции.
так как тогда наша задача была интегрировать всякое (python, C#, java) и 1С — был придуман проект One-S-Connectors
(https://code.google.com/archive/p/one-c-connectors/source/default/source). Сейчас он имеет сугубо академический интерес (так как в 1С мире моя персона достаточно известна и на Хабре много 1С специалистов из сообщества “воинствующих 1С-ников” — эта ссылка специально для них).
Однако уже тогда (в 2006 году) стало понятно, что по большому счету конечному разработчику придется менять/выбирать протокол под бизнес-задачу. А инфраструктурщикам придется обеспечить максимально широкий спектр интеграционных протоколов. От ODBC до Kafka/NATs/ModBus.
Но вернемся к дню сегодняшнему — когда я начал использовать в проектах уровня ГИС (госсударственные информационные системы) различные транспорта данных внезапно выяснилось
, что универсальные адаптеры — это не только концепт воинствующих 1С-ников, но и соседей. Поэтому многие идеи при внедрении черпались из еще двух интересных проектов
маленькое примечание для менеджеров про Kombu — как то так получилось, что имплементация протокола Apache Kafka до сих пор открыта https://github.com/celery/kombu/issues/301 и почему-то перешла в разряд “Дайте денег”, поэтому для Python проектов приходится использовать дополнительно https://github.com/confluentinc/confluent-kafka-python
Когда вы дочитаете до этого момента — предполагаю, что вы зададите вопрос про остальные языки: Java, GoLang, RUST, etc. Но во первых я не зря выше указал что по серьезному в наш обсуждаемый сегодня гибрид нужно добавить историю про NATs и ActiveMQ и внезапно JMS — поэтому просьба дочитать до конца: Java будет, а во вторых мы переходим к еще трем полезным ссылкам
Прокоментируем их? Дело в том, что как бы вы не хотели, а для полноценного использования “в длинную” — вам придется подписаться на историю релизов как сервера RabbitMQ и самое главное на те самые расширения (лежат в каталоге /deps
) которые постоянно добавляются в ядро RabbitMQ, так и на портал компании Confluent где она публикует приложения полезные для конечного бизнеса использующего Apache Kafka в продуктиве.
подход к расширяемости за счет активируемых расширений также используется в экосистеме PostgreSQL — тот который
CREATE EXTENSION hypopg
, так что подход реализованный компанией Pivotal/vmWare далеко не новый в нашем чудесном мире архитектуры программного обеспечения
Дополнительно — на чудесном рынке облачных услуг в формате “Серьезная штука как сервис” есть еще один игрок — это компания 84Codes
https://github.com/84codes. Когда в рамках проектов внедрения нет нормальных инженеров по инфраструктуре — именно 84Codes спасает пилотные проекты, потому как у них можно легко арендовать бесплатные/сильнодешевые контура CloudAMQP и CloudKarafka.
Я как бы обещал, что не буду ничего говорить про деньги, однако придется отразить 2 ключевых момента:
Когда-то давно существовал https://github.com/jcustenborder/kafka-connect-rabbitmq, примечателен тот факт что товарищ Джереми его скрыл, оставив только свои наработки для Java разработчиков в виде Maven Archetype https://github.com/jcustenborder/kafka-connect-archtype — еще раз обращаю Ваше внимание, что компания Confluent будет и дальше пытаться монетизировать свою деятельность, так что переводить всю интеграцию только на Kafka я бы на вашем месте поостерегся.
Поэтому когда вам топят за Kafka
учитывайте, что вы либо изучаете Java, либо платите за Enterprise лицензию. А когда вам топят за RabbitMQ
учитывайте, что либо вы изучаете системное администрирование (Erlang накладывает особенности системного администрирования), либо покупаете сервис у провайдеров типа 84Codes
. Кодить на Erlang никогда не придется — там это не нужно, если только вы не контрибьюторы OpenStack.
Приближаемся к дальнейшему пониманию. Данный раздел уже будет полезен инфраструктурщикам, хотя и разработчикам важно знать, что в эпоху когда семимильными шагами развивается имплементация ITILv4, для того чтобы перейти от текста и менеджерских хитростей про риски и деньги к реальности нам придется осознать 3 тезиса
раз в 3 месяца
— TDD, BDD, CICD, ScallableAgile и DevOps (DocOps, DevSecOps)
— эти инженерные практики и методики управления не просто так развиваются. Всем очень хочется сокращать себестоимость и TimeToMarket.Абзац выше важен, как финальный аккорд, прежде чем мы перейдем к Docker-Compose. А именно к нему я вел — чтобы и разработчики и инфраструктурщики понимали что такое гибридная инфраструктура в режиме мультипротокольности (с) — нужно сделать так, чтобы каждый мог поэкспериментировать с предлагаемым контуром. Как я уже указал выше — первично подобное применительно к Kafka+RabbitMQ было подсмотрено именно у коллег из 84Codes
(хорошие ребята — всем советую https://www.84codes.com/).
Итак подходим к примерам, так как обоснования и вводных уже хватит. Предположим вы уже поняли, что вам также нужна мультипротокольность, однако мы же помним, что все рекламные материалы про Apache Kafka нам рассказывают что это единственное решение с реализацией exactly-ones
доставки сообщений от отправителя получателю. Собственно на самом деле — нам и нужен гибрид, чтобы сделать из связки ТочкаОбмена->Очередь
журнал Kafka (это тот который Topic
) — чтобы возникла сущность под называнием Offsets
у нашей очереди событий.
проверка на внимательность читающего exactly-ones — это шутка в формате “Хотя бы один раз из 1С”, а имеется в виду концепт Exactly once — строго однократная доставка сообщений получателю, без необходимости повторной отправки от отправителя.
Предлагаю попробовать. Концепт для проверки Вашими руками будет состоять из:
и трех приложений приложений
Еще интересное замечание: когда вы смотрите на всякие обучающие видео по Apache Kafka — авторы часто (но не всегда) старательно пишут примеры на Java, это они делают скорее всего для того, чтобы скрыть от вас особенности использования librdkafka — C++ библиотеки на основе которой сделаны многие не-джава адаптеры,. Я же наоборот предлагаю вам начинать исследование интеграции с Kafka именно с неё, чтобы четко оценивать риски “куда вы ввязываетесь”: очень примечательно что там работает фактически один разработчик, формально в одиночку https://github.com/edenhill/librdkafka/pulse/monthly, а допустим wmWare старается поддерживать свою линейку клиентов под своим брендом https://github.com/rabbitmq
ну и самое главное и тяжелое:
контур содержит открытый форк старого RabbitMQ-Kafka-Sinc-Connector
— того самого который товарищи из Confluent в своё время скрыли с Github.
Для показательного эксперимента мы сделаем 2 композитных приложения — инфраструктурное-трансформационное и непосредственно бизнес-приложения.
контур инфраструктуры который нам понадобится — запускается достаточно просто
docker-compose -f dockers/infra.yml up -d
Если вам интересно что же там внутри, нашего композитного приложения, то в конце статьи дается ссылка на полный комплект исходников, наиболее интересен в нем Kafka-UI и непосредственно RabbitMQ-Sinc, все остальное обычно и штатно для всех известных примеров по Kafka или RabbitMQ
image: provectuslabs/kafka-ui:latest
ports:
- 8080:8080
depends_on:
- kafka-broker
- zookeeper
environment:
KAFKA_CLUSTERS_0_NAME: local
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: broker:29092
KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181
KAFKA_CLUSTERS_0_JMXPORT: 9101
Но самое главное кроется в репозитории Java
<parent>
<groupId>com.github.jcustenborder.kafka.connect</groupId>
<artifactId>kafka-connect-parent</artifactId>
<version>1.0.0</version>
</parent>
Если подробно изучить pom.xml — то выяснится, что существует заглавный проект для всех конекторов к Кафка https://github.com/jcustenborder/kafka-connect-parent, в котором используется Java-Kafka-Adapter
И непосредственно синхронизацией c RMQ занимается штатный Java клиент — https://www.rabbitmq.com/java-client.html
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>${rabbitmq.version}</version>
Таким образом — по правильному, чтобы получились повторить тот же эксперимент что и у меня, необходимо выполнить:
-1-build-connect-jar.bat
00-build-connect-image.sh
и уже потом запустить полный инфраструктурный контур
01-start-infra.sh
обратите внимание — так как Docker использует разное поведение при работе с PWD для Windows и Linux — приходится делать дубликаты скриптов. В остальных случаях — под обоими операционными системами используется интерпретатор sh
В итоге вы получите следующий комплект сервисов
На картинке можно увидеть как подключаются конфигурационные файлы к RabbitMQ и какая топология сетевых портов у нас будет участвовать в эксперименте:
Назначение портов:
curl
трансформатором протоколовВ этот момент нужно остановится и прокомментировать особенность развертывания RabbitMQ в Docker:
enabled-rmq-plugins
[
rabbitmq_management,
rabbitmq_amqp1_0,
rabbitmq_mqtt,
rabbitmq_federation,
rabbitmq_federation_management,
rabbitmq_shovel,
rabbitmq_shovel_management,
rabbitmq_prometheus
].
rmq_definitions.json
"bindings":[
{
"source":"orders-send",
"vhost":"/",
"destination":"orders-amqp-10-consumer",
"destination_type":"queue",
"routing_key":"",
"arguments":{
Остается только запустить наши приложения эмулирующие подключения
docker-compose -f dockers/infra.yml restart protocol-connect-sync
docker-compose -f applications.yml build
docker-compose -f applications.yml up
Топология наших тестовых приложений достаточно простая
Исходный код также максимально упрощён:
Васи
с периодичностью в 2 секунды producer = conn.Producer(serializer='json')
producer.publish({'client': 'Вася', 'count': 10, 'good': 'АйФончик'},
exchange=order_exchange,
declare=[kafka_queue, amqp10_queue])
time.sleep(2)
RUN python -m pip install
kombu
librabbitmq
причем используется для этого максимально производительная библиотека на Си для AMQP 0.9 — librabbitmq наследуется именно от неё https://github.com/alanxz/rabbitmq-c
Attach recvAttach = new Attach()
{
Source = new Source()
{
Address = "orders-amqp-10-consumer",
Durable = 1,
},
ReceiverLink receiver =
new ReceiverLink(session,"netcore_amqp_10_consumer", recvAttach, null);
Console.WriteLine("Receiver connected to broker.");
while (true) {
Message message = receiver.Receive();
if (message == null)
{
Console.WriteLine("Client exiting.");
break;
}
Console.WriteLine("Received "
+ System.Text.Encoding.UTF8.GetString((byte[])message.Body)
Причем в качестве драйвера выбран
<ItemGroup>
<PackageReference Include="AMQPNetLite.Core" Version="2.4.1" />
</ItemGroup>
именно его https://github.com/Azure/amqpnetlite Microsoft использует для маркетинга своей реализации сервисной шины. Собственно именно AMQP 1.0 как протокол они и рекламируют https://docs.microsoft.com/ru-ru/azure/service-bus-messaging/service-bus-amqp-overview
Ну и финально
AutoOffsetReset = AutoOffsetReset.Earliest
c.Subscribe("orders-from-amqp");
while (true)
{
try
{
var cr = c.Consume(cts.Token);
Выглядит наш контур в итоге следующим образом:
Не волнуйтесь — при таком гибридном подходе, без неё никуда, для того чтобы всё вышеуказанное заработало пришлось сделать форк и актуализировать версии Kafka-Connect-Base
[submodule "dockers/rabbitmq-kafka-sink"]
path = dockers/rabbitmq-kafka-sink
url = https://github.com/aliczin/kafka-connect-rabbitmq
Но самое интересное не это, самое интересное что в этом самом Kafka-Connect нет по сути никакой магии — только код трансформации.
По сути нам предлагают:
public class RabbitMQSourceTask extends SourceTask {
this.channel.basicConsume(queue, this.consumer);
log.info("Setting channel.basicQos({}, {});", this.config.prefetchCount, this.config.prefetchGlobal);
this.channel.basicQos(this.config.prefetchCount, this.config.prefetchGlobal);
@Override
public List<SourceRecord> poll() throws InterruptedException {
List<SourceRecord> batch = new ArrayList<>(4096);
while (!this.records.drain(batch)) {
Отдельно можно выделить чудесный трансформатор сообщений из AMQP 0.9 в Кафка. У несведующего в Java глаз может задергаться. У автора чувствуется многолетный опыт работы в J2EE.
private static final Logger log = LoggerFactory.getLogger(MessageConverter.class);
static final String FIELD_ENVELOPE_DELIVERYTAG = "deliveryTag";
static final String FIELD_ENVELOPE_ISREDELIVER = "isRedeliver";
static final String FIELD_ENVELOPE_EXCHANGE = "exchange";
static final String FIELD_ENVELOPE_ROUTINGKEY = "routingKey";
static final Schema SCHEMA_ENVELOPE = SchemaBuilder.struct()
.name("com.github.jcustenborder.kafka.connect.rabbitmq.Envelope")
.doc("Encapsulates a group of parameters used for AMQP's Basic methods. See " +
"`Envelope <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html>`_")
.field(FIELD_ENVELOPE_DELIVERYTAG, SchemaBuilder.int64().doc("The delivery tag included in this parameter envelope. See `Envelope.getDeliveryTag() <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html#getDeliveryTag-->`_").build())
.field(FIELD_ENVELOPE_ISREDELIVER, SchemaBuilder.bool().doc("The redelivery flag included in this parameter envelope. See `Envelope.isRedeliver() <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html#isRedeliver-->`_").build())
.field(FIELD_ENVELOPE_EXCHANGE, SchemaBuilder.string().optional().doc("The name of the exchange included in this parameter envelope. See `Envelope.getExchange() <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html#getExchange-->`_"))
.field(FIELD_ENVELOPE_ROUTINGKEY, SchemaBuilder.string().optional().doc("The routing key included in this parameter envelope. See `Envelope.getRoutingKey() <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html#getRoutingKey-->`_").build())
.build();
Но… Не будем критиковать, мы же в самом начале договорились — что наша главная задача добиться конечного результата удобным на сегодня способом. А итоги у нас получаются следующие.
Все что здесь продемонстрировано естественно лежит на Github.
В репозитории — https://github.com/aliczin/hybrid-eventing. Лицензия выставленна простая до невозможности Creative Commons Attribution 4.0 International.
Полезно использовать в обучающих целях для команд разработки и инфраструктуры — и поиграться с DevOps и поиграться с мультипротокольными приложениями. Ничего особо экстравагантного в данном концепте конечно нет, ключевое тут я как я написал в самом начале — мы делаем избыточное количество интеграционных протоколов, добавляя транформаторов между потоками интеграции.
Схема коммуникации в итоге для “разработчика интеграционных потоков” (с) выглядит следующим образом — для источника и брокеров
orderEventsApp->Amqp09: send order
Amqp09->Amqp10: fanoutn copy event
Amqp09->KafkaQ: fanoutn copy event
KafkaQ->KafkaConnect: consumen on message
KafkaConnect->KafkaConnect: transformn message
KafkaConnect->Kafka: publish to topic
а для приемников — все упрощается
Amqp10->orderEventSubApp: subcriben for event
orderJournalApp->Kafka: read kafka journal
Приемники берут нужные им данные только по нужному им протоколу
Ключевые моменты которые я хотел расскрыть данной статьей
стройте эксперименты и продуктивы с Apache Kafka не со штатным Java клиентом, а librdkafka и базирующихся на ней адаптерах — это позволит вам отладить сценарии разных версий протоколов KafkaAPI
. Java вам пригодится в другом месте.
не ввязывайтесь с священные войны, что лучше RabbitMQ/Kafka/Nats/ActiveMQ — просто развертывайте сервисы и публикуйте протоколы и пробуйте свои бизнес-сценарии.
начните уже внедрять продуктивный Docker, или хотя бы пилотные и разработческие контура.
реальный ИТ ландшафт почти всегда будет мультипротокольным
чтобы гибриды развивались дальше:
Mosquito — очень удобен как встраиваемый брокер на уровне контролера SCADA для преобразования из ModBus/OPC-UA. Хотя как вы уже поняли из статьи — интересны реализации “мостов из протокола в протокол” — пример https://github.com/mainflux/mainflux
ActiveMQ — удобен для Java разработчиков, потому что у них есть боязнь Erlang, но как мы выше уже сказали — мост RabbitMQ AMQP 1.0 -> ActiveMQ
легко организуется средствами RabbitMQ, кстати также как и JMS.
NATs — интересен как часть OpenFaaS
платформы, при внедрении “своего маленького” Amazon Lambda
с преферансом. И опять же подход будет всё тот же — мультипротокольные мосты с трансформацией: https://github.com/nats-io/nats-kafka — если Вам не страшно посмотрите эксперименты с OpenFaaS веселых 1С-ников — 2.5 часа примеров https://youtu.be/8sF-oGGVa9M
Надеюсь мой архитектурный подход Вам придется по душе и вы перестанете тратить деньги заказчика (инвестора/свои — если вы стартапщик: Маша это замечание специально для тебя) на бессмысленные обсуждения что же выбрать в качестве брокера/платформы, и начнете наконец-то делать функциональность, которая будет использовать тот протокол, который удобен прямо сейчас. С возможностью переключения в случае “если чё”
Функциональность: Мультипротокольный адаптер
Как разработчик я хочу иметь абстракцию Produser/Consumer
С возможность изменения протокола интеграции
Чтобы под каждую задачу выбирать разные протоколы
и единый интерфейс вызова для обеспечения независимости от вендора предоставляющего транспорт
Сценарий: vmWare реализует протокол Stream средствами RabbitMQ
Когда vmWare закончит свой плагин для потоков
Тогда я активирую новый протокол
И быстро воткну его в приложение
И так как у меня есть продуктивный кластер RabbitMQ
И мне нужно будет просто поменять канал для отдельных бизнес сценариев
Сценарий: Завтра придут 1С-ники со своим ActiveMQ из Шины для 1С
Когда мне нужно быстро включить очереди 1С в общий контур
И чтобы на Питоне использовать старые наработки с Kafka API
Тогда я добавляю трансформацию ActivemeMQ2Kafka
и живу по старому а события ходят уже и из 1С
etc
А чтобы вы не думали, что данный подход — это нечто уникальное — вот Вам еще интересная ссылка: https://github.com/fclairamb/ftpserver/pull/34 — это когда нужен FTP сервер, а хочется S3.
Ну и в качестве финального момента — обратите внимание: есть и риски данного подхода: но они я думаю Вам и так понятны.
так как Хабр читают любители цифровой трансформации (чтобы кто не понимал под этим словом) советую в техническое задание добавлять не упоминание конкретных реализации серверов, а что-то примерно следующее:
комплект программ для интеграции должен реализовывать коммуникацию конечных приложений по открытым протоколам HTTP, AMQP 0.9, AMQP 1.0, Apache Kafka не ниже версии 23, MQTT, WebSockets, <ЛюбойДругойХотьSOAPХотяЭтоЖуть> с возможность преобразования между протоколами дополнительными средствами администрирования
Надеюсь моя публикация после долгого перерыва Вам будет полезна в ваших интеграционных проектах. Предполагаю что будет вопрос про 1С — и тут у меня совет только один. Используйте Google по ключевым словам 1С+RabbitMQ или 1С+Kafka или 1С+OpenFaas
— и RabbitMQ и Kafka “в 1С” давно и непринужденно используются. Потому что 1С — это не только язык, но и несколько сообществ где уже давно сделаны все возможные адаптеры и платные и бесплатные. Собственно как и в Java/C#/Python/C++/Rust/etc.
Данная статья написана с применением расширения https://shd101wyy.github.io/markdown-preview-enhanced для Visual Studio Code — за что автору летят дополнительные лучи добра.
Ну и в качестве финального момента хотел бы заметить, что выбор Cunfluent Inc
в качестве платформы разработки Kafka-Connect
— экосистемы JDK выглядит все таки странно. Не удивлюсь если их конкуренты сделают такое же, но на GoLang, NodeJS (что-нибудь типа Kafka-Beats-Hub
)
Красивые картинки в формате GraphViz я делаю при помощи хитрого проекта Docker2GraphViz — помогает поддерживать актуальный контур и техническую документацию в формате Markdown
set CURPATH=%~dp0
set DOCKER_DIR=%CURPATH%dockers
docker run --rm -it --name dcv -v %DOCKER_DIR%:/input pmsipilot/docker-compose-viz render -m image --force --output-file=infra-topology.png infra.yml
docker run --rm -it --name dcv -v %CURPATH%:/input pmsipilot/docker-compose-viz render -m image --force --output-file=apps-topology.png applications.yml
copy /b/v/y dockersinfra-topology.png contentassetsinfra-topology.png
copy /b/v/y apps-topology.png contentassetsapps-topology.png
Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…
Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…
Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…
У частных продавцов на Авито появилась возможность составлять текст объявлений с помощью нейросети. Новый функционал доступен в категории «Обувь, одежда,…
24 апреля 2024 года в Москве состоялась церемония вручения наград международного конкурса Workspace Digital Awards. В этом году участниками стали…
27 июня Яндекс проведет гик-фестиваль Young Con для студентов и молодых специалистов, которые интересуются технологиями и хотят работать в IT.…