第1课redis_基本介绍
热度🔥:48 免费课程
授课语音
Redis 基本知识
1. 介绍
Redis 是用 C 语言开发的开源高性能键值数据库,广泛应用于缓存、消息队列等场景。2023 年 Redis 7.0 版本发布,具有以下特点:
- 高性能:在读写性能方面表现卓越,适用于高并发场景。
- 持久化:支持 RDB(快照备份)和 AOF(文件追加)两种持久化方式,确保数据安全。
- 丰富的数据类型:支持字符串、列表、哈希、集合、有序集合等多种数据类型。
- 发布/订阅:支持消息的发布和订阅,适用于简单的实时消息系统。
- 事务:通过事务和 Lua 脚本,确保多个命令的原子执行。支持简单的事务,确保数据一致性。
- 分布式:支持主从复制、分片和高可用架构(如 Redis Sentinel、Redis Cluster)。
Redis 支持多种数据结构和操作,具体包括:
- 字符串(
String
):存储简单的键值对(K/V),值可以是字符串、整数或二进制数据,用于计数(如点赞数)、缓存对象(序列化的对象)、分布式锁(设置带有过期时间的键)等。 - 列表(
List
):按插入顺序的字符串集合,支持双端操作,可用作消息队列、任务队列、存储文章评论(按时间顺序)等。 - 集合(
Set
):不允许重复元素的集合,支持并集、交集和差集操作,适用于抽奖活动(参与一次抽奖的用户)、标签系统(文章或用户标签)等。 - 有序集合(
Sorted Set
):有序的集合,每个元素都有一个分数,根据分数排序,可用在游戏积分排行榜、范围查询(商品价格排序)等。 - 哈希(
Hash
):存储键值对的集合,可用作配置管理(存储应用程序的配置信息)等。 - 位图(
Bitmap
):字符串类型的扩展,用于存储连续的二进制数字,极大节省空间,可用在用户签到(记录用户每天签到情况)、状态标记(用户是否在线)、统计数据(统计某个时间段内的活跃用户)等。 - 基数统计(
HyperLoglog
):占用空间非常小(12KB 可存储接近2^64
个不同元素),计数结果存在一定的误差,可用于基数统计(统计网站的独立访问用户数)。 - 地理位置(
GEO
):有序集合的扩展,存储地理位置信息,可用在位置服务(搜索附近餐馆、商店等)。 - 流(
Stream
):支持按时间排序的消息流,可用于日志系统(存储和处理日志数据)、消息队列。
2. 代码案例
下面的代码展示了 Redis 的各种应用场景实践,使用 Java 进行实现。
2.1 Maven 依赖
在项目的 pom.xml
中添加 Jedis 依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.6</version>
</dependency>
2.2 Java 示例代码
package com.zhilitech;
import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.args.GeoUnit;
import redis.clients.jedis.params.GeoRadiusParam;
import redis.clients.jedis.params.XAddParams;
import redis.clients.jedis.params.XTrimParams;
import redis.clients.jedis.resps.GeoRadiusResponse;
import redis.clients.jedis.resps.StreamEntry;
import redis.clients.jedis.resps.Tuple;
import java.util.*;
public class RedisExamples {
// 创建 Jedis 连接池
private static JedisPool jedisPool;
// 配置和初始化 Jedis 连接池
private static void setupJedis() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(10); // 设置最大连接数
poolConfig.setMinIdle(2); // 设置最小空闲连接数
poolConfig.setMaxIdle(5); // 设置最大空闲连接数
poolConfig.setTestOnBorrow(true); // 在借用连接前进行验证
String redisHost = "localhost"; // Redis 服务器地址
int redisPort = 6379; // Redis 服务器端口
String redisPassword = null; // Redis 密码(如有)
int redisDb = 3; // Redis 数据库索引
jedisPool = new JedisPool(poolConfig, redisHost, redisPort, 2000, redisPassword, redisDb);
}
// 检查用户是否连续签到 N 天
private static boolean checkConsecutiveDays(Jedis jedis, String bitmapKey, int days) {
// 获取位图中签到的总天数
long bitmapLength = jedis.bitcount(bitmapKey);
// 如果签到天数小于要求的天数,则返回 false
if (bitmapLength < days) {
return false;
}
// 遍历每个可能的连续签到窗口
for (int i = 0; i <= bitmapLength - days; i++) {
long count = jedis.bitcount(bitmapKey, i, i + days - 1);
// 如果该窗口内的所有位都为 1,则表示连续签到
if (count == days) {
return true;
}
}
return false;
}
public static void main(String[] args) {
setupJedis();
// 创建 Jedis 客户端
try (Jedis jedis = jedisPool.getResource()) {
// **字符串(String)操作示例**
// 设置和获取简单的键值对
jedis.set("user:1000:likes", "50");
String likes = jedis.get("user:1000:likes");
System.out.println("User 1000 likes: " + likes); // 输出: User 1000 likes: 50
// 设置带有过期时间的键(用于分布式锁)
jedis.setex("lock:resource", 60, "locked");
String lockStatus = jedis.get("lock:resource");
System.out.println("Resource lock status: " + lockStatus); // 输出: Resource lock status: locked
// 增加整数值
jedis.set("page_views", "0"); // 初始化页面浏览量
jedis.incr("page_views"); // 增加 1
jedis.incrBy("page_views", 10); // 增加 10
System.out.println("Page views count: " + jedis.get("page_views")); // 输出: Page views count: 11
// **列表(List)操作示例**
// 使用列表作为任务队列
jedis.del("task_queue"); // 清空任务队列
jedis.rpush("task_queue", "task1"); // 将任务加入队列
jedis.rpush("task_queue", "task2");
String task = jedis.lpop("task_queue"); // 从队列中取出任务
System.out.println("Processed task: " + task); // 输出: Processed task: task1
// 存储文章评论
jedis.del("comments:article:123"); // 清空评论
jedis.rpush("comments:article:123", "Great article!"); // 添加评论
jedis.rpush("comments:article:123", "Very informative.");
List<String> comments = jedis.lrange("comments:article:123", 0, -1); // 获取所有评论
System.out.println("Article comments: " + comments); // 输出: Article comments: [Great article!, Very informative.]
// **集合(Set)操作示例**
// 管理用户标签
jedis.sadd("tags:user:1000", "python"); // 添加标签
jedis.sadd("tags:user:1000", "redis");
Set<String> tags = jedis.smembers("tags:user:1000"); // 获取用户标签
System.out.println("User 1000 tags: " + tags); // 输出: User 1000 tags: [python, redis]
// 执行集合操作:并集、交集、差集
jedis.sadd("setA", "apple", "banana", "cherry"); // 创建集合 A
jedis.sadd("setB", "banana", "cherry", "date"); // 创建集合 B
Set<String> union = jedis.sunion("setA", "setB"); // 获取并集
Set<String> intersection = jedis.sinter("setA", "setB"); // 获取交集
Set<String> difference = jedis.sdiff("setA", "setB"); // 获取差集
System.out.println("Union: " + union); // 输出: Union: [apple, banana, cherry, date]
System.out.println("Intersection: " + intersection); // 输出: Intersection: [banana, cherry]
System.out.println("Difference: " + difference); // 输出: Difference: [apple]
// **有序集合(Sorted Set)
操作示例**
// 存储游戏积分排行榜
jedis.zadd("leaderboard", 100, "player1"); // 添加玩家及其分数
jedis.zadd("leaderboard", 150, "player2");
jedis.zadd("leaderboard", 120, "player3");
// 获取排行榜前两名
Set<Tuple> topPlayers = new HashSet<>(jedis.zrevrangeWithScores("leaderboard", 0, 1));
System.out.println("Top players: " + topPlayers); // 输出: Top players: [player2:150.0, player3:120.0]
// 根据分数范围查询
Set<String> playersInRange = new HashSet<>(jedis.zrangeByScore("leaderboard", 100, 130));
System.out.println("Players with scores between 100 and 130: " + playersInRange); // 输出: Players with scores between 100 and 130: [player1, player3]
// **哈希(Hash)操作示例**
// 存储用户配置
jedis.hset("user:1000:config", "theme", "dark"); // 设置用户配置
jedis.hset("user:1000:config", "notifications", "enabled");
Map<String, String> config = jedis.hgetAll("user:1000:config"); // 获取用户配置
System.out.println("User 1000 config: " + config); // 输出: User 1000 config: {theme=dark, notifications=enabled}
// **位图(Bitmap)操作示例**
// 记录用户签到
jedis.setbit("user:1000:sign_in", 0, true); // 第一天签到
jedis.setbit("user:1000:sign_in", 1, false); // 第二天未签到
// 统计签到情况
long signInDays = jedis.bitcount("user:1000:sign_in");
System.out.println("User 1000 sign-in days: " + signInDays); // 输出: User 1000 sign-in days: 1
// 位图键
String userId = "user:1000";
String bitmapKey = userId + ":sign_in";
// 模拟签到数据
// 假设第 0 位表示第 1 天签到,第 1 位表示第 2 天签到,以此类推
for (int i = 0; i < 7; i++) {
jedis.setbit(bitmapKey, i, true); // 设置用户第 1 天到第 7 天都已签到
}
// 检查用户是否连续签到 7 天
boolean isConsecutive = checkConsecutiveDays(jedis, bitmapKey, 7);
System.out.println("User has signed in for 7 consecutive days: " + isConsecutive); // 输出: User has signed in for 7 consecutive days: true
// **基数统计(HyperLoglog)操作示例**
// 统计唯一访问用户数
jedis.pfadd("unique_users", "user1", "user2", "user3", "user1"); // user1 访问了多次,但只算一次
long uniqueUsersCount = jedis.pfcount("unique_users"); // 计算唯一用户数量
System.out.println("Estimated unique users: " + uniqueUsersCount); // 输出: Estimated unique users: 3
// **地理位置(GEO)操作示例**
// 存储地理位置
jedis.geoadd("places", 13.361389, 38.115556, "Palermo"); // 帕勒莫,意大利
jedis.geoadd("places", 15.087269, 37.502669, "Catania"); // 卡塔尼亚,意大利
jedis.geoadd("places", 2.173404, 41.385064, "Barcelona"); // 巴塞罗那,西班牙
jedis.geoadd("places", 10.451526, 51.165691, "Germany"); // 德国,中心位置
jedis.geoadd("places", -3.703790, 40.416775, "Madrid"); // 马德里,西班牙
// 查询附近地点(半径 100km)
List<GeoRadiusResponse> nearbyPlaces = jedis.georadius("places", 15.087269, 37.502669, 100, GeoUnit.KM);
System.out.println("Nearby places within 100 km:");
for (GeoRadiusResponse item : nearbyPlaces) {
String name = item.getMemberByString(); // 获取地点名称
GeoCoordinate coord = item.getCoordinate(); // 获取坐标
System.out.println("Place: " + name + (coord != null ? ", Location: " + coord.getLongitude() + ", " + coord.getLatitude() : ""));
// 预期输出:
// Place: Catania, Location: 15.087269, 37.502669
// Place: Palermo, Location: 13.361389, 38.115556
}
// 获取指定地点的地理坐标
List<GeoCoordinate> positions = jedis.geopos("places", "Palermo", "Catania", "Barcelona", "Germany", "Madrid");
System.out.println("Geographic positions:");
for (GeoCoordinate position : positions) {
String place = position == null ? "Unknown" : position.toString();
System.out.println("Place: " + place);
}
// 查找特定半径内的地点及其距离
List<GeoRadiusResponse> placesWithDistance = jedis.georadius("places", 15.087269, 37.502669, 1000, GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist());
System.out.println("Nearby places with distance within 1000 km:");
for (GeoRadiusResponse item : placesWithDistance) {
String name = item.getMemberByString(); // 获取地点名称
double distance = item.getDistance(); // 获取距离,单位为公里
System.out.println("Place: " + name + ", Distance: " + distance + " km");
}
// **流(Stream)操作示例**
// 记录日志消息
Map<String, String> logEntry1 = new HashMap<>();
logEntry1.put("message", "System started");
logEntry1.put("level", "info");
jedis.xadd("logs", logEntry1, new XAddParams());
Map<String, String> logEntry2 = new HashMap<>();
logEntry2.put("message", "User login failed");
logEntry2.put("level", "error");
jedis.xadd("logs", logEntry2, new XAddParams());
// 读取日志消息
List<StreamEntry> messages = jedis.xrange("logs", "0", "+", 2);
System.out.println("Log messages: " + messages);
// 限制流长度(保留最新的 1000 条消息)
jedis.xtrim("logs", new XTrimParams().maxLen(1000));
System.out.println("Stream length after trimming: " + jedis.xlen("logs")); // 输出: Stream length after trimming: 4
}
}
}
代码说明
- 连接池配置:使用
JedisPool
来管理 Redis 连接,设置最大连接数、最小空闲连接数等。 - 字符串操作:展示了设置和获取键值对、使用过期时间的分布式锁以及整数增量操作。
- 列表操作:演示了如何使用 Redis 列表作为任务队列和存储文章评论。
- 集合操作:示范了标签管理及集合的并集、交集和差集操作。
- 有序集合操作:展示了如何存储游戏积分排行榜以及根据分数范围查询。
- 哈希操作:演示了如何存储和获取用户配置。
- 位图操作:记录用户签到情况,并检查是否连续签到。
- 基数统计:使用 HyperLoglog 统计唯一用户数。
- 地理位置操作:存储地点信息并查询附近地点及其距离。
- 流操作:记录日志信息,读取日志流,并限制流长度。
此代码案例涵盖了 Redis 的基本操作和多种应用场景,为开发者提供了良好的参考。根据实际需要,可以在此基础上扩展功能和细节。