Redis 入门
1、初识 Redis
1.1、认识 NoSQL
1.2、认识 Redis
Redis 诞生于2009年,全称是 Remote Dictionary Server,远程词典服务器,是一个基于内存的键值型 NoSQL 数据库。
特征:
- 键值(key-value)型:value支持多种不同数据结构,功能丰富;
- 单线程:每个命令具备原子性;
- 低延迟,速度快(基于内存、IO多路复用、良好的编码);
- 支持数据持久化;
- 支持主从集群、分片集群;
- 支持多语言客户端。
1.3、安装 Redis
1.3.1、Redis 安装说明
大多数企业都是基于 Linux 服务器来部署项目,而且 Redis 官方也没有提供 Windows 版本的安装包。因此我们会基于 Linux 系统来安装 Redis。
此处选择的 Linux 版本位 CentOS 7.
Redis 的官方网站地址:https://redis.io/
1.3.2、单机安装 Redis
1.3.2.1、安装 Redis 依赖
Redis 是基于 C 语言编写的,因此首选需要安装 Redis 所需要的 gcc 依赖。
yum install -y gcc tcl
1.3.2.2、上传压缩包并解压
将Redis压缩包 redis-6.2.13.tar.gz 上传到虚拟机的任意目录并进行解压:
tar -zxvf redis-6.2.13.tar.gz
进入redis目录:
cd ./redis-6.2.13/
运行编译命令:
make && make install
如果没有出错,应该就安装成功了。默认的安装路径是在 /usr/local/bin 目录下。该目录已经默认配置到环境变量,因此可以在任意目录下运行这些命令。其中:
- redis-cli:是 redis 提供的命令行客户端;
- redis-server:是 redis 的服务端启动脚本;
- redis-sentinel:是 redis 的哨兵启动脚本。
1.3.2.3、启动
安装完成后,在任意目录输入 redis-server 命令脚本可启动 Redis:
redis-server
1、默认启动
直接命令行输入 redis-server ,前台启动(ctrl+c 会退出)。
2、指定配置启动
如果要让 Redis以后台方式启动,则必须修改 Redis 配置文件,就在我们之前解压的 redis 安装包下(/usr/local/src/redis-6.2.13),名字叫 redis.conf。
我们先将这个配置文件备份一份:
cp redis.conf redis.conf.bak
然后修改 redis.conf 文件中的一些配置:
# 允许访问的地址,默认是 127.0.0.1,会导致只能在本地访问,修改为 0.0.0.0 则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 守护进程,修改位 yes 后即可后台运行
daemonize yes
# 密码,设置后访问 Redis 必须输入密码
requirepass 123456
Redis 的其他常见配置:
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行 redis-server 时的命令,日志、持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认16个库,编号0到15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
启动 Redis:
# 进入 Redis 安装目录
cd /usr/local/src/redis-6.2.13
# 启动
redis-server redis.conf
停止服务:
# 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务
# 因为之前配置了密码,因此需要通过 -u 来指定密码
redis-cli -u 123456 shutdown
3、开机自启
我们也可以通过配置来实现开机自启。首先,新建一个系统服务文件:
vi /etc/systemd/system/redis.service
内容如下:
[Unit]
Description=redis-server
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /home/redis-6.2.13/redis.conf
PrivateTmp=true
[Install]
WantedBy=multi-user.target
然后重载系统服务:
systemctl daemon-reload
现在,我们可以用下面这组命令来操作redis了:
# 启动
systemctl start redis
# 停止
systemctl stop redis
# 重启
systemctl restart redis
# 查看状态
systemctl status redis
执行下面的命令,可以让 redis 开机自启:
systemctl enable redis
2、Redis 客户端
安装完成 Redis,我们就可以操作 Redis,实现数据的 CRUD 了。这需要用到 Redis 客户端,包括:
- 命令行客户端;
- 图形化桌面客户端;
- 编程客户端。
2.1、Redis 命令行客户端
Redis 安装完成后就自带了命令行客户端:redis.cli,使用方式如下:
redis-cli [options] [commands]
其中常见的 options 有:
- -h 127.0.0.1:指定要连接的 redis 节点的 IP 地址,默认是 127.0.0.1;
- -p 6379:指定要连接的 redis 节点的端口,默认是 6379;
- -a 123456:指定 redis 的访问密码。
其中 commands 就是 redis 的操作命令,例如:
- ping:与 redis 服务端做心跳测试,服务端正常返回 pong。
不指定 commands 时,会进入 redis-cli 的交互控制台。
[root@test-host ~]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> redis-cli -h 127.0.0.1 -a 123456
(error) ERR unknown command `redis-cli`, with args beginning with: `-h`, `127.0.0.1`, `-a`, `123456`,
127.0.0.1:6379>
[root@test-host ~]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379>
[root@test-host ~]# redis-cli -h 127.0.0.1 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379>
[root@test-host ~]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> AUTH 123456
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name jack
OK
127.0.0.1:6379> set age 21
OK
127.0.0.1:6379> get name
"jack"
127.0.0.1:6379> get age
"21"
127.0.0.1:6379>
2.2、图形化桌面客户端
GitHub 上的大神写了 Redis 的图形化桌面客户端,地址:https://github.com/uglide/RedisDesktopManager 。不过该仓库提供的是 RedisDesktopManager 的源码,并未提供 Windows 安装包。
在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases
3、Redis 命令
3.1、Redis 数据结构介绍
Redis 是一个 key-value 的数据库,key 一般是 String 类型,不过 value 的类型多种多样:
- String:hello world
- Hash:{name:"jack", age:23}
- List:[A -> B -> C -> C]
- Set:{A, B, C}
- SortedSet:{A:1, B:2, C:3}
- GEO:{A:(120.3, 30.5)}
- BitMap:0101110001101
- HyperLog:011100001010
3.2、Redis 通用命令
通用指定是部分数据类型的,都可以使用的指令,常见的有:
- KEYS:查看符合模板的所有 key,不建议在生产环境设备上使用;
127.0.0.1:6379> keys name 1) "name"
- DEL:删除一个指定的key。
127.0.0.1:6379> mset k1 v1 k2 k2 k3 v3 OK 127.0.0.1:6379> keys * 1) "k2" 2) "age" 3) "k3" 4) "k1" 127.0.0.1:6379> del k1 k2 k3
- EXISTS:判断某一个键是否存在,1表示存在,0表示不存在。
127.0.0.1:6379> EXISTS name (integer) 1
- EXPIRE:给一个 key 设置有效期,有效期到期时该 key 会被自动删除。
# 设置 age 的剩余有效期为20秒 127.0.0.1:6379> expire age 20 (integer) 1
- TTL:查看一个 KEY 的剩余有效期。
-
127.0.0.1:6379> expire age 20 (integer) 1 127.0.0.1:6379> TTL age (integer) -2 127.0.0.1:6379> TTL age (integer) -2 127.0.0.1:6379> keys * 1) "name" 127.0.0.1:6379> expire name 1 (integer) 1 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> set age 18 OK 127.0.0.1:6379> TTL name (integer) -2 127.0.0.1:6379> TTL age (integer) -1
通过 help [command] 可以查看一个命令的具体用法,例如:
127.0.0.1:6379> help keys
KEYS pattern
summary: Find all keys matching the given pattern
since: 1.0.0
group: generic
3.3、String 类型
String 类型,也就是字符串类型,是 Redis 中最简单的存储类型。其 value 是字符串,不过根据字符串的格式不同,又可以分为3类:
- string:普通字符串;
- int:整数类型,可以做自增、自减操作;
- float:浮点类型,可以做自增、自减操作。
不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过 512m。
3.3.1、String 类型的常见命令
String 的常见命令有:
- SET:添加或者修改已经存在的一个String类型的键值对;
- GET:根据 key 获取 String 类型的 value;
- MSET:批量添加多个 String 类型的键值对;
- MGET:根据多个 key 获取多个 String 类型的 value;
- INCR:让一个整型的 key 自增1;
- INCRBY:让一个整型的 key 自增并指定步长,例如:incrby num2 让 num 值自增2;
- INCRBYFLOAT:让一个浮点类型的数字自增并指定步长;
- SETNX:添加一个 String 类型的键值对,前提是这个 key 不存在,否则不执行;如 setnx name jack 和 set name jack nx 等效。
- SETEX:添加一个 String 类型的键值对,并且指定有效期。如 setex name 10 jack 和 set name jack ex 10 等效。
3.4、key 的结构
Redis 的 key 允许有多个单词形成层级结构,多个单词之间用 ':' 隔开,格式举例如下:项目名:业务名:类型:id 。
如果 value 是一个 Java 对象,例如一个 User 对象,则可以将对象序列化为 JSON 字符串后存储:
key | value |
heima:user:1 | {"id":1, "name":"Jack","age":21} |
heima:product:1 | {"id":1,"name":"小米11","price":4999} |
3.5、Hash 类型
Hash 类型,也叫散列,其 value 是一个无序字典,类似于 Java 中的 HashMap 结构。String 结构是将对象序列化为 JSON 字符串后存储,当需要修改对象某个字段时很不方便,Hash 结构可以将对象中的每个字段独立存储,可以针对单个字段做 crud。
3.5.1、Hash 类型的常见命令
Hash 类型的常见命令有:
- HSET key field value:添加或者修改 hash 类型的 field 的值;如 HSET user name jack
- HGET key field:获取一个 hash 类型 key 的 field 的值;如 HGET user name
- HMSET:批量添加多个 hash 类型 key 的 field 的值;如 HSET user name jack age 18
- HMGET:批量获取多个 hash 类型的 key 的field 的值;如 HMGET user name age
- HGETALL:获取一个 hash 类型的 key 中的所有的 field 的 value;如 HGETALL user
- HKEYS:获取一个 hash 类型的 key 中的所有的 field;如 HKEYS user
- HVALS:获取一个 hash 类型的 key 中的所有的 value;如 HVALS user
- HINCRBY:让一个 hash 类型 key 的字段自增并指定步长;如 HINCRBY user age
- HSETNX:添加一个 hash 类型的 key 的 field 值,前提是这个 field 不存在,否则不执行。如 HSETNX user name lisi(如果已经设置过,那么该次设置无效)
3.6、List 类型
Redis 中的 List 类型与 Java 中的 LinkedList 类似,可以看作是一个双向链表结构。既可以支持正向检索也可以支持反向检索。
特征也与 LinkedList 类似:
- 有序;
- 元素可以重复;
- 插入和删除快;
- 查询速度一般。
3.6.1、List 类型的常见命令
List 的常见命令有:
- LPUSH key element ...:向列表左侧插入一个或多个元素;如 LPUSH users 1 2 3
- LPOP key:移除并返回列表左侧的第一个元素,没有则返回 nil;如 RPOP users 1(从左侧取1个元素)
- RPUSH key element ...:向列表右侧插入一个或多个元素;如 RPUSH users 1
- RPOP key:移除并返回列表右侧的第一个元素,没有则返回 nil;如 RPOP users 1(从右侧取1个元素)
- LRANGE key star end:返回一段角标范围内的所有元素;LRANGE users 1 2(索引从0开始,包括边界1和2,不会从列表中删除这些元素)
- BLPOP 和 BRPOP:与 LPOP 和 RPOP 类似,只不过在没有元素时等待指定时间,而不是直接返回 nil。如 BLPOP users 100(从左侧取1个元素,如果没有则等100秒)
3.7、Set 类型
Redis 的 Set 结构与 Java 中的 HashSet 类似,可以看作是一个 value 为 null 的 HashMap。因为也是一个 hash 表,因此具备与 HashSet 类似的特征:
- 无序;
- 元素不可重复;
- 查找快;
- 支持交集、并集、差集等功能。
3.7.1、Set 类型的常见命令
String 的常见命令有:
- SADD key member ... :向 set 中添加一个或多个元素;如 SADD s1 a b c
- SREM key memeber ...:移除 set 中的指定元素;如 SREM s1
- SCARD key:返回 set 中元素的个数;如 SCARD s1
- SISMEMBER key member:判断一个元素是否存在于 set 中。如 SISMEMBER s1 a
- SMEMBERS key:获取 set 中所有的元素;如 SMEMBERS s1
- SINTER key1 key2 ... :求 key1、key2 ... 的交集;如 SINTER s1 s2
- SDIFF key1 key2 ...:求 key1和key2的差集(key1中有而key2中没有的元素);如 SDIFF s1 s2
- SUNION key1 key2 ... :求 key1 和 key2 的并集。如 SUNION s1 s2
3.8、SortedSet 类型
Redis 的 SortedSet 是一个可排序的 set 集合,与 Java 中的 TreeSet 有些类似,但底层数据结构却差别很大。SortedSet 中的每一个元素都带有一个 score 属性,可以基于 score 属性对元素排序,底层的实现是一个跳表(SkipList)加 hash 表。
SortedSet 具备以下特性:
- 可排序;
- 元素不重复;
- 查询速度快。
因为 SortedSet 的可排序特性,经常被用来实现排行榜这样的功能。
3.8.1、SortedSet 类型的常见命令
SortedSet 的常见命令有:
- ZADD key score member:添加一个或多个元素到 sorted set,如果已经存在则更新其 score 值;ZADD students 85 jack 89 lucy 95 tom
- ZREM key member:删除 sorted set 中的一个指定元素;ZREM students tom
- ZSCORE key member:获取 sorted set 中的指定元素的 score 值;ZSCORE students jack
- ZRANK key member:获取 sorted set 中的指定元素的排名;ZRANK students jack
- ZCARD key:获取 sorted set 中的元素个数;ZCARD students
- ZCOUNT key min max:统计 score 值在给定范围内的所有元素个数;ZCOUNT students 80 90
- ZINCRBY key increment member:让 sorted set 中的指定元素自增,步长为指定的 increment 的值;ZINCRBY students 2 jack
- ZRANGE key min max:按照 score 排序后,获取指定排名范围内的元素;ZRANGE students 0 2(即前3名)
- ZRANGEBYSCORE key min max:按照 score 排序后,获取指定 score 范围内的元素;
- ZDIFF、ZINTER、ZUNION:求差集、交集、并集。
注意:所有的排名默认都是升序,如果要降序则在命令的 Z 后面添加 REV 即可(如 ZRANK 改成降序 ZRAEVRANK)。
4、Redis 的 Java 客户端
在 Redis 官网中提供了各种语言的客户端,地址:https://redis.io/clients
- jedis:以 Redis 命令作为方法名称,学习成本低,简单实用。但是 Jedis 实例是线程不安全的,多线程环境下需要基于连接池来使用。
- lettuce:Lettuce 是基于 Netty 实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持 Redis 的哨兵模式、集群模式和管道模式。
- Redisson:Redisson 是一个基于 Redis 实现的分布式、可伸缩的 java 数据结构集合。包含了诸如 Map、Queue、Lock、Semaphore、AtomicLong 等强大功能。
4.1、Jedis
Jedis 的官网地址:https://github.com/redis/jedis
1、引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
2、建立连接
private Jedis jedis;
@BeforeEach
void setUp() {
// 建立连接
jedis = new Jedis("192.168.1.1", 6379);
// 设置密码
jedis.auth("123456");
// 选择库
jedis.select(0);
}
3、测试 string
private static void testString() {
// 插入数据,方法名称就是 redis 命令名称,非常简单
String result = jedis.set("name", "zhangsan");
System.out.println("result=" + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name=" + name);
}
结果:
result=OK
name=zhangsan
4、释放资源
jedis.close()
代码演示:
public class TestJedis {
private Jedis jedis;
@Before
public void setUp() throws Exception {
jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("123456");
jedis.select(0);
}
@Test
public void testString() {
// 插入数据,方法名称就是 redis 命令名称,非常简单
String result = jedis.set("name", "zhangsan");
System.out.println("result=" + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name=" + name);
}
@Test
public void testHash() {
// 插入 hash 数据
jedis.hset("user:1", "name", "Jack");
jedis.hset("user:1", "age", "21");
// 获取
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
}
@After
public void after() throws Exception {
jedis.close();
}
}
4.1.1、Jedis 连接池
Jedis 本身是线程不安全的,并且频繁地创建和销毁连接会有性能损耗,因此我们推荐大家使用 Jedis 连接池代替 Jedis 的直连方式。
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接
jedisPoolConfig.setMaxTotal(8);
// 最大空闲连接
jedisPoolConfig.setMaxIdle(8);
// 最小空闲连接
jedisPoolConfig.setMinIdle(0);
// 设置最长等待时间 ms(没有空闲连接的等待时间)
jedisPoolConfig.setMaxWaitMillis(200);
jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379, 1000, "123456");
}
// 获取 Jedis 对象
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
4.2、SpringDataRedis
SpringDataRedis 是 Spring 中数据操作的模块,包含对各种数据库的集成,其中对 Redis 的集成模块就叫做 SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis
- 提供了对不同 Redis 客户端的整合(Lettuce 和 Jedis);
- 提供了 RedisTemplate 统一 API 来操作 Redis;
- 支持 Redis 的发布订阅模型;
- 支持 Redis 哨兵和 Redis 集群;
- 支持基于 Lettuce 的响应式编程;
- 支持基于 JDK、JSON、字符串、Spring 对象的数据序列化及反序列化;
- 支持基于 Redis 的 JDKCollection 实现。
4.2.1、SpringDataRedis 快速入门
SpringDataRedis 中提供了 RedisTemplate 工具类,其中封装了各种对 Redis 的操作,并且将不同数据类型的操作 API 封装到了不同的类型中。
API | 返回值类型 | 说明 |
redisTemplate.opsForValue() | ValueOperations | 操作 String 类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作 Hash 类型数据 |
redisTemplate.opsForList() | ListOperations | 操作 List 类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作 Set 类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作 SortedSet 类型数据 |
redisTemplate | 通用的命令 |
SpringBoot 已经提供了对 SpringDataRedis 的支持,使用非常简单:
1、引入依赖
<!-- Redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、配置文件
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
lettuce:
pool:
max-active: 8 # 最大连接
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: 100 # 连接等待时间
3、注入 RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
4、编写测试
@SpringBootTest
class SpringBootRedisStudyApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testString() {
// 插入一条 String 类型数据
redisTemplate.opsForValue().set("name", "李四");
// 读取一条 String 类型数据
Object name = redisTemplate.opsForValue().get("name");
System.out.println("name=" + name);
}
}
4.2.2、SpringDataRedis 的序列化方式
RedisTemplate 可以接收任意的 Object 作为值写入 Redis,只不过写入前会把 Object 序列化为字节形式,默认是采用 JDK 序列化。
缺点:可读性差、内存占用较大。
我们可以自定义 RedisTemplate 的序列化方式,代码如下:
<!-- jackson 依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 创建 Template
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// key 和 value
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
// value 和 hashValue 采用 Json 序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setValueSerializer(jsonRedisSerializer);
return redisTemplate;
}
}
4.2.3、StringRedisTemplate
尽管 JSON 的序列化方式可以满足我们的要求,但依然存在一些问题,如:
{
"@class": "com.redis.pojo.User",
"name": "Jack",
'age": 21
}
为了在反序列化时知道对象的类型,JSON 序列化器会将类的 class 类型写入 json 的结果中,存入 Redis,会带来额外的内存开销。
为了节省空间,我们并不会使用 JSON 序列化器来处理 Value,而是统一使用 String 序列化器,要求只能存储 String 类型的 key 和 value。当需要存储 Java 对象时,手动完成对象的序列化和反序列化。
Spring默认提供了一个 StringRedisTemplate 类,它的 key 和 value 的序列化方式默认就是 String 的方式。省去了我们自定义 RedisTemplate 的过程:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
}
@SpringBootTest
public class Test {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON 工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
public void testStringTemplate() throws JsonProcessingException {
// 准备对象
User user = new User("Jack", 18);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入一条数据到 Redis
stringRedisTemplate.opsForValue().set("user:200", json);
// 读取数据
String val = stringRedisTemplate.opsForValue().get("user:200");
// 反序列化
User user1 = mapper.readValue(val, User.class);
System.out.println("user1=" + user1);
}
@Test
public void testHash() {
stringRedisTemplate.opsForHash().put("user:400", "name", "Jack");
stringRedisTemplate.opsForHash().put("user:400", "age", "20");
Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:400");
System.out.println("entries=" + entries);
}
}