In the previous example we have seen how to use Kubernetes and Docker to deploy a Node.js microservice to interact with Redis, also showing in a basic way how Redis persistence works and how to scale up application’s microservice.
In this example we will test our application using ApacheBench.
This will show the applications behaviour when we have many requests and what happens in case of high concurrency with Redis.
To try this example on your PC you only need to install Docker Desktop and Node.js then follow the described steps.
Create a directory for this example and inside copy the two directories from the previous example, redis-server and webservice.
mkdir node-redis-example-4
cd node-redis-example-4
cp -r ../node-redis-example-3/redis-server/ .
cp -r ../node-redis-example-3/webservice/ .
Enter the redis-server directory and build our redis-server Docker image.
If you didn’t tried the previous example, read it to know about the Redis persistence directory and how to configure it on your PC.
cd redis-server
docker build -t redis-server:1.0.0 -f Dockerfile .
Apply the redis-server Kubernetes deployment configuration.
kubectl apply -f ./deploy.yml
Apply the redis-server Kubernetes service configuration.
kubectl apply -f ./service.yml
Inside the Node.js webservice directory, edit index.js to add a log both when we send a SET key pair request and when we send a GET key pair request.
// Import packages.
const express = require('express')
const redis = require('redis')
const { promisify } = require('util')
// Create and configure a webserver.
const app = express()
app.use(express.json())
// Create and configure a Redis client.
const redisClient = redis.createClient('6379', process.env.REDIS_SERVER_IP)
redisClient.on('error', error => console.error(error))
const redisSet = promisify(redisClient.set).bind(redisClient)
const redisGet = promisify(redisClient.get).bind(redisClient)
// Create an endpoint to set a key value pair.
app.post('/setValue', async (req, res) => {
if (req.body.key && req.body.value) {
try {
await redisSet(req.body.key, req.body.value)
console.log(`SET key=${req.body.key} value=${req.body.value}`)
res.send()
} catch (e) {
res.json(e)
}
} else {
res.status(400).json({ error: 'Wrong input.' })
}
})
// Create an endpoint to get a key value pair.
app.get('/getValue/:key', async (req, res) => {
if (!req.params.key) {
return res.status(400).json({ error: 'Wrong input.' })
}
try {
const value = await redisGet(req.params.key)
console.log(`GET key=${req.params.key} value=${value}`)
res.json(value)
} catch (e) {
res.json(e)
}
})
// Start the webserver.
app.listen(3000, () => {
console.log('Server is up on port 3000')
})
Enter the webservice directory and build the microservice image.
cd ../webservice
docker build -t webservice:1.0.0 -f Dockerfile .
Apply the webservice Kubernetes deployment configuration.
kubectl apply -f ./deploy.yml
Apply the webservice Kubernetes service configuration.
kubectl apply -f ./service.yml
Create a directory for our ApacheBench test inside our project folder.
mkdir ../ab-test
cd ../ab-test
If you don’t have ApacheBench on your PC you will need to install it, for example by running the following command if you are using Ubuntu 18.04:
sudo apt-get install apache2-utils
Create inside an ab-test.sh file inside the ab-test folder.
This will use ApacheBench to execute 1000 requests, 100 concurrent requests at a time, to set on Redis the key name with ‘Roberto’ as value and in the same time other 1000 requests, 100 concurrent at a time to set on Redis the key name with ‘Bandini’.
The POST requests body it will then be contained in two json files, test1.json and test2.json and the test results will be written to test1-result.txt and test2-restult.txt files.
#!/bin/sh
ab -n 1000 -c 100 -T application/json -p test1.json http://localhost:30000/setValue > test1-result.txt &
ab -n 1000 -c 100 -T application/json -p test2.json http://localhost:30000/setValue > test2-result.txt &
Create the test1.json file inside the ab-test directory.
{
"key": "name",
"value": "Roberto"
}
Create the test2.json file inside the ab-test directory.
{
"key": "name",
"value": "Bandini"
}
List the container’s running.
docker ps -a
Look for the two microservice’s container ids, for example:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c56eea6576ab 505727042d09 "docker-entrypoint.s…" 25 seconds ago Up 25 seconds k8s_webservice_webservice-6dcb45b866-nxk7x_default_ee7a8dbf-70f5-42e5-85a3-dd05dd74a575_0
825ae02e5a54 505727042d09 "docker-entrypoint.s…" 25 seconds ago Up 25 seconds k8s_webservice_webservice-6dcb45b866-jsk8s_default_5177d653-9170-4043-bb89-e845a827e982_0
745b20252558 fd4f89ede5e6 "sh -c /run.sh" About a minute ago Up About a minute k8s_redis-server_redis-server-7c58c68b7b-rr6wj_default_2a5c4430-9e09-44ee-97f4-843caa4868d6_0
Open a second shell window to watch logs of the first container.
docker logs -f c56eea6576ab
Open a third shell window to watch logs of the second container.
docker logs -f 825ae02e5a54
Run the ab-test.sh script on the first shel windows, from the ab-test directory.
./ab-test.sh
In the first shell you will see that ApacheBench will perform the requests, 100 a time for both the value, at the same time.
Completed 100 requests
Completed 100 requests
Completed 200 requests
Completed 200 requests
Completed 300 requests
Completed 300 requests
Completed 400 requests
Completed 400 requests
Completed 500 requests
Completed 500 requests
Completed 600 requests
Completed 600 requests
Completed 700 requests
Completed 700 requests
Completed 800 requests
Completed 800 requests
Completed 900 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests
Completed 1000 requests
Finished 1000 requests
In the second and in the third shell you will see that the requests are received from the webservice Kubernetes service and distributed to the two Node.js microservice containers.
But if you run the script several times you will see that you can’t know before which will be the final value of the key on Redis.
You can also check with Postman as in the previous examples.
You can see also the results of ApacheBench tests inside test1-result.txt and test2-result.txt inside the ab-test directory.
Here an example of the test1-result.txt.
This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Server Software:
Server Hostname: localhost
Server Port: 30000
Document Path: /setValue
Document Length: 0 bytes
Concurrency Level: 100
Time taken for tests: 0.751 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 98000 bytes
Total body sent: 192000
HTML transferred: 0 bytes
Requests per second: 1332.07 [#/sec] (mean)
Time per request: 75.071 [ms] (mean)
Time per request: 0.751 [ms] (mean, across all concurrent requests)
Transfer rate: 127.48 [Kbytes/sec] received
249.76 kb/s sent
377.25 kb/s total
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.9 1 4
Processing: 31 72 18.8 70 123
Waiting: 21 58 16.9 57 108
Total: 32 73 18.8 70 125
Percentage of the requests served within a certain time (ms)
50% 70
66% 79
75% 84
80% 88
90% 102
95% 107
98% 110
99% 120
100% 125 (longest request)
Redis is, mostly, a single-threaded server, “How fast is Redis?”, and we have a limit to the number of clients handled simultaneously, “Redis Clients Handling”, so we can’t know the order in which clients are served in this case.
So what if we want to give an order to these requests and what if we want to lock a key to prevent another client from writing it in the same time? We will talk about these arguments in the nexts posts.
You can find the source code on this GitHub repository:
https://github.com/robertobandini/node-redis-example-4
It also includes the Postman collection used and a sw-version.txt file to specify the softwares used for this project and their versions.