├── .gitignore ├── LICENSE ├── README.md ├── hapi-api-gateway ├── api-gateway.js ├── api-proxy.js ├── friends-plugin.js ├── friends-using-plugin.js ├── friends-with-plugin-and-prefix.js ├── friends.js ├── legacy.js ├── package.json └── start-all.bash ├── redis-cache ├── ad-cache-generator.js ├── ad-client.js ├── find-next-ad.js ├── package.json └── start-redis.bash └── redis-queue ├── bench-get.bash ├── bench-lpush.bash ├── mail-client.js ├── mail-reliable-service.js ├── mail-service.js ├── package.json ├── redis-show-mail-in-transit.bash ├── redis-show-mail-queue.bash └── start-redis.bash /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | .DS_Store 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jeff Barczewski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microservices Examples 2 | 3 | Examples of building simple microservices using Node.js, Redis, and Hapi 4 | 5 | ## Talks referencing these examples 6 | 7 | - [Building simple Node.js microservices using Hapi and Redis - Nodevember 2015](http://codewinds.com/nc2015) 8 | 9 | ## Installation 10 | 11 | Enter the directory of the example and `npm install` 12 | 13 | ## redis-queue 14 | 15 | An example of using a redis list as a queue for a microservice mail server. Microservice mail delivery processes can be spun up to listen to the queue and wait for work. 16 | 17 | Another version done using a reliable queue where an additional list is used to track messages in transit. If a processes fails to deliver with in a specified amount of time, an item could be moved back to the original queue for delivery. 18 | 19 | ## redis-cache 20 | 21 | An example of using microservice(s) to prepopulate a redis list cache. An expensive computation like determining the next ads to display across campaigns could be done in advance by microservice workers and the results put into a cache queue for use. The microservice(s) can monitor the queue height and generate more when the count drops to a certain level. 22 | 23 | ## hapi-api-gateway 24 | 25 | Examples using hapi to build simple microservices and using hapi to build simple API proxies or smart API gateways. An API gateway can aggregate data from to many microservices into one call. 26 | 27 | Hapi plugins are a nice mechanism for moving microservice funtionality between servers. By splitting off groups of functionality into plugins they can be used in a monolith application or easily moved into their own containers and servers. 28 | -------------------------------------------------------------------------------- /hapi-api-gateway/api-gateway.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | const Hapi = require('hapi'); 5 | const H2O2 = require('h2o2'); 6 | const once = require('once'); 7 | const Wreck = require('wreck'); 8 | 9 | const proxyMap = { 10 | '/friends': 'http://localhost:6000/friends', 11 | '/recent': 'http://localhost:5000/recent', 12 | '/prefs': 'http://localhost:5000/prefs' 13 | }; 14 | 15 | var server = new Hapi.Server(); 16 | 17 | server.register({ register: H2O2 }, err => { 18 | if (err) { return console.error(err); } 19 | 20 | server.connection({ host: 'localhost', port: 7000 }); 21 | 22 | const proxyHandler = { 23 | proxy: { 24 | mapUri: (req, cb) => { 25 | const uri = proxyMap[req.path]; 26 | if (!uri) { return cb(Boom.notFound()); } 27 | cb(null, uri, req.headers); 28 | }, 29 | onResponse: (err, res, req, reply, settings, ttl) => { 30 | if (err) { return reply(err); } 31 | reply(res); 32 | } 33 | } 34 | }; 35 | 36 | server.route([ 37 | { method: 'GET', path: '/friends', handler: proxyHandler }, 38 | { method: 'GET', path: '/recent', handler: proxyHandler }, 39 | { method: 'GET', path: '/prefs', handler: proxyHandler } 40 | ]); 41 | 42 | server.route({ 43 | method: 'GET', path: '/main', handler: (req, reply) => { 44 | reply = once(reply); // only reply once 45 | 46 | Wreck.get('http://localhost:6000/friends', { json: true }, 47 | (err, res, friends) => next(err, { friends })); 48 | 49 | Wreck.get('http://localhost:5000/recent', { json: true }, 50 | (err, res, recent) => next(err, { recent })); 51 | 52 | Wreck.get('http://localhost:5000/prefs', { json: true }, 53 | (err, res, prefs) => next(err, { prefs })); 54 | 55 | let results = {}; 56 | function next(err, result) { 57 | if (err) { return reply(err); } 58 | results = Object.assign({}, results, result); // merge 59 | if (results.friends && results.recent && results.prefs) { 60 | return reply(results); 61 | } 62 | } 63 | } 64 | }); 65 | 66 | server.start(() => { 67 | console.log(`running at ${server.info.uri}`); 68 | }); 69 | 70 | }); 71 | 72 | 73 | function shutdown() { 74 | server.stop(() => console.log('shutdown successful')); 75 | } 76 | 77 | process 78 | .once('SIGINT', shutdown) 79 | .once('SIGTERM', shutdown); 80 | -------------------------------------------------------------------------------- /hapi-api-gateway/api-proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | const Hapi = require('hapi'); 5 | const H2O2 = require('h2o2'); 6 | 7 | const proxyMap = { 8 | '/friends': 'http://localhost:5000/friends', 9 | '/recent': 'http://localhost:5000/recent', 10 | '/prefs': 'http://localhost:5000/prefs' 11 | }; 12 | 13 | var server = new Hapi.Server(); 14 | 15 | server.register([ 16 | { register: H2O2 } 17 | ], err => { 18 | if (err) { return console.error(err); } 19 | 20 | server.connection({ host: 'localhost', port: 7000 }); 21 | 22 | const proxyHandler = { 23 | proxy: { 24 | mapUri: (req, cb) => { 25 | const uri = proxyMap[req.path]; 26 | if (!uri) { return cb(Boom.notFound()); } 27 | cb(null, uri, req.headers); 28 | }, 29 | onResponse: (err, res, req, reply, settings, ttl) => { 30 | if (err) { return reply(err); } 31 | reply(res); 32 | } 33 | } 34 | }; 35 | 36 | server.route([ 37 | { method: 'GET', path: '/friends', handler: proxyHandler }, 38 | { method: 'GET', path: '/recent', handler: proxyHandler }, 39 | { method: 'GET', path: '/prefs', handler: proxyHandler } 40 | ]); 41 | 42 | server.start(() => { 43 | console.log(`running at ${server.info.uri}`); 44 | }); 45 | 46 | }); 47 | 48 | 49 | function shutdown() { 50 | server.stop(() => console.log('shutdown successful')); 51 | } 52 | 53 | process 54 | .once('SIGINT', shutdown) 55 | .once('SIGTERM', shutdown); 56 | -------------------------------------------------------------------------------- /hapi-api-gateway/friends-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function register(server, options, next) { 4 | server.route({ 5 | method: 'GET', 6 | path: '/friends', 7 | handler: function (request, reply) { 8 | reply([ 9 | 'Alice', 10 | 'Bob', 11 | 'Carol' 12 | ]).header('x-version', '2.0'); 13 | } 14 | }); 15 | 16 | next(); 17 | } 18 | 19 | register.attributes = { 20 | name: 'friends', 21 | version: '1.0.0' 22 | }; 23 | 24 | // or using package.json 25 | // register.attributes = { 26 | // pkg: require('./package.json') 27 | // }; 28 | 29 | module.exports = { 30 | register 31 | }; 32 | -------------------------------------------------------------------------------- /hapi-api-gateway/friends-using-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const friends = require('./friends-plugin'); 4 | const Hapi = require('hapi'); 5 | 6 | var server = new Hapi.Server(); 7 | server.connection({ host: 'localhost', port: 6000 }); 8 | 9 | server.register({ register: friends }, err => { 10 | if (err) { return console.error(err); } 11 | 12 | server.start(() => { 13 | console.log(`running at ${server.info.uri}`); 14 | }); 15 | }); 16 | 17 | function shutdown() { 18 | server.stop(() => console.log('shutdown successful')); 19 | } 20 | 21 | process 22 | .once('SIGINT', shutdown) 23 | .once('SIGTERM', shutdown); 24 | -------------------------------------------------------------------------------- /hapi-api-gateway/friends-with-plugin-and-prefix.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const friends = require('./friends-plugin'); 4 | const Hapi = require('hapi'); 5 | 6 | var server = new Hapi.Server(); 7 | server.connection({ host: 'localhost', port: 6000 }); 8 | 9 | server.route({ 10 | method: 'GET', 11 | path: '/friends', 12 | handler: function (request, reply) { 13 | reply([ 14 | 'Alice', 15 | 'Bob', 16 | 'Carol' 17 | ]).header('x-version', '3.0'); 18 | } 19 | }); 20 | 21 | server.register({ register: friends }, 22 | { routes: { prefix: '/foo' }}, 23 | err => { 24 | if (err) { return console.error(err); } 25 | 26 | server.start(() => { 27 | console.log(`running at ${server.info.uri}`); 28 | }); 29 | }); 30 | 31 | function shutdown() { 32 | server.stop(() => console.log('shutdown successful')); 33 | } 34 | 35 | process 36 | .once('SIGINT', shutdown) 37 | .once('SIGTERM', shutdown); 38 | -------------------------------------------------------------------------------- /hapi-api-gateway/friends.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('hapi'); 4 | 5 | var server = new Hapi.Server(); 6 | server.connection({ host: 'localhost', port: 6000 }); 7 | 8 | server.route({ 9 | method: 'GET', 10 | path: '/friends', 11 | handler: function (request, reply) { 12 | reply([ 13 | 'Alice', 14 | 'Bob', 15 | 'Carol' 16 | ]).header('x-version', '2.0'); 17 | } 18 | }); 19 | 20 | server.start(() => { 21 | console.log(`running at ${server.info.uri}`); 22 | }); 23 | 24 | function shutdown() { 25 | server.stop(() => console.log('shutdown successful')); 26 | } 27 | 28 | process 29 | .once('SIGINT', shutdown) 30 | .once('SIGTERM', shutdown); 31 | -------------------------------------------------------------------------------- /hapi-api-gateway/legacy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('hapi'); 4 | 5 | var server = new Hapi.Server(); 6 | server.connection({ host: 'localhost', port: 5000 }); 7 | 8 | server.route({ 9 | method: 'GET', 10 | path: '/friends', 11 | handler: function (request, reply) { 12 | reply([ 13 | 'alice', 14 | 'bob', 15 | 'carol' 16 | ]); 17 | } 18 | }); 19 | 20 | server.route({ 21 | method: 'GET', 22 | path: '/recent', 23 | handler: function (request, reply) { 24 | reply([ 25 | 'update-a', 26 | 'update-b', 27 | 'update-c' 28 | ]); 29 | } 30 | }); 31 | 32 | server.route({ 33 | method: 'GET', 34 | path: '/prefs', 35 | handler: function (request, reply) { 36 | reply({ 37 | pref1: true, 38 | pref2: 'blue', 39 | pref3: 100 40 | }); 41 | } 42 | }); 43 | 44 | server.start(() => { 45 | console.log(`running at ${server.info.uri}`); 46 | }); 47 | 48 | function shutdown() { 49 | server.stop(() => console.log('shutdown successful')); 50 | } 51 | 52 | process 53 | .once('SIGINT', shutdown) 54 | .once('SIGTERM', shutdown); 55 | -------------------------------------------------------------------------------- /hapi-api-gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-api-gateway", 3 | "version": "1.0.0", 4 | "description": "Examples using hapi to build simple microservices and using hapi to build simple API proxies or smart API gateways. An API gateway can aggregate data from to many microservices into one call.", 5 | "author": "Jeff Barczewski", 6 | "license": "MIT", 7 | "dependencies": { 8 | "boom": "^3.0.0", 9 | "h2o2": "^4.0.2", 10 | "hapi": "^11.1.0", 11 | "once": "^1.3.2", 12 | "wreck": "^7.0.0" 13 | }, 14 | "devDependencies": { 15 | "parallelshell": "^2.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /hapi-api-gateway/start-all.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -v # echo commands 3 | node_modules/.bin/parallelshell "node legacy.js" "node friends.js" "node api-gateway.js" 4 | -------------------------------------------------------------------------------- /redis-cache/ad-cache-generator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const findNextAd = require('./find-next-ad'); 4 | const Redis = require('ioredis'); 5 | 6 | const adCacheKey = 'adCache'; 7 | const lowThreshold = 100; 8 | const highThreshold = 400; 9 | const checkLevelMSecs = 1000; 10 | const redis = new Redis(); 11 | let shuttingDown = false; 12 | let generating = false; 13 | 14 | function checkLevel() { 15 | if (shuttingDown) { return; } 16 | redis.llen(adCacheKey, (err, len) => { 17 | if (err) { return console.error(err); } 18 | if (generating) { 19 | if (len > highThreshold) { stopGenerator(); } 20 | } else { // not generating 21 | if (len < lowThreshold) { startGenerator(); } 22 | } 23 | console.log(`level: ${len} ${(generating) ? 'generating' : ''}`); 24 | }); 25 | } 26 | 27 | function startGenerator() { 28 | if (generating) { return; } 29 | generating = true; 30 | generate(); 31 | } 32 | 33 | function stopGenerator() { 34 | generating = false; 35 | } 36 | 37 | function generate() { 38 | if (!generating) { return; } 39 | findNextAd((err, ad) => { 40 | if (shuttingDown) { return; } 41 | if (err) { return console.error(err); } 42 | redis.lpush(adCacheKey, ad, (err, result) => { 43 | if (err) { console.error(err); } 44 | setImmediate(generate); 45 | }); 46 | }); 47 | } 48 | 49 | const checkInterval = setInterval(checkLevel, checkLevelMSecs); 50 | 51 | function shutdown() { 52 | shuttingDown = true; 53 | generating = false; 54 | clearInterval(checkInterval); 55 | redis.quit(); 56 | } 57 | 58 | process 59 | .once('SIGINT', shutdown) 60 | .once('SIGTERM', shutdown); 61 | -------------------------------------------------------------------------------- /redis-cache/ad-client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const findNextAd = require('./find-next-ad'); 4 | const Redis = require('ioredis'); 5 | 6 | const adCacheKey = 'adCache'; 7 | const delayBetweenAdUseMSecs = 20; 8 | 9 | const redis = new Redis(); 10 | let shuttingDown = false; 11 | 12 | function useAd() { 13 | if (shuttingDown) { return; } 14 | redis.rpop(adCacheKey, (err, ad) => { 15 | if (err) { return console.error(err); } 16 | if (ad) { 17 | console.log(ad); 18 | setTimeout(useAd, delayBetweenAdUseMSecs); 19 | return; 20 | } 21 | findNextAd((err, ad) => { 22 | if (err) { return console.error(err); } 23 | console.log(`*********** cache-miss ${ad}`); 24 | setTimeout(useAd, delayBetweenAdUseMSecs); 25 | }); 26 | }); 27 | } 28 | 29 | useAd(); 30 | 31 | function shutdown() { 32 | shuttingDown = true; 33 | redis.quit(); 34 | } 35 | 36 | process 37 | .once('SIGINT', shutdown) 38 | .once('SIGTERM', shutdown); 39 | -------------------------------------------------------------------------------- /redis-cache/find-next-ad.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const faker = require('faker'); 4 | 5 | const ADS = ['CodeWinds', 'Nodevember', 'NashJS']; 6 | 7 | function findNextAd(cb) { 8 | setTimeout(function () { 9 | // secret ad balancing method 10 | const ad = faker.random.arrayElement(ADS); 11 | cb(null, ad); 12 | }, 10); 13 | } 14 | 15 | module.exports = findNextAd; 16 | -------------------------------------------------------------------------------- /redis-cache/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-cache", 3 | "version": "1.0.0", 4 | "description": "An example of using microservices to prepopulate a redis list cache", 5 | "author": "Jeff Barczewski", 6 | "license": "MIT", 7 | "dependencies": { 8 | "faker": "^3.0.1", 9 | "ioredis": "^1.10.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /redis-cache/start-redis.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -v # echo commands 3 | redis-server --bind 127.0.0.1 --appendonly yes --appendfsync always 4 | -------------------------------------------------------------------------------- /redis-queue/bench-get.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -v # echo commands 3 | redis-benchmark -t GET -d 256 -n 10000 -P 10 4 | -------------------------------------------------------------------------------- /redis-queue/bench-lpush.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -v # echo commands 3 | redis-benchmark -t LPUSH -d 256 -n 10000 -P 10 4 | -------------------------------------------------------------------------------- /redis-queue/mail-client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Redis = require('ioredis'); 4 | const times = require('lodash.times'); 5 | 6 | const mailQueueKey = 'mailQueue'; 7 | 8 | if (process.argv.length < 3) { 9 | console.error('usage: node mail-client.js N_MSGS_TO_QUEUE'); 10 | process.exit(1); 11 | } 12 | 13 | const redis = new Redis(); 14 | const rPipeline = redis.pipeline(); 15 | const nMsgsToQueue = process.argv[2]; 16 | 17 | function queueMsg(i) { 18 | const msg = { 19 | body: `my msg ${i}` 20 | }; 21 | rPipeline.lpush(mailQueueKey, JSON.stringify(msg)); 22 | } 23 | 24 | times(nMsgsToQueue, queueMsg); 25 | rPipeline.exec((err, results) => { 26 | if (err) { return console.error(err); } 27 | console.log(`${nMsgsToQueue} messages queued`); 28 | }); 29 | 30 | redis.quit(); 31 | -------------------------------------------------------------------------------- /redis-queue/mail-reliable-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Redis = require('ioredis'); 4 | 5 | const redis = new Redis(); 6 | const redis2 = redis.duplicate(); 7 | const mailQueueKey = 'mailQueue'; 8 | const mailInTransitKey = 'mailInTransit'; 9 | const forever = 0; 10 | let shuttingDown = false; 11 | let msgDeliveryInProgress = false; 12 | 13 | function waitForMail() { 14 | redis.brpoplpush(mailQueueKey, mailInTransitKey, forever, (err, result) => { 15 | if (err) { 16 | if (shuttingDown) return; 17 | console.error(err); 18 | return process.nextTick(waitForMail); 19 | } 20 | const val = result; 21 | sendMail(val, (err) => { 22 | if (err) { console.error(err); } 23 | process.nextTick(waitForMail); 24 | }); 25 | }); 26 | } 27 | 28 | function sendMail(val, cb) { 29 | // simulate mail delivery 30 | msgDeliveryInProgress = true; 31 | setTimeout(() => { 32 | console.log(`mail sent: ${val}`); 33 | redis2.lrem(mailInTransitKey, -1, val, (err, result) => { 34 | cb(err); 35 | if (shuttingDown) redis2.quit(); 36 | msgDeliveryInProgress = false; 37 | }); 38 | }, 10); 39 | } 40 | 41 | function shutdown() { 42 | shuttingDown = true; 43 | // use disconnect rather than quit since brpop blocks 44 | redis.disconnect(); 45 | if (!msgDeliveryInProgress) redis2.quit(); 46 | } 47 | 48 | process 49 | .once('SIGINT', shutdown) 50 | .once('SIGTERM', shutdown); 51 | 52 | waitForMail(); 53 | -------------------------------------------------------------------------------- /redis-queue/mail-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Redis = require('ioredis'); 4 | 5 | const redis = new Redis(); 6 | const mailQueueKey = 'mailQueue'; 7 | const forever = 0; 8 | let shuttingDown = false; 9 | 10 | function waitForMail() { 11 | redis.brpop(mailQueueKey, forever, (err, result) => { 12 | if (err) { 13 | if (shuttingDown) return; 14 | console.error(err); 15 | return process.nextTick(waitForMail); 16 | } 17 | const val = result[1]; 18 | console.log(val); // deliver email here 19 | process.nextTick(waitForMail); 20 | }); 21 | } 22 | 23 | function shutdown() { 24 | shuttingDown = true; 25 | // use disconnect rather than quit since brpop blocks 26 | redis.disconnect(); 27 | } 28 | 29 | process 30 | .once('SIGINT', shutdown) 31 | .once('SIGTERM', shutdown); 32 | 33 | waitForMail(); 34 | -------------------------------------------------------------------------------- /redis-queue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-queue", 3 | "version": "1.0.0", 4 | "description": "An example of using a redis list as a queue for a microservice mail server", 5 | "author": "Jeff Barczewski", 6 | "license": "MIT", 7 | "dependencies": { 8 | "ioredis": "^1.10.0", 9 | "lodash.times": "^3.0.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /redis-queue/redis-show-mail-in-transit.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -v # echo commands 3 | redis-cli lrange mailInTransit 0 -1 4 | -------------------------------------------------------------------------------- /redis-queue/redis-show-mail-queue.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -v # echo commands 3 | redis-cli lrange mailQueue 0 -1 4 | -------------------------------------------------------------------------------- /redis-queue/start-redis.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -v # echo commands 3 | redis-server --bind 127.0.0.1 --appendonly yes --appendfsync always 4 | --------------------------------------------------------------------------------