Redis: 从入门到精通(二)
高级数据结构
HyperLogLog
HyperLogLog 是一种概率数据结构,用于估算唯一值的数量。相比于存储所有唯一值,HyperLogLog 的内存占用要小得多。
# 添加元素到 HyperLogLog
PFADD myhyperloglog element1 element2 element3
# 获取 HyperLogLog 的基数估计值
PFCOUNT myhyperloglog
Bitmaps
Bitmaps 是一种可以高效存储和操作二进制数据的数据结构。可以用于用户签到等场景。
# 设置位值
SETBIT mybitmap 1001 1
# 获取位值
GETBIT mybitmap 1001
# 统计位值为 1 的数量
BITCOUNT mybitmap
Geospatial Indexes
Redis 支持地理空间索引,允许存储和查询地理位置数据。
```bash
# 添加地理位置
GEOADD mygeo 13.361389 38.115556 "Palermo"
GEOADD mygeo 15.087269 37.502669 "Catania"
# 获取位置的经纬度
GEOPOS mygeo "Palermo"
# 计算两个位置之间的距离
GEODIST mygeo "Palermo" "Catania"
# 查找半径范围内的地理位置
GEORADIUS mygeo 15 37 200 km
redis + node.js
常见应用案例
- 实现分布式锁,以保证多个进程或线程之间对共享资源的互斥访问。
const redis = require('redis');
const client = redis.createClient();
async function acquireLock(lockKey, lockValue, lockTimeout) {
return new Promise((resolve, reject) => {
client.set(lockKey, lockValue, 'PX', lockTimeout, 'NX', (err, reply) => {
if (err) return reject(err);
resolve(reply === 'OK');
});
});
}
async function releaseLock(lockKey, lockValue) {
return new Promise((resolve, reject) => {
client.get(lockKey, (err, value) => {
if (err) return reject(err);
if (value === lockValue) {
client.del(lockKey, (err, reply) => {
if (err) return reject(err);
resolve(true);
});
} else {
resolve(false);
}
});
});
}
// 使用示例
(async () => {
const lockKey = 'lock:key';
const lockValue = 'lock:value';
const lockTimeout = 30000; // 30 秒
if (await acquireLock(lockKey, lockValue, lockTimeout)) {
try {
// 获得锁,执行任务
console.log('Lock acquired');
} finally {
await releaseLock(lockKey, lockValue);
console.log('Lock released');
}
} else {
console.log('Unable to acquire lock');
}
client.quit();
})();
- 使用 Redis 的 INCR 和 EXPIRE 命令实现简单的限流器
const redis = require('redis');
const client = redis.createClient();
async function rateLimiter(key, limit, windowSize) {
return new Promise((resolve, reject) => {
client.multi()
.incr(key)
.expire(key, windowSize)
.exec((err, replies) => {
if (err) return reject(err);
const count = replies[0];
if (count > limit) {
resolve(false);
} else {
resolve(true);
}
});
});
}
// 使用示例
(async () => {
const limitKey = 'limit:key';
const limit = 10;
const windowSize = 60; // 60 秒
if (await rateLimiter(limitKey, limit, windowSize)) {
console.log('Request allowed');
} else {
console.log('Rate limit exceeded');
}
client.quit();
})();
- 使用 Redis 存储会话数据,提高会话管理的效率和可靠性
const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const client = redis.createClient();
const app = express();
app.use(session({
store: new RedisStore({ client }),
secret: 'your_secret_key',
resave: false,
saveUninitialized: false,
cookie: { secure: false, maxAge: 60000 } // 1 分钟
}));
app.get('/', (req, res) => {
if (!req.session.views) {
req.session.views = 1;
} else {
req.session.views += 1;
}
res.send(`Number of views: ${req.session.views}`);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 使用 Redis 实现简单的任务队列。
const redis = require('redis');
const client = redis.createClient();
async function pushTask(queue, task) {
return new Promise((resolve, reject) => {
client.rpush(queue, JSON.stringify(task), (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
});
}
async function popTask(queue) {
return new Promise((resolve, reject) => {
client.lpop(queue, (err, reply) => {
if (err) return reject(err);
resolve(JSON.parse(reply));
});
});
}
// 使用示例
(async () => {
const queue = 'task:queue';
await pushTask(queue, { task: 'Task 1' });
await pushTask(queue, { task: 'Task 2' });
const task1 = await popTask(queue);
console.log('Popped task:', task1);
const task2 = await popTask(queue);
console.log('Popped task:', task2);
client.quit();
})();
- 使用 Redis 实现数据缓存,提升读取性能。
const redis = require('redis');
const client = redis.createClient();
async function cacheGet(key) {
return new Promise((resolve, reject) => {
client.get(key, (err, reply) => {
if (err) return reject(err);
resolve(JSON.parse(reply));
});
});
}
async function cacheSet(key, value, ttl) {
return new Promise((resolve, reject) => {
client.setex(key, ttl, JSON.stringify(value), (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
});
}
// 使用示例
(async () => {
const key = 'cache:key';
const value = { data: 'Cached data' };
const ttl = 60; // 60 秒
await cacheSet(key, value, ttl);
const cachedValue = await cacheGet(key);
console.log('Cached value:', cachedValue);
client.quit();
})();
- 使用 Redis 实现简单的计数器。
const redis = require('redis');
const client = redis.createClient();
async function incrementCounter(key) {
return new Promise((resolve, reject) => {
client.incr(key, (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
});
}
async function getCounter(key) {
return new Promise((resolve, reject) => {
client.get(key, (err, reply) => {
if (err) return reject(err);
resolve(parseInt(reply, 10));
});
});
}
// 使用示例
(async () => {
const counterKey = 'counter:key';
await incrementCounter(counterKey);
await incrementCounter(counterKey);
const counterValue = await getCounter(counterKey);
console.log('Counter value:', counterValue);
client.quit();
})();
- 使用 Redis 进行实时统计,例如网站访问量统计。
const redis = require('redis');
const client = redis.createClient();
async function trackVisit(key) {
const currentMinute = Math.floor(Date.now() / 60000);
const visitKey = `${key}:${currentMinute}`;
client.incr(visitKey);
client.expire(visitKey, 3600); // 1 小时
}
async function getVisits(key) {
return new Promise((resolve, reject) => {
client.keys(`${key}:*`, (err, keys) => {
if (err) return reject(err);
if (keys.length === 0) return resolve(0);
client.mget(keys, (err, values) => {
if (err) return reject(err);
const visits = values.reduce((acc, value) => acc + parseInt(value, 10), 0);
resolve(visits);
});
});
});
}
// 使用示例
(async () => {
const visitKey = 'visit:count';
await trackVisit(visitKey);
await trackVisit(visitKey);
const visitCount = await getVisits(visitKey);
console.log('Visit count:', visitCount);
client.quit();
})();
- 使用 Redis 进行数据自动过期处理。
const redis = require('redis');
const client = redis.createClient();
async function setExpiringData(key, value, ttl) {
return new Promise((resolve, reject) => {
client.setex(key, ttl, value, (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
});
}
async function getData(key) {
return new Promise((resolve, reject) => {
client.get(key, (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
});
}
// 使用示例
(async () => {
const key = 'expiring:key';
const value = 'This is expiring data';
const ttl = 10; // 10 秒
await setExpiringData(key, value, ttl);
setTimeout(async () => {
const data = await getData(key);
console.log('Data after 5 seconds:', data);
}, 5000);
setTimeout(async () => {
const data = await getData(key);
console.log('Data after 15 seconds:', data);
client.quit();
}, 15000);
})();
- 利用 Redis 的发布/订阅功能实现实时聊天应用的消息广播。
const redis = require('redis');
const client = redis.createClient();
async function subscribeToChat(channel, callback) {
const subscriber = redis.createClient();
subscriber.subscribe(channel);
subscriber.on('message', (ch, message) => {
callback(message);
});
return subscriber;
}
async function publishMessage(channel, message) {
client.publish(channel, message);
}
// 使用示例
(async () => {
const chatChannel = 'chatroom';
const subscriber = await subscribeToChat(chatChannel, (message) => {
console.log('Received message:', message);
});
await publishMessage(chatChannel, 'Hello, World!');
await publishMessage(chatChannel, 'How are you?');
setTimeout(() => {
subscriber.unsubscribe();
subscriber.quit();
client.quit();
}, 5000);
})();
- 使用 Redis 的数据结构(如哈希表)和发布/订阅功能实现实时数据更新。
const redis = require('redis');
const client = redis.createClient();
async function updateData(key, field, value) {
return new Promise((resolve, reject) => {
client.hset(key, field, value, (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
});
}
async function subscribeToUpdates(key, callback) {
const subscriber = redis.createClient();
subscriber.subscribe(key);
subscriber.on('message', (ch, message) => {
callback(JSON.parse(message));
});
return subscriber;
}
// 使用示例
(async () => {
const dataKey = 'realtime:data';
await updateData(dataKey, 'temperature', '25°C');
const subscriber = await subscribeToUpdates(dataKey, (data) => {
console.log('Updated data:', data);
});
setTimeout(() => {
subscriber.unsubscribe();
subscriber.quit();
client.quit();
}, 5000);
})();
- 使用 Redis 的列表结构实现简单的消息队列,支持生产者-消费者模式。
const redis = require('redis');
const client = redis.createClient();
async function enqueue(queue, message) {
return new Promise((resolve, reject) => {
client.lpush(queue, message, (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
});
}
async function dequeue(queue) {
return new Promise((resolve, reject) => {
client.brpop(queue, 0, (err, reply) => {
if (err) return reject(err);
resolve(reply[1]);
});
});
}
// 使用示例
(async () => {
const queueName = 'message:queue';
await enqueue(queueName, 'Message 1');
await enqueue(queueName, 'Message 2');
const message1 = await dequeue(queueName);
console.log('Dequeued message:', message1);
const message2 = await dequeue(queueName);
console.log('Dequeued message:', message2);
client.quit();
})();
- 利用 Redis 的地理空间索引功能存储和查询地理位置数据。
const redis = require('redis');
const client = redis.createClient();
async function addLocation(place, longitude, latitude) {
return new Promise((resolve, reject) => {
client.geoadd('locations', longitude, latitude, place, (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
});
}
async function getLocationsNearby(longitude, latitude, radius) {
return new Promise((resolve, reject) => {
client.georadius('locations', longitude, latitude, radius, 'km', 'WITHDIST', 'ASC', (err, locations) => {
if (err) return reject(err);
resolve(locations);
});
});
}
// 使用示例
(async () => {
await addLocation('New York', -74.005974, 40.712776);
await addLocation('Los Angeles', -118.243683, 34.052235);
const locationsNearby = await getLocationsNearby(-73.9857, 40.7484, 100);
console.log('Locations nearby:', locationsNearby);
client.quit();
})();
- 使用 Redis 缓存穿透解决方案,通过设置空值或者布隆过滤器防止缓存穿透问题
缓存穿透是指恶意访问者或者用户请求不存在于缓存中的数据,导致每次请求都需要访问数据库或者其他存储,给系统带来巨大的压力和性能问题。为了解决这个问题,可以使用以下几种方法:
1.空值缓存:
当数据库查询结果为空时,将空结果(如 null 或者特定的空对象)也缓存起来,并设置一个较短的过期时间。这样,下次相同的请求就会命中缓存,而不会直接穿透到数据库。
2.布隆过滤器:
布隆过滤器是一种数据结构,用于快速检查某个元素是否存在于集合中。可以在缓存层前部署布隆过滤器,如果请求的数据在布隆过滤器中不存在,可以直接返回,避免对后端存储的查询。
3.缓存预热:
在系统启动或者数据更新时,预先将常用的数据加载到缓存中,避免在高流量时因为缓存为空而直接请求数据库。
4.数据库层面的优化:
在数据库层面设置合适的索引,降低查询的成本,同时可以使用数据库缓存(如 MySQL 的查询缓存)来减轻数据库负担。
const redis = require('redis');
const client = redis.createClient();
//空值缓存
async function getDataFromCache(key) {
return new Promise((resolve, reject) => {
client.get(key, (err, reply) => {
if (err) return reject(err);
if (reply === null) {
resolve(null);
} else {
resolve(JSON.parse(reply));
}
});
});
}
async function fetchDataAndCache(key) {
// Simulate fetching data from database
const data = { /* ... */ };
// Cache the data with a short TTL to prevent cache staleness
await client.setex(key, 60, JSON.stringify(data));
return data;
}
// 使用示例
(async () => {
const cacheKey = 'cached:data';
let cachedData = await getDataFromCache(cacheKey);
if (!cachedData) {
cachedData = await fetchDataAndCache(cacheKey);
}
console.log('Cached data:', cachedData);
client.quit();
})();
//布隆过滤器
const { BloomFilter } = require('bloomfilter');
// Example of using Bloom Filter to check existence
const filter = new BloomFilter(32, 16); // 32 bits and 16 hash functions
async function checkExistence(key) {
const exists = filter.test(key);
if (!exists) {
// Data likely doesn't exist, skip cache and database
return null;
}
// Proceed with normal cache and database operations
}
//缓存预热
async function warmUpCache() {
const popularData = await fetchPopularData();
for (const data of popularData) {
client.setex(data.key, 3600, JSON.stringify(data.value));
}
}
总结
Redis 不仅仅是一个简单的缓存系统,而是一个功能强大的数据存储和处理平台,适用于各种需要高性能和实时性的应用场景。