1. 什么是Redis?
Redis(Remote Dictionary Server)本质上是一个Key-Value类型的内存数据库,数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
2. Redis的特点
- 速度快
- 内存读写每秒110000/81000次
- 持久化
- 数据的更新将异步地保存到硬盘(RDB和 AOF)
- 多种数据结构
- key-value 类型数据,还支持:字符串、hash、列表、集合、有序集合
- 简单稳定
- 源码少、单线程模型
- 功能丰富
- HyperLogLog、GEO、发布订阅、Lua脚本、事务、Pipeline、Bitmaps,key 过期
2. Redis的使用场景
1. 缓存系统
- 页面缓存:存储频繁访问的网页内容
- 数据库查询结果缓存:减少
database压力 - 会话存储:存储用户登录状态和会话信息
- API 响应缓存:缓存第三方
API调用结果
2. 数据库功能
- 主数据库:存储结构化数据和非结构化数据
- 实时数据存储:存储实时更新的数据如排行榜
- 计数器应用:实现访问统计、点赞数等功能
3. 消息队列
- 任务队列:异步处理后台任务
- 发布/订阅:实现消息广播和实时通信
- 事件驱动架构:支持微服务间的消息传递
4. 实时应用
- 聊天应用:存储聊天记录和在线用户状态
- 游戏排行榜:维护实时分数和排名
- 实时分析:处理流式数据和实时统计
5. 分布式系统
- 分布式锁:实现跨服务的资源锁定
- 服务发现:存储服务注册信息
- 配置管理:集中管理应用配置
6. 会话管理
- 用户认证:存储
token和认证信息 - 购物车:电商网站的购物车数据存储
- 临时数据存储:存储临时会话数据
3.Redis安装
这里分享的是windows用户使用wsl将redis下载安装的教程。
- 在终端安装Ubuntu Linux操作系统(如有,忽略)
# 通过 Microsoft Store 安装 Ubuntu
# 打开 Microsoft Store
start ms-windows-store://search/?query=Ubuntu
点击get下载,然后Open打开,install结束
2. 在终端进入Wsl Linux系统
# 启动 Ubuntu
wsl -d Ubuntu
- 在Linux系统下安装Redis 并启动
# 更新包管理器
sudo apt update
# 安装 Redis
sudo apt install redis-server
# 启动 Redis 服务
sudo service redis-server start
# 查看状态
sudo service redis-server status
好了,现在redis运行在你的windows里面的linux环境了
4. 连接Redis
# 启动 Redis CLI
redis-cli
# 连接远程 Redis 服务器
redis-cli -h hostname -p port
现在操作redis数据库的命令行界面cli也配好了,可以对数据库做操作了。
4. Redis 数据结构
String(字符串)
string是redis最基本的类型,一个key对应一个value。
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
实例
redis 127.0.0.1:6379> SET name "w3cschool.cn"
OK
redis 127.0.0.1:6379> GET name
"w3cschool.cn"
在以上实例中我们使用了 Redis 的 SET 和 GET 命令。键为 name,对应的值为w3cschool.cn。
注意: 一个键最大能存储512MB。
Hash(哈希)
Redis hash 是一个键值 (key=>value) 对集合。
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
实例
redis 127.0.0.1:6379> HMSET user:1 username w3cschool.cn password w3cschool.cn points 200
OK
redis 127.0.0.1:6379> HGETALL user:1
1) "username"
2) "w3cschool.cn"
3) "password"
4) "w3cschool.cn"
5) "points"
6) "200"
redis 127.0.0.1:6379>
以上实例中 hash 数据类型存储了包含用户脚本信息的用户对象。实例中我们使用了 Redis HMSET, HGETALL 命令,user:1 为键值。每个 hash 可以存储 232 - 1键值对(40多亿)。
List(列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
实例
redis 127.0.0.1:6379> lpush w3cschool.cn redis
(integer) 1
redis 127.0.0.1:6379> lpush w3cschool.cn mongodb
(integer) 2
redis 127.0.0.1:6379> lpush w3cschool.cn rabitmq
(integer) 3
redis 127.0.0.1:6379> lrange w3cschool.cn 0 10
1) "rabitmq"
2) "mongodb"
3) "redis"
redis 127.0.0.1:6379>
列表最多可存储 232 -1元素 (4294967295, 每个列表可存储40多亿)。
Set(集合)
Redis 的 Set是 string 类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
sadd 命令
添加一个 string 元素到key 对应的 set 集合中,成功返回1,如果元素已经在集合中返回0,key 对应的 set 不存在返回错误。
sadd key member
实例
redis 127.0.0.1:6379> sadd w3cschool.cn redis
(integer) 1
redis 127.0.0.1:6379> sadd w3cschool.cn mongodb
(integer) 1
redis 127.0.0.1:6379> sadd w3cschool.cn rabitmq
(integer) 1
redis 127.0.0.1:6379> sadd w3cschool.cn rabitmq
(integer) 0
redis 127.0.0.1:6379> smembers w3cschool.cn
1) "rabitmq"
2) "mongodb"
3) "redis"
注意: 以上实例中 rabitmq 添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略。
集合中最大的成员数为 232-1 (4294967295, 每个集合可存储40多亿个成员)。
zset(sorted set:有序集合)
Redis zset 和 set 一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
zset 的成员是唯一的,但分数(score)却可以重复。
zadd 命令
添加元素到集合,元素在集合中存在则更新对应 score
zadd key score member
实例
redis 127.0.0.1:6379> zadd w3cschool.cn 0 redis
(integer) 1
redis 127.0.0.1:6379> zadd w3cschool.cn 0 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd w3cschool.cn 0 rabitmq
(integer) 1
redis 127.0.0.1:6379> zadd w3cschool.cn 0 rabitmq
(integer) 0
redis 127.0.0.1:6379> ZRANGEBYSCORE w3cschool.cn 0 1000
1) "redis"
2) "mongodb"
3) "rabitmq"
5. Redis 常用命令
1. 字符串操作命令
SET key value- 设置键值对GET key- 获取键的值INCR key- 将键的值递增1DECR key- 将键的值递减1INCRBY key increment- 将键的值增加指定数值APPEND key value- 将值追加到键的现有值末尾
2. 哈希操作命令
HSET key field value- 设置哈希表键的字段值HGET key field- 获取哈希表键的字段值HMSET key field1 value1 field2 value2- 同时设置多个哈希字段HGETALL key- 获取哈希表中所有的字段和值HDEL key field- 删除哈希表中的一个或多个字段
3. 列表操作命令
LPUSH key value- 将值插入到列表头部RPUSH key value- 将值插入到列表尾部LPOP key- 移除并返回列表的第一个元素RPOP key- 移除并返回列表的最后一个元素LRANGE key start stop- 获取列表中指定范围的元素
4. 集合操作命令
SADD key member- 向集合添加成员SMEMBERS key- 获取集合中的所有成员SISMEMBER key member- 判断成员是否是集合的成员SREM key member- 移除集合中的指定成员SINTER key1 key2- 返回多个集合的交集
5. 有序集合操作命令
ZADD key score member- 向有序集合添加成员ZRANGE key start stop- 返回有序集合指定范围的成员ZSCORE key member- 返回有序集合中指定成员的分数ZREM key member- 移除有序集合中的一个或多个成员ZRANK key member- 返回有序集合中指定成员的排名
6. 键管理命令
KEYS pattern- 查找所有符合给定模式的键EXISTS key- 检查键是否存在DEL key- 删除键TTL key- 获取键的剩余生存时间EXPIRE key seconds- 设置键的过期时间TYPE key- 返回键所存储值的类型
7. 服务器管理命令
PING- 测试连接是否存活INFO- 获取服务器信息和统计FLUSHDB- 清空当前数据库FLUSHALL- 清空所有数据库SAVE- 同步保存数据到磁盘BGSAVE- 异步保存数据到磁盘
六、Redis+Javascript应用
String的应用:为个人博客文章内容添加文章缓存
连接配置
// redis.js
const redis = require('redis');
const client = redis.createClient({
host: 'localhost',
port: 6379,
// password: 'your_password', // 如果有密码
retry_strategy: (options) => {
if (options.error && options.error.code === 'ECONNREFUSED') {
return new Error('Redis server refused the connection');
}
if (options.total_retry_time > 1000 * 60 * 60) {
return new Error('Retry time exhausted');
}
if (options.attempt > 10) {
return undefined;
}
return Math.min(options.attempt * 100, 3000);
}
});
client.on('connect', () => {
console.log('Redis client connected');
});
client.on('error', (err) => {
console.error('Redis error:', err);
});
module.exports = client;
方法参考
// blogService.js
const redis = require('./redis');
const blogsData = require('../assets/blogs.json');
class BlogService {
// 获取单篇文章(带缓存)
async getPostById(postId) {
try {
// 先尝试从 Redis 获取缓存
const cachedPost = await redis.get(`post:${postId}`);
if (cachedPost) {
console.log(`Cache hit for post ${postId}`);
return JSON.parse(cachedPost);
}
// 缓存未命中,从原始数据源获取
const post = blogsData.posts.find(p => p.id === parseInt(postId));
if (post) {
// 将文章存入 Redis 缓存,设置1小时过期时间
await redis.setex(`post:${postId}`, 3600, JSON.stringify(post));
console.log(`Cache set for post ${postId}`);
}
return post;
} catch (error) {
console.error('Error getting post:', error);
// 出错时直接从数据源获取,不使用缓存
return blogsData.posts.find(p => p.id === parseInt(postId));
}
}
// 获取所有文章列表(带缓存)
async getAllPosts() {
try {
const cachedPosts = await redis.get('posts:all');
if (cachedPosts) {
console.log('Cache hit for all posts');
return JSON.parse(cachedPosts);
}
const posts = blogsData.posts;
// 缓存所有文章列表,设置30分钟过期时间
await redis.setex('posts:all', 1800, JSON.stringify(posts));
console.log('Cache set for all posts');
return posts;
} catch (error) {
console.error('Error getting all posts:', error);
return blogsData.posts;
}
}
// 搜索文章(带缓存)
async searchPosts(query) {
try {
const cacheKey = `search:${query.toLowerCase()}`;
const cachedResults = await redis.get(cacheKey);
if (cachedResults) {
console.log(`Cache hit for search query: ${query}`);
return JSON.parse(cachedResults);
}
// 执行搜索逻辑
const results = blogsData.posts.filter(post =>
post.title.toLowerCase().includes(query.toLowerCase()) ||
post.content.toLowerCase().includes(query.toLowerCase()) ||
post.tags.some(tag => tag.toLowerCase().includes(query.toLowerCase())) ||
post.author.toLowerCase().includes(query.toLowerCase())
);
// 缓存搜索结果,设置15分钟过期时间
await redis.setex(cacheKey, 900, JSON.stringify(results));
console.log(`Cache set for search query: ${query}`);
return results;
} catch (error) {
console.error('Error searching posts:', error);
// 出错时直接执行搜索,不使用缓存
return blogsData.posts.filter(post =>
post.title.toLowerCase().includes(query.toLowerCase()) ||
post.content.toLowerCase().includes(query.toLowerCase()) ||
post.tags.some(tag => tag.toLowerCase().includes(query.toLowerCase())) ||
post.author.toLowerCase().includes(query.toLowerCase())
);
}
}
// 更新文章时清除相关缓存
async clearPostCache(postId) {
try {
// 清除单篇文章缓存
await redis.del(`post:${postId}`);
// 清除文章列表缓存
await redis.del('posts:all');
// 清除可能的相关搜索缓存(这里简化处理)
console.log(`Cache cleared for post ${postId}`);
} catch (error) {
console.error('Error clearing cache:', error);
}
}
// 更新文章浏览量
async incrementViewCount(postId) {
try {
const viewKey = `post:views:${postId}`;
const currentViews = await redis.incr(viewKey);
// 设置过期时间,防止键永久存在
if (currentViews === 1) {
await redis.expire(viewKey, 86400); // 24小时过期
}
return currentViews;
} catch (error) {
console.error('Error incrementing view count:', error);
return 0;
}
}
// 获取文章浏览量
async getViewCount(postId) {
try {
const viewKey = `post:views:${postId}`;
const views = await redis.get(viewKey);
return views ? parseInt(views) : 0;
} catch (error) {
console.error('Error getting view count:', error);
return 0;
}
}
}
module.exports = new BlogService();
Redis 哈希(Hash)数据结构在博客网站中的应用
1. 存储文章元数据
// 使用 Redis Hash 存储文章基本信息
await redis.hset(`post:${postId}`, {
'title': post.title,
'author': post.author,
'created_at': post.created_at,
'excerpt': getExcerpt(post.content),
'view_count': 0
});
// 获取文章信息
const postInfo = await redis.hgetall(`post:${postId}`);
2. 用户会话管理
// 存储用户会话信息
await redis.hset(`session:${sessionId}`, {
'user_id': userId,
'username': username,
'login_time': new Date().toISOString(),
'last_activity': new Date().toISOString()
});
// 更新用户最后活动时间
await redis.hset(`session:${sessionId}`, 'last_activity', new Date().toISOString());
3. 文章统计信息
// 存储文章的各种统计数据
await redis.hset(`post:stats:${postId}`, {
'views': viewCount,
'likes': likeCount,
'comments': commentCount,
'shares': shareCount
});
// 增加文章浏览量
await redis.hincrby(`post:stats:${postId}`, 'views', 1);
4. 博客配置信息
// 存储博客网站配置
await redis.hset('blog:config', {
'site_title': 'Harry的主页',
'site_subtitle': '个人展示/心得分享/技术笔记',
'posts_per_page': 10,
'enable_comments': true
});
5. 搜索索引数据
// 为文章创建搜索索引
await redis.hset(`search:index:${postId}`, {
'title': post.title.toLowerCase(),
'content': getExcerpt(post.content).toLowerCase(),
'tags': post.tags.join(',').toLowerCase(),
'author': post.author.toLowerCase()
});