Redx (or redis-nginx) is an embedded lua based approach of having a dynamic configuration of nginx of frontends and backends with redis as its data store. It was inspired by hipache. It has a restful API (that runs within nginx itself) to manage the many-to-one relationships between frontends to backends, set configurations, and more.
One of the main benefits of redx is the ability to update your nginx config without needing to reload nginx. This is useful for environments with a highly dynamic topology where backends and frontends are added/removed several times a second (ie Paas). Also, this allows you to have a single nginx config across multiple nginx servers making it easier to have true high availability and scalability on your load balancing layer.
At rstudio, we are using it in production serving all web traffic for shinyapps.
Redx is composed of two components; the api and main. The api is a restful api embedded in lua and runs within the nginx process. It runs on its own port (for ease of firewall security) and is manages the frontends and backends in the nginx config by editing the redis database.
The other component is main, and this is what takes regular traffic from your users. It looks up the proper backend to proxy the request to based on the host and path.
In the event that there isn't a frontend or backend match for an incoming request OR the backend server (ie a cache miss), the request was proxied to a server that isn't responding, the request is sent to the fallback
. This is typically your application server, which handles these failure scenario. Several headers are added to the request to help your application server understand the context in which the request is coming in (ie cache miss vs port not open). In some cases, you may want to update redx by hitting the API and insert the missing frontend and/or backend and send them back to nginx to try again, or maybe you want to forward them to a custom 404 page. Its up to you to decide what the behavior you want it to be.
At rstudio, we find that redx performs slightly slower than regular redis config files. Of course, this makes sense, as you're now querying a remote redis server to lookup frontends and backends, instead of caching all that in local memory. In our unofficial benchmarks, we see a 10ms increase in response time with using a third party redis hosting service. Of course latency to your redis server is a big factor. Redx though, keeps a pool of connections that are reused instead of making separate calls on each request. That being said, the payoff of having dynamic configuration and being able to easily do high availability with active active nginx load balancers is well worth the 10ms cost in my opinion. Each environment is different and has different requirements, goals, etc. So its up to you to decide what is worth what.
lapis version 1.0.4-1
openresty 1.7.2 or greater
A redis server
Setup and start vagrant
vagrant plugin install vagrant-berkshelf --plugin-version '>= 2.0.1'
vagrant plugin install vagrant-omnibus
vagrant up
The redx code on your local workstation is run within vagrant (due to sharing the redx directory with vagrant at /home/vagrant/redx
). As you make code changes, they should take affect immediately and do not require reloading nginx. You will however need to reload nginx when you change the nginx config located vagrant:https://etc/nginx/sites-available/redx.conf
.
To see redx logs, see /var/log/nginx/[access,error].log
It is recommended that you setup two git hooks.
The pre-commit hook (./git/hooks/pre-commit
) should be used to ensure you don't make changes to the moonscript code without recompiling the lua code.
#!/bin/sh
moonc -t lua/ .
The pre-push hook (./git/hooks/pre-push
) should be used to ensure you've run all tests and they all pass.
#!/bin/sh
busted lua/spec/
Redx uses a testing framework, busted, to run integration tests. To run these tests, execute busted lua/spec
. Continuous integration is setup with travis ci.
The configuration of redx is loaded from lua/conf/config.lua
. This file consists of the following configuration options
An IP address or hostname to a redis server
An port number of the redis server to connect to
If you have a redis password, put it here. If you don't leave it an empty string (ie ''
)
This is the connection timeout for any redis connection, in milliseconds
The pool size of keepalive connections to maintain, per nginx worker.
max idle timeout for keepalive connection, in milliseconds
The max number of parts to the path to look up. This defines the max length of a path to be to looked up to a corresponding frontend in redis.
Say in your service, your path always consists of an account name and service name ( ie https://sasservice.com/jdoe/app1). So the max path length you want to support for your application here is 2. If the user, comes into nginx with more to the path than that (ie https://sasservice.com/jdoe/app1/static/js/base.js), but when request comes in, redx will only search for "sasservice.com/jdoe/app1" first and "sasservice.com/jdoe" second and "sasservice.com" third, for frontends in the database.
For another example, say you only want to route traffic based on the domain (ie 'chad.myserver.com'). Setting the max_path_length to 0 will cause redx to only look for frontends on the domain with no path.
A list of plugins to enable. Plugins are executed in the order they are given. If you wish to pass a parameter to a plugin, make a plugin an array, where the first element is the plugin name and the second is the parameter. Here is an example.
M.plugins = {
'stickiness',
{ 'score', 'most' },
'random'
}
The amount of time (in seconds) you wish the session cookie to keep alive. This is applicable when using cookies as a user specific persistence datastore (ie stickiness).
The default score is the score that is inserted into backends in the case where a score is not provided (ie batch upating). If this config option isn't specified, it defaults to 0 (zero).
The api is documented here
Redx has a plugin architecture to allow others to easily expand its capability to do more (publically or privately).
See the plugins documentation