详细入门 Redis

123 阅读6分钟

前言

本文详细介绍 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 做缓存
    写:写入数据库,删除缓存
    读:读取缓存,没命中则读取数据库,再进行缓存