From b8c693b7792f4be3040e03aba493f2b03caa225c Mon Sep 17 00:00:00 2001 From: sszuev Date: Sat, 24 Aug 2024 18:39:35 +0300 Subject: [PATCH 1/2] deploy: add redis --- tutor-deploy/data/nats/nats-config.conf | 2 ++ tutor-deploy/data/redis/redis.conf | 9 +++++++++ tutor-deploy/docker-compose-app.yml | 26 ++++++++++++++++++++----- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 tutor-deploy/data/nats/nats-config.conf create mode 100644 tutor-deploy/data/redis/redis.conf diff --git a/tutor-deploy/data/nats/nats-config.conf b/tutor-deploy/data/nats/nats-config.conf new file mode 100644 index 0000000..b8c1668 --- /dev/null +++ b/tutor-deploy/data/nats/nats-config.conf @@ -0,0 +1,2 @@ +port: 4222 +max_payload: 5242880 \ No newline at end of file diff --git a/tutor-deploy/data/redis/redis.conf b/tutor-deploy/data/redis/redis.conf new file mode 100644 index 0000000..8d33fb6 --- /dev/null +++ b/tutor-deploy/data/redis/redis.conf @@ -0,0 +1,9 @@ +maxmemory 100mb +maxmemory-policy allkeys-lru +save 900 1 +appendonly yes +appendfsync everysec +auto-aof-rewrite-percentage 50 +auto-aof-rewrite-min-size 64mb +logfile "" +loglevel notice \ No newline at end of file diff --git a/tutor-deploy/docker-compose-app.yml b/tutor-deploy/docker-compose-app.yml index ea0b960..483a616 100644 --- a/tutor-deploy/docker-compose-app.yml +++ b/tutor-deploy/docker-compose-app.yml @@ -1,5 +1,3 @@ -version: "3.9" - networks: flashcards-net: @@ -73,9 +71,8 @@ services: flashcards-nats: image: nats:2.10.14-alpine - ports: - - "4222:4222" - - "8222:8222" + volumes: + - "${DATA_DIR}/nats/nats-config.conf:/etc/nats/nats-config.conf" healthcheck: test: [ "CMD-SHELL", "netstat -an | grep 4222 | grep LISTEN" ] interval: 10s @@ -84,6 +81,22 @@ services: start_period: 10s networks: - flashcards-net + command: [ "-c", "/etc/nats/nats-config.conf" ] + + flashcards-redis: + image: "redis:7.4.0" + volumes: + - "${DATA_DIR}/redis:/data" + - "${DATA_DIR}/redis/redis.conf:/usr/local/etc/redis/redis.conf" + command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ] + restart: unless-stopped + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + interval: 30s + timeout: 10s + retries: 5 + networks: + - flashcards-net flashcards-tts-server: image: sszuev/open-tutor-tts-server:2.0.0-snapshot @@ -92,8 +105,11 @@ services: depends_on: flashcards-nats: condition: service_healthy + flashcards-redis: + condition: service_healthy environment: TTS_SERVER_NATS_HOST: "flashcards-nats" + TTS_SERVER_REDIS_HOST: "flashcards-redis" TTS_SERVICE_VOICERSS_KEY: "${TTS_SERVICE_VOICERSS_KEY}" flashcards-cards-server: From f221708776b01c19add5ed65abd81cecfe1b1c52 Mon Sep 17 00:00:00 2001 From: sszuev Date: Sat, 24 Aug 2024 21:17:45 +0300 Subject: [PATCH 2/2] app-tts & tts-lib: use redis as a cache --- app-tts/build.gradle.kts | 4 +++ .../src/main/kotlin/GetResourceListeners.kt | 23 ++++++++++++ app-tts/src/main/kotlin/NatsConfig.kt | 2 +- app-tts/src/main/kotlin/RedisConfig.kt | 5 +++ .../src/main/kotlin/RedisConnectionFactory.kt | 36 +++++++++++++++++++ app-tts/src/main/kotlin/RedisResourceCache.kt | 28 +++++++++++++++ app-tts/src/main/kotlin/TTSServerMain.kt | 20 +++++++---- app-tts/src/main/kotlin/TTSServerSettings.kt | 12 ++++--- .../src/main/resources/application.properties | 2 ++ gradle.properties | 2 ++ .../src/main/kotlin/TextToSpeechService.kt | 8 +++-- .../impl/CombinedTextToSpeechService.kt | 6 ++-- tutor-deploy/README.md | 1 + 13 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 app-tts/src/main/kotlin/GetResourceListeners.kt create mode 100644 app-tts/src/main/kotlin/RedisConfig.kt create mode 100644 app-tts/src/main/kotlin/RedisConnectionFactory.kt create mode 100644 app-tts/src/main/kotlin/RedisResourceCache.kt diff --git a/app-tts/build.gradle.kts b/app-tts/build.gradle.kts index 6d6cf04..b42f857 100644 --- a/app-tts/build.gradle.kts +++ b/app-tts/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { val testContainersVersion: String by project val mockkVersion: String by project val typesafeConfigVersion: String by project + val lettuceVersion: String by project implementation(project(":tts-lib")) implementation(project(":common")) @@ -27,6 +28,7 @@ dependencies { implementation(project(":utilities")) implementation("io.nats:jnats:$natsVersion") + implementation("io.lettuce:lettuce-core:$lettuceVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion") implementation("com.typesafe:config:$typesafeConfigVersion") @@ -46,8 +48,10 @@ tasks.test { tasks.dockerCreateDockerfile { arg("TTS_SERVER_NATS_HOST") + arg("TTS_SERVER_REDIS_HOST") arg("TTS_SERVICE_VOICERSS_KEY") environmentVariable("TTS_SERVER_NATS_HOST", "\${TTS_SERVER_NATS_HOST}") + environmentVariable("TTS_SERVER_REDIS_HOST", "\${TTS_SERVER_REDIS_HOST}") environmentVariable("TTS_SERVICE_VOICERSS_KEY", "\${TTS_SERVICE_VOICERSS_KEY}") } diff --git a/app-tts/src/main/kotlin/GetResourceListeners.kt b/app-tts/src/main/kotlin/GetResourceListeners.kt new file mode 100644 index 0000000..75f901e --- /dev/null +++ b/app-tts/src/main/kotlin/GetResourceListeners.kt @@ -0,0 +1,23 @@ +package com.gitlab.sszuev.flashcards.speaker + +import io.lettuce.core.api.sync.RedisCommands +import org.slf4j.LoggerFactory +import java.time.Duration +import java.time.Instant + +private val logger = LoggerFactory.getLogger("com.gitlab.sszuev.flashcards.speaker.GetResourceListeners") + +fun onGetResource(commands: RedisCommands) = try { + val res1 = commands.incr("words.count.total") + var res2 = commands.incr("words.count.daily") + val date = commands.get("words.date")?.let { Instant.parse(it) } + val now = Instant.now() + if (date != null && Duration.between(date, now).seconds > 24 * 60 * 60) { + // new day + res2 = commands.del("words.count.daily") + commands.set("words.date", now.toString()) + } + logger.info("Total count $res1, today's count $res2") +} catch (ex: Exception) { + logger.error("Unexpected error on get resource", ex) +} \ No newline at end of file diff --git a/app-tts/src/main/kotlin/NatsConfig.kt b/app-tts/src/main/kotlin/NatsConfig.kt index 96271b2..3127879 100644 --- a/app-tts/src/main/kotlin/NatsConfig.kt +++ b/app-tts/src/main/kotlin/NatsConfig.kt @@ -1,7 +1,7 @@ package com.gitlab.sszuev.flashcards.speaker data class NatsConfig( - val url: String = "nats://${TTSServerSettings.host}:${TTSServerSettings.port}", + val url: String = "nats://${TTSServerSettings.natsHost}:${TTSServerSettings.natsPort}", val user: String = TTSServerSettings.user, val password: String = TTSServerSettings.password, val topic: String = TTSServerSettings.topic, diff --git a/app-tts/src/main/kotlin/RedisConfig.kt b/app-tts/src/main/kotlin/RedisConfig.kt new file mode 100644 index 0000000..8e9551f --- /dev/null +++ b/app-tts/src/main/kotlin/RedisConfig.kt @@ -0,0 +1,5 @@ +package com.gitlab.sszuev.flashcards.speaker + +data class RedisConfig( + val url: String = "redis://${TTSServerSettings.redisHost}:${TTSServerSettings.redisPort}", +) \ No newline at end of file diff --git a/app-tts/src/main/kotlin/RedisConnectionFactory.kt b/app-tts/src/main/kotlin/RedisConnectionFactory.kt new file mode 100644 index 0000000..0df2684 --- /dev/null +++ b/app-tts/src/main/kotlin/RedisConnectionFactory.kt @@ -0,0 +1,36 @@ +package com.gitlab.sszuev.flashcards.speaker + +import io.lettuce.core.RedisClient +import io.lettuce.core.api.sync.RedisCommands +import io.lettuce.core.codec.ByteArrayCodec +import io.lettuce.core.codec.RedisCodec +import io.lettuce.core.codec.StringCodec + +class RedisConnectionFactory( + connectionUrl: String = "redis://localhost:6379", +) : AutoCloseable { + + private val client by lazy { + RedisClient.create(connectionUrl) + } + private val stringToStringConnection by lazy { + client.connect() + } + private val stringToByteArrayConnection by lazy { + client.connect(RedisCodec.of(StringCodec(), ByteArrayCodec())) + } + + val stringToByteArrayCommands: RedisCommands by lazy { + stringToByteArrayConnection.sync() + } + + val stringToStringCommands: RedisCommands by lazy { + stringToStringConnection.sync() + } + + override fun close() { + stringToByteArrayConnection.close() + stringToStringConnection.close() + client.shutdown() + } +} \ No newline at end of file diff --git a/app-tts/src/main/kotlin/RedisResourceCache.kt b/app-tts/src/main/kotlin/RedisResourceCache.kt new file mode 100644 index 0000000..6786bd5 --- /dev/null +++ b/app-tts/src/main/kotlin/RedisResourceCache.kt @@ -0,0 +1,28 @@ +package com.gitlab.sszuev.flashcards.speaker + +import io.lettuce.core.api.sync.RedisCommands +import org.slf4j.LoggerFactory + +private val logger = LoggerFactory.getLogger(RedisResourceCache::class.java) + +class RedisResourceCache( + private val commands: RedisCommands +) : ResourceCache { + + override fun get(id: String): ByteArray? = try { + commands.get(id) + } catch (ex: Exception) { + logger.error("unexpected error while redis#get", ex) + null + } + + override fun put(id: String, data: ByteArray) { + try { + if (commands.set(id, data) != "OK") { + logger.error("Can't redis#set") + } + } catch (ex: Exception) { + logger.error("unexpected error while redis#put", ex) + } + } +} \ No newline at end of file diff --git a/app-tts/src/main/kotlin/TTSServerMain.kt b/app-tts/src/main/kotlin/TTSServerMain.kt index 0728edd..ec04eea 100644 --- a/app-tts/src/main/kotlin/TTSServerMain.kt +++ b/app-tts/src/main/kotlin/TTSServerMain.kt @@ -6,16 +6,24 @@ import kotlin.concurrent.thread private val logger = LoggerFactory.getLogger("com.gitlab.sszuev.flashcards.speaker.TTSServerMain") fun main() { - val config = NatsConfig() + val natsConfig = NatsConfig() + val redisConfig = RedisConfig() + val redis = RedisConnectionFactory( + connectionUrl = redisConfig.url, + ) val processor = NatsTTSServerProcessorImpl( - service = createTTSService(), - topic = config.topic, - group = config.group, - connectionUrl = config.url, + service = createTTSService( + cache = RedisResourceCache(redis.stringToByteArrayCommands), + onGetResource = { onGetResource(redis.stringToStringCommands) }, + ), + topic = natsConfig.topic, + group = natsConfig.group, + connectionUrl = natsConfig.url, ) Runtime.getRuntime().addShutdownHook(thread(start = false) { - logger.info("Close connection on shutdown.") + logger.info("Close connections on shutdown.") processor.close() + redis.close() }) logger.info("Start processing.") TTSServerController(processor).start() diff --git a/app-tts/src/main/kotlin/TTSServerSettings.kt b/app-tts/src/main/kotlin/TTSServerSettings.kt index 54e3c07..e21fa8e 100644 --- a/app-tts/src/main/kotlin/TTSServerSettings.kt +++ b/app-tts/src/main/kotlin/TTSServerSettings.kt @@ -10,8 +10,10 @@ object TTSServerSettings { private val conf: Config = ConfigFactory.load() - val host = conf.get(key = "tts-server.nats.host", default = "localhost") - val port = conf.get(key = "tts-server.nats.port", default = 4222) + val natsHost = conf.get(key = "tts-server.nats.host", default = "localhost") + val natsPort = conf.get(key = "tts-server.nats.port", default = 4222) + val redisHost = conf.get(key = "tts-server.redis.host", default = "localhost") + val redisPort = conf.get(key = "tts-server.redis.port", default = 6379) val user = conf.get(key = "tts-server.nats.user", default = "dev") val password = conf.get(key = "tts-server.nats.password", default = "dev") val topic = conf.get(key = "tts-server.nats.topic", default = "TTS") @@ -24,12 +26,14 @@ object TTSServerSettings { private fun printDetails(): String { return """ | - |nats-host = $host - |nats-port = $port + |nats-host = $natsHost + |nats-port = $natsPort |nats-user = *** |nats-password = *** |nats-topic = $topic |nats-group = $group + |redis-hos = $redisHost + |redis-por = $redisPort """.replaceIndentByMargin("\t") } } \ No newline at end of file diff --git a/app-tts/src/main/resources/application.properties b/app-tts/src/main/resources/application.properties index c4ca43c..fda77f9 100644 --- a/app-tts/src/main/resources/application.properties +++ b/app-tts/src/main/resources/application.properties @@ -4,5 +4,7 @@ tts-server.nats.user=dev tts-server.nats.password=dev tts-server.nats.topic=TTS tts-server.nats.group=TTS +tts-server.redis.host=localhost +tts-server.redis.port=6379 tts.local.data-directory=classpath:/data \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f2163ca..0ddd864 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,6 +18,8 @@ bmuschkoVersion=9.4.0 ktorVersion=2.3.12 # https://mvnrepository.com/artifact/io.nats/jnats natsVersion=2.20.0 +# https://mvnrepository.com/artifact/io.lettuce/lettuce-core +lettuceVersion=6.4.0.RELEASE # https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-serialization-core kotlinxSerializationVersion=1.7.1 # https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind diff --git a/tts-lib/src/main/kotlin/TextToSpeechService.kt b/tts-lib/src/main/kotlin/TextToSpeechService.kt index f177964..8d5f12b 100644 --- a/tts-lib/src/main/kotlin/TextToSpeechService.kt +++ b/tts-lib/src/main/kotlin/TextToSpeechService.kt @@ -1,5 +1,6 @@ package com.gitlab.sszuev.flashcards.speaker +import com.gitlab.sszuev.flashcards.speaker.impl.CaffeineResourceCache import com.gitlab.sszuev.flashcards.speaker.impl.CombinedTextToSpeechService import com.gitlab.sszuev.flashcards.speaker.impl.EspeakNgTestToSpeechService import com.gitlab.sszuev.flashcards.speaker.impl.LocalTextToSpeechService @@ -33,10 +34,13 @@ interface TextToSpeechService { /** * Creates a [TextToSpeechService]. */ -fun createTTSService(): TextToSpeechService { +fun createTTSService( + cache: ResourceCache = CaffeineResourceCache(), + onGetResource: () -> Unit = {} +): TextToSpeechService { return if (TTSSettings.ttsServiceVoicerssKey.isNotBlank() && TTSSettings.ttsServiceVoicerssKey != "secret") { logger.info("::[TTS-SERVICE] init voicerss service") - CombinedTextToSpeechService() + CombinedTextToSpeechService(cache = cache, onGetResource = onGetResource) } else if (EspeakNgTestToSpeechService.isEspeakNgAvailable()) { logger.info("::[TTS-SERVICE] init espeak-ng service") EspeakNgTestToSpeechService() diff --git a/tts-lib/src/main/kotlin/impl/CombinedTextToSpeechService.kt b/tts-lib/src/main/kotlin/impl/CombinedTextToSpeechService.kt index 463454f..9576b69 100644 --- a/tts-lib/src/main/kotlin/impl/CombinedTextToSpeechService.kt +++ b/tts-lib/src/main/kotlin/impl/CombinedTextToSpeechService.kt @@ -1,5 +1,6 @@ package com.gitlab.sszuev.flashcards.speaker.impl +import com.gitlab.sszuev.flashcards.speaker.ResourceCache import com.gitlab.sszuev.flashcards.speaker.TTSConfig import com.gitlab.sszuev.flashcards.speaker.TextToSpeechService import com.gitlab.sszuev.flashcards.speaker.toResourcePath @@ -7,14 +8,14 @@ import com.gitlab.sszuev.flashcards.speaker.toResourcePath class CombinedTextToSpeechService( resourceIdMapper: (String) -> Pair? = { toResourcePath(it) }, config: TTSConfig = TTSConfig(), + private val cache: ResourceCache = CaffeineResourceCache(), + private val onGetResource: () -> Unit = {}, ) : TextToSpeechService { private val voicerssTextToSpeechService = VoicerssTextToSpeechService(resourceIdMapper = resourceIdMapper, config = config) private val espeakNgTestToSpeechService = EspeakNgTestToSpeechService(resourceIdMapper = resourceIdMapper, config = config) - private val cache = CaffeineResourceCache() - override suspend fun getResource(id: String, vararg args: String): ByteArray? { var res = cache.get(id) @@ -23,6 +24,7 @@ class CombinedTextToSpeechService( } res = voicerssTextToSpeechService.getResource(id, *args) if (res != null) { + onGetResource() cache.put(id, res) return res } diff --git a/tutor-deploy/README.md b/tutor-deploy/README.md index db647fb..708db1c 100644 --- a/tutor-deploy/README.md +++ b/tutor-deploy/README.md @@ -6,6 +6,7 @@ The directory contains docker-composer files allowing to set up environment. - flashcards-db (postgres). it is requires by [:db-pg](../db-pg) - flashcards-keycloak. Authorization (demo:demo). - flashcards-nats (transport) +- flashcards-redis (cache) - flashcards-tts-server - flashcards-cards-server - flashcards-dictionaries-server