Многие современные высоконагруженные системы построены с использованием очередей. Не является исключением и внутренний сервис обработки OAuth токенов, который создала наша команда. Исключением является то, что и в качестве основного хранилища, и в качестве всех очередей используется один и тот же продукт - Tarantool. Более того, мы поставили себе амбициозную цель по отказоустойчивости - полную доступность сервиса, когда уходят любые два из трёх датацентров, и успешно её достигли.
При решении мы столкнулись с массой интересных инженерных задач и в нашем докладе мы расскажем вам о том, какие технологии и подходы использовались. В частности, рассмотрим более детально такие вещи, как:
- создание deadline очереди и проблемы, с ней связанные;
- создание кольцевой очереди;
- интеграция между собой шардинга, Raft и очередей;
- как мы победили split brain ;)
6. О чём мы будем рассказывать?
Оглянемся назад: что было 3 года назад?
6 / 67
7. О чём мы будем рассказывать?
Оглянемся назад: что было 3 года назад?
Какие проблемы были?
7 / 67
8. О чём мы будем рассказывать?
Оглянемся назад: что было 3 года назад?
Какие проблемы были?
Master ⟷ Master репликация
8 / 67
9. О чём мы будем рассказывать?
Оглянемся назад: что было 3 года назад?
Какие проблемы были?
Master ⟷ Master репликация
В поисках Ra 'а
9 / 67
10. О чём мы будем рассказывать?
Оглянемся назад: что было 3 года назад?
Какие проблемы были?
Master ⟷ Master репликация
В поисках Ra 'а
Объединяем Ra и шардинг
10 / 67
11. О чём мы будем рассказывать?
Оглянемся назад: что было 3 года назад?
Какие проблемы были?
Master ⟷ Master репликация
В поисках Ra 'а
Объединяем Ra и шардинг
Оцениваем результат
11 / 67
18. Проблемы?
25% Outage за 15 минут
50% Outage за 30 минут
100% Outage за 1 час
2015 г. - 100% CPU (много бизнес-логики и индексов)
Вторичная логика влияет на основную задачу
18 / 67
56. to refresh no need to refreshexpired
60s
first
order
old age
second
order
4 min
third
order
1 hour
expira on me index
Refresh
56 / 67
57. no fy
put()
index
lookup or wait
wait
take()
session
stash
disconnect
channel
id status payloadme
pk
57 / 67
58. queue:put
function put(data)
local t = box.space.queue:auto_increment({
'r', --[[ status ]]
util.time(), --[[ time ]]
data --[[ any payload ]]
})
return t
end
58 / 67
59. queue:take
function take(timeout)
local start_time = util.time()
local q_ind = box.space.tokens.index.queue
local _,t
while true do
local it = util.iter(q_ind, {'r'}, { iterator = box.index.GE })
_,t = it()
if t and t[F.tokens.status] ~= 't' then
break
end
local left = (start_time + timeout) - util.time()
if left <= 0 then return end
t = q:wait(left)
if t then break end
end
t = q:taken(t)
return t
end
59 / 67
60. queue:taken
function queue:taken(task)
local sid = box.session.id()
if self._consumers[sid] == nil then
self._consumers[sid] = {}
end
local k = task[self.f_id]
local t = self:set_status(k, 't')
self._consumers[sid][k] = { util.time(), box.session.peer(sid), t }
self._taken[k] = sid
return t
end
60 / 67
61. queue:on_disconnect
function on_disconnect()
local sid = box.session.id
local now = util.time()
if self._consumers[sid] then
local consumers = self._consumers[sid]
for k,rec in pairs(consumers) do
time, peer, task = unpack(rec)
local v = box.space[self.space].index[self.index_primary]:get({k})
if v and v[self.f_status] == 't' then
v = self:release(v[self.f_id])
end
end
self._consumers[sid] = nil
end
end
61 / 67