Планировщик задач и использование таймера в Spring MVC на примере ScheduleTask и Quartz.
Настроить приложение Spring MVC на выполнение запланированных задач по графику. Для этой цели мы будем использовать встроенную поддержку планировщика задач в Spring — Quartz Scheduler. Рассмотрим как запускать задачу по триггеру (по интервалу времени или выражению cron), а так же применение аннотаций для планирования задач.
2. Структура проекта
3. Зависимости в pom.xml
Относительно предыдущих глав была добавлена следующая зависимость.
org . quartz — scheduler < / groupId >
< project
xmlns : xsi = «http://www.w3.org/2001/XMLSchema-instance»
xsi : schemaLocation = «http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd» >
ru . javastudy < / groupId >
mvc_html5_angular < / artifactId >
< spring — framework . version >4.2.4.RELEASE < / spring — framework . version >
< spring — framework . data . version >1.9.1.RELEASE < / spring — framework . data . version >
org . springframework < / groupId >
spring — framework — bom < / artifactId >
org . springframework < / groupId >
spring — webmvc < / artifactId >
javax . servlet < / groupId >
javax . servlet < / groupId >
Прямой эфир с ответами на вопросы 22.11| Реалити ч.4 «Дропшиппинг бизнес на Shopify»
javax . servlet — api < / artifactId >
javax . servlet . jsp < / groupId >
commons — fileupload < / groupId >
commons — fileupload < / artifactId >
commons — io < / groupId >
commons — io < / artifactId >
org . apache . poi < / groupId >
com . lowagie < / groupId >
org . hsqldb < / groupId >
org . springframework < / groupId >
spring — jdbc < / artifactId >
org . springframework < / groupId >
spring — test < / artifactId >
org . slf4j < / groupId >
slf4j — api < / artifactId >
ch . qos . logback < / groupId >
logback — classic < / artifactId >
org . springframework < / groupId >
spring — context — support < / artifactId >
javax . mail < / groupId >
org . apache . velocity < / groupId >
com . fasterxml . jackson . core < / groupId >
jackson — databind < / artifactId >
org . hibernate < / groupId >
hibernate — entitymanager < / artifactId >
org . hibernate < / groupId >
hibernate — validator < / artifactId >
org . springframework . data < / groupId >
spring — data — jpa < / artifactId >
org . quartz — scheduler < / groupId >
org . apache . maven . plugins < / groupId >
maven — compiler — plugin < / artifactId >
— Xlint : all < / compilerArgument >
< ! — need to find configs in tests in package web — inf like
src / main / webapp / WEB — INF / config < / directory >
4. web.xml, spring config
Приведу под катом полные файлы конфигурации для того, чтобы не нужно было скачивать проект и т.к. эти настройки не поменялись относительно предыдущих глав.
ПОЛУЧАЮ МНОГО ВОПРОСОВ ОТ УЧЕНИКОВ И ПОДПИСЧИКОВ/МНОГИЕ УЖЕ НАХОДЯТСЯ НА ВЫСОКОМ УРОВНЕ ОСОЗНАННОСТИ
< web — app
xmlns : xsi = «http://www.w3.org/2001/XMLSchema-instance»
xsi : schemaLocation = «http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd»
version = «3.1» >
< display — name >mvc — html5 — angularjs < / display — name >
< param — name >contextConfigLocation < / param — name >
WEB — INF / config / application — context . xml
< listener — class >org . springframework . web . context . ContextLoaderListener < / listener — class >
< listener — class >org . springframework . web . context . request . RequestContextListener < / listener — class >
< servlet — name >dispatcherServlet < / servlet — name >
< servlet — class >org . springframework . web . servlet . DispatcherServlet < / servlet — class >
< param — name >contextConfigLocation < / param — name >
< param — value >/ WEB — INF / config / mvc — config . xml < / param — value >
< servlet — name >dispatcherServlet < / servlet — name >
< filter — name >encodingFilter < / filter — name >
< filter — class >org . springframework . web . filter . CharacterEncodingFilter < / filter — class >
< param — name >forceEncoding < / param — name >
< filter — name >encodingFilter < / filter — name >
< welcome — file >/ WEB — INF / view / index . jsp < / welcome — file >
/ WEB — INF / view / error / errorstatus . jsp < / location >
< beans
xmlns : xsi = «http://www.w3.org/2001/XMLSchema-instance»
xmlns : mvc = «http://www.springframework.org/schema/mvc»
xmlns : context = «http://www.springframework.org/schema/context»
xmlns : tx = «http://www.springframework.org/schema/tx»
xsi : schemaLocation = «http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd» >
mvc : annotation — driven configures Spring MVC annotations
< ! — themes can be put in different folder such as
application-context.xml (добавленное будет описано ниже):
< beans
xmlns : xsi = «http://www.w3.org/2001/XMLSchema-instance»
xmlns : mvc = «http://www.springframework.org/schema/mvc»
xmlns : jdbc = «http://www.springframework.org/schema/jdbc»
xmlns : context = «http://www.springframework.org/schema/context»
xmlns : task = «http://www.springframework.org/schema/task»
xsi : schemaLocation = «http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd» >
< prop key = «mail.smtp.socketFactory.class» >javax . net . ssl . SSLSocketFactory < / prop >
< bean id = «entityManagerFactory»
class = «org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean» >
< prop key = «hibernate.dialect» >org . hibernate . dialect . HSQLDialect < / prop >
5. Описание Quartz, Cron
Сначала о Quartz и в чем смысл его использования. Quartz — это библиотека с открытым исходным кодом, которая может быть встроена в любое Java приложение. С помощью нее можно планировать и выполнять сотни задач. Библиотека поддерживает много фич из enterprise технологий.
Cron — демон-планировщик заданий в Unix-подобных операционных системах, использующийся для периодического выполнения заданий в определённое время. Выражения cron имеют вид ‘ * * * * * ? ‘ (minutes, hours, day of month, month, day of week, year(optional) ).
Более подробную информацию о выражения cron можно найти в интернете, для нас же будет важно понимание следующего:
* — выбирает все величины. То есть на месте позиции часа символ * означает, что задание будет выполняться каждый час;
, — отделяет дополнительные величины. Например, триггер “ 0 0 11,12 * * ? ” будет срабатывать в 11 и 12 часов;
/ — определяет инкремент величины. Например, “ 0 0 0/2 * * ? ” означает, что триггер будет срабатывать каждые 2 часа.
Сравнение Cron и Quartz
- Использование cron подразумевает подключение еще одной точки входа в приложение, в то время как Quartz уже встроен в него. Для Quartz появляется много возможностей внутренней коммуникации в приложении, а для cron всё сложнее. Quartz можно запустить в многопоточной среде без особых сложностей.
- cron зависит от платформы, Quartz — нет.
- Quartz позволяет гарантировать запуск задач после прошедшего времени вызова (например если сервер в нужное время лежал, то задача всё равно запуститься при восстановлении работы). В то время как чистый cron это не гарантирует (если не настроено вручную).
- Quartz поддерживает более гибкие выражения и настройки.
- Quartz позволяет работать в многопоточном режиме, что добавляет множество возможностей.
- Если вы используете cron из ОС, то при изменении состоянии jvm любое состояние cron может быть потеряно.
- С Quartz вы получаете возможность портирования (например на Win OS где cron недоступен).
- Возможность администрирования состояния Quartz (например на Tomcat можно смотреть что там происходит с планировщиком).
6. Описание классов и настроек
Источник: javastudy.ru
Программы с ранним доступом
Примечание: данная программа в раннем доступе находится на стадии разработки. Она может измениться в будущем, а может остаться в текущем состоянии, так что если вам не по вкусу то, что программа может предложить сейчас, рекомендуем дождаться ее дальнейшего развития. Узнать больше
Почему ранний доступ?
“We wish create the best VR painting and exhibition software ever made, however we are a small team, we have many ideas to improve Quartz but we don’t want be out of step with community expectations.
That’s why we believe it’s essential to continue development with your help.”
Сколько примерно эта программа будет в раннем доступе?
“We don’t have a release date yet. But we hope to be able to launch Quartz in 6 to 12 months. This will depend on dealing with community feedback in addition to the development of planned features.”
Чем планируемая полная версия будет отличаться от версии в раннем доступе?
“The final release of Quartz plans to add new painting tools (Brushes and settings, multilayers, import 3D mesh and more) along with new environments.
Also, Quartz is an exhibition and sharing app, so we want to add a gallery editor and community features like multi-user, comments and more. Your gallery will be accessible to everyone on PC, mobile and VR.
We would like incorporate all these features during the early access.
A feature integration roadmap will be available soon.”
Каково текущее состояние версии в раннем доступе?
“You can now paint on 6 different 3D meshes (canvas, sphere, statue, . ) in 2 environments.
Moreover, you can use 4 painting tools like spray, marker, stencil and eraser which combine with related brushes and settings. Additionally, you can use a set of color charts or a color picker to create your own.
It’s possible to add a name and a description to your work. Watch the whole creation process with the replay mode and share it with the community.
Finally, you can follow other artists, like their works and create your own profile.”
Будет ли разница в цене до и после раннего доступа?
“We don’t plan to increase the price during early access, however, depending on the content and new features added, the price might be adjusted in the time of the final release.”
Как вы планируете вовлекать сообщество в разработку игры?
“Because Quartz is a creative app with community features, we want to hear all of yours relevant feedback that will help Quartz to move in the right way.
Please send your comments or suggestions and follow the app development on our Discord channel (https://discord.gg/vPPHz3CNGX) or via the community HUB on Steam.
We will try to be as responsive as possible and attentive.”
Не поддерживается русский язык
Этот продукт не поддерживает ваш язык. Пожалуйста, перед покупкой ознакомьтесь со списком поддерживаемых языков.
Внимание: Требуется шлем виртуальной реальности. Посмотрите системные требования, чтобы узнать больше.
Источник: store.steampowered.com
Выполняем задачи по расписанию с Quartz и Spring Boot
Представим, что нужно написать функцию, которая должна срабатывать каждый час, день, месяц и выполнять бизнесовую задачу: housekeeping базы данных, рассылку почты, расчёт метрик, составление отчетов по накопившемся данным.
При этом известно, что разрабатываемое приложение должно работать корректно при работе нескольких копий (инстансов) одновременно. Другими словами, должна сохраняться возможность горизонтального масштабирования приложения.
На мой взгляд, любой грамотный разработчик перед тем, как приступить к созданию собственного велосипеда, должен провести некоторый анализ, изучив уже существующие решения. Этим мы и займёмся.
Меня зовут Денис Шевцов, я бэкенд-разработчик в Surf. В статье расскажу, с решением каких проблем при разработке горизонтально-масштабируемых Spring Boot-приложений поможет планировщик задач Quartz, приведу примеры использования библиотеки.
Общая концепция работы Quartz
Quartz — это библиотека с открытым исходным кодом на языке Java. Помогает реализовать выполнение функций с определенным интервалом или по расписанию.
Quartz предоставляет готовое решение для этой проблемы: он использует общую базу данных. В ней сохраняется информация о джобах (JobDetail) и триггерах (Trigger), а также синхронизируется запуск задачи между планировщиками.
Чтобы понять устройство библиотеки, рассмотрим основные понятия:
- SchedulerFactory — интерфейс фабрики для создания Scheduler.
- Scheduler — основной класс библиотеки, через который происходит управление планировщиком задач.
- Job — интерфейс для создания задач с запланированным выполнением.
- JobDetail — интерфейс для создание инстансов Job.
- Trigger — интерфейс для определения расписания выполнения задач
- JobBuilder и TriggerBuilder — вспомогательные классы для создания инстансов JobDetail и Trigger.
В общем случае связь между частями библиотеки можно представить в виде схемы:
Чем Spring Boot помогает в связке с Quartz
При работе с Quartz, Spring Boot даёт разработчику преимущества для более простого и эффективного использования библиотеки:
- Значительно упрощает настройку Scheduler — благодаря возможности указать требуемые параметры в конфигурационном файле приложения.
- Даёт возможность внедрять зависимости прямо в джобы, предоставляя для этого абстрактный класс QuartzJobBean, который реализует интерфейс Job.
Подготовка к работе
Важные аспекты работы с библиотекой разберём на примере без лишних усложнений. Представим, что раз в час нужно выводить в консоль текущее время, а также время предыдущего срабатывания функции. Код приложения на Github
Не буду углубляться в настройку и запуск типичного Spring Boot приложения, подробнее об этом можно узнать в гайде Building an Application with Spring Boot. Только отмечу, что все примеры кода буду приводить на языке Kotlin, а в качестве системы сборки воспользуюсь Gradle.
Для работы создаваемого приложения понадобятся зависимости:
implementation(«org.springframework.boot:spring-boot-starter-jdbc») implementation(«com.h2database:h2») implementation(«org.flywaydb:flyway-core») //опционально implementation(«org.springframework.boot:spring-boot-starter-quartz»)
Перед тем, как перейдём к написанию кода, нам понадобится схема БД со всеми таблицами, которые нужны Quartz для работы.
Конечно, у Quartz есть возможность работы в in-memory режиме. Но чтобы можно было синхронизировать работы планировщиков, запущенных в разных инстансах приложения, необходимо использовать внешнюю по отношению к приложению сущность. В Quartz для этого используется база данных.
Чтобы создать нужные таблицы, я предлагаю воспользоваться официальными миграциями БД, которые можно применять с любым удобным вам инструментом. Я буду использовать библиотеку Flyway. Для этого мне необходимо создать директорию resources/bd/migration и поместить туда подходящий для выбранной БД скрипт.
Настраиваем выполнение регулярных задач при запуске приложения
Первый шаг в создании джобы — создание классов с функциями, которые должны выполняться в соответствии с расписанием. Для этого создадим сервисный класс ShowTimeService, содержащий единственный метод showTime.
В методе выводится информация о предыдущем времени запуска, если она есть, а также выводится и возвращается текущее время.
Далее потребуется класс, наследующий от QuartzJobBean : в нём будем получать из специальной сущности JobDataMap информацию о предыдущем запуске джобы и вызывать метод из сервисного слоя. Класс в последующем будет использован в JobDetail, чтобы Scheduler мог работать с инстансами этой джобы.
Стоит обратить внимание на несколько важных моментов:
Теперь, когда у нас есть джоба, необходимо настроить JobDetail и Trigger : чтобы Scheduler мог запускать выполнение джобы в соответствии с расписанием.
Чтобы иметь возможность настраивать приложение с помощью конфигурационного файла, воспользуемся возможностям Spring Boot и создадим класс SchedulerProperties , который будет хранить в себе некоторые параметры, необходимые для создания джобы.
Теперь можем добавить в нашу конфигурацию (application.yml) следующие строки:
scheduler: permanent-jobs-group-name: PERMANENT show-time-job-cron: $
- Параметр permanent-jobs-group-name понадобится, чтобы иметь возможность выделить среди всех созданных джоб только те, которые относятся к создаваемым при запуске приложения.
- Параметр show-time-job-cron позволит задавать расписание запуска джобы в формате крона. Подробнее об этом формате можно прочитать в Cron Trigger Tutorial. В данном случае значением по умолчанию стоит запуск джобы в начале каждого часа. Чтобы изменить расписание, достаточно указать новое значение в переменной окружения SHOW_TIME_JOB_CRON .
Конфигурационный класс для настройки джобы есть. Создадим бины для JobDetail и Trigger, который в последующем используем для того, чтобы запланировать выполнение джобы с помощью Scheduler.
В двух методах, представленных выше, происходит настройка и создание JobDetail и Trigger . Для этого используются специальные классы-билдеры: JobDetailBuilder и TriggerBuilder .
Подробнее обо всех возможностях — в документации:
Последним шагом в настройке приложение будет конфигурация класса Scheduler, а точнее, SchedulerFactory, отвечающего за создание Scheduler .
Чтобы настроить SchedulerFactory , используя конфигурационный файл, необходимо в application.yml (application.properties) добавить следующую конфигурацию:
spring: dataSource: #Настраиваем подключение к БД url: jdbc:h2:file:./quartz username: sa password: password driver-class-name: org.h2.Driver quartz: job-store-type: jdbc #Указываем, что будем хранить информацию о джобах в БД, а не в памяти jdbc: initialize-schema: never #Мы будем инициализировать схему БД вручную, поэтому ставим never properties: org: quartz: scheduler: instanceId: AUTO #Используев AUTO, для того, чтобы каждый новый инстанс Scheduler`a имел уникальное название. jobStore: driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #Указываем диалект для запросов к БД useProperties: false #Указываем, что все данные в БД будут храниться в качестве строк, а не в двоичном формате tablePrefix: QRTZ_ #Префикс таблиц в БД clusterCheckinInterval: 5000 #Указываем частоту сверки инстанса Scheduler с остальными инстансами в кластере isClustered: true #Включаем режим работы в кластере threadPool: #Указываем настройки для создания пула поток, на котором будут выполняться джобы class: org.quartz.simpl.SimpleThreadPool threadCount: 10 threadsInheritContextClassLoaderOfInitializingThread: true auto-startup: false #Выключаем автоматический старт для scheduler, т.к. запуск будет выполнен вручную
Со всеми возможностями, по настройке можно ознакомиться в Quartz Configuration Reference.
Наконец, реализуем класс, отвечающий за создание и настройку бина Scheduler.
Разберём подробнее, что происходит при создании бина:
- Перед созданием scheduler устанавливаем в true флаг, отвечающий за ожидание завершения всех джоб при выключении самого scheduler.
- Все джобы и триггеры хранятся в базе, поэтому необходимо проверить, что в БД нет джоб, которые были удалены из кода.
- Похожую процедуру нужно выполнить и для триггеров: триггер мог измениться с последнего запуска приложения.
- После этого выполняем запуск scheduler и возвращаем его в качестве бина, чтобы его можно было проинжектить в любое нужное место.
Теперь можем полноценно проверить работу приложения. Для этого советую изменить частоту вызова джобы, задав новый крон для переменной окружения SHOW_TIME_JOB_CRON = */3 * * * * ?
Запускаем приложение и видим, что каждые 3 секунды в консоль выводится текущее и предыдущее время запуска джобы.
Преимущества и недостатки Quartz
Из основных преимуществ:
- Встроенная поддержка многоинстансного режима значительно облегчает горизонтальное масштабирование приложений.
- Есть возможность сохранять нужные для работы данные между запусками джобы.
- Возможность динамического создания джоб.
- Quartz позволяет привязывать одну и ту же джобу к разным триггерам: можно создавать комплексное расписание работы джобы. Но этого момента в статье мы не коснулись.
- Синхронизация между инстансами происходит только при помощи реляционной БД. Другие виды внешних систем не поддерживаются.
- По сравнению с аналогами Quartz требует больше усилий и строк кода для начальной настройки приложения.
- Если требуется запускать какую-либо джобу на каждом из инстансов одновременно, лучше использовать один из аналогов. В Quartz нет встроенной возможности для выполнения одной и той же джобы на разных инстансах одновременно.
Источник: habr.com
Фреймворк Quartz: решаем вопрос запуска задач
Представим достаточно популярную задачу – утилизацию вычислительных ресурсов в дни с минимальной нагрузкой. Допустим, у нас есть какой-то сайт, с которым обычно работают по будням. И есть тяжёлые фоновые задачи, которые хотим запускать в выходные, чтобы не пересекаться с клиентами.
Но тут возникает небольшая проблема – выходные в РФ совсем не ограничиваются субботой и воскресеньем. Да и суббота и воскресенье не всегда могут быть выходными.
Попробуем решить эту задачу с помощью Quartz – одной из самых навороченных библиотек для запуска задач по расписанию и, конечно, Spring.
Естественно, мы напишем приложение на Spring Boot. Создадим его с помощью Spring Initializr.
pom.xml:
xml version=»1.0″ encoding=»UTF-8″?> project> parent> groupId>org.springframework.bootgroupId> artifactId>spring-boot-starter-parentartifactId> version>2.2.1.RELEASEversion> relativePath/> parent> properties> java.version>11java.version> properties> dependencies> dependency> groupId>org.springframework.bootgroupId> artifactId>spring-boot-starterartifactId> dependency> dependencies> и ещё немного —> project>
Надо найти одновременно простое, полное и поддерживаемое API для получения списка выходных дней. Воспользуемся не самым простым для интеграции (это XML) http://xmlcalendar.ru/, но и не самым сложным с точки зрения полноты данных.
Данное API возвращает XML следующего вида:
src/test/resources/calendar.xml:
xml version=»1.0″ encoding=»UTF-8″?> calendar year=»2018″ lang=»ru» date=»2017.10.22″> holidays> ещё немножко XML —> holiday id=»6″ title=»День Победы» /> holidays> days> ещё немножко XML —> day d=»05.09″ t=»1″ h=»6″ /> t=»1″ — выходной, t=»2″ — сокращённый, t=»3″ — рабочий, суббота и воскрсенье — выходные по умолчанию. да, формат даты ММ.ДД —> day d=»06.09″ t=»2″ /> days> calendar>
Немножко разобравшись с форматом ответа, получается следующий алгоритм определения выходного дня:
День выходной == если это понедельник… пятница и он присутствует в праздничном календаре с типом t=»1″, а если это суббота или воскресенье, то день должен отсутствовать в праздничном календаре с типами t=»2″ или t=»3″.
Напишем с помощью Jackson маппер данного XML. Итак, для начала настроим Jackson:
pom.xml:
dependency> groupId>com.fasterxml.jackson.dataformatgroupId> artifactId>jackson-dataformat-xmlartifactId> dependency>
src/main/java/ru/otus/springquartzexample/config/ApplicationConfig.java:
Напишем классы для элементов:
src/main/java/ru/otus/springquartzexample/xmlcalendar/CalendarElement.java:
src/main/ru/otus/springquartzexample/xmlcalendar/DayElement.java:
Да, не удивляйтесь, что здесь присутствуют аннотации для Json. Всё-таки, это Jackson.
Чтобы убедиться, что мы всё сделали правильно, напишем небольшой тест:
src/main/java/ru/otus/springquartzexample/xmlcalendar/CalendarElementTest.java:
Ну и напишем клиента, который получает соответствующий календарь:
И куда без теста:
Теперь пришло время написать нашу бизнес-логику. Для приличия создадим интерфейс сервиса:
src/main/java/ru/otus/springquartzexample/service/RussianHolidaysService.java:
public interface RussianHolidaysService boolean isHoliday(LocalDate date); >
Но неприлично реализуем его прямо в клиенте:
Ну и куда без тестов:
Здесь мы не будем рассматривать особенности кэширования вызовов методов, хотя со Spring можно сделать кэширование очень просто.
Пришло время разобраться c Quartz
Начиная со Spring Boot 2.0, для него существует отдельный стартер:
dependency> groupId>org.springframework.bootgroupId> artifactId>spring-boot-starter-quartzartifactId> dependency>
Главная сущность, которую мы хотели:
src/main/java/ru/otus/springquartzexample/quartz/VeryHardJob.java:
Quartz — очень сложный фреймворк. Он хранит выполненные работы в БД и для того, чтобы его настроить, необходимо создать множество бинов, один из которых главный – Quartz Sheduler.
Spring Boot спасает нас от подобной необходимости, имеет настроенные бины, включая хранение в памяти данных работ.
Поэтому мы просто настроим только несколько бинов, а благодаря автоконфигурации они будут использованы.
src/main/java/ru/otus/springquartzexample/quartz/RussianHolidaysQuartzCalendar.java:
Запустив это в будний день, мы увидим:
2019-11-15 09:57:01.315 INFO 18580 — [eduler_Worker-1] r.o.s.quartz.VeryHardJob : Very very very hard job.
Подробности реализации этой задачи вы можете посмотреть здесь.
Источник: otus.ru