Как стать автором
Обновить
22
0
Сергей Солдатов @ssa-company

Разработчик на Go

Отправить сообщение

В общем случае это проблема с сертификацией специалиста.

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

В 1937 году в Нью-Йорке Билл Уилсон, в прошлом злоупотреблявший алкогольными напитками, вместе со своими единомышленниками организовал работу первой официальной «Группы Анонимных Алкоголиков».

В настоящее время движение, которое способствовало появлению «12 Шагового» метода в лечении и реабилитации химической зависимости, широко распространено как в мире, так и в России, и более 3,5 млн человек в 141 стране мира начинают новую трезвую жизнь.

Доказательством признания программы «12 шагов» врачебным сообществом является то, что общество АА в 1951 году получило наивысшую американскую премию в области медицинских наук - премию Ласкера, «За уникальный и успешный подход к вековой проблеме алкоголизма».

Я соглашусь с Вами, сам где-то использую авторизацию через соцсети, а где-то только прямую. Мы предоставляем выбор пользователю. И этот выбор, судя по анализу статистики, привлекает новых пользователей.

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

На некоторых сервисах, особенно, если аккаунт становиться корпоративным, авторизации разными способами приводят к попаданию в разные аккануты.

Не все готовы оставлять свой номер телефона. Одно дело банк или крупный маркетплейс, другое дело, если некая новая платформа. Никто не хочет потом телефонного спама. К тому же, по номеру телефона гораздо проще отследить владельца. Ваша целевая аудитория согласится на это? В-третьих, смс не бесплатны, в отличии от пинкода отправленного на почту. Ну и стоит напомнить, в про уязвимость SS7 - Уязвимость в протоколе SS7 уже несколько лет используют для перехвата SMS и обхода двухфакторной аутентификации / Хабр (habr.com).

И чуть не забыл про кейс с переходом номера от одного владельца к другому. Зачастую, если человек не пользуется номером более 6 месяцев, номер считается свободным и может уйти к другому человеку. Сможет ли он зарегистрироваться на вашем сервисе? Ведь номер может оказаться занят.

Ну так у меня сам паблишер ограничен только конкретным exchange, ему он передаётся при создании. Был бы в моей реализации паблишер, который шлёт в разные exchange, тогда да, это было серьёзное сужение, т.к. в текущей реализации я бы городил отдельные сессии. Я подумаю в эту сторону.

Сейчас можно выдернуть сессию через GetChannelFromPool и послать через неё в другой Exchange.

Мне кажется, проблема в том, что я явно не подсветил в начале статьи - я не делаю ещё одну реализацию протокола amqp. Я использую готовую библиотеку, где всё уже сделано за меня. Но там нет автореконнекта.

Сдается мне, вы совсем не понимаете, что такое каналы в amqp :-) При чем тут, вообще exchange?! Еще раз... что со стороны сервера, что со стороны клиента - канал - это сессия. Более того... до 0.10 для сервера, конкретный канал - это не просто сессия... это ещё и "привязка" конкретного экземпляра клиента... со всеми его qos, таймингами, фреймингами и т.п.

Да, канал это сессия. Под exchange в моём ответе имелось ввиду его имя, чтобы по имени в пуле на один и тот же exchange вернуть один и тот же канал. Т.е. какдый раз, когда будут отправлять сообщение в один и тот же exchange, будет использоваться одна и таже сессия. Причём у меня не только коннект описан, но и паблишер, который получит строго один раз сессию и будет её использовать до получения ошибки или до его удаления. При получении ошибки канал/сессия будут пересозданы и опять будут единственные для этого паблишера.

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

Я никак не подменяю логику работы с каналами в github.com/streadway/amqp. Сделана просто обёртка над ней для реконнекта. И пока косьюмер/паблишер жив, он использует один и тот же канал, который он получит при запуске.

а то можно ненароком подумать, что паблишер не может в разные exchange сообщения посылать. Или, что ему для этого разные каналы нужны :-)

Как это возможно в моей реализации? Здесь указывается конкретный exchange для паблишера при его создании.

?! Оно, насколько я помню, всю жизнь работало так: посылаешь серверу connection.close, он тебе в ответ засылает пачку channel.close, ты их обрабатываешь, в конце концов получая от сервера connection.close-ok... после чего закрываешь сокет.

Повторю, я не выдумываю свою реализацию взаимодействия по протоколу amqp, используется готовая библиотека github.com/streadway/amqp, я сделал только реконнект поверх неё.

Вы расписываете про то, как на уровне протокола работает, я это знаю. Но я не пишу свою реализацию, зачем, если есть хорошая библиотека.

Закрытие каналов в github.com/streadway/amqp это не только отправить сообщения по протоколу, это ещё и грохнуть созданные структуры. И для прозрачности сделано по примеру github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/send.go, создали подключение, потом канал, при завершении выполняем в обратном порядке.

Вот как раз с точностью до наоброт. Если этого одна "логика" - например, какой-нибудь сервис типа запрос/ответ - то прием запроса, и отправка ответа могут конечно "жить" в разных каналах. Но это, скажем так, сильно сужает использование возможностей amqp.

Поясните, в чём сужение? Напомню, я не делаю свою реализацию протокола.

А причем тут exchange?! У него просто нет экслюзивности. В отличие, например, от queue... а их, как я понял, вы тож через свой "служебный канал" объявляете. Т.е. - если на пальцах - очереди, внезапно, могут быть и "приватными"... консумить которые можно только через тот же канал, через который они были объявлены. И это не просто так придумано :-)

  • Exclusive (used by only one connection and the queue will be deleted when that connection closes)

  • An exclusive queue can only be used (consumed from, purged, deleted, etc) by its declaring connection

Из документации на RabbitMQ, где здесь речь про канал? А соединение у меня одно.

Хм... а что этот "пул" должен решать? Каналы же (если речь о каналах amqp) не шарятся. Вы не можете, грубо говоря, сделать consume в одном канале, а cancel сделать в другом. Какой смысл в этом "пуле"?

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

То, что каналы для консумеров и паблишеров "не должны пересекаться" - это, мягко говоря, бред. Каким образом - при таком "непересечении" - вы собераетесь обеспечивать, например, транзакционность?!

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

Да, консумер и паблишер могут прекрасно работать на одном канале. Ничего не сломается, наверное. Но лучше же их наверно разнести?

За такое, в приличном обществе, я извиняюсь, "бьют канделябрами". Каким образом, при таком подходе, вы собираетесь обеспечивать, например, экслюзивность деклараций?

Вы точно знакомы с RabbitMQ? Ниже кусок из документации на github.com/streadway/amqp

ExchangeDeclare declares an exchange on the server. If the exchange does notalready exist, the server will create it.  If the exchange exists, the server verifies that it is of the provided type, durability and auto-delete flags.

Вопрос, а почему не используете workflow-решения, например, https://temporal.io/?

Не очень понятно, как это тривиально решается. Максимум возможно исключить запрос из БД тех объектов, которые в данный момент удаляются из кэша. Ну запросите вы объект, который есть в кэше и конкретно сейчас не протух. А сразу после обновления в кэше запустится процедура очистки протухших объектов. И ваш обновлённый объект попадёт под удаление (не пользуется им никто). А если так получилось с несколькими экземплярами сервиса? Сколько «нужных» запросов прилетит в БД?
В общем, принципиально вопрос не решается, как запрашивали на авось, так и будете. Это основная идея приведённого вами алгоритма «у меня есть обновление! ну давай, может пригодится».
В кэше протухнет — ttl истёк, объект удалили из кэша. Вернётся значение которое уже не нужно, поскольку нечего обновлять в кэше. Конечно можно добавить его снова в кэш, но это уже избыточность.
Возможно и нет сложностей, но я не вижу:
  • проверку связи с БД
  • проверку, что нет пропущенных нотификаций

Зато вижу, что после получения нотификации делается запрос в БД по принципу «мне повезёт» и данные в кэше не протухнут к моменту ответа.
А что в моем варианте мешает делать так же? Если коннект потерян, значит данные в кеше неверные. А что с этим делать, сбрасывать кеш или перезагружать его или перезагружать только часть, решается по месту.

В вашем варианте есть асинхронность обработки NOTIFY и проверки связи с БД. Я уже выше написал, вы неизбежно будете сталкиваться (особенно с ростом количества транзакций и количества экземпляров сервиса), что NOTIFY ещё не обработали, а VERSIONS запросили.
И будете ли вы удалять или перезагружать и то и другое плохо. В первом случае вы просто будете регулярно удалять валидный кэш, что приведёт к нагрузке на БД на время наполнения кэша. Во втором случае вы сделаете запрос в БД всего, что >=VERSIONS в кэше, а значит запросите и то чего нет в кэше, и то что в следующую секунду протухнет, и опять-таки нагрузите БД и ваш сервис громадным и скорее всего избыточным запросом.
Даже если вы решите перед выполнением запроса пройтись по кэшу и для запроса указать только реально содержащиеся данные, всё равно есть риск, что к моменту выполнения запроса часть данных из кэша уже устареют. Т.е. получаем запрос на авось, а не нужных данных.

При получении сообщения нужно проверить версию и если она не «версия кеша + 1» то нужно перечитывать данные из базы.

А здесь тоже интересно. Какие данные? Все что => VERSIONS? Или те, что есть в кэше? Но только часть из них протухнет, пока запрос делали.
Удаляем из кеша, берем из БД и добавляем в редис это 3 разные операции. Будет ли все работать, если между этими шагами будут проходить часы, и сотни запросов на другие сервера, а не миллисекунды?


Аналогичный вопрос можно задать, будет ли всё работать если между завершением транзакции в БД и обработкой полученной нотификации пройдут часы?

— делаете update, транзакция прошла, отправили нотификацию
— делаете запрос versions, добавляем в кеш все, чего в нем нет.
— пришла нотификация, ее версия уже есть в кеше, нотификацию можно пропустить


Тогда если произошло обновление всей БД (пессимистичный вариант), всю БД и придётся запрашивать (запрос всего, что старше versions в кэше). И это полбеды, другая проблема, что запрашивается то, что или уже протухло в кэше, или вот-вот протухнет или вовсе не было в кэше.

В моём варианте, из БД всегда запрашивается только то, чем пользуются клиенты сервиса.

Кеш видел все нотификации с версиями <= максимальной версии в кеше.

Но недостаток, что надо всем сервисам, проверять, а есть ли у них в кэше объект из нотификации. Т.е. обновили объект в БД, но на чтение его никто не запрашивает, в кэше его нет, но нотификацию обработать надо (поискать объект в кэше) и после обработки сменить versions кэша.
Это понятно, что не имеет отношения. UUID генерируется же писателем в базу, который очищает редис и локальный кеш. Затем читается читателем (одним или несколькими одновременно) и так попадает в редис, верно?


Нет, не верно. UUID не пишется в БД, он используется только в Redis и в кэше. UUID генерируется при записи в Redis.

Если прилетела нотификаця с версией X, то из базы гарантировано будет прочитана версия >=X. Эти гарантии дает нам комит транзакции в БД. Если пришла нотификация о старой версии или о версии, которая уже есть в кеше, то она, конечно, просто игнорируется. Ничего ждать не нужно


Последовательности событий:
— делаете update, транзакция прошла, отправили нотификацию
— делаете запрос versions, запрос прошёл, значит связь есть
— versions отличается, сбрасываем кэш
— прилетает нотификация

Что делать, чтобы зря не сбрасывать кэш?
Генерируемый UUID не имеет никакого отношения к ID-объекта в БД. Я мог бы вместо него использовать CRC объекта, чтобы отслеживать изменения, но генерировать UUID дешевле, чем CRC считать.
В Redis лежит пара: ID-объекта (ключ)<->UUID (значение)
В кэше лежит: ID-объекта (ключ)<->UUID+содержимое объекта.

Сколько бы не было писателей в Redis, всё изменения будут видны по изменению UUID, при обновлении старая пара ID-объекта (ключ)<->UUID (значение) будет удалена, и добавлена новая (ID-объекта тот же, а UUID другой). Если проскочило другое обновление, всё равно не совпадёт UUID в Redis и локальных кэшах и все серверы запросят БД.

Кеш нужно сбрасывать только если коннект потеряли.

Хорошо, вы периодически делаете запрос versions, запрос прошёл, значит связь есть, а versions отличается (нотификация ещё не долетела или не обработана), что делать будете? Добавлять таймаут ожидания нотификации?
Сброс кэша, возможно только из-за одного обновлённого элемента, не самая хорошая идея. С учётом того, что soft-удаление, по сути, тот же update, с вытекающим отсюда изменением versions в БД то возможна такая последовательность событий:
— обновили запись в БД
— БД отправила нотификацию
— проверили versions в БД,
— выявили несоответствие и сбросили кэш
— получили и обработали нотификацию с одним элементом

Частота такого «ложного» сброса кэша будет зависеть от того, как часто обновляют/удаляют записи и того, как часто проверяется versions в БД.

Насчёт описанного вами примера, да, такого возможно. Но решение простое — при добавлении в кэш сразу делать разный UUID между тем, что в Redis и тем, что в кэше. Тогда при следующем обращении к кэшу, будет выявлена эта разница и будет выполнено обновление кэша. Да, это чуть-чуть снизит производительность, поскольку даже при отсутствии супер быстрого обновления параллельно с добавлением в кэш, будет выполняться один лишний запрос в БД. Но при этом не надо следить за версиями в БД, выполнять сброс кэша, добавлять тригер в БД или менять запросы. А все изменения в кэше будут точечные, только для конкретных элементов.
1. А если произошло удаление записи?
2. А что с консистентностью кэша и БД между периодами опроса базы?
3. Зачем перечитывать в кэш все данные с бОльшей версией? Кэш должен содержать только то, чем пользуемся, а не на авось.
Ваш алгоритм при учёте всех моментов становиться гораздо сложнее моего, а преимущество только одно — нет Redis.
Да, ошибся, байтслайсы кладутся.
Но у меня в исходной структуре часть полей это интерфейсы. У protobuf3 есть тип Any, но это равносильно interface{}, а мне-то надо MyInterface. Не говоря уже о том, что у меня уже есть описанная структура, и мне надо её обрабатывать, а попытка подружить с тем, что сгенерируется в описании для protobuf3 также приведёт к снижению производительности из-за необходимости множества промежуточных копирований.
Внутри тоже map и тоже есть удаление/добавление в него. Поэтому не очень понятно, как поможет против пожирания памяти map.
Я смотрел в сторону Binary Serializer
protobuf в Redis нет, есть конечно какие-то сторонние модули, особо не прижившиеся, а непонятно что мне DevOps точно не позволят запускать)
Если есть что-то, что позволит в Redis использовать protobuf3, с интересом посмотрю.
Я тоже думал, что конвертирование в бинарную форму будет производительнее. Погуглив и проверив понял, что напротив, оказывается медленнее.
1

Информация

В рейтинге
Не участвует
Работает в
Зарегистрирован
Активность