Георги Стоянов е Java Team Lead в Delasport. Има близо 10 години опит като програмист, а голяма част от кариерата му до момента преминава в iGaming индустрията. Именно затова е запознат отблизо как функционират системите на Delasport, какво им коства на екипите и каква скорост стои зад това, което компанията прави.
Георги обръща внимание на тези теми в своя авторски текст за DEV.BG.
Представете си, че гледате футболен мач с високи залози. В 89-ата минута е отбелязан гол. В рамките на част от секундата приложението ви се актуализира – коефициентите се променят, пазарите се заключват или отварят и всичко е безпроблемно.
Но зад това гладко изживяване се крие необятна комплексност.
Системата на Delasport трябва да обработва в реално време хиляди такива актуализации едновременно – голове, фаулове, контузии, смени, извънредно време. Мащабът е зашеметяващ, но оттук започва и забавлението (поне за тези от нас, които се наслаждават на предизвикателства, свързани с множество паралелни дейности).
Ето как се справяме с това, използвайки Java, Kafka и здравословно уважение към неизменността.
Предизвикателството: Обработка на събития в реално време и с голям обем
В сферата на Delasport скоростта не е просто важна – тя е сред най-високите приоритети.
Всяка една промяна в играта може да повлияе на:
- Кои пазари са отворени или спрени;
- Коефициентите на живо;
- Наличието на кеш аут;
- Какво трябва да показва интерфейсът във всяка секунда.
Умножете това за стотици или дори хиляди мачове, които се случват по едно и също време. Това не е просто поток от актуализации – това е потоп. И системата трябва да реагира в реално време, без да губи темпо.
Нашият подход включва Java Multithreading и Kafka Partitioning
Изградихме нашата система на един прост принцип – разделяй и владей.
Kafka: Контролер на трафика на събития
Kafka действа като гръбнак на нашите данни в реално време. Всеки мач има свой собствен поток от актуализации, които се вливат в тема на Kafka и се разделят на части – често по идентификатор на мача.
Защо на части? Защото те ни позволяват да се мащабираме хоризонтално и да обработваме данни паралелно. Kafka също така гарантира реда на съобщенията в рамките на един дял, което е от решаващо значение, например за определяне дали голът е дошъл преди червения картон.
Нишки на Java: Двигателят за обработка
Всеки дял на Kafka се обработва от специална нишка на Java (или пул от нишки, когато е необходимо). Тази нишка отговаря за:
- Консумиране и десериализиране на събитието;
- Преизчисляване на коефициентите въз основа на новото състояние;
- Актуализиране на състоянието на съответните пазари;
- Генериране на нови данни за предния край.

Неизменност: Най-добрият ни приятел в света на едновременните процеси
Конкурентността е трудна. Грешките, причинени от споделено променливо състояние, са едни от най-изтънчените и разочароващи в софтуерното инженерство.
Ето защо изградихме нашата система около неизменни структури от данни. Всяко съобщение за събитие, след като бъде десериализирано, се опакова в неизменен обект. Всяка трансформация или актуализация създава нов обект.
Ползите са:
- Безопасност на нишките без „ключалки“;
- Няма риск от състезателни условия;
- По-лесно отстраняване на грешки и обосноваване на поведението на системата.
Този избор на дизайн значително подобри надеждността на нашия конвейер за обработка – и опрости начина, по който нашите разработчици работят с данни.
Explore more
Пример от реалния свят
Нека разгледаме какво се случва, когато бъде отбелязан гол в мач 5678:
- Kafka поглъща събитието в темата match-5678-events.
- Съобщението се насочва към съответния дял.
- Нишка на Java, назначена в този дял, приема съобщението.
- Тя обработва събитието – актуализира вътрешното състояние, преизчислява коефициентите и генерира нов изглед на пазара.
- Актуализираните данни се изпращат към front-end-a, обикновено в рамките на милисекунди.
Всичко това се случва с минимално заключване, без споделено променливо състояние и без намеса от събития в други мачове.
Научените уроци
След изграждането и усъвършенстването на тази система се открояват няколко урока:
Пуловете от нишки се нуждаят от настройка. Твърде много нишки внасят режийни разходи и могат да доведат до прекомерно превключване на контекста; твърде малко нишки създават тесни места при натоварване.
Моделът за разделяне на Kafka е суперсила, особено когато се използва за налагане на логически граници като идентификатори на мачове или пазари. Той ни позволява чисто мащабиране и осигурява подредена, изолирана обработка.
Неизменността се отплаща. Тя премахва цял клас грешки, свързани с едновременността, подобрява надеждността и опростява мисловния модел за програмистите, работещи в многонишкова среда.
Мониторингът е от решаващо значение. При толкова много движещи се части разчитаме силно на наблюденията – показатели за латентност, активност на нишките, дълбочина на опашката и забавяне на разделянето ни помагат да поддържаме производителността и да откриваме проблеми, преди да се разраснат.
Проучваме виртуални нишки. С въвеждането на Project Loom в съвременната Java виртуалните нишки предлагат обещаващ път за още по-голямо опростяване на едновременността – позволявайки ни да се справяме с много повече леки задачи с по-малко настройки и режийни разходи. В момента преценяваме как биха могли да се впишат в нашата архитектура, особено в области с голямо време за изчакване на входно-изходни операции или блокиращи операции.
Накрая да обобщим
Изграждането на мащабируем двигател за спортни залагания в реално време, базиран на Java, е едно от най-удовлетворяващите инженерни предизвикателства, с които сме се справяли. Наложи се да мислим задълбочено за паралелните дейности, архитектурата и устойчивостта на грешки – като същевременно предоставим безпроблемно изживяване на потребителите, които очакват всичко да работи.
Ако работите по нещо в областта на реалното време или високата производителност – системи за търговия, платформи за анализ, дори мултиплейърни игри – много от същите принципи са приложими.
А ако сте дълбоко в окопите на Java едновременността и архитектурата, управлявана от събития, ще се радваме да чуем как подхождате към това.