Как и защо написахме силно натоварена мащабируема услуга за 1C: Enterprise: Java, PostgreSQL, Hazelcast

В тази статия ще говорим за това как и защо се развихме Система за взаимодействие - механизъм, който прехвърля информация между клиентските приложения и сървърите на 1C: Enterprise - от поставянето на задача до обмислянето на архитектурата и детайлите на изпълнението.

Системата за взаимодействие (наричана по-долу - CB) е разпределена устойчива на грешки система за съобщения с гарантирана доставка. CB е проектиран като високонатоварена услуга с висока мащабируемост, достъпна както като онлайн услуга (предоставена от 1C), така и като продукт за масово производство, който може да бъде внедрен на собствени сървърни съоръжения.

SW използва разпределено съхранение леска и търсачката Elasticsearch. Ще говорим също за Java и как хоризонтално мащабираме PostgreSQL.
Как и защо написахме силно натоварена мащабируема услуга за 1C: Enterprise: Java, PostgreSQL, Hazelcast

Проблем изявление

За да стане ясно защо направихме системата за взаимодействие, ще ви разкажа малко за това как работи разработката на бизнес приложения в 1C.

Първо, малко за нас за тези, които все още не знаят какво правим :) Ние разработваме технологичната платформа 1C:Enterprise. Платформата включва инструмент за разработка на бизнес приложения, както и среда за изпълнение, която позволява на бизнес приложенията да работят в междуплатформена среда.

Парадигма на разработка клиент-сървър

Бизнес приложенията, създадени на 1C:Enterprise, работят на три нива клиентски сървър архитектура "СУБД - сървър на приложения - клиент". Кодът на приложението е написан на вграден език 1C, може да работи на сървъра на приложения или на клиента. Цялата работа с обектите на приложението (директории, документи и т.н.), както и четенето и записването на базата данни се извършва само на сървъра. Формулярите и функционалността на командния интерфейс също са внедрени на сървъра. На клиента се получават, отварят и показват формуляри, „комуникация“ с потребителя (предупреждения, въпроси ...), малки изчисления във формуляри, които изискват бърз отговор (например умножаване на цената по количеството), работа с локални файлове, работа с оборудване.

В кода на приложението заглавките на процедурите и функциите трябва изрично да посочват къде ще бъде изпълнен кодът - чрез директивите &AtClient / &AtServer (&AtClient / &AtServer в английската версия на езика). Сега разработчиците на 1C ще ме коригират, като кажат, че директивите са всъщност по-добре, но за нас това не е важно сега.

Можете да извиквате сървърен код от клиентски код, но не можете да извиквате клиентски код от сървърен код. Това е основно ограничение, направено от нас поради редица причини. По-специално, защото кодът на сървъра трябва да бъде написан по такъв начин, че да се изпълнява по един и същи начин, независимо откъде се извиква - от клиента или от сървъра. И в случай на повикване към сървърния код от друг сървърен код, няма клиент като такъв. И тъй като по време на изпълнението на кода на сървъра, клиентът, който го е извикал, може да затвори, да излезе от приложението и сървърът няма да има на кого да се обади.

Как и защо написахме силно натоварена мащабируема услуга за 1C: Enterprise: Java, PostgreSQL, Hazelcast
Код, който обработва щракване върху бутон: извикването на сървърна процедура от клиента ще работи, извикването на клиентска процедура от сървъра няма

Това означава, че ако искаме да изпратим някакво съобщение от сървъра до клиентското приложение, например, че формирането на „дългоиграещ“ отчет е приключило и отчетът може да бъде прегледан, ние нямаме такъв начин. Трябва да използвате трикове, например периодично да анкетирате сървъра от клиентския код. Но този подход зарежда системата с ненужни обаждания и като цяло не изглежда много елегантно.

И има нужда, например, когато телефон SIP-повикване, уведомете клиентското приложение за това, така че да може да го намери в базата данни на контрагента по номера на обаждащия се и да покаже на потребителя информация за повикващия контрагент. Или, например, когато поръчка пристигне в склада, уведомете клиентското приложение на клиента за това. Като цяло има много случаи, в които подобен механизъм би бил полезен.

Всъщност настройка

Създайте механизъм за съобщения. Бързо, надеждно, с гарантирана доставка, с възможност за гъвкаво търсене на съобщения. Въз основа на механизма внедрете месинджър (съобщения, видео разговори), който работи в приложенията на 1C.

Проектирайте системата хоризонтално мащабируема. Увеличаващото се натоварване трябва да бъде покрито от увеличаване на броя на възлите.

Изпълнение

Решихме да не вграждаме сървърната част на CB директно в платформата 1C:Enterprise, а да я внедрим като отделен продукт, чийто API може да бъде извикан от кода на приложните решения на 1C. Това беше направено по редица причини, основната от които беше да се даде възможност за обмен на съобщения между различни 1C приложения (например между Министерството на търговията и счетоводството). Различните 1C приложения могат да работят на различни версии на платформата 1C:Enterprise, да се намират на различни сървъри и т.н. При такива условия внедряването на CB като отделен продукт, разположен „отстрани“ на 1C инсталациите, е оптималното решение.

Затова решихме да направим CB като отделен продукт. За по-малки компании препоръчваме да използвате CB сървъра, който инсталирахме в нашия облак (wss://1cdialog.com), за да избегнете излишните разходи, свързани с инсталирането и конфигурацията на локалния сървър. Големите клиенти обаче може да сметнат за целесъобразно да инсталират собствен CB сървър в своите съоръжения. Използвахме подобен подход в нашия облачен SaaS продукт. 1cFresh – пуснат е като производствен продукт за инсталиране от клиенти и също така е внедрен в нашия облак https://1cfresh.com/.

App

За разпределение на натоварването и толерантност към грешки ще разположим не едно Java приложение, а няколко, ще поставим балансьор на натоварването пред тях. Ако трябва да изпратите съобщение от възел до възел, използвайте публикуване/абониране в Hazelcast.

Комуникация между клиент и сървър - чрез websocket. Той е много подходящ за системи в реално време.

Разпределен кеш

Изберете между Redis, Hazelcast и Ehcache. Навън през 2015 г. Redis току-що пусна нов клъстер (твърде нов, страшен), има Sentinel с много ограничения. Ehcache не знае как да клъстерира (тази функционалност се появи по-късно). Решихме да опитаме с Hazelcast 3.4.
Hazelcast е групиран извън кутията. В режим на единичен възел не е много полезен и може да пасне само като кеш - не знае как да изхвърля данни на диска, ако се загуби единственият възел, данните се губят. Внедряваме няколко Hazelcasts, между които архивираме критични данни. Ние не архивираме кеша - не ни е жал за него.

За нас Hazelcast е:

  • Съхранение на потребителски сесии. Отнема много време, за да отидете в базата данни за сесия, така че поставихме всички сесии в Hazelcast.
  • Кеш памет. Търсите потребителски профил - проверете в кеша. Написахте ново съобщение - поставете го в кеша.
  • Теми за комуникация на инстанции на приложения. Възелът генерира събитие и го поставя в тема Hazelcast. Други възли на приложения, абонирани за тази тема, получават и обработват събитието.
  • клъстерни брави. Например, ние създаваме дискусия по уникален ключ (дискусия-singleton в рамките на базата 1C):

conversationKeyChecker.check("БЕНЗОКОЛОНКА");

      doInClusterLock("БЕНЗОКОЛОНКА", () -> {

          conversationKeyChecker.check("БЕНЗОКОЛОНКА");

          createChannel("БЕНЗОКОЛОНКА");
      });

Проверихме, че няма канал. Взеха ключалката, провериха я отново, създадоха я. Ако не проверите след като сте заключили, тогава има шанс друга тема също да е проверила в този момент и сега да се опита да създаде същата дискусия - и тя вече съществува. Невъзможно е да се направи заключване чрез синхронизиран или обикновен Java Lock. През базата - бавно, а основата е жалко, през Hazelcast - това, от което се нуждаете.

Избор на СУБД

Имаме богат и успешен опит с PostgreSQL и сътрудничество с разработчиците на тази СУБД.

С клъстер PostgreSQL не е лесно - има XL, XC, Цитус, но като цяло не е noSQL, който се мащабира извън кутията. NoSQL не се считаше за основно хранилище, достатъчно беше да вземем Hazelcast, с който не сме работили преди.

Тъй като трябва да мащабирате релационна база данни, това означава шардинг. Както знаете, при шардинга разделяме базата данни на отделни части, така че всяка от тях да може да бъде поставена на отделен сървър.

Първата версия на нашето шардинг предполагаше възможността всяка от таблиците на нашето приложение да се разпространява на различни сървъри в различни пропорции. Много съобщения на сървър A - моля, нека преместим част от тази таблица на сървър B. Това решение просто крещи за преждевременна оптимизация, така че решихме да се ограничим до подход с множество клиенти.

Можете да прочетете за мулти-наемател, например, на уебсайта Данни на Citus.

В SV има концепции за приложението и абоната. Приложението е специфична инсталация на бизнес приложение, като ERP или счетоводство, с неговите потребители и бизнес данни. Абонат е организация или физическо лице, от чието име е регистрирано приложението в CB сървъра. Един абонат може да има няколко регистрирани приложения и тези приложения да обменят съобщения помежду си. Абонатът стана наемател в нашата система. Съобщенията на няколко абоната могат да бъдат разположени в една физическа база; ако видим, че някой абонат е започнал да генерира много трафик, ние го преместваме в отделна физическа база данни (или дори отделен сървър на база данни).

Имаме основна база данни, в която се съхранява маршрутизиращата таблица с информация за местоположението на всички бази данни на абонати.

Как и защо написахме силно натоварена мащабируема услуга за 1C: Enterprise: Java, PostgreSQL, Hazelcast

За да предотвратим главната база данни да бъде тясно място, ние съхраняваме маршрутизиращата таблица (и други често искани данни) в кеша.

Ако базата данни на абоната започне да се забавя, ние ще я нарежем на дялове вътре. В други проекти, за разделяне на големи маси, ние използваме pg_pathman.

Тъй като загубата на потребителски съобщения е лоша, ние архивираме нашите бази данни с реплики. Комбинацията от синхронни и асинхронни реплики ви позволява да се застраховате срещу загуба на основната база данни. Загуба на съобщение ще възникне само в случай на едновременен отказ на основната база данни и нейната синхронна реплика.

Ако синхронната реплика се загуби, асинхронната реплика става синхронна.
Ако основната база данни се загуби, синхронната реплика става основна база данни, асинхронната реплика става синхронна реплика.

Elasticsearch за търсене

Тъй като, наред с други неща, CB е и месинджър, тук се нуждаем от бързо, удобно и гъвкаво търсене, като се вземе предвид морфологията, чрез неточни съвпадения. Решихме да не преоткриваме колелото и да използваме безплатната търсачка Elasticsearch, създадена на базата на библиотеката Луцен. Ние също внедряваме Elasticsearch в клъстер (основни - данни - данни), за да елиминираме проблеми в случай на повреда на възлите на приложението.

В github намерихме Плъгин за руска морфология за Elasticsearch и го използвайте. В индекса Elasticsearch ние съхраняваме корени на думи (които приставката дефинира) и N-грами. Докато потребителят въвежда текст за търсене, ние търсим въведения текст сред N-грами. Когато се запише в индекса, думата „текстове“ ще бъде разделена на следните N-грама:

[te, tech, tex, текст, текстове, ek, eks, ext, exts, ks, kst, ksty, st, sty, you],

И също така коренът на думата "текст" ще бъде запазен. Този подход ви позволява да търсите в началото, в средата и в края на думата.

Цялостна картина

Как и защо написахме силно натоварена мащабируема услуга за 1C: Enterprise: Java, PostgreSQL, Hazelcast
Повтаряне на картинката от началото на статията, но с пояснения:

  • Балансьор, изложен на интернет; имаме nginx, може да е всякакъв.
  • Екземплярите на Java приложения комуникират помежду си чрез Hazelcast.
  • За да работим с уеб сокет, използваме Нети.
  • Java приложение, написано на Java 8, се състои от пакети OSGi. Плановете са мигриране към Java 10 и преминаване към модули.

Разработване и тестване

По време на разработването и тестването на CB се натъкнахме на редица интересни характеристики на продуктите, които използваме.

Тестване на натоварването и изтичане на памет

Пускането на всяко издание на CB е тест за натоварване. Премина успешно, когато:

  • Тестът работи няколко дни и няма откази от услугата
  • Времето за реакция за ключови операции не надвишава удобния праг
  • Влошаването на производителността в сравнение с предишната версия е не повече от 10%

Попълваме тестовата база данни с данни - за това получаваме информация за най-активния абонат от производствения сървър, умножаваме числата му по 5 (броя съобщения, дискусии, потребители) и така тестваме.

Извършваме тестване на натоварването на системата за взаимодействие в три конфигурации:

  1. стрес тест
  2. Само връзки
  3. Регистрация на абонати

По време на стрес тест стартираме няколкостотин нишки и те зареждат системата без спиране: пишат съобщения, създават дискусии, получават списък със съобщения. Ние симулираме действията на обикновените потребители (получаване на списък с моите непрочетени съобщения, писане на някого) и програмни решения (прехвърляне на пакет към друга конфигурация, обработка на предупреждение).

Ето как например изглежда част от стрес теста:

  • Потребителят влиза в системата
    • Изисква вашите непрочетени теми
    • 50% шанс да прочетете съобщения
    • 50% шанс да пишете съобщения
    • Следващ потребител:
      • 20% шанс за създаване на нова тема
      • Произволно избира някоя от своите дискусии
      • Влиза вътре
      • Заявки за съобщения, потребителски профили
      • Създава пет съобщения, адресирани до произволни потребители от тази нишка
      • Извън обсъждането
      • Повтаря се 20 пъти
      • Излиза, връща се в началото на скрипта

    • В системата влиза чатбот (емулира съобщения от кода на приложените решения)
      • 50% шанс за създаване на нов канал за данни (специална дискусия)
      • 50% шанс да напишете съобщение във всеки от съществуващите канали

Сценарият „Само връзки“ се появи с причина. Има ситуация: потребителите са свързали системата, но все още не са включени. Всеки потребител сутрин в 09:00 включва компютъра, установява връзка със сървъра и мълчи. Тези момчета са опасни, има много от тях - те имат само PING / PONG от пакетите, но поддържат връзката със сървъра (не могат да я поддържат - и изведнъж ново съобщение). Тестът възпроизвежда ситуацията, когато голям брой такива потребители се опитват да влязат в системата за половин час. Прилича на стрес тест, но фокусът му е точно върху този първи вход - за да няма сривове (човек не използва системата, но тя вече пада - трудно може да се измисли нещо по-лошо).

Сценарият за регистрация на абонати произхожда от първото стартиране. Проведохме стрес тест и бяхме сигурни, че системата не се забавя в кореспонденцията. Но потребителите отидоха и регистрацията започна да пада след изчакване. При регистрацията използвахме / Сътрудничество / случайна, което е свързано с ентропията на системата. Сървърът нямаше време да натрупа достатъчно ентропия и когато беше поискан нов SecureRandom, той замръзна за десетки секунди. Има много изходи от тази ситуация, например: преминете към по-малко сигурен /dev/urandom, инсталирайте специална платка, която генерира ентропия, генерирайте произволни числа предварително и ги съхранявайте в пула. Временно затворихме проблема с пула, но оттогава пускаме отделен тест за регистриране на нови абонати.

Като генератор на натоварване използваме JMeter. Той не знае как да работи с websocket, необходим е плъгин. Първите в резултатите от търсенето за заявката "jmeter websocket" са статии с BlazeMeterв които препоръчват плъгин от Maciej Zaleski.

Оттам решихме да започнем.

Почти веднага след началото на сериозното тестване открихме, че в JMeter започват течове на памет.

Плъгинът е отделна голяма история, със 176 звезди има 132 разклонения в github. Самият автор не се е ангажирал с него от 2015 г. (взехме го през 2015 г., тогава не предизвика подозрение), няколко проблема с github за изтичане на памет, 7 незатворени заявки за изтегляне.
Ако решите да заредите тест с този плъгин, моля, обърнете внимание на следните дискусии:

  1. В многонишкова среда беше използван обичайният LinkedList, в резултат на което получихме NPE по време на изпълнение. Решава се или чрез преминаване към ConcurrentLinkedDeque, или чрез синхронизирани блокове. Ние избрахме първия вариант за себе сиhttps://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Изтичане на памет, информацията за връзката не се изтрива при прекъсване на връзката (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. В режим на поточно предаване (когато websocket не е затворен в края на пробата, а се използва по-нататък в плана), моделите на отговор не работят (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Това е един от тези в github. Какво направихме:

  1. взе вилица на Елиран Коган (@elyrank) - коригира проблеми 1 и 3
  2. Решен проблем 2
  3. Актуализиран кей от 9.2.14 до 9.3.12
  4. Опакован SimpleDateFormat в ThreadLocal; SimpleDateFormat не е безопасен за нишки, което води до NPE по време на изпълнение
  5. Поправено е друго изтичане на памет (връзката се затваря неправилно при прекъсване на връзката)

И въпреки това тече!

Паметта започна да свършва не след ден, а след два. Изобщо нямаше време, решихме да пуснем по-малко теми, но на четирима агенти. Това трябваше да е достатъчно поне за седмица.

Минаха два дни...

Сега Hazelcast няма памет. Дневниците показаха, че след няколко дни тестване Hazelcast започва да се оплаква от липса на памет и след известно време клъстерът се разпада и възлите продължават да умират един по един. Свързахме JVisualVM към hazelcast и видяхме „нагоре трион“ - той редовно извикваше GC, но не можеше да изчисти паметта по никакъв начин.

Как и защо написахме силно натоварена мащабируема услуга за 1C: Enterprise: Java, PostgreSQL, Hazelcast

Оказа се, че в hazelcast 3.4 при изтриване на карта / multiMap (map.destroy()) паметта не се освобождава напълно:

github.com/hazelcast/hazelcast/issues/6317
github.com/hazelcast/hazelcast/issues/4888

Грешката вече е коригирана в 3.5, но тогава беше проблем. Създадохме нова multiMap с динамични имена и изтрихме според нашата логика. Кодът изглеждаше нещо подобно:

public void join(Authentication auth, String sub) {
    MultiMap<UUID, Authentication> sessions = instance.getMultiMap(sub);
    sessions.put(auth.getUserId(), auth);
}

public void leave(Authentication auth, String sub) {
    MultiMap<UUID, Authentication> sessions = instance.getMultiMap(sub);
    sessions.remove(auth.getUserId(), auth);

    if (sessions.size() == 0) {
        sessions.destroy();
    }
}

Извикване:

service.join(auth1, "НОВЫЕ_СООБЩЕНИЯ_В_ОБСУЖДЕНИИ_UUID1");
service.join(auth2, "НОВЫЕ_СООБЩЕНИЯ_В_ОБСУЖДЕНИИ_UUID1");

multiMap се създава за всеки абонамент и се премахва, когато не е необходим. Решихме да започнем карта , ключът ще бъде името на абонамента, а стойностите ще бъдат идентификатори на сесии (чрез които след това можете да получите потребителски идентификатори, ако е необходимо).

public void join(Authentication auth, String sub) {
    addValueToMap(sub, auth.getSessionId());
}

public void leave(Authentication auth, String sub) { 
    removeValueFromMap(sub, auth.getSessionId());
}

Графиките се подобриха.

Как и защо написахме силно натоварена мащабируема услуга за 1C: Enterprise: Java, PostgreSQL, Hazelcast

Какво още научихме за тестването на натоварване

  1. JSR223 трябва да бъде написан на groovy и да включва кеш за компилация - това е много по-бързо. Връзка.
  2. Диаграмите на Jmeter-Plugins са по-лесни за разбиране от стандартните. Връзка.

За нашия опит с Hazelcast

Hazelcast беше нов продукт за нас, започнахме да работим с него от версия 3.4.1, сега имаме версия 3.9.2 на нашия производствен сървър (по време на писането на тази статия най-новата версия на Hazelcast е 3.10).

генериране на ID

Започнахме с целочислени идентификатори. Нека си представим, че имаме нужда от друг Дълги за нов обект. Последователността в базата данни не е подходяща, таблиците участват в шардинг - оказва се, че има съобщение ID=1 в DB1 и съобщение ID=1 в DB2, не можете да поставите този идентификатор в Elasticsearch, в Hazelcast също, но най-лошото е, ако искате да намалите данните от две бази данни до една (например да решите, че една база данни е достатъчна за тези абонати). Можете да имате няколко AtomicLong в Hazelcast и да държите брояча там, тогава производителността за получаване на нов идентификатор е incrementAndGet плюс времето за заявка в Hazelcast. Но Hazelcast има нещо по-оптимално - FlakeIdGenerator. При контакт на всеки клиент се дава диапазон от идентификатори, например първият - от 1 до 10 000, вторият - от 10 001 до 20 000 и т.н. Сега клиентът може сам да издава нови идентификатори, докато диапазонът, който му е издаден, приключи. Работи бързо, но рестартирането на приложението (и клиента Hazelcast) започва нова последователност - оттук и пропуските и т.н. Освен това за разработчиците не е много ясно защо идентификаторите са цели числа, но те са толкова противоречиви. Претеглихме всичко и преминахме към UUID.

Между другото, за тези, които искат да бъдат като Twitter, има такава библиотека Snowcast - това е имплементация на Snowflake върху Hazelcast. Можете да видите тук:

github.com/noctarius/snowcast
github.com/twitter/snowflake

Но още не сме стигнали до него.

TransactionalMap.replace

Друга изненада: TransactionalMap.replace не работи. Ето един тест:

@Test
public void replaceInMap_putsAndGetsInsideTransaction() {

    hazelcastInstance.executeTransaction(context -> {
        HazelcastTransactionContextHolder.setContext(context);
        try {
            context.getMap("map").put("key", "oldValue");
            context.getMap("map").replace("key", "oldValue", "newValue");
            
            String value = (String) context.getMap("map").get("key");
            assertEquals("newValue", value);

            return null;
        } finally {
            HazelcastTransactionContextHolder.clearContext();
        }        
    });
}

Expected : newValue
Actual : oldValue

Трябваше да напиша моя собствена замяна с помощта на getForUpdate:

protected <K,V> boolean replaceInMap(String mapName, K key, V oldValue, V newValue) {
    TransactionalTaskContext context = HazelcastTransactionContextHolder.getContext();
    if (context != null) {
        log.trace("[CACHE] Replacing value in a transactional map");
        TransactionalMap<K, V> map = context.getMap(mapName);
        V value = map.getForUpdate(key);
        if (oldValue.equals(value)) {
            map.put(key, newValue);
            return true;
        }

        return false;
    }
    log.trace("[CACHE] Replacing value in a not transactional map");
    IMap<K, V> map = hazelcastInstance.getMap(mapName);
    return map.replace(key, oldValue, newValue);
}

Тествайте не само обикновените структури от данни, но и техните транзакционни версии. Случва се IMap да работи, но TransactionalMap вече не съществува.

Прикачете нов JAR без престой

Първо, решихме да напишем обекти от нашите класове в Hazelcast. Например, имаме клас Application, искаме да го съхраним и прочетем. Запазване:

IMap<UUID, Application> map = hazelcastInstance.getMap("application");
map.set(id, application);

Четем:

IMap<UUID, Application> map = hazelcastInstance.getMap("application");
return map.get(id);

Всичко работи. Тогава решихме да създадем индекс в Hazelcast, за да го търсим:

map.addIndex("subscriberId", false);

И когато пишат нов обект, те започват да получават ClassNotFoundException. Hazelcast се опита да добави към индекса, но не знаеше нищо за нашия клас и искаше да постави JAR с този клас в него. Направихме точно това, всичко работи, но се появи нов проблем: как да актуализираме JAR, без да спрем напълно клъстера? Hazelcast не взима нов JAR при актуализация на възел. На този етап решихме, че можем да живеем без търсене на индекси. В крайна сметка, ако използвате Hazelcast като хранилище за ключ-стойност, тогава всичко ще работи? Не точно. Тук отново различно поведение на IMap и TransactionalMap. Когато IMap не се интересува, TransactionalMap извежда грешка.

IMap. Записваме 5000 обекта, четем. Всичко се очаква.

@Test
void get5000() {
    IMap<UUID, Application> map = hazelcastInstance.getMap("application");
    UUID subscriberId = UUID.randomUUID();

    for (int i = 0; i < 5000; i++) {
        UUID id = UUID.randomUUID();
        String title = RandomStringUtils.random(5);
        Application application = new Application(id, title, subscriberId);
        
        map.set(id, application);
        Application retrieved = map.get(id);
        assertEquals(id, retrieved.getId());
    }
}

Но не работи в транзакция, получаваме ClassNotFoundException:

@Test
void get_transaction() {
    IMap<UUID, Application> map = hazelcastInstance.getMap("application_t");
    UUID subscriberId = UUID.randomUUID();
    UUID id = UUID.randomUUID();

    Application application = new Application(id, "qwer", subscriberId);
    map.set(id, application);
    
    Application retrievedOutside = map.get(id);
    assertEquals(id, retrievedOutside.getId());

    hazelcastInstance.executeTransaction(context -> {
        HazelcastTransactionContextHolder.setContext(context);
        try {
            TransactionalMap<UUID, Application> transactionalMap = context.getMap("application_t");
            Application retrievedInside = transactionalMap.get(id);

            assertEquals(id, retrievedInside.getId());
            return null;
        } finally {
            HazelcastTransactionContextHolder.clearContext();
        }
    });
}

В 3.8 се появи механизмът за разполагане на потребителски клас. Можете да посочите един главен възел и да актуализирате JAR файла върху него.

Сега напълно променихме подхода си: ние сами сериализираме в JSON и запазваме в Hazelcast. Hazelcast не трябва да знае структурата на нашите класове и можем да актуализираме без прекъсване. Версиите на обекти на домейн се контролират от приложението. Различни версии на приложението могат да бъдат стартирани едновременно и е възможно ново приложение да пише обекти с нови полета, докато старото все още не знае за тези полета. И в същото време новото приложение чете обектите, написани от старото приложение, които нямат нови полета. Ние се справяме с такива ситуации вътре в приложението, но за по-лесно не променяме или премахваме полетата, а само разширяваме класовете чрез добавяне на нови полета.

Как осигуряваме висока производителност

Четири пътувания до Hazelcast са добри, две пътувания до базата данни са лоши

Търсенето на данни в кеша винаги е по-добро, отколкото в базата данни, но също така не искате да съхранявате непотърсени записи. Решаването какво да се кешира е оставено на последния етап от разработката. Когато новата функционалност е кодирана, ние разрешаваме регистриране на всички заявки в PostgreSQL (log_min_duration_statement до 0) и изпълняваме тестове за натоварване за 20 минути.Помощни програми като pgFouine и pgBadger могат да изграждат аналитични отчети въз основа на събраните регистрационни файлове. В отчетите ние търсим предимно бавни и чести заявки. За бавни заявки ние изграждаме план за изпълнение (ОБЯСНЕНИЕ) и оценяваме дали такава заявка може да бъде ускорена. Честите заявки за едно и също въвеждане се вписват добре в кеша. Опитваме се да поддържаме заявките „плоски“, една таблица на заявка.

Експлоатация

CB като онлайн услуга стартира през пролетта на 2017 г., като отделен CB продукт беше пуснат през ноември 2017 г. (по това време в бета статус).

За повече от година работа не е имало сериозни проблеми в работата на онлайн услугата CB. Ние наблюдаваме онлайн услугата чрез Zabbix, събирайте и внедрявайте от Бамбук.

Дистрибуцията на CB сървъра идва под формата на собствени пакети: RPM, DEB, MSI. Плюс това, за Windows, ние предоставяме един инсталатор под формата на един EXE, който инсталира сървъра, Hazelcast и Elasticsearch на една машина. Първоначално нарекохме тази версия на инсталацията "демо", но сега стана ясно, че това е най-популярната опция за внедряване.

Източник: www.habr.com

Добавяне на нов коментар