При создании приложений, работающих на Kubernetes, вы хотите, чтобы время запуска и занимаемая площадь были небольшими. Традиционные Java-приложения любят потреблять больше, чем их ограниченная память и процессор, и вы могли видеть ошибки OOMKilled
в ваших производственных приложениях. Приготовьтесь, поскольку этот блог отправит вас в «сверхзвуковое» путешествие с Quarkus (быстрым и легким Java-фреймворком) и Redis®* (быстрым хранилищем структур данных в памяти).
Создание небольшого приложения для одноразового пароля
Представьте, что вы создаете функцию одноразового пароля в рамках более крупного проекта. Ваша команда использует Java, с которой она хорошо знакома, и Quarkus в качестве фреймворка, поскольку хочет, чтобы приложение было совместимо с Kubernetes (с точки зрения размера и времени запуска).
Помимо реляционной базы данных, команда также использует Redis для быстрого запроса сгенерированных одноразовых паролей. В этом примере используется Aiven для Redis, и вы можете подписаться на бесплатную пробную версию, если хотите опробовать ее.
Важным моментом для вашей команды является то, что и Quarkus, и Redis — это проекты с открытым исходным кодом.
Прежде чем начать
Чтобы следовать практической части этого блога, вот требования к программному обеспечению:
- JDK 11+
- Apache Maven 3.8.1+
- Локальный или управляемый экземпляр Redis
Время для создания приложения
Создание проекта Maven
Следующая команда создает новый проект Maven и добавляет необходимые расширения в ваш проект. Расширение Quarkus redis-client
позволяет пользователю подключаться к серверу Redis и выполнять команды Redis. Расширение resteasy-jackson
позволяет создавать RESTful веб-сервисы и обрабатывать данные в формате JSON. Расширение resteasy-mutiny
помогает создавать реактивные API для асинхронных систем.
mvn io.quarkus.platform:quarkus-maven-plugin:2.7.5.Final:create
-DprojectGroupId=org.acme
-DprojectArtifactId=one-time-password
-Dextensions="redis-client,resteasy-jackson,resteasy-mutiny"
-DnoCode
cd one-time-password
Используйте следующую команду для добавления зависимости redis-client
в ваш файл pom.xml
:
./mvnw quarkus:add-extension -Dextensions="redis-client"
Получение информации о сервере Redis
Создайте экземпляр Aiven for Redis. Для этого упражнения подойдет любой план обслуживания. Когда служба Redis будет запущена, скопируйте URI службы с вкладки Overview > Connection information.
Перейдите в ваш любимый редактор кода и откройте проект one-time-password
. Вставьте URI службы в файл src > main > resources > application.properties
:
quarkus.redis.hosts=[YOUR REDIS CONNECTION INFORMATION GOES HERE]
Если вы запускаете Redis на локальной машине, используйте следующие настройки:
quarkus.redis.hosts=redis://localhost:6379
Создание POJO Otp (Plain Old Java Object)
Класс Otp
создает POJO (Plain Old Java Object) для хранения session_key
и otp_value
. Метод generateRandomOtp
генерирует случайное значение в заданном диапазоне.
Создайте файл src/main/java/org/acme/redis/Otp.java
и добавьте следующее:
package org.acme.redis;
import java.util.Random;
public class Otp {
final int lowRange = 100000;
final int highRange = 999999;
public String session_key;
public int otp_value;
public Otp(String session_key) {
this.session_key = session_key;
this.otp_value = generateRandomOtp(lowRange, highRange);
}
public Otp() {}
private static int generateRandomOtp(int low, int high) {
// Generate random int value from $low to ($high - 1)
return low + new Random().nextInt(high - low);
}
}
Создание службы Otp
Вы собираетесь создать OtpService
, который будет играть роль клиента Redis. Этот класс поможет вам выполнять команды Redis GET
, EXISTS
, SETEX
, SETNX
и TTL
. Документацию по ним можно найти на официальной странице команд Redis.
Создайте файл src/main/java/org/acme/redis/OtpService.java
и добавьте следующее:
package org.acme.redis;
import io.quarkus.redis.client.RedisClient;
import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
class OtpService {
final int timeInSeconds = 20;
@Inject
RedisClient redisClient;
public String getOtp(String session_key) {
return redisClient.get(session_key).toString();
}
public void newOtp(String session_key) {
Otp otp = new Otp(session_key);
// SETNX will only create a key if it doesn't already exist
// - so we won't overwrite an existing OTP value
// Unfortunately SETNX can't set the TTL/expiration time
if (redisClient.setnx(otp.session_key.toString(),
String.valueOf(otp.otp_value)).toBoolean()) {
// Only update TTL/expiration if the OTP value was set
redisClient.setex(otp.session_key.toString(),
String.valueOf(timeInSeconds),
String.valueOf(otp.otp_value));
}
}
public String getOtpTTL(String session_key) {
return redisClient.ttl(session_key).toString();
}
public boolean keyExists(String session_key) {
return redisClient.exists(Arrays.asList(session_key)).toBoolean();
}
}
Создайте ресурс Otp
Создайте файл src/main/java/org/acme/redis/OtpResource.java
, в котором вы определите конечные точки HTTP для вашей службы одноразовых паролей.
Обратите внимание на аннотацию @Inject
для простого создания экземпляра службы и @Path("/otp")
для указания создания конечных точек HTTP. Одна и та же конечная точка используется для вызовов GET
и POST
. Преимущество использования такого фреймворка, как Quarkus, заключается в том, что без аннотаций вам пришлось бы написать несколько строк кода для достижения одного и того же результата.
package org.acme.redis;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
import io.vertx.core.json.JsonObject;
@Path("/otp")
public class OtpResource {
@Inject
OtpService service;
@GET
public JsonObject getOtp(@QueryParam String session_key) {
// If the key doesn't exist, return an error response rather than the usual
// object response
if (!service.keyExists(session_key)) {
return errorResponse();
}
JsonObject result = new JsonObject();
result.put("OTP: ", service.getOtp(session_key));
result.put("TTL: ", service.getOtpTTL(session_key));
return result;
}
JsonObject errorResponse() {
JsonObject result = new JsonObject();
result.put("Message: ", "The OTP key doesn't exist.");
return result;
}
@POST
public void newOtp(@QueryParam String session_key) {
service.newOtp(session_key);
}
}
Запустите ваше новое приложение Quarkus
В корне проекта с одноразовым паролем выполните следующую команду:
./mvnw quarkus:dev
Перейдите по адресу http://localhost:8080/otp?session_key=[SOME_KEY]
, заменив [SOME_KEY]
на любой текст. При первом запуске вы получите ответ об ошибке, подобный этому:
"Message: ": "The OTP key doesn't exist."
Это происходит потому, что данный ключ еще не существует в Redis. В терминале выполните следующий POST-запрос для создания ключа:
curl --location --request POST 'http://localhost:8080/otp?session_key=[SOME_KEY]'
Теперь перезагрузите браузер, и вы должны получить случайное 6-значное число в качестве одноразового пароля, срок действия которого истекает через 20 секунд. Если вы подождете более 20 секунд, срок действия пароля истечет, и вы снова получите ответ об ошибке.
Если вы продолжите обновлять страницу, вы будете получать один и тот же OTP, пока не истечет время жизни (time-to-live) TTL.
Вы также можете понять, что вам не пришлось выполнять настройку/обработку сервера для этого приложения, поскольку Quarkus позаботился об этом за вас. Довольно круто, да?
Дальнейшее обучение
В этом блоге я рассказал вам о начале работы с Redis и продемонстрировал пример использования, создав простое приложение для одноразовых паролей с помощью фреймворка Quarkus. Проектирование и реализация реального программного обеспечения для одноразовых паролей намного сложнее, чем простое приложение, упомянутое здесь; например, оно, вероятно, не захочет предоставлять один и тот же OTP разным пользователям.
Чтобы узнать больше о Quarkus:
- Документация проекта Quarkus
Чтобы узнать больше о Redis:
- Документация по Redis
- Aiven для Redis — документация для разработчиков
*Redis является зарегистрированной торговой маркой компании Redis Ltd., а логотип Redis box является маркой компании Redis Ltd. Все права на них принадлежат Redis Ltd. Любое использование Aiven исключительно в справочных целях и не указывает на какое-либо спонсорство, одобрение или связь между Redis и Aiven. Все названия продуктов и услуг, используемые на этом сайте, предназначены только для идентификации и не подразумевают одобрения.