Design and Implementation of a Short URL Service Using Redis and MySQL
The article explains why short URLs are popular, outlines their basic workflow, and details a backend service design that uses an incremental ID generator, Redis caching, MySQL storage, batch allocation, and distributed strategies to efficiently create and manage short links.
Short URLs are widely used in SMS and many platforms because they save characters, look cleaner, enable statistics, and improve security by hiding parameters.
The basic workflow consists of mapping a long URL to a short one, embedding the short URL in a message, redirecting via HTTP 301/302, and finally displaying the target content.
This article focuses on the first step: generating the short URL.
Service Design
An ideal algorithm would uniquely convert each long URL to a short URL with reversible mapping, but such a universal compression is impossible; therefore a simple ID generator (auto‑increment) is used, assigning sequential numbers and converting them to a higher base (e.g., base‑32) to form short strings.
Storage of Mapping
The long‑short mapping must be persisted to survive restarts; a relational database such as MySQL with an auto‑increment primary key can store the mapping when traffic is modest.
Ensuring One‑to‑One Mapping
The naive ID generator does not guarantee a one‑to‑one relationship because the same long URL can receive different IDs on repeated requests; achieving strict one‑to‑one requires additional space, such as a key‑value store for hot entries or a cache layer.
Short URL Storage
Short URLs are often represented in base‑32; storing the underlying numeric ID (decimal) is more space‑efficient and facilitates range queries, while conversion to other bases can further shorten the string.
High Concurrency
Direct writes to MySQL for each ID can become a bottleneck under high QPS. Solutions include caching popular mappings in Redis, batch ID allocation (fetching a block of IDs, e.g., 10,000, and serving them from memory), and asynchronous write‑back to the database.
Cache
Cache recent or frequently accessed long URLs in memory or Redis to avoid regeneration and reduce database load.
Batch Allocation
Periodically request a large range of IDs from the database, serve them locally, and write back the remaining range when it falls below a threshold, thus minimizing continuous DB interactions.
Distributed Considerations
A single ID generator is a single point of failure. A simple distributed approach partitions the ID space: for example, 1,000 services each handle numbers ending with a specific suffix and increment by 1,000, eliminating the need for inter‑service synchronization.
Implementation
A sample Java implementation demonstrates using Redis for ID generation, caching, and base conversion. The code includes constants, a Redis client, methods for retrieving/creating short URLs, base‑conversion utilities, and a main method that generates sample short URLs.
package util;
import redis.clients.jedis.Jedis;
/**
* Created by pfliu on 2019/06/23.
*/
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 String redisConfig;
private final Jedis jedis;
public ShortURLUtil(String redisConfig) {
this.redisConfig = redisConfig;
this.jedis = new Jedis(this.redisConfig);
}
public String getShortURL(String longURL, Decimal decimal) {
// Query cache
String cache = jedis.get(CACHE_KEY_PREFIX + longURL);
if (cache != null) {
return LOCALHOST + toOtherBaseString(Long.valueOf(cache), decimal.x);
}
// Increment ID
long num = jedis.incr(SHORT_URL_KEY);
// Save short‑long mapping (could be stored in MySQL)
jedis.set(SHORT_LONG_PREFIX + num, longURL);
// Write to cache
jedis.setex(CACHE_KEY_PREFIX + longURL, CACHE_SECONDS, String.valueOf(num));
return LOCALHOST + toOtherBaseString(num, decimal.x);
}
/** Characters for base conversion */
final static 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 another base */
private String toOtherBaseString(long n, int base) {
long num = 0;
if (n < 0) {
num = ((long) 2 * 0x7fffffff) + n + 2;
} else {
num = 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));
}
enum Decimal {
D32(32),
D64(64);
int x;
Decimal(int x) {
this.x = x;
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(new ShortURLUtil("localhost").getShortURL("www.baidudu.com", Decimal.D32));
System.out.println(new ShortURLUtil("localhost").getShortURL("www.baidu.com", Decimal.D64));
}
}
}Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.