An ability to change service behavior at runtime without restarting the service is priceless! We have that ability, it is called dynamic configs and it allows you:
to dynamically switch from one HTTP proxy to another or turn off proxying (USERVER_HTTP_PROXY)
to write your own runtime dynamic configs:
to create experiments that could be adjusted or turned on/off without service restarts
to do whatever you like
In previous example we made a simple HTTP server with some dynamic configs set in stone. To make the dynamic configs dynamic for real the following steps should be done:
the above components should be configured to retrieve dynamic configs from the configuration service:
dynamic-config-client:# A client that knows how to request configs via HTTPconfig-url:http://localhost:8083/# URL of dynamic config servicehttp-retries:5http-timeout:20sservice-name:configs-servicefallback-to-no-proxy:false# On error do not attempt to retrieve configs # by bypassing proxy from USERVER_HTTP_PROXY dynamic configdynamic-config-client-updater:# A component that periodically uses `dynamic-config-client` to retrieve new valuesfallback-path:/var/cache/service-name/dynamic_cfg.json# Fallback to the values from this file on errorload-only-my-values:true# Do not request all the configs, only the ask for the ones we are using right store-enabled:true# Store the retrieved values into the components::DynamicConfigupdate-interval:5s# Request for new configs every 5 secondsfull-update-interval:1mconfig-settings:false
Now let's create a configuration service.
HTTP handler component
Dynamic configs are requested via JSON requests, so we need to create a simple JSON handler that is responding with config values.
Handle* functions are invoked concurrently on the same instance of the handler class. Use synchronization primitives or do not modify shared data in Handle*.
Note the rcu::Variable. There may be (and will be!) multiple concurrent requests and the HandleRequestJsonThrow would be invoked concurrently on the same instance of ConfigDistributor. The rcu::Variable allows us to atomically update the config value, concurrently with the HandleRequestJsonThrow invocations.
Function ConfigDistributor::SetNewValues is meant for setting config values to send. For example, you can write another component that accepts JSON requests with new config values and puts those values into ConfigDistributor via SetNewValues(new_values).
HandleRequestJsonThrow
All the interesting things happen in the HandleRequestJsonThrow function, where we grab a rcu::Variable snapshot, fill the update time and the configuration from it:
LOG_ERROR() << "Failed to find config with name '" << key << "'";
}
}
return configs;
}
Note that the service name is sent in the "service" field of the JSON request body. Using it you can make service specific dynamic configs.
Static config
Now we have to configure our new HTTP handle. The configuration is quite straightforward:
handler-config:path:/configs/valuesmethod:POST# Only for HTTP POST requests. Other handlers may reuse the same URL but use different method.task_processor:main-task-processor
To build the sample, execute the following build steps at the userver root directory:
mkdir build_release
cd build_release
cmake -DCMAKE_BUILD_TYPE=Release ..
make userver-samples-config_service
The sample could be started by running make start-userver-samples-config_service. The command would invoke testsuite start target that sets proper paths in the configuration files and starts the service.
To start the service manually run ./samples/config_service/userver-samples-config_service -c </path/to/static_config.yaml> (do not forget to prepare the configuration files!).
Now you can send a request to your server from another terminal:
Note that there is a ready to use opensource uservice-dynconf dynamic configs service. Use it for your projects or just disable dynamic config updates and keep developing without a supplementary service.