Redis介绍
什么是Redis?
Redis是一个基于内存的键值对(Key-Value)数据库,它支持多种类型的数据结构,如字符串,散列,列表,集合,有序集合,位图, hyperloglogs和地理空间索引,并在这些数据类型上定义了原子操作。它内置了复制,LUA脚本,LRU驱动事件,事务和不同级别的 磁盘持久化, 并通过Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性。
Redis的优缺点
优点:
- 基于内存操作,内存读写速度快。
- Redis是单线程的,避免线程切换开销及多线程的竞争问题。单线程是指网络请求使用一个线程来处理,即一个线程处理所有网络请求,Redis 运行时不止有一个线程,比如数据持久化的过程会另起线程。
- 支持多种数据类型,包括String、Hash、List、Set、ZSet等。
- 支持持久化。Redis支持RDB和AOF两种持久化机制,持久化功能可以有效地避免数据丢失问题。
- 支持事务。Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
- 支持主从复制。主节点会自动将数据同步到从节点,可以进行读写分离。
缺点:
- 对结构化查询的支持比较差。
- 数据库容量受到物理内存的限制,不适合用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的操作。
- Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
Redis客户端
在Java中,与Redis交互的三种主要客户端是Jedis、Lettuce和Redisson。以下是每种客户端的特点和适用场景:
-
Jedis:
- 简介:Jedis是Redis官方推荐的Java客户端库,提供了比较全面的Redis命令支持。
- 优点:简单易用,支持连接池,支持pipelining、事务、LUA Scripting、Redis Sentinel、Redis Cluster等Redis高级特性。
- 缺点:非线程安全,不适合在多线程环境中共享同一个Jedis实例,性能相对较差。使用Jedis连接池可能可以解决。
- 适用场景:适合中小规模项目,或者对线程安全要求不高的场景。
- 文档:Jedis GitHub
-
Lettuce:
- 简介:Lettuce是一个可伸缩的线程安全的Redis客户端,支持同步和异步操作。
- 优点:基于Netty框架,支持异步非阻塞I/O,性能高,线程安全,适合在多线程环境中使用。
- 缺点:学习曲线相对较陡,API相对复杂。
- 适用场景:适合需要高性能和线程安全的场景,特别是在Spring Boot 2.x版本中作为默认的Redis客户端。
- 文档:Lettuce官网
-
Redisson:
- 简介:Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。
- 优点:提供了丰富的分布式数据结构和分布式服务,如分布式锁、原子计数器、分布式集合等,支持异步操作和事件驱动的通信。
- 缺点:与Jedis和Lettuce相比,功能较为复杂,对字符串的操作支持较差。
- 适用场景:适合需要使用Redis高级功能和分布式服务的复杂应用场景。
- 文档:Redisson官网
在选择Redis客户端时,应根据项目需求、性能要求以及团队的技术栈来决定。如果需要简单的键值存储和基本的Redis操作,Jedis可能是一个快速且简单的选择。如果需要高性能和线程安全的操作,Lettuce可能是更好的选择。而对于需要分布式缓存和复杂数据结构的场景,Redisson提供了更多的高级功能和便利性。
Redis为什么快?
-
基于内存:Redis是使用内存存储,没有磁盘IO上的开销。数据存在内存中,读写速度非常快。当然Redis也存在持久化操作,但是是fork子进程和利用 Linux 系统的页缓存技术来完成,并不会影响Redis的读写性能。
-
单线程实现( Redis 6.0以前):Redis使用单个线程处理请求,避免了多个线程之间线程切换和锁资源争用的开销。
-
IO多路复用模型:Redis 采用 IO 多路复用技术。Redis 使用单线程来轮询描述符,将数据库的操作都转换成了事件,不在网络I/O上浪费过多的时间。
-
高效的数据结构:Redis 每种数据类型底层都做了优化,目的就是为了追求更快的速度。
因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈。Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。
Redis 6.0在网络模型上加入多线程IO来解决网络IO的性能瓶颈。 此时IO读写是多线程的,执行命令依旧是单线程的。
怎么判断redis操作是否是原子操作?
首先,redis的命令执行(数据操作)都是原子操作。
其次,多个命令执行。如果set+expire被组成了一条lua脚本,它是原子操作。如果set、expire分成两条lua脚本执行,它不是原子操作,因为set与expire之间,可能会发生其它数据操作。