[БЕЗ_ЗВУКА] В этом видео мы поговорим про то, каким образом можно реализовать изменение конфига без перезагрузки программы. Часто это называется online conf или hot reloadConfig. Зачем это надо и почему это удобно? Дело в том, что часто хочется развозить config без того, чтобы перезагружать сервис, выводить его из нагрузки, например, а используя какую-то админку. В данном случае админку мы будем использовать консул, где есть key value хранилище, которое можно организовать, как config. Я создал там папку myapi, и у меня есть несколько полей: api_token и max_length. Это какие-то абстрактные поля, я не привязываю к ним никакой бизнес-логики в этом примере. Давайте создадим ещё один, например, назовем его address. Нет, вот так, пусть будет session_addr и там какой-то адрес. Я добавил ещё один config. Согласитесь, добавлять config таким образом довольно удобно. Очень опасно, безусловно, но удобно. Итак, давайте теперь рассмотрим, каким образом это можно сделать в коде. Для начала мы распарсим флаги, всё-таки совсем всё через онлайн conf не пустить. И хотя бы адрес консула нужно откуда-то передавать. Далее мы подключаемся к консулу, загружаем config, запускаем его онлайн обновление в отдельной горутине, которая у нас будет работать постоянно. Дальше мы регистрируем наш обработчик запросов и оборачиваем это всё в configMiddleware. Начнем рассматривать это со стороны загрузки config для начала. Итак, загружаем config. Сначала я создаю QueryOptions. Когда я приводил пример с консулом в grpc для service discovery, там был polling. Polling это значит, что я периодически опрашиваю внешний сервис для получения какой-то информации. В данном случае я немножко оптимизирую этот механизм, я не опрашиваю постоянно, я предаю туда последний известный мне индекс консула. Это своего рода инкремент, который обновляется с каждым обновлением консула. Там есть ещё, например, параметр wait time, можно сказать давай ждать больше. Таким образом, мне прилетает обновление сразу же, как только оно появилось на стороне консула. Далее, я получаю config, я обращаюсь к сервису key value в консул, говорю, дай мне список из префикса. cfgPrefix у меня это myapi слэш. Как раз таки то, что мы видим вот здесь. Итак, и передаю туда опции о том, чтобы говорить, что давай ждать последнего индекса. По умолчанию при старте программы он равен у меня нулю, поэтому consul мне ответит сразу. Возвращает он мне список его значений и информацию какую-то о самом консуле. Из этой информации я беру LastIndex и обновляю его, то есть я печатаю, что на той стороне последний индекс такой-то. Если он вдруг не изменился, вдруг такое может случиться, я говорю, что он не изменился. Тут нужно написать return. Мне config обновлять не надо. Далее, я создаю map с новым config'ом, это map типа string string. Дело в том, что consul возвращает вообще как слайс байт данные, но я их буду как строк интерпретировать. Если я запрашиваю по префиксу, то мне вернется в том числе и сама эта папка, сам этот folder. Он мне не нужен, поэтому я его буду пропускать. Далее я буду убирать префикс этой папки из ключа и класть его в map. После этого дела под локом я буду обновлять уже глобальный config. Сейчас config у меня сделан в виде глобальной переменной, дальше расскажу, почему так и что с этим мы будем делать. Итак, обновляя config, обновляя последний индекс, который у меня был, и печатаю на экран, что теперь у меня config изменился. Посмотрим вживую, как это работает. Запустим нашу программу. При старте она у меня запустилась. Вот мой токен, тут есть индекс создания, индекс модификации, есть лок, флаги и прочее. Вот мой max_length, вот мой session_addr, и последний индекс 1563. Таким образом, индекс моего config это 1563, о чём я написал. Config обновился до версии 1563. Вейт представляет из себя и сейчас вот такую map. token, max_length и session_addr. Хорошо. Я обновил. Теперь надо посмотреть, что я могу получить. Я вывел теперь обращение к этому config уже в вебе. В вебе моя бизнес-логика обратилась к этому config для того, чтобы получить какие-то значения. Тут сразу можно заметить какую-то проблему. Какая проблема? Config глобальный, global cfg. На самом деле там не так, но почему это проблема, я объясню сейчас. Может возникнуть такой случай, что у вас под каким-то config в вашем приложении включается или выключается какой-то функционал в начале вашего запроса и в конце вашего запроса. Однако тот функционал, который включается в конце, он требует того, что было проинициализировано, например, вот здесь. А если его не будет, произойдет бабах. Что это значит? Это значит что, если между этими вещами где-нибудь вот здесь произойдет обновление config, этой глобальной map, то будет плохо. Может возникнуть рейз. Рейз даже не по обращению к map, не на то, что она конкурентна и безопасна, а рейз именно по самой логике. Поэтому даже покрытие локом не поможет. Как быть и что делать? Вроде как у меня есть Lock, но всё равно он от этой ситуации не спасает. Хорошо. Теперь уже посмотрим наш config MiddleWare. Что в нём происходит? В нём я в контекст записывал локальный config. Я получаю его под ридлоком. Read lock — это значит, что я беру информацию какую-то просто на чтение, я не буду ее изменять внутри. Поэтому если у меня будет много конкурентных запросов, в этом месте тормозить не будет, не будет глобального лока, он будет только в случае, когда config обновляется. Хорошо, теперь я получил localCfg. В чём весь смысл этой операции? А весь смысл этой операции в том, что map — это ссылочный тип. Таким образом, я получаю ссылку на эту map, и дальше я прокидываю ее вместе с контекстом везде, через контекст WithValue. И, таким образом, когда я буду получать эту map из контекста, я буду всегда обращаться к одной и той же map. Таким образом, если я вот здесь в начале запроса ее создам, она мне неизменной пройдет через весь мой запрос. Таким образом, в случае, когда вот здесь я требую того, чтобы здесь нет, у меня не случится. Но вы скажете, а как же так, GlobalCfg он же обновляется. Дело в том, что я меняю только саму ссылку на GlobalCfg и причём под локом. Я не меняю саму оригинальную map, которая была там ранее. Поэтому, как только я меняю ссылку на GlobalCfg, то, что там находится внутри, оно уже теперь лежит только в тех контекстах, которые использует этот config, ту версию предыдущую. Что это значит? Это значит, что как только все эти контексты завершатся, выйдут из памяти, то старый config просто уберется сборщиком мусора. Довольно удобно. Я кладу в контекст ссылку на текущую неизменяемую map с контекстом и протаскиваю ее сквозь весь свой запрос весь реквест. Это request scoped config такой. Хорошо, config WithValue. Теперь я могу получить его через ctxValue. Я обращаюсь к config, получаю map string string и преобразую его... В контекст Value, напомню, лежит пустой интерфейс. Я получаю его, преобразую, если вдруг он не преобразовался либо там ещё что-то, я говорю, что ошибка, config нет. Дальше я его получаю и вывожу вам на экран то, что вы видели. Вот мой config теперь 1563. Теперь, если я что-то обновлю, то с той стороны он сразу же обновится 1599. Всё, config обновился. По-прежнему есть некоторые опасности, конечно же, его использования. Конечно, это создает некоторое параллельное api, но иногда всё-таки бывает нужно бизнес-логику каким-то образом конфигурировать быстро. Это вот один из вариантов, это пища вам для размышления. Это не продакшн вариант, конечно же, но, я надеюсь, он вас натолкнет на какие-то решения, которые подходят к вам. Какие есть недостатки у этого config? Во-первых, это map. Это map string string, она не структурирована, то есть вы можете опечататься, там может не быть нужного ключа, он может быть не в том формате, валидации тут никакой нет, поэтому, возможно, сюда стоит провести какую-то валидацию с сообщением о том, что config неправильный. Либо хотя бы писать там Json, который вы уже будете распаковывать в нужную вам структуру. Тут даже есть специальная опция Validate Json Также это удобно, когда вы конфигурируете какие-то ваши бизнес-опции, например, вкл-выкл. Потому что они всё-таки простые. Либо ещё какие-то именно бизнес-опции для бизнес-логики. Если же вы будете конфигурировать, например, адреса к каким-то базам данных или каким-то микросервисам, то тут могут возникнуть проблемы в том, что вам нужно обрабатывать код для переинициализации, вам нужно покрывать эти сервисы локом для того, чтобы не обратиться случайно туда, где уже никакого сервиса нет. Поэтому к этому нужно подходить осторожно. Это, ещё раз напомню, учебный пример. Просто для того чтобы показать вообще, как можно, каким образом можно организовать что-то вообще подобное, и надеюсь, что вы с умом подойдете к организации вашего config и у вас не будет тех страшных вещей, которые я описал в этом видео.