Согласен, что лучше использовать подходящее готовое решение, чем делать свое. Но для того чтобы выбрать наиболее подходящее решение и обосновать его использование нужно обладать некоторым опытом. Суть моих статей - попытаться объяснить как можно спроектировать архитектуру и на ее основе создать систему людям, которые опытом не обладают.
Система не упадет. В описанном Вами случае она должна вернуть пользователю ошибку. Например, HTTP Статус 503. Service Unavailable (Сервис недоступен). Это временная проблема на стороне сервера. Она показывает, что сама ссылка, возможно, существует, но сервер сейчас не может её обработать. Ситуация недоступности URL Shortener является исключительной. Проблему поддержания нужного количества экземпляров сервисов можно решить при помощи механизма health check. Насколько я знаю, такие механизмы доступны при развертывании в облаке.
По поводу обхода анализа незаконных ссылок. Универсальной защиты от обхода придумать, скорее всего, нельзя. Но следуя Вашему предложению, можно, например, проверять уже сохраненные ссылки на попадание в список опасных и таким образом периодически чистить свою базу и кэш.
292G урлов стоит понимать как теоретический максимум, вытекающий из анализа требований. По данным из https://www.techcult.ru/internet/14774-kolichestvo-sajtov-prevysilo-milliard на начало 2025 года в инете насчитывается примерно 1,1 млрд сайтов. Но, как, говорится, есть нюансы. Во-первых, активно развиваемых владельцами сайтов будет намного меньше (15-20%), а остальные, насколько я понимаю, доступны для просмотра, но информацию на них уже давно не обновляют. При этом ссылки доступны и отвечают. Во-вторых, насколько я понимаю, Вы исходите из предположения, что на один доступный URL сайта можно сделать только одну ссылку. Но есть такие ресурсы как ютуб, на котором каждое видео имеет свой урл. По оценке, опубликованной здесь https://habr.com/ru/companies/ru_mts/articles/784806/ таких урлов уже больше 13 миллиардов.
Приведенная в комментарии ссылка у меня не открывается. Обращение к БД всегда происходит только при создании ссылок. Если нужно перейти по короткой ссылке, то система вначале ищет её в кэше Redis и только при отсутствии ссылки там происходит обращение к БД. Но и в этом случае можно оптимизировать нагрузку на БД путем создания реплик. Запись идет только на мастер ноду, а чтение со слейв.
Про алгоритм генерации uid и про балансировку нагрузки будут отдельные статьи. Материала довольно много, поэтому решил не делать эту статью ещё длиннее.
Честно говоря, не совсем понял как бинарное дерево может быть корутиной. Бинарное дерево - это иерархическая структура, данные в которой упорядочены. Например, двоичное дерево поиска. У всех узлов левого поддерева произвольного узла X значения ключей меньше либо равны, значению ключа узла X. У всех узлов правого поддерева X значения ключей больше, нежели значение ключа узла X. Корутина - это, грубо говоря, код, обрабатывающий некоторые данные. Она не является структурой данных. В статье речь идет о двух связном списке планирования для сопрограмм. Список последовательно просматривается с целью найти сопрограмму, которую можно запустить на выполнение или продолжить её выполнение. Такой упорядоченности как в бинарном дереве здесь нет. Просто проходим по всем элементам списка.
Постараюсь объяснить на примере функций setjmp/longjmp int setjmp(jmp_buf env) - сохраняет состояние программы, устанавливая точку возврата при помощи структуры типа jmp_buf. Возвращаемое значение этой функции зависит от контекста. Если функция setjmp вызывается впервые, она возвращает 0. Если управление передается обратно в setjmp из longjmp, то возвращается ненулевое значение аргумента val функции longjmp. Функция void longjmp(jmp_buf env, int val) выполняет переход в точку, заданную ранее с помощью setjmp. При этом восстанавливается сохраненное состояние, и выполнение продолжается с места, где был сделан вызов setjmp. Функция longjmp передает управление обратно в функцию, которая вызывала setjmp и с этого места продолжается выполнение программы. Параметр val представляет значение, которое будет возвращено из функции setjmp при восстановлении состояния программы - своего рода статус выполнения. Оно должно быть отличным от нуля, чтобы отличить этот случай от первого вызова setjmp. Если же это значение устновить равным 0, то все равно setjmp возвращает 1. Сделано это для того чтобы предотвратить переход программы в бесконечный цикл. Рассмотрим пример:
jmp_buf env;
void test() {
puts("В функции test перед longjmp.");
longjmp(env, 1); // Возвращаемся к точке, где был вызван setjmp
puts("Эта строка не будет выведена.");
}
int main() {
if (setjmp(env) == 0) { // Точка возврата из longjmp
// Первый вызов setjmp возвращает 0 при нормальном вызове
puts("Выполняем код до вызова longjmp.");
test(); // В этой функции будет вызван longjmp
} else {
// Второй вызов - longjmp возвращает ненулевое значение
puts("Возвращение из longjmp");
}
return 0;
}
Если бы можно было при помощи longjmp заставить setjmp повторно вернуть 0, то программа бы зациклилась.
"Протопоток" относится к user space thread. Но я не считаю эти пониятия тождественными так как fiber и goroutine также относятся к user space thread. Да, "протопотоки" stackless и используются, в первую очередь, в языке Си для написания программ в системах с сильно ограниченным объемом памяти типа микроконтроллеров. Что касается fiber, то тут надо уточнить. Есть библиотека Fiber для С++, входящая в Boost. Там Fiber stackful. Есть еще библиотека Fiber в Windows. Я с ней не работал, но вроде они тоже stackful, как описано здесь. Горутины не для Си/С++, но тоже stackful.
Select упомянут в тексте статьи, но в примерах программ его нет. Есть только иллюстрация использования неблокирующего режима работы файлового дескриптора в корутине. В будущем планирую отдельную статью в которой будет рассмотрено использование epoll с сопрограммами на примере простого tcp сервера.
Смысл в том, чтобы можно было перейти из одной функции в другую, например из планировщика сопрограмм в корутину, запланированную к выполнению. В качестве примера реализации можно привести язык Си, где есть функция setjmp для сохранения состояния выполнения программы и longjmp для перехода к сохраненному состоянию. Неограниченное продолжение пришло из языка Scheme, где реализовывалось при помощи функции call/cc. В качестве второго примера можно назвать библиотеку Boost Context, где есть функция callcc, представляющая собой аналог call/cc из Scheme.
Согласен, что лучше использовать подходящее готовое решение, чем делать свое. Но для того чтобы выбрать наиболее подходящее решение и обосновать его использование нужно обладать некоторым опытом. Суть моих статей - попытаться объяснить как можно спроектировать архитектуру и на ее основе создать систему людям, которые опытом не обладают.
Система не упадет. В описанном Вами случае она должна вернуть пользователю ошибку. Например, HTTP Статус 503. Service Unavailable (Сервис недоступен). Это временная проблема на стороне сервера. Она показывает, что сама ссылка, возможно, существует, но сервер сейчас не может её обработать. Ситуация недоступности URL Shortener является исключительной. Проблему поддержания нужного количества экземпляров сервисов можно решить при помощи механизма health check. Насколько я знаю, такие механизмы доступны при развертывании в облаке.
По поводу обхода анализа незаконных ссылок. Универсальной защиты от обхода придумать, скорее всего, нельзя. Но следуя Вашему предложению, можно, например, проверять уже сохраненные ссылки на попадание в список опасных и таким образом периодически чистить свою базу и кэш.
292G урлов стоит понимать как теоретический максимум, вытекающий из анализа требований. По данным из https://www.techcult.ru/internet/14774-kolichestvo-sajtov-prevysilo-milliard на начало 2025 года в инете насчитывается примерно 1,1 млрд сайтов. Но, как, говорится, есть нюансы. Во-первых, активно развиваемых владельцами сайтов будет намного меньше (15-20%), а остальные, насколько я понимаю, доступны для просмотра, но информацию на них уже давно не обновляют. При этом ссылки доступны и отвечают. Во-вторых, насколько я понимаю, Вы исходите из предположения, что на один доступный URL сайта можно сделать только одну ссылку. Но есть такие ресурсы как ютуб, на котором каждое видео имеет свой урл. По оценке, опубликованной здесь https://habr.com/ru/companies/ru_mts/articles/784806/ таких урлов уже больше 13 миллиардов.
Приведенная в комментарии ссылка у меня не открывается. Обращение к БД всегда происходит только при создании ссылок. Если нужно перейти по короткой ссылке, то система вначале ищет её в кэше Redis и только при отсутствии ссылки там происходит обращение к БД. Но и в этом случае можно оптимизировать нагрузку на БД путем создания реплик. Запись идет только на мастер ноду, а чтение со слейв.
Непонятно почему в первый же день такой сервис окажется во всех черных списках.
Про похожие статьи по данной теме я написал во введении
Согласен)))
Про алгоритм генерации uid и про балансировку нагрузки будут отдельные статьи. Материала довольно много, поэтому решил не делать эту статью ещё длиннее.
Большое спасибо за комментарий, исправил.
Честно говоря, не совсем понял как бинарное дерево может быть корутиной. Бинарное дерево - это иерархическая структура, данные в которой упорядочены. Например, двоичное дерево поиска. У всех узлов левого поддерева произвольного узла X значения ключей меньше либо равны, значению ключа узла X. У всех узлов правого поддерева X значения ключей больше, нежели значение ключа узла X. Корутина - это, грубо говоря, код, обрабатывающий некоторые данные. Она не является структурой данных. В статье речь идет о двух связном списке планирования для сопрограмм. Список последовательно просматривается с целью найти сопрограмму, которую можно запустить на выполнение или продолжить её выполнение. Такой упорядоченности как в бинарном дереве здесь нет. Просто проходим по всем элементам списка.
Постараюсь объяснить на примере функций setjmp/longjmp
int setjmp(jmp_buf env) - сохраняет состояние программы, устанавливая точку возврата при помощи структуры типа jmp_buf.
Возвращаемое значение этой функции зависит от контекста. Если функция setjmp вызывается впервые, она возвращает 0. Если управление передается обратно в setjmp из longjmp, то возвращается ненулевое значение аргумента val функции longjmp.
Функция void longjmp(jmp_buf env, int val) выполняет переход в точку, заданную ранее с помощью setjmp. При этом восстанавливается сохраненное состояние, и выполнение продолжается с места, где был сделан вызов setjmp. Функция longjmp передает управление обратно в функцию, которая вызывала setjmp и с этого места продолжается выполнение программы.
Параметр val представляет значение, которое будет возвращено из функции setjmp при восстановлении состояния программы - своего рода статус выполнения. Оно должно быть отличным от нуля, чтобы отличить этот случай от первого вызова setjmp. Если же это значение устновить равным 0, то все равно setjmp возвращает 1. Сделано это для того чтобы предотвратить переход программы в бесконечный цикл. Рассмотрим пример:
Если бы можно было при помощи longjmp заставить setjmp повторно вернуть 0, то программа бы зациклилась.
"Протопоток" относится к user space thread. Но я не считаю эти пониятия тождественными так как fiber и goroutine также относятся к user space thread. Да, "протопотоки" stackless и используются, в первую очередь, в языке Си для написания программ в системах с сильно ограниченным объемом памяти типа микроконтроллеров. Что касается fiber, то тут надо уточнить. Есть библиотека Fiber для С++, входящая в Boost. Там Fiber stackful. Есть еще библиотека Fiber в Windows. Я с ней не работал, но вроде они тоже stackful, как описано здесь. Горутины не для Си/С++, но тоже stackful.
Select упомянут в тексте статьи, но в примерах программ его нет. Есть только иллюстрация использования неблокирующего режима работы файлового дескриптора в корутине. В будущем планирую отдельную статью в которой будет рассмотрено использование epoll с сопрограммами на примере простого tcp сервера.
Смысл в том, чтобы можно было перейти из одной функции в другую, например из планировщика сопрограмм в корутину, запланированную к выполнению. В качестве примера реализации можно привести язык Си, где есть функция setjmp для сохранения состояния выполнения программы и longjmp для перехода к сохраненному состоянию. Неограниченное продолжение пришло из языка Scheme, где реализовывалось при помощи функции call/cc. В качестве второго примера можно назвать библиотеку Boost Context, где есть функция callcc, представляющая собой аналог call/cc из Scheme.