Building a Scalable Short‑URL Service with Redis and Java
Short URLs are popular in SMS and social media because they save characters, look tidy, enable analytics, and hide parameters; this article explains their benefits, the basic redirect workflow, and provides a detailed backend design—including storage choices, high‑concurrency strategies, distributed ID generation, and a Java‑Redis implementation.
Why Short URLs?
Short URLs are used in SMS and other character‑limited platforms because they reduce length, look cleaner, enable traffic statistics and hide query parameters, improving user experience and security.
Basic Principle
The workflow consists of four steps: (1) a service maps a long URL to a short token, (2) the short URL is embedded in the message, (3) the user clicks the short URL, the browser follows a 301/302 redirect to the original long URL, and (4) the target content is displayed.
Service Design
Since a deterministic compression algorithm that guarantees a one‑to‑one mapping is impossible, the common approach is to use an incremental identifier generator (a “ticket‑issuer”). Each new long URL receives the next numeric ID, which is then encoded into a short string (e.g., www.x.cn/0, www.x.cn/1).
Mapping Storage
The ID‑to‑URL mapping must be persisted. For modest traffic a relational database such as MySQL can store the mapping with an auto‑increment primary key. The numeric ID can be stored either as a string (for exact‑match lookups) or as a decimal number (to save space and enable range queries).
One‑to‑One Mapping
Using only an auto‑increment key does not guarantee that the same long URL always receives the same short token; repeated requests generate different IDs. To achieve true one‑to‑one mapping you need an additional lookup table (e.g., a key‑value store) that maps the long URL to its existing ID, at the cost of extra memory.
Short URL Representation
Numeric IDs are usually converted to a higher base (commonly base‑32 or base‑64) to shorten the string. The conversion uses a custom digit set that includes 0‑9, A‑Z, a‑z, giving up to 62 characters per digit.
High Concurrency
Directly incrementing a MySQL counter for every request becomes a bottleneck under high QPS. Two common mitigations are caching and batch allocation.
Cache
Popular or recently accessed long URLs can be cached in an in‑memory store such as Redis. When a request hits the cache, the short URL is returned without a database round‑trip.
Batch Allocation
Instead of fetching a single ID each time, the service can request a block of IDs (e.g., 10 000) from the database, serve them from memory, and replenish the block when the remaining count falls below a threshold. Unused IDs are written back asynchronously, reducing constant DB load.
Distributed Architecture
The single ticket‑issuer is a single point of failure. A distributed solution can partition the ID space among multiple generators. For example, 1 000 instances could each handle a distinct suffix range (0‑999) and increment by 1 000, eliminating inter‑service coordination.
Implementation Example (Java + Redis)
The following Java utility demonstrates a practical implementation that uses Redis (via Jedis) for ID generation, mapping storage, and caching. It includes base‑conversion logic, cache lookup, and batch‑friendly ID handling.
package util;
import redis.clients.jedis.Jedis;
public class ShortURLUtil {
private static final String SHORT_URL_KEY = "SHORT_URL_KEY";
private static final String LOCALHOST = "http://localhost:4444/";
private static final String SHORT_LONG_PREFIX = "short_long_prefix_";
private static final String CACHE_KEY_PREFIX = "cache_key_prefix_";
private static final int CACHE_SECONDS = 1 * 60 * 60;
private final Jedis jedis;
public ShortURLUtil(String redisConfig) {
this.jedis = new Jedis(redisConfig);
}
/** Return a short URL for the given long URL using the specified base. */
public String getShortURL(String longURL, Decimal decimal) {
// 1. Check cache
String cache = jedis.get(CACHE_KEY_PREFIX + longURL);
if (cache != null) {
return LOCALHOST + toOtherBaseString(Long.valueOf(cache), decimal.base);
}
// 2. Generate next ID
long id = jedis.incr(SHORT_URL_KEY);
// 3. Persist mapping (could be MySQL; here we store in Redis for demo)
jedis.set(SHORT_LONG_PREFIX + id, longURL);
// 4. Write cache entry
jedis.setex(CACHE_KEY_PREFIX + longURL, CACHE_SECONDS, String.valueOf(id));
// 5. Convert ID to chosen base and return short URL
return LOCALHOST + toOtherBaseString(id, decimal.base);
}
/** Characters used for base conversion (0‑9, A‑Z, a‑z). */
private static final char[] DIGITS = {
'0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p',
'q','r','s','t','u','v','w','x','y','z'
};
/** Convert a decimal number to a string in the given base (e.g., 32 or 64). */
private String toOtherBaseString(long n, int base) {
long num = n < 0 ? (2L * 0x7fffffff) + n + 2 : n;
char[] buf = new char[32];
int charPos = 32;
while (num / base > 0) {
buf[--charPos] = DIGITS[(int) (num % base)];
num /= base;
}
buf[--charPos] = DIGITS[(int) (num % base)];
return new String(buf, charPos, 32 - charPos);
}
/** Supported bases. */
enum Decimal {
D32(32), D64(64);
final int base;
Decimal(int base) { this.base = base; }
}
public static void main(String[] args) {
ShortURLUtil util = new ShortURLUtil("localhost");
System.out.println(util.getShortURL("www.baidudu.com", Decimal.D32));
System.out.println(util.getShortURL("www.baidu.com", Decimal.D64));
}
}This example can be extended to use a relational database for persistent storage while keeping Redis for fast cache and ID generation. Batch allocation can be added by reserving a range of IDs with a single INCRBY command.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
