Кэширование данных#
Рассмотрим ситуацию, когда необходимо реализовать следующие взаимодействия.
Клиент 1 отправляет запрос на backend, ответ от backend валидируется и возвращается как к клиенту. Кроме этого помещаем как запрос, так и ответ в кэш, чтобы другой поток (например, поток логирования) периодически забирал эти данные.
Создадим отдельный профиль для этого с названием profile_cache.
profile: profile_cache
system:
conn_count: 4096
wrk_count: 1
optional:
log_level: debug
storages:
- name: request
- name: response
version: 2.0.3
service:
service_http_proxy:
- id: 1
allowed_queries:
- method: get
url: /proxy
listen:
- port: 20208
chains:
request_chains:
- actions:
- action: SharedMapSetter
params:
shared_store_name: request
shared_store_key: $arg_Id
operation: ADD
message: "{\"message_id\": \"$arg_id\", \"type\": \"request\"}"
response_chains:
- actions:
- action: JsonValidation
params:
validation_schema: {type: file, path: schemas/sel_schema_1.json}
message: $clj_response_body
- actions:
- action: SharedMapSetter
params:
shared_store_name: response
shared_store_key: $arg_Id
operation: ADD
message: "{\"message_id\": \"$arg_id\", \"type\": \"response\", \"body\": $clj_response_body}"
upstream_group_id: 1
upstream_groups:
- id: 1
servers:
- server: 127.0.0.1:40001
service_static:
- id: 1
listen:
- port: 20209
#allowed_queries:
# - method: delete
url: /cache
chains:
request_chains:
- actions:
- action: SharedMapRemover
conditions:
- {var: $arg_type, operator: "=", val: "request"}
params:
operation: REMOVE_FIRST
shared_store_name: request
actions:
- action: SetVarFromMessage
params:
variable: $clj_message
- actions:
- action: SharedMapRemover
conditions:
- {var: $arg_type, operator: "=", val: "response"}
params:
operation: REMOVE_FIRST
shared_store_name: response
actions:
- action: SetVarFromMessage
params:
variable: $clj_message
response_generator:
rules_file: rules.yml
content_type: application/json
pattern_file: response.json
Рассмотрим отдельные элементы нашей конфигурации.
В секции optional раздела system определяем два разделяемых кэша для запросов и ответов соответственно - каждый из них по умолчанию имеет размер 10Mb.
Далее идет описание сервиса прокси:
service_http_proxy:
- id: 1
allowed_queries:
- method: get
url: /proxy
listen:
- port: 20208
chains:
request_chains:
- actions:
- action: SharedMapSetter
params:
shared_store_name: request
shared_store_key: $arg_Id
operation: ADD
message: "{\"message_id\": \"$arg_id\", \"type\": \"request\"}"
response_chains:
- actions:
- action: JsonValidation
params:
validation_schema: {type: file, path: schemas/sel_schema_1.json}
message: $clj_response_body
- actions:
- action: SharedMapSetter
params:
shared_store_name: response
shared_store_key: $arg_Id
operation: ADD
message: "{\"message_id\": \"$arg_id\", \"type\": \"response\", \"body\": $clj_response_body}"
upstream_group_id: 1
upstream_groups:
- id: 1
servers:
- server: 127.0.0.1:40001
Здесь задаем, что для запроса допустим метод GET (в секции allowed_quieries), а также определяем цепочки действий для запроса и ответа. В цепочке обработки запроса помещаем в кэш request запись с идентификатором, равному значению параметра запроса id и телом, представляющим собой json из двух элементов. В цепочке обработки ответа, сначала валидируем ответ по схеме, которую мы задавали ранее:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"lastname": {
"type": "string"
},
"firstname": {
"type": "string"
},
"phone": {
"type": "string"
},
"version": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"lastname", "firstname", "version"
]
}
А затем помещаем в кэш элемент с ключом, соответствующим значению параметра id из запроса b и с данными вида : "{"message_id": "$arg_id", "type": "response", "body": $clj_response_body}".
Второй сервис, в зависимости от значения параметра запроса type, удаляет первый элемент кэша запроса или ответа и возвращает его с помощью генератора ответа.
service_static:
- id: 1
listen:
- port: 20209
#allowed_queries:
# - method: delete
url: /cache
chains:
request_chains:
- actions:
- action: SharedMapRemover
conditions:
- {var: $arg_type, operator: "=", val: "request"}
params:
operation: REMOVE_FIRST
shared_store_name: request
actions:
- action: SetVarFromMessage
params:
variable: $clj_message
- actions:
- action: SharedMapRemover
conditions:
- {var: $arg_type, operator: "=", val: "response"}
params:
operation: REMOVE_FIRST
shared_store_name: response
actions:
- action: SetVarFromMessage
params:
variable: $clj_message
response_generator:
rules_file: rules.yml
content_type: application/json
pattern_file: response.json
Здесь для обеих цепочек осуществляется извлечение с удалением первого элемента по дате вставки из кэша . В зависимости от значения параметра type, элемент извлекается либо из кэша запросов, либо из кэша ответов. После чего результат извлечения помещается в переменную clj_message.
Секция response_generator здесь отвечает за генерацию ответа клиенту. Здесь правила генерации ответа, задаваемые в файле rules.yml, который имеет следующий вид:
rules:
- condition:
- var: $clj_shared_map_error
operator: "!="
val: ""
replacements:
- pattern: message
val: "{\"status\": \"error\"}"
- default: True
replacements:
- pattern: message
val: $clj_message
status: 200
Соответственно, правила генерации свидетельствуют о том, что если операция извлечения завершилась с ошибкой, то возвращаем ошибочный ответ с телом {"status": "error"}, который мы подставим в шаблон, определенный в файле response.json. Если же ошибки не было, то мы возвращаем ответ со статусом 200 и телом со значением, извлеченным в переменную clj_message.
Файл с шаблоном ответа response.json cодержит следующую подстановочную конструкцию:
{message}
Выполним следующие действия:
Добавим профиль profile_cache.
Скопируем файлы response.json и rules.yml в каталог /sowa/profile_storage/custom/profile_cache.
Создадим каталог /sowa/profile_storage/custom/profile_cache/schemas.
Скопируем sel_schema_1.json в каталог, созданный в п. 3.
Сконфигурируем профиль.
Запустим профиль.
Добавим в профиль заглушку сервис, возвращающий статичные данные:
- id: 5
name: simple_5
listen:
- port: 40001
url: /proxy
optional:
response_body: "{\"version\": \"v1\", \"lastname\": \"Ivanov\", \"firstname\": \"Ivan\"}"
response_code: 200
response_headers:
- name: IDENT
value: $server_addr:$server_port
Переконфигурируем профиль с заглушками и перезапустим его.
[sowacfg@741037ad695c chains_examples]$ curl -v http://localhost:20208/proxy?id=2
* About to connect() to localhost port 20208 (#0)
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 20208 (#0)
> GET /proxy?id=2 HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:20208
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 08 Apr 2020 10:07:33 GMT
< Content-Type: application/octet-stream
< Content-Length: 60
< Connection: keep-alive
< Server: nginx-clojure
< IDENT: 127.0.0.1:40001
< Accept-Ranges: bytes
<
* Connection #0 to host localhost left intact
{"version": "v1", "lastname": "Ivanov", "firstname": "Ivan"}[sowacfg@741037ad695c chains_examples]
Если обратимся к кэшу, то получим:
curl -v -X DELETE http://localhost:20209/cache?type=response
* About to connect() to localhost port 20209 (#0)
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 20209 (#0)
> DELETE /cache?type=response HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:20209
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 08 Apr 2020 10:08:54 GMT
< Content-Type: application/json
< Content-Length: 109
< Connection: keep-alive
< Server: SOWA
<
* Connection #0 to host localhost left intact
{"message_id": "1", "type": "response", "body": {"version": "v1", "lastname": "Ivanov", "firstname": "Ivan"}}
Общие рекомендации:
Обратите внимание, что т.к. кэш in-memory, то все операции чувствительны к памяти, поэтому избегайте хранения больших объектов и операций над ними.
Использование конструкций вида "{"message_id": "$arg_id", "type": "response", "body": $clj_response_body}" не рекомендовано для больших объектов, т.к. в отличии от простого обращения к clj_request_body, clj_response_body, такие сообщения не кэшируются при обработки в цепочках, что также накладывает повышенные требования к ресурсу памяти.