010
Базовая концепция архитектуры
011
012
Требовалось создать платформу с динамической структурой данных,
013 которая, к тому же, будет изменяться в процессе эксплуатации,
014 притом на "горячую", т.е. нужна возможность изменять структуру
015 данных во время работы с этими данными, т.е.
016 добавления/изменения/удаления оных. И, при этом, обеспечивать
017 высокую нагрузку.
018
019
Логически платформа представляет набор бизнес-объектов (БО), у
020 которых есть поля различных типов. У каждого бизнес-объекта можно
021 создавать инстанции - данные. Значения полей инстанций в
022 последствии можно менять. Инстанции могут быть помечены как:
023
024
1) Актуальные - первичное состояние после создания. В этом
025 состоянии инстанция находиться при работе с ней.
026
027
2) Удалённые - такие инстанции считаются удалёнными - они не
028 участвуют в работе системы. Так же есть возможность удалить
029 физически инстанции.
030
031
3) Архивные - давно не используемые инстанции, ушедшие в
032 архив.
033
034
4) Тестовые - инстанции, которые используют для тестирования
035 системы.
036
037
Для этого были выбраны системы управления баз данных, которые
038 поддерживают масштабирование "в ширину". А именно:
039
040
1) MongoDB
041
042
2) Apache Kafka
043
044
3) ElasticSearch
045
046
4) PostgreSQL
047
048
5) Apache Zookeeper
049
050
MongoDB - главное оперативное хранилище
051
052
Главной базой данных является MongoDB. В ней хранятся все данные
053 системы в оперативном режиме.
054
055
В оперативном режиме - это значит, что, например, если у клиента
056 фамилия поменялась с "Иванова" на "Сидорова", то в MongoDB остаётся
057 только "Сидорова", а "Иванова" стирается бесследно.
058
059
Более того, база данных MongoDB используется только в режиме
060 Key-Value-Storage, это обозначает, что платформа в неё делает
061 запросы только по идентификаторам с ожиданием единичного документа
062 по этому идентификатору.
063
064
На платформе часто требуется получить списки - использовать для
065 этого MongoDB запрещено, чтобы не нагружать её. Для этого
066 используется ElasticSearch.
067
068
ElasticSearch - получение информации
069 списками
070
071
ElasticSearch используется для получения оперативной информации
072 в виде списков, которые отфильтрованы и отсортированы по желанию
073 пользователя в режиме постраничной загрузки.
074
075
В ElasticSearch тоже хранится только оперативная информация.
076
077
Для того чтобы синхронизировать данные в ElasticSearch и MongoDB
078 используется ApacheKafka.
079
080
Apache Kafka - синхронизация данных +
081 хранение истории изменений данных
082
083
Apache Kafka на платформе MyBPM используется для двух целей:
084
085
1) Хранение исторической информации;
086
087
2) Синхронизация данных между различными компонентами
088 системы.
089
090
Все изменения в данных системы сбрасывается в Kafka в виде
091 дельты данных. В дельте данных содержится только та информация,
092 которая изменилась в данный момент в системе. А именно, например,
093 если у клиента сорок полей, а изменилось только фамилия, то в
094 дельту попадёт только эта фамилия, при том, только новое значение
095 этого поля. Так же в дельте будут координаты того, что изменилось -
096 идентификатор инстанции. А также кто и когда это изменил. Резюмируя
097 сказанное, в дельте содержится следующая информацию:
098
099
1) Новые значения полей, которые изменились
100
101
2) Координаты того, что изменилось: идентификатор инстанции
102 бизнес-объекта + идентификатор самого бизнес-объекта
103
104
3) Идентификатор пользователя, кто сделал это изменение
105
106
4) Дата и время этого изменения
107
108
Дальше Kafka-Consumer-ы подгружают эти изменения и применяют их
109 для соответствующих компонентов системы. Например, соответствующий
110 Consumer обновляет данные в ElasticSearch тем самым синхронизируя
111 их с оригиналом в MongoDB.
112
113
Apache Kafka - как база исторических
114 данных
115
116
К Apache Kafka следует относиться как к базе данных, в которой
117 храниться история всех изменений системы.
118
119
На платформе Apache Kafka организована динамическая система
120 получения отчётов.
121
122
Вначале пользователь подготавливает структуру отчёта и сохраняет
123 её в БД MongoDB. Далее платформа создаёт в PostgreSQL таблицы для
124 получения данного отчёта, но эти таблицы пустые. Так как в Apache
125 Kafka храниться вся история изменений, то можно пройтись по ним и
126 подготовить данные в этих таблицах, что и делается. Эти таблицы
127 заполняются и отчёт готов для эксплуатации. Далее Kafka-Consumer
128 отслеживает дальнейшие изменения и держит таблицы в актуальном
129 состоянии.
130
131
Для того чтобы данная функциональность работала, необходимо
132 исторически данные держать постоянно в Apache Kafka и не стирать
133 их. В Apache Kafka для это есть весь необходимый инструментарий -
134 репликация, партиции и прочее доступные из коробки.
135
136
Следует отметить, что платформа MyBPM позволяет разделять данные
137 инстанций БО на разные Kafka Topic-и. Это позволяет зачищать те БО,
138 которые сильно растут в размере. При этом по ним теряется
139 историческая информация. А это означает, что по ним не должно быть
140 отчётов с исторической информацией.
141
142
Предназначение PostgreSQL
143
144
PostgreSQL используется для решения сложных логических задач.
145 Например, в PostgreSQL хранятся ловушки событий, которые
146 срабатывают, если наступает заранее ожидаемое событие, например
147 ожидание сохранение определённой инстанции БО, чтобы запустился
148 конкретный скрипт в бизнес-процессе.
149
150
Предназначение Apache Zookeeper
151
152
В Apache Zookeeper платформа MyBPM хранить всю конфигурационную
153 информацию - параметры доступа, таймауты, размеры буферов и
154 прочее.
155
156
В дальнейшем планируется перенести всю эту конфигурацию в БД
157 MongoDB и управлять ей через интерфейс.
158
159
160
at kz.greetgo.md_reader.util.MdUtil.xmlTextToDoc(MdUtil.java:80)
at kz.greetgo.md_reader.core.MdConverter.prepareHtmlFileFrom(MdConverter.java:136)
at kz.greetgo.md_reader.core.MdConverter.convert(MdConverter.java:208)
at kz.greetgo.md_reader.controller.RenderController.downloadToc(RenderController.java:360)
at kz.greetgo.md_reader.controller.RenderController.request(RenderController.java:108)
at jdk.internal.reflect.GeneratedMethodAccessor3.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at kz.greetgo.md_reader.interceptors.TextReplaceFilter.doFilter(TextReplaceFilter.java:36)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.io.IOException: Server returned HTTP response code: 429 for URL: http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:2000)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:677)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1397)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1333)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.startPE(XMLDTDScannerImpl.java:732)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.skipSeparator(XMLDTDScannerImpl.java:2101)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDecls(XMLDTDScannerImpl.java:2064)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDTDExternalSubset(XMLDTDScannerImpl.java:299)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDriver.dispatch(XMLDocumentScannerImpl.java:1165)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDriver.next(XMLDocumentScannerImpl.java:1040)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:943)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:542)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:889)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:825)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
at java.xml/com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:247)
at java.xml/com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:342)
at java.xml/javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:122)
at kz.greetgo.md_reader.util.MdUtil.xmlTextToDoc(MdUtil.java:71)
... 48 more