в этом задании мы пишем аналог unix pipeline, что-то вроде:
cat common.go | grep func | sort | uniq -c | sort -n
когда STDOUT одной программы передаeтся как STDIN в другую программу
но в нашем случае эти роли выполняют каналы, которые мы передаeм из одной функции в другую.
само задание по сути состоит из двух частей
- написание функции
RunPipeline
которая обеспечивает нам конвейерную обработку функций-воркеров, которые что-то делают. - написание нескольких функций, которые считают нам информацию по письмам пользователей
если кратко, то мы хотим запустить следующую цепочку:
cat emails.txt | SelectUsers | SelectMessages | CheckSpam | CombineResults
то есть мы получаем на вход имейлы юзеров, селектим их из "базы" и получаем каждому юзеру user_id. затем селектим по каждому user_id список писем(msg_id) этого юзера. дальше проверяем эти письма на спам и выдаем итоговый результат: какие у письма со спамом, а какие нет.
а теперь подробнее про реализацию этой цепочки:
SelectUsers()
in
-string
внутриinterface{}
. это имейлы юзеровout
- отдает структуркиUser{}
. это результат функцииGetUser()
- особенности:
GetUser()
выполняется 1 секунду. его можно вызывать параллельно для нескольких юзеров. это экономит время- у некоторых юзеров есть alias'ы(псевдонимы). то есть на вид это два разных имейла, но на самом деле это один и тот же юзер в базе. например "[email protected]" - это алиас к "[email protected]".
SelectUsers()
должен отдавать вout
только уникальных юзеров.
SelectMessages()
in
-User{}
внутриinterface{}
отSelectUsers()
out
- отдаетMsgID
. это айдишники писем юзеров - результат функцииGetMessages()
- особенности:
GetMessages()
выполняется 1 секунду. его тоже можно вызывать параллельно.- но
GetMessages()
позволяет использовать "батчи". то есть за раз в нее можно запихнуть не 1 юзера, а несколько. максимальное кол-во юзеров = 2. то есть если мы хотим селектнуть у 10ти юзеров письма, то это можно сделать за 5 вызововGetMessages()
. в тестах проверяется, что кол-во вызовов оптимальное
CheckSpam()
in
-MsgID
внутриinterface{}
. это айдишники писемout
-MsgData{}
. это структура с парой полей: id и факт того является ли письмо спамом. это результат работыHasSpam()
- особенности:
HasSpam()
симулирует поход в сервис антиспама, чтоб проверить письмо на наличие спама. один запрос выполняется за 100мс. и у этого сервиса есть "антибрут" - его нельзя вызывать бесконтрольно в кучу потоков. если сделать к нему более 5 параллельных запросов, то он начнет возвращать ошибку и данные о наличии спама вы не получите.
CombineResults()
in
-MsgData
внутриinterface{}
out
-string
. это строки вида "<has_spam> <msg_id>", например "true 17696166526272393238"- особенности:
-
CombineResults()
ждет все результаты изin
, а потом сортирует их по наличию спама и по msg_id. то есть пример вывода может быть такой:true 123 true 5555 true 5556 false 140 false 3000 false 3005
-
В чем подвох:
- из-за описанных выше особенностей у вас либо не будет асинхрона и функции последовательно будут выполняться слишком долго
- либо вы можете наоборот безконтрольно все распаралелить и тогда будут ошибки тк нарветесь на антибрут
- либо вы не соптимизируетe запросы "батчами" и будете лишний раз вызывать функции
- на все расчеты у нас 3 сек.
Код писать в spammer.go.
Запускать как go test -v -race
пример лога, для теста TestTotal
:
2023/01/01 11:26:21 [GetUser() 1.000449332s] args:[email protected] res:{6935840902946149476 [email protected]}
2023/01/01 11:26:21 [GetUser() 1.000461251s] args:[email protected] res:{12499983457589032104 [email protected]}
2023/01/01 11:26:21 [GetUser() 1.000632207s] args:[email protected] res:{2436524555453976083 [email protected]}
2023/01/01 11:26:21 [GetUser() 1.000856246s] args:[email protected] res:{6411680653583780021 [email protected]}
2023/01/01 11:26:21 [GetUser() 1.000894362s] args:[email protected] res:{15459287427396367812 [email protected]}
2023/01/01 11:26:21 [GetUser() 1.000934361s] args:[email protected] res:{1193889969480132348 [email protected]}
2023/01/01 11:26:21 [GetUser() 1.000910476s] args:[email protected] res:{13707254613635009050 [email protected]}
2023/01/01 11:26:21 [GetUser() 1.000992091s] args:[email protected] res:{14529939612954000739 [email protected]}
2023/01/01 11:26:21 [GetUser() 1.001022818s] args:[email protected] res:{14126455436348693091 [email protected]}
2023/01/01 11:26:21 [GetUser() 1.001016139s] args:[email protected] res:{12499983457589032104 [email protected]}
2023/01/01 11:26:22 [GetMessages() 1.000166253s] args:[{ID:14126455436348693091 Email:[email protected]}] res:[6652443725402098015 221945221381252775 9656111811170476016 221962074543525747] err:<nil>
2023/01/01 11:26:22 [GetMessages() 1.00059829s] args:[{ID:2436524555453976083 Email:[email protected]} {ID:6411680653583780021 Email:[email protected]}] res:[12728377754914798838 14498495926778052146 12975933273041759035 12386730660396758454 9323185346293974544 8065084208075053255 10523043777071802347 12792092352287413255 12556782602004681106 12026159364158506481] err:<nil>
2023/01/01 11:26:22 [GetMessages() 1.000343439s] args:[{ID:15459287427396367812 Email:[email protected]} {ID:1193889969480132348 Email:[email protected]}] res:[7594744397141820297 1877225754447839300] err:<nil>
2023/01/01 11:26:22 [GetMessages() 1.000365591s] args:[{ID:13707254613635009050 Email:[email protected]} {ID:14529939612954000739 Email:[email protected]}] res:[2803967521226628027 13245035231559086127 10493933060383355848 378045830174189628 10462184946173556768 10167774218733491071 11204847394727393252 10463884548348336960 15784986543485231004 16476037061321929257 17259218828069106373 4652873815360231330 357347175551886490 11512743696420569029 1595319133252549342 17696166526272393238] err:<nil>
2023/01/01 11:26:22 [GetMessages() 1.000755755s] args:[{ID:6935840902946149476 Email:[email protected]} {ID:12499983457589032104 Email:[email protected]}] res:[5108368734614700369 16728486308265447483 7829088386935944034 26236336874602209 15161554273155698590 14107154567229229487 15728889559763622673 15262116397886015961 59892029605752939 17087986564527251681] err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.728826ms] args:14498495926778052146 res:false err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.808515ms] args:12386730660396758454 res:true err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.727841ms] args:12728377754914798838 res:true err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.864612ms] args:12975933273041759035 res:false err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.793487ms] args:6652443725402098015 res:false err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.478291ms] args:221945221381252775 res:true err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.613594ms] args:2803967521226628027 res:false err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.809267ms] args:9323185346293974544 res:true err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.646974ms] args:5108368734614700369 res:true err:<nil>
2023/01/01 11:26:22 [HasSpam() 101.083156ms] args:7594744397141820297 res:false err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.900365ms] args:9656111811170476016 res:false err:<nil>
2023/01/01 11:26:22 [HasSpam() 100.846218ms] args:16728486308265447483 res:true err:<nil>
2023/01/01 11:26:22 [HasSpam() 101.563802ms] args:8065084208075053255 res:true err:<nil>
2023/01/01 11:26:22 [HasSpam() 101.528313ms] args:13245035231559086127 res:true err:<nil>
2023/01/01 11:26:22 [HasSpam() 101.388859ms] args:1877225754447839300 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.415444ms] args:7829088386935944034 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.775029ms] args:10493933060383355848 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.571509ms] args:12792092352287413255 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.631663ms] args:221962074543525747 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.960382ms] args:10523043777071802347 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.102036ms] args:10462184946173556768 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.165203ms] args:15161554273155698590 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.306838ms] args:12556782602004681106 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.394269ms] args:26236336874602209 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.545377ms] args:378045830174189628 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.12091ms] args:11204847394727393252 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.022293ms] args:15728889559763622673 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.511417ms] args:12026159364158506481 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.34448ms] args:14107154567229229487 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.479134ms] args:10167774218733491071 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.458353ms] args:16476037061321929257 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.550921ms] args:59892029605752939 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.674563ms] args:15262116397886015961 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.737998ms] args:10463884548348336960 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.700208ms] args:15784986543485231004 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.22859ms] args:11512743696420569029 res:false err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.34849ms] args:17087986564527251681 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.455444ms] args:4652873815360231330 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.603069ms] args:17259218828069106373 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 101.523291ms] args:357347175551886490 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.361413ms] args:1595319133252549342 res:true err:<nil>
2023/01/01 11:26:23 [HasSpam() 100.437573ms] args:17696166526272393238 res:true err:<nil>
Подсказки:
- помните что порядок выполнения горутин заранее не предопредлен
- задание построено так чтобы хорошо разобраться со всем материалом лекции, т.е. вдумчиво посмотреть примеры и применить их на практике. искать по гуглу или стек оферфлоу ничего не надо
- вам не надо накапливать данные - сразу передаeм их дальше ( например awk из кода выше - на это есть отдельный тест. Разве что функция сама не решает накопить - у нас это CombineResults или sort из кода выше)
- подумайте, как будет организовано завершение функции если данные конечны. Что для этого надо сделать?
- если вам встретился рейс ( опция
-race
) - исследуйте его вывод - когда читаем, когда пишем, из каких строк кода. там как правило содержится достаточно информации для нахождения источника проблемы. - прежде чем приступать к распараллеливанию функций, чтобы уложиться в отведeнный таймаут - сначала напишите линейный код, который будет выдавать правильный результат, лучше даже начать с меньшего количества значений чтобы совпадало с тем что в задании
- ответ на вопрос "когда закрывается цикл по каналу" помогает в реализации
RunPipeline
- Хорошо помогает нарисовать схему рассчетов
- естественно нельзя самим вычислять данные в обход предоставляемых функций - их вызов будет проверяться.
- select в этом задании не нужен
- time.Sleep использовать нельзя
Эталонное решение занимает 130 строк