Building a Simple Memcached Client with Consistent Hashing, TCP, and Connection Pool in Node.js
This tutorial walks through creating a lightweight Memcached client in Node.js, covering cluster routing with consistent hashing, low‑level TCP communication, a reusable connection‑pool implementation, and a custom protocol for get operations, complete with practical code examples.
Memcached does not provide built‑in clustering, so the article demonstrates how to implement client‑side routing using consistent hashing to map keys to specific Memcached nodes.
It introduces the hashring library and shows basic usage:
const HashRing = require('hashring');
const ring = new HashRing(['127.0.0.1:11211', '127.0.0.2:11211']);
const host = ring.get(key);The guide then explains TCP programming in Node.js, presenting a simple socket setup with event handlers for connection, errors, data reception, and sending data.
const net = require('net');
const socket = new net.Socket();
socket.connect({ host, port });
socket.setKeepAlive(true);
socket.on('connect', () => console.log('socket connected'));
socket.on('error', error => console.log(`socket error: ${error}`));
socket.on('data', data => { /* handle data */ });
socket.write(data);Because establishing TCP connections is resource‑intensive, a generic connection‑pool is introduced using the generic-pool library. The pool creates, validates, and destroys sockets, allowing reuse of connections.
const genericPool = require('generic-pool');
function _buildPool(remote_server) {
const factory = {
create: () => new Promise((resolve, reject) => {
const [host, port] = remote_server.split(':');
const socket = new net.Socket();
socket.connect({ host, port });
socket.setKeepAlive(true);
socket.on('connect', () => resolve(socket));
socket.on('error', err => reject(err));
}),
destroy: socket => new Promise(resolve => { socket.destroy(); resolve(); }),
validate: socket => new Promise(resolve => {
const ok = !(socket.connecting || socket.destroyed || !socket.readable || !socket.writable);
resolve(ok);
})
};
return genericPool.createPool(factory, { max: 10, min: 0, testOnBorrow: true });
}
const pool = _buildPool('127.0.0.1:11211');Next, the article describes the custom Memcached text protocol, showing the request format ( get <key>\r\n ) and the expected response format.
VALUE
\r\n
\r\nA request helper function is provided that acquires a socket from the pool, writes the command, collects response buffers until the terminating \r\n , releases the socket, and resolves with the full response string.
function _request(command) {
return new Promise(async (resolve, reject) => {
const s = await pool.acquire();
const bufs = [];
s.on('data', async buf => {
bufs.push(buf);
if (Buffer.from('\r\n').equals(buf.slice(-2))) {
s.removeAllListeners('data');
await pool.release(s);
resolve(Buffer.concat(bufs).toString());
}
});
s.write(command);
});
}The get method builds the command string, calls _request , parses the response, handles the "END" case, and returns the retrieved value.
async function get(key) {
const command = `get ${key}\r\n`;
const data = await _request(command);
if (data === 'END\r\n') return undefined;
const parts = data.split('\r\n');
const [_, flags, bytes] = parts[0].split(' ');
const value = unescapeValue(parts.slice(1, -2).join(''));
return value;
}All pieces are combined into a Memcached class that stores server locations, creates a hash ring, maintains a pool per server, and exposes asynchronous methods such as get . An example usage creates an instance, configures the pool, and retrieves a key.
class Memcached {
constructor(serverLocations, options) {
this._configs = { pool: { max: 1, min: 0, idle: 30000 }, timeout: 5000, retries: 5, maxWaitingClients: 10000, ...options };
this._hashring = new HashRing(serverLocations);
this._pools = {};
}
// ..._buildPool, _request, get, etc.
}
const memcached = new Memcached(['127.0.0.1:11211'], { pool: { max: 10 } });
const result = await memcached.get('testkey');For the full source code, see the npm package io-memcached .
System Architect Go
Programming, architecture, application development, message queues, middleware, databases, containerization, big data, image processing, machine learning, AI, personal growth.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.