阅读量:0
文章目录
深入探索Java开发世界:Redis~类型分析大揭秘
Redis数据库基础知识,类型知识点梳理~
一、数据结构类型
Redis是一种基于内存的开源键值对存储系统,支持多种数据结构类型。
String(字符串)
- 特点:
- 最基本的数据类型。
- 可以存储任何形式的字符串,包括二进制数据。
- 最大值为512MB。
- 常用命令:
SET key value
:设置键值。GET key
:获取键值。INCR key
/DECR key
:对值进行原子递增/递减操作。APPEND key value
:追加字符串。
- 使用场景:
- 缓存:将常用的数据存储在Redis中以提高访问速度。
- 计数器:如网站访问量统计、点赞数等。
- 会话存储:保存用户登录状态和信息。
- 分布式锁
- 特点:
List(列表)
- 特点:
- 有序的字符串集合。
- 可以从两端推入和弹出元素(双向链表)。
- 常用命令:
LPUSH key value
/RPUSH key value
:从左/右插入元素。LPOP key
/RPOP key
:从左/右弹出元素。LRANGE key start stop
:获取指定范围内的元素。
- 使用场景:
- 消息队列:实现简单的生产者 - 消费者模式。
- 最新消息列表:如社交媒体的动态更新。
- 任务队列:后台任务调度。
- 特点:
Set(集合)
- 特点:
- 无序的字符串集合。
- 集合中的元素是唯一的。
- 常用命令:
SADD key member
:添加元素。SREM key member
:删除元素。SMEMBERS key
:获取所有元素。SINTER key1 key2
:求交集。SUNION key1 key2
:求并集。SDIFF key1 key2
:求差集。
- 使用场景:
- 标签系统:如文章标签。
- 共同好友:计算用户之间的共同好友。
- 唯一性保证:如注册用户的唯一标识。
- 特点:
Sorted Set(有序集合)
- 特点:
- 有序的元素集合,每个元素附带一个分数。
- 元素根据分数排序。
- 常用命令:
ZADD key score member
:添加元素及其分数。ZRANGE key start stop [WITHSCORES]
:按排名获取元素。ZREM key member
:删除元素。ZRANK key member
:获取元素的排名。
- 使用场景:
- 排行榜:游戏积分榜、竞赛排名等。
- 优先级队列:任务调度,根据优先级处理任务。
- 范围查询:如获取一定范围内的高分用户。
- 特点:
Hash(哈希表)
- 特点:
- 键值对集合。
- 适合存储对象。
- 常用命令:
HSET key field value
:设置字段的值。HGET key field
:获取字段的值。HGETALL key
:获取所有字段和值。HDEL key field
:删除字段。
- 使用场景:
- 对象存储:存储用户信息、商品信息等。
- 缓存对象:避免序列化和反序列化操作。
- 特点:
HyperLogLog
- 特点:
- 基数估算法。
- 用于快速估算集合的基数(不重复元素的数量)。
- 占用内存非常小(每个HyperLogLog数据结构占用12KB内存)。
- 常用命令:
PFADD key element
:添加元素。PFCOUNT key
:返回基数估算结果。
- 使用场景:
- 独立总数统计:如网站的独立访客数、点击量等。
- 特点:
Bitmap
- 特点:
- 位数组。
- 用于记录布尔值(0或1)。
- 常用命令:
SETBIT key offset value
:设置位的值。GETBIT key offset
:获取位的值。BITCOUNT key [start end]
:统计特定位范围内1的数量。
- 使用场景:
- 签到系统:记录用户每日签到情况。
- 用户在线情况:记录用户在线时长。
- 大规模布尔值存储:如A/B测试组的分配。
- 特点:
Geospatial(地理位置)
- 特点:
- 存储地理位置数据。
- 提供半径查询、距离计算等功能。
- 常用命令:
GEOADD key longitude latitude member
:添加地理位置。GEORADIUS key longitude latitude radius unit
:查询指定半径内的成员。GEODIST key member1 member2 unit
:计算两个成员之间的距离。
- 使用场景:
- 附近的人/店:社交应用、电子商务应用的地理位置查询。
- 路线规划:计算距离和路径优化。
- 特点:
Streams
- 特点:
- 日志数据结构。
- 支持消费组,可以实现消费者分组。
- 常用命令:
XADD key * field value [field value ...]
:添加日志条目。XRANGE key start end
:按范围获取条目。XREAD COUNT count STREAMS key ID
:读取新条目。XGROUP CREATE key groupname id
:创建消费组。
- 使用场景:
- 日志收集:实时日志收集和处理。
- 消息队列:复杂的消息队列系统,支持多消费者。
- 事件溯源:记录和回放事件。
- 特点:
二、分布式锁类型
使用分布式锁时,要考虑锁的超时时间、锁的可重入性、锁的安全性等方面的问题,以确保系统的正确性和性能。
Redis中的分布式锁类型:
- 基于SETNX命令的分布式锁:
- 实现:利用Redis的原子性操作,通过
SETNX
命令来实现锁的获取,当某个客户端成功地将一个特定的键设置为锁时,其他客户端无法再设置该键,从而实现分布式锁的效果。 - 优点:简单、易于实现。
- 缺点:存在死锁问题,因为如果持有锁的客户端在执行完业务逻辑之前出现异常或者宕机,锁将永远得不到释放。
- 解决方案:
- 引入看门狗机制:通过为获取到锁的客户端设置一个过期时间,并使用一个后台线程(即看门狗)来监控锁的状态,确保锁在业务处理完成之后能够自动释放。
- 原理:即使持有锁的客户端异常退出或宕机,由于锁设置了过期时间并且看门狗会不断更新锁的过期时间,其他客户端在一定时间内仍然能够获取到锁。
- 实现:利用Redis的原子性操作,通过
- 基于Redlock算法的分布式锁:
- 实现:
Redlock算法
是由Redis的作者提出的一种分布式锁算法,它通过在多个独立的Redis实例上加锁来实现分布式锁。 - 具体实现过程:获取当前时间戳、尝试在多个实例上加锁、验证大多数实例是否成功加锁、如果大多数实例成功加锁则表示获取锁成功,否则表示获取失败。
- 优点:相对较为安全,能够避免单点故障导致的死锁问题。
- 缺点:实现相对复杂,需要确保各个Redis实例的时间同步。
- 实现:
- 使用Redisson实现分布式锁:
- 原理:
Redisson
实现的分布式锁利用了Redis的特性,并且内置了看门狗机制,并且Redisson内部已经处理好了看门狗机制。当你获取到锁后,Redisson会启动一个后台线程,周期性地延长锁的过期时间(每隔10秒),确保锁在持有期间不会因为过期而被自动释放。这个机制有效地防止了因意外长时间持锁导致的死锁问题。 - 实现步骤:
- 客户端尝试获取锁时,设置一个带有过期时间的键值对(即锁),并给该键设置一个适当的过期时间,一般可以使用
SET key value PX milliseconds NX
命令来实现。 - 在获取锁成功后,启动一个后台线程(即看门狗),不断延长锁的过期时间以防止锁过早释放。
- 在业务处理完成后,客户端主动释放锁,通过
DEL key
命令删除锁的键。
- 客户端尝试获取锁时,设置一个带有过期时间的键值对(即锁),并给该键设置一个适当的过期时间,一般可以使用
- 实现流程:
- 原理:
(1)引入Redisson的依赖。
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.3</version> </dependency>
(2)配置Redisson客户端。
import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; public class RedissonConfig { public static RedissonClient createClient() { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); return Redisson.create(config); } }
(3)通过Redisson客户端来获取和释放分布式锁。
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; public class DistributedLockExample { public static void main(String[] args) { RedissonClient redissonClient = RedissonConfig.createClient(); // 获取名为 "myLock" 的锁 RLock lock = redissonClient.getLock("myLock"); try { // 尝试获取锁,等待时间为100秒,如果获取成功,则持有锁10秒 if (lock.tryLock(100, 10, TimeUnit.SECONDS)) { try { // 锁获取成功,执行需要加锁的业务逻辑 System.out.println("锁定成功,执行业务逻辑"); } finally { // 释放锁 lock.unlock(); } } else { System.out.println("未能获取到锁"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { redissonClient.shutdown(); } } }
使用分布式锁场景:
- 控制对共享资源的访问:例如限制对数据库的并发访问,保证数据的一致性。
- 防止重复操作:例如在订单支付时,需要防止用户重复支付同一笔订单。
- 避免缓存击穿:通过加锁避免大量并发请求同时穿透缓存直接请求数据库。
三、事物命令类型
Redis中的事务是一组命令的原子性操作,可以保证这组命令要么全部执行成功,要么全部失败。Redis事务使用
MULTI、EXEC、DISCARD和WATCH
等命令进行控制。
Redis事务命令类型:
- MULTI命令:开始事务。
- MULTI和EXEC之间:可以执行多个命令,这些命令会被放入一个队列中,而不是立即执行。
- EXEC命令:提交事务,Redis会按照顺序执行队列中的命令,如果所有命令都执行成功,事务成功提交;否则,事务失败。
- DISCARD命令:取消事务,清空事务队列中的所有命令。
- WATCH命令:监视一个或多个键,如果在事务执行期间被监视的键发生了变化,事务将被中断。
Redis事务使用场景:
- 原子性操作:Redis事务可以确保一组操作的原子性,要么全部执行成功,要么全部失败回滚。这对于需要执行多个命令才能完成的操作非常有用,例如购买商品时同时减少库存、记录订单等操作可以放在一个事务中。
- 批量操作:Redis事务可以将多个命令一次性发送给服务器执行,减少网络延迟开销。通过将多个操作放在一个事务中,可以提高性能和效率。
- 乐观锁机制:通过
WATCH
命令和事务结合,可以实现乐观锁机制。在操作某个键之前,先通过WATCH
命令监视该键,如果在执行事务期间该键的值被修改,则事务执行失败。可以利用这个特性实现乐观锁来保证并发操作的一致性。 - 批量写入缓存:在某些场景下,需要将多个键值对同时写入Redis缓存中,保证数据的一致性。使用事务可以将这些写入命令放入队列中,然后一次性执行,避免了在执行过程中被其他操作干扰。
- 实现简单的队列:通过将任务放入队列中,然后使用事务和
BRPOP
命令可以实现简单的队列功能。多个客户端可以并发地将任务推入队列,然后通过事务的方式一次性取出多个任务进行处理。
Redis事物使用Demo:
import redis.clients.jedis.Jedis; import redis.clients.jedis.Response; import redis.clients.jedis.Transaction; public class RedisTransactionExample { public static void main(String[] args) { // 使用Jedis库连接Redis服务器 Jedis jedis = new Jedis("localhost", 6379); // 开启事务 Transaction transaction = jedis.multi(); // 将多个命令添加到事务中 transaction.set("key1", "value1"); transaction.set("key2", "value2"); // 执行事务,并返回执行结果 Response<String> response1 = transaction.get("key1"); Response<String> response2 = transaction.get("key2"); // 执行事务 // 在事务中,命令并不会立即执行,而是放入队列中等待exec()命令执行 transaction.exec(); // 获取执行结果 String value1 = response1.get(); String value2 = response2.get(); System.out.println("Value of key1: " + value1); System.out.println("Value of key2: " + value2); // 关闭连接 jedis.close(); } }
**ps:**Redis事务并不是严格的ACID事务,它没有提供隔离级别和回滚日志等特性。在Redis事务中,如果某个命令执行失败,后续的命令仍然会继续执行,而不会回滚到事务开始之前的状态,对于强一致性要求较高的场景,Redis事务可能不适用。
四、事物三大特性类型
Redis事务适用于对数据一致性要求较低但需要确保操作顺序的场景。对于更复杂的事务管理和更高的数据一致性要求,可能需要考虑使用关系型数据库或其他解决方案。
- 单独隔离操作
- 特性描述:
- Redis事务中的所有命令都会被依次执行,中间不会插入其他客户端的命令。
- 这意味着在一个事务中,一系列命令要么全部执行,要么全部不执行,不存在部分执行的情况。
- 使用场景:
- 计数器操作:确保多个增减操作在同一时间点的原子性。例如,某个文章的点赞数同时被多个用户点击时,通过事务确保计数器的正确性。
- 集合操作:对集合进行批量操作时,确保整个操作的原子性和一致性。
- 代码Demo:
- 特性描述:
import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; public class RedisTransactionExample { public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); // 开启事务 Transaction transaction = jedis.multi(); try { // 将多个命令打包到一个事务中 transaction.incr("counter"); transaction.rpush("queue", "message1"); transaction.set("key1", "value1"); // 执行事务 transaction.exec(); } catch (Exception e) { e.printStackTrace(); // 处理事务执行失败的情况,放弃事务 transaction.discard(); } finally { jedis.close(); } } }
- 没有隔离级别的概念
- 特性描述:
- Redis事务中的命令是按照顺序串行执行的,不存在并发访问数据的情况,因此也就不存在传统数据库中的隔离级别(如读已提交、可重复读、幻读等)。
- 使用场景:
- 简单的读写操作:避免复杂的并发控制需求,确保单一客户端在事务期间的数据操作不被其他客户端干扰。
- 确保顺序性:在需要严格顺序执行的操作场景下,事务提供了一种方便的实现方式。
- 代码Demo:
- 特性描述:
import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; public class RedisTransactionExample { public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); // 开启事务 Transaction transaction = jedis.multi(); try { // 将多个命令按顺序打包到一个事务中 transaction.set("key1", "value1"); transaction.set("key2", "value2"); transaction.incr("counter"); // 执行事务 transaction.exec(); } catch (Exception e) { e.printStackTrace(); // 处理事务执行失败的情况,放弃事务 transaction.discard(); } finally { jedis.close(); } } }
- 不保证原子性
- 特性描述:
- 虽然Redis事务将多个命令打包成一个执行单元,但它并不完全保证原子性。
- 如果在事务执行过程中出现了错误,事务中已执行的命令不会回滚,而是会继续执行未执行的命令。
- 使用场景:
- 批量更新操作:在需要更新多个键值对的场景下,事务可以简化代码逻辑,尽管不能完全保证原子性,但通过合理的错误处理可以满足业务需求。
- 数据一致性要求不高的操作:例如日志记录、缓存更新等,即使部分命令执行失败,也不会对整体系统造成巨大影响。
- Java代码示例:
- 特性描述:
import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; public class RedisTransactionExample { public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); // 开启事务 Transaction transaction = jedis.multi(); try { // 将多个命令打包到一个事务中 transaction.set("key1", "value1"); transaction.incr("counter"); // 故意引入错误的命令(例如对不存在的哈希字段进行递增) transaction.hincrBy("nonexistent_hash", "field", 1); // 执行事务 transaction.exec(); } catch (Exception e) { e.printStackTrace(); // 处理事务执行失败的情况,放弃事务 transaction.discard(); } finally { jedis.close(); } } }
无论世界如何纷扰,心中有梦,便有了方向