前言
本文详细介绍 redis 的用法:包括基础 API、数据备份机制、redis <=> 数据库 同步、过期操作、redis 发布订阅和事务。
基本命令
基础 操作命令
// 切换数据库
redis.select(dbIndex)
// 清空当前数据库
redis.flushdb()
// 清空所有数据库
redis.flushall()
键值
redis 键值操作,它的数据结构类似这样:
{
"keys": [
{ "name": "name", "value": "John" },
{ "name": "age", "value": 30 },
{ "name": "city", "value": "New York" }
// 可能还有其他键值
]
}
API
const Redis = require('ioredis');
const redis = new Redis();
(async () => {
// 设置键值对
await redis.set('mykey', 'myvalue');
// 获取键值对的值
const value = await redis.get('mykey');
console.log('Value:', value);
// 判断键是否存在
const keyExists = await redis.exists('mykey');
console.log('Key exists:', keyExists);
// 删除键
await redis.del('mykey');
// 判断键是否存在(删除后)
const keyExistsAfterDelete = await redis.exists('mykey');
console.log('Key exists after delete:', keyExistsAfterDelete);
// 设置键值对并设置过期时间(单位:秒)
await redis.setex('mykey', 60, 'myvalue');
// 获取键的过期时间(单位:秒)
const keyTTL = await redis.ttl('mykey');
console.log('Key TTL:', keyTTL);
// 设置键值对并设置过期时间(单位:毫秒)
await redis.psetex('mykey', 60000, 'myvalue');
// 关闭 Redis 连接
redis.quit();
})();
哈希
redis 哈希操作,它的数据结构类似这样:
{
"hashes": [
{
"key": "myhash",
"fields": [
{ "name": "field1", "value": "value1" },
{ "name": "field2", "value": "value2" }
]
},
// 可能还有其他哈希
]
}
API
const Redis = require('ioredis');
const redis = new Redis();
(async () => {
// 设置 Hash 字段值
await redis.hset('myhash', 'field1', 'value1');
await redis.hset('myhash', 'field2', 'value2');
// 获取 Hash 字段值
const fieldValue = await redis.hget('myhash', 'field1');
console.log('Field value:', fieldValue);
// 获取 Hash 所有字段及对应值
const allFieldsAndValues = await redis.hgetall('myhash');
console.log('All fields and values:', allFieldsAndValues);
// 判断字段是否存在于 Hash 中
const fieldExists = await redis.hexists('myhash', 'field2');
console.log('Field exists:', fieldExists);
// 获取 Hash 所有字段名
const fieldNames = await redis.hkeys('myhash');
console.log('Field names:', fieldNames);
// 获取 Hash 所有字段值
const fieldValues = await redis.hvals('myhash');
console.log('Field values:', fieldValues);
// 增加 Hash 字段值
await redis.hincrby('myhash', 'field1', 5);
// 删除 Hash 字段
await redis.hdel('myhash', 'field2');
// 获取 Hash 字段数量
const fieldCount = await redis.hlen('myhash');
console.log('Field count:', fieldCount);
// 关闭 Redis 连接
redis.quit();
})();
集合
redis 集合操作,它的数据结构类似这样:
{
"sets": [
{
"key": "myset",
"members": ["member1", "member2", "member3"]
},
// 可能还有其他集合
]
}
API
const Redis = require('ioredis');
const redis = new Redis();
(async () => {
// 添加元素到集合
await redis.sadd('myset', 'member1', 'member2', 'member3');
// 获取集合中的成员数量
const memberCount = await redis.scard('myset');
console.log('Member count:', memberCount);
// 获取集合中的所有成员
const members = await redis.smembers('myset');
console.log('Members:', members);
// 判断成员是否在集合中
const isMember = await redis.sismember('myset', 'member1');
console.log('Is member:', isMember);
// 从集合中移除成员
await redis.srem('myset', 'member1');
// 获取随机成员
const randomMember = await redis.srandmember('myset');
console.log('Random member:', randomMember);
// 获取多个随机成员
const randomMembers = await redis.srandmember('myset', 2);
console.log('Random members:', randomMembers);
// 计算多个集合的交集
const intersection = await redis.sinter('set1', 'set2');
console.log('Intersection:', intersection);
// 计算多个集合的并集
const union = await redis.sunion('set1', 'set2');
console.log('Union:', union);
// 关闭 Redis 连接
redis.quit();
})();
有序集合
redis 有序集合操作,它的数据结构类似这样:
{
"sortedSets": [
{
"key": "scores",
"members": [
{ "member": "player1", "score": 100 },
{ "member": "player2", "score": 90 },
{ "member": "player3", "score": 80 }
]
},
// 可能还有其他有序集合
]
}
API
const Redis = require('ioredis');
const redis = new Redis();
(async () => {
// 添加有序集合成员
await redis.zadd('mysortedset', 1, 'member1');
await redis.zadd('mysortedset', 2, 'member2');
await redis.zadd('mysortedset', 3, 'member3');
// 获取有序集合成员数量
const sortedSetSize = await redis.zcard('mysortedset');
console.log('Sorted set size:', sortedSetSize);
// 获取有序集合指定范围内的成员
const range = await redis.zrange('mysortedset', 0, -1);
console.log('Range:', range);
// 获取有序集合指定分数范围内的成员
const membersByScore = await redis.zrangebyscore('mysortedset', 2, 3);
console.log('Members by score:', membersByScore);
// 获取有序集合成员的分数
const memberScore = await redis.zscore('mysortedset', 'member2');
console.log('Member score:', memberScore);
// 增加有序集合成员的分数
await redis.zincrby('mysortedset', 1, 'member1');
// 删除有序集合成员
await redis.zrem('mysortedset', 'member3');
// 获取有序集合成员在排名中的位置
const memberRank = await redis.zrank('mysortedset', 'member2');
console.log('Member rank:', memberRank);
// 关闭 Redis 连接
redis.quit();
})();
列表
redis 列表操作,它的数据结构类似这样:
{
"lists": [
{
"key": "tasks",
"values": ["task1", "task2", "task3"]
},
// 可能还有其他列表
]
}
API
const Redis = require('ioredis');
const redis = new Redis();
(async () => {
// 从左侧推入元素
await redis.lpush('mylist', 'element1');
await redis.lpush('mylist', 'element2');
// 从右侧推入元素
await redis.rpush('mylist', 'element3');
await redis.rpush('mylist', 'element4');
// 获取列表长度
const listLength = await redis.llen('mylist');
console.log('List length:', listLength);
// 获取列表指定范围的元素
const range = await redis.lrange('mylist', 0, -1);
console.log('Range:', range);
// 弹出左侧元素
const leftPop = await redis.lpop('mylist');
console.log('Left popped element:', leftPop);
// 弹出右侧元素
const rightPop = await redis.rpop('mylist');
console.log('Right popped element:', rightPop);
// 获取指定索引处的元素
const elementAtIndex = await redis.lindex('mylist', 1);
console.log('Element at index 1:', elementAtIndex);
// 在指定元素前或后插入新元素
await redis.linsert('mylist', 'BEFORE', 'element3', 'newelement');
// 关闭 Redis 连接
redis.quit();
})();
备份
本地备份
RDB
redis 内存数据定时备份到磁盘,先写入到临时文件,成功后在替换原有文件
vim /usr/local/etc/redis.conf
config set appendonly yes
brew services restart redis
# 自动备份
save 900 1 # 在 900 秒(15 分钟)内如果至少有 1 个 key 被改动,则生成 RDB 快照
save 300 10 # 在 300 秒(5 分钟)内如果至少有 10 个 key 被改动,则生成 RDB 快照
save 60 10000 # 在 60 秒内如果至少有 10000 个 key 被改动,则生成 RDB 快照
# 手动备份
redis save | redis bgsave
AOF
只记录写和删操作,并且以文件追加的方式,实时操作
appendonly yes # 开启 AOF 持久化
定时备份至数据库
const fs = require('fs');
const RedisRdbParser = require('rdb-parser');
const { MongoClient } = require('mongodb');
const schedule = require('node-schedule');
const rdbFilePath = '/path/to/your/rdbfile.rdb';
const mongodbUrl = 'mongodb://localhost:27017';
const databaseName = 'rdb_backup';
const collectionName = 'rdb_data';
// 创建 MongoDB 连接
const client = new MongoClient(mongodbUrl, { useNewUrlParser: true, useUnifiedTopology: true });
// 解析并备份数据到 MongoDB
async function backupToMongoDB() {
try {
// 连接 MongoDB
await client.connect();
const db = client.db(databaseName);
const collection = db.collection(collectionName);
// 解析 RDB 数据
const rdbData = fs.readFileSync(rdbFilePath);
const parser = new RedisRdbParser();
const parsedData = await parser.parse(rdbData);
// 备份数据到 MongoDB
await collection.insertOne(parsedData);
console.log('Backup completed successfully');
} catch (error) {
console.error('Error during backup:', error);
} finally {
// 关闭 MongoDB 连接
client.close();
}
}
// 每天凌晨执行备份任务
const backupJob = schedule.scheduleJob('0 0 * * *', () => {
backupToMongoDB();
});
过期操作
const Redis = require('ioredis');
const redis = new Redis();
(async () => {
// 设置字符串的过期时间
await redis.set('mystring', 'Hello, Redis!');
await redis.expire('mystring', 60);
// 设置哈希的过期时间
await redis.hset('myhash', 'field1', 'value1');
await redis.expire('myhash', 60);
// 设置列表的过期时间
await redis.lpush('mylist', 'value1', 'value2', 'value3');
await redis.expire('mylist', 60);
// 设置集合的过期时间
await redis.sadd('myset', 'value1', 'value2', 'value3');
await redis.expire('myset', 60);
// 设置有序集合的过期时间
await redis.zadd('mysortedset', 10, 'value1');
await redis.expire('mysortedset', 60);
// 关闭 Redis 连接
redis.quit();
})();
发布订阅
redis 发布订阅需要新建一个 client
const Redis = require('ioredis');
const redis = new Redis();
// 订阅频道
redis.subscribe('news', (err, count) => {
if (err) {
console.error('Error subscribing:', err);
return;
}
console.log(`Subscribed to ${count} channel(s)`);
});
// 发布消息到频道
redis.publish('news', 'New article published');
// 关闭 Redis 连接
// 注意:订阅操作会保持连接,所以需要谨慎关闭连接
// redis.quit();
事务
支持原子性和回滚
const Redis = require('ioredis');
const redis = new Redis();
async function performTransaction() {
try {
// 开启事务
await redis.multi();
// 添加多个命令到事务中
redis.set('foo', '123');
redis.incr('bar');
// 执行事务
const results = await redis.exec();
console.log('Transaction Results:', results);
} catch (error) {
console.error('Transaction Error:', error);
// 取消事务
redis.discard();
} finally {
redis.disconnect();
}
}
performTransaction();
同步
- 双写策略 写入数据库的同时,写入缓存
- 定时任务 定时同步数据到数据库,或者从数据库同步到缓存
数据库同步到 redis
const schedule = require('node-schedule');
const Redis = require('ioredis');
const redis = new Redis();
const mongoose = require('mongoose');
// 连接 MongoDB
mongoose.connect('mongodb://localhost:27017/mydb', { useNewUrlParser: true, useUnifiedTopology: true });
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB 连接错误:'));
db.once('open', () => {
console.log('成功连接到 MongoDB');
});
// 定时任务,每小时更新数据到 Redis
const updateTask = schedule.scheduleJob('0 * * * *', async () => {
try {
// 从 MongoDB 中获取数据
const dataFromMongo = await Model.find(); // 使用你的模型和查询
// 更新数据到 Redis
const redisKey = 'your_data_key';
await redis.set(redisKey, JSON.stringify(dataFromMongo));
console.log('数据已更新到 Redis');
} catch (error) {
console.error('更新数据到 Redis 失败:', error);
}
});
最佳实践
-
redis 做数据库
定时更新到 数据库 -
redis 做缓存
写:写入数据库,删除缓存
读:读取缓存,没命中则读取数据库,再进行缓存