Redis简介
Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存、消息中间件。
官网:Redis官网
Redis是用C语言开发的一个高性能键值对(key-value)数据库,他存储的value类型比较丰富,也被成为结构化NoSql数据库。
NoSql(Not Only SQL),不仅仅是SQL,泛指非关系型数据库。NoSql数据库并不是要取代关系型数据库,而是关系型数据库的补充。
-
关系型数据库(RDBMS): Mysql、Oracal、DB2、SQLServer
-
非关系型数据库(NoSql): Redis、Mongo db、MemCached
-
Redis应用场景: 缓存、任务队列、消息队列、分布式锁
Redis下载与安装
Redis安装包分为windows版和Linux版
- Windows版下载地址:点击进入Windows版
- Linux版下载地址:点击进入Linux版
在Linux系统安装Redis步骤:
1、将Redis安装包上传到Linux
2、解压安装包,命令:tar -zxvf redis-4.0.0.tar.gz -C /usr/local
3、安装Redis的依赖环境gcc,命令:yum install gcc-c++
4、进入/usr/local/redis-4.0.0,进行编译,命令:make
5、进入Redis的src目录,进行安装,命令:make install
Redis的Windows版属于绿色软件,直接解压即可使用
Redis服务启动与停止
Linux环境中Redis服务启动,可以使用redis-server,切换到Redis的src目录,使用命令:./redis-server,默认端口号是6379,Ctrl + C 停止Redis服务。
Windows环境中Redis服务启动,直接点击redis-server.exe,默认端口号为6379,,Ctrl + C 停止Redis服务,如下:
客户端点击redis-cli.exe,如下:
Redis数据类型
Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:
- 字符串 string
- 哈希 hash
- 列表 list
- 集合 set
- 有序集合 sorted set
更多Redis命令详见:Redis中文网
字符串 string 操作命令
哈希 hash 操作命令
列表 list 操作命令
集合 set 操作命令
有序集合 sorted set 操作命令
通用命令
Java操作Redis
介绍
Redis 的 Java 客户端很多,官方推荐的有三种:
- Jedis
- Lettuce
- Redisson
Spring 对 Redis 客户端进行了整合,提供了 Spring Data Redis,在SpringBoot项目中还提供了对应的starter,即spring-boot-starter-redis
Jedis
Jedis的maven坐标:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
使用Jedis操作Redis的步骤:
- 获取连接
- 执行操作
- 关闭连接
第一步:新建Maven工程,导入以下依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
第二步:在测试中编写如下代码:
/**
* 使用Jedis操作Redis
* API和指令同名,自行测试
*/
public class JedisTest {
@Test
public void testRedis(){
// 1、获取连接(保证Redis服务是启动的)
Jedis jedis = new Jedis("localhost",6379);
// 2、执行具体的操作
String s = jedis.set("username", "test_limou");
System.out.println(s); // 输出OK
Set<String> keys = jedis.keys("*");
for (String key : keys) {
System.out.println(key);
}
// 3、关闭连接
jedis.close();
}
}
Spring Data Redis
Spring Data Redis 中提供了一个高度封装的类:RedisTemplate,针对Jedis客户端中大量api进行了归类,将同一类型的操作封装为operation接口,具体分类如下:
- ValueOperations:简单K-V操作
- SetOperations:set类型数据操作
- ZSetOperations:zset类型数据操作
- HashOperations:针对map类型的数据操作
- ListOperations:针对list类型的数据操作
在 SpringBoot 项目中。可以使用Spring Data Redis 来简化Redis操作,maven坐标如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
快速上手Spring Data Redis
1、创建springboot工程,引入依赖(我这里springboot版本2.4.5):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
2、配置application.yml文件
spring:
application:
name: springdataredis_demo
# Redis相关配置
redis:
host: localhost
port: 6379
# password: xxx
database: 0
jedis:
# Redis连接池配置
pool:
max-active: 8 # 最大连接数
max-wait: 1ms # 连接池最大阻塞等待时间
max-idle: 4 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中最小空闲连接
3、编写测试类,测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;
/**
* 操作String类型数据
*/
@Test
public void testString(){
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("city", "shanxi");
// 这时候再去key * 查看所有key的时候,会发现我们设置的key 并不是 city
// 而是:\xac\xed\x00\x05t\x00\x04city ,这样一串字符
// 这是因为这里设置key的时候是采用默认的方式将key序列化后存入数据库的
// 我们可以通过编写Redis配置类,修改默认序列化方式
// 在编写完配置类后,测试发现key:city 正常添加,但是value:\xac\xed\x00\x05t\x00\x06shanxi
// 这是因为配置类中并没有修改value的序列化方式
// 一般也不对value进行修改,当我们在程序中再去获取value值时,它会反序列化为正常值:shanxi
String value = (String) valueOperations.get("city");
System.out.println(value); // shanxi
valueOperations.set("key1", "value1", 10L, TimeUnit.SECONDS); // 10s过期
Boolean flag = valueOperations.setIfAbsent("city", "yulin"); // 如果有就设置,对应返回布尔值
System.out.println(flag);
}
}
4、Redis配置类如下:
/**
* Redis配置类
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// 默认的Key序列化器为:JdkSerializationRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
以下内容是个人练习使用的API,大家也可以自行测试:
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;
/**
* 操作String类型数据
*/
@Test
public void testString() {
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("city", "shanxi");
// 这时候再去key * 查看所有key的时候,会发现我们设置的key 并不是 city
// 而是:\xac\xed\x00\x05t\x00\x04city ,这样一串字符
// 这是因为这里设置key的时候是采用默认的方式将key序列化后存入数据库的
// 我们可以通过编写Redis配置类,修改默认序列化方式
// 在编写完配置类后,测试发现key:city 正常添加,但是value:\xac\xed\x00\x05t\x00\x06shanxi
// 这是因为配置类中并没有修改value的序列化方式
// 一般也不对value进行修改,当我们在程序中再去获取value值时,它会反序列化为正常值:shanxi
String value = (String) valueOperations.get("city");
System.out.println(value); // shanxi
valueOperations.set("key1", "value1", 10L, TimeUnit.SECONDS); // 10s过期
Boolean flag = valueOperations.setIfAbsent("city", "yulin"); // 如果有就设置,对应返回布尔值
System.out.println(flag);
}
/**
* 操作Hash类型数据
*/
@Test
public void testHash() {
HashOperations hashOperations = redisTemplate.opsForHash();
// 存数据
hashOperations.put("dog", "name", "大黄");
hashOperations.put("dog", "food", "骨头");
hashOperations.put("dog", "age", "6");
// 取数据
String name = (String) hashOperations.get("dog", "name");
String food = (String) hashOperations.get("dog", "food");
String age = (String) hashOperations.get("dog", "age");
System.out.println(name + " / " + food + " / " + age);
// 获得hash结构所有字段
Set dog = hashOperations.keys("dog");
for (Object o : dog) {
System.out.println(o); // name、food、age
}
// 获得hash结构所有值
List dogValues = hashOperations.values("dog");
for (Object dogValue : dogValues) {
System.out.println(dogValue); // 大黄、骨头、6
}
}
/**
* 操作List类型数据(存取类似队列)
*/
@Test
public void testList() {
ListOperations listOperations = redisTemplate.opsForList();
// 存数据(有序,可重复)
listOperations.leftPush("mylist", "a"); // 存一个
listOperations.leftPushAll("mylist", "b", "c", "d", "a"); // 存多个
// 取数据
List mylist = listOperations.range("mylist", 0, -1);// 取所有元素
for (Object o : mylist) {
System.out.println(o);
}
// 获得列表长度
Long sizeLong = listOperations.size("mylist");
int size = sizeLong.intValue();
for (int i = 0; i < size; i++) {
// 出队列
Object element = listOperations.rightPop("mylist");
System.out.println((i + 1) + " 移出--> " + element);
}
}
/**
* 操作Set类型数据
*/
@Test
public void testSet() {
SetOperations setOperations = redisTemplate.opsForSet();
// 存数据(无序、不可重复)
setOperations.add("myset-java", "a", "b", "c", "a");
// 取数据
Set myset = setOperations.members("myset-java");
for (Object o : myset) {
System.out.println(o);
}
// 删数据
Long remove = setOperations.remove("myset-java", "a", "b");
System.out.println("remove: " + remove);
// 取数据
Set myset2 = setOperations.members("myset-java");
for (Object o : myset2) {
System.out.println(o);
}
}
/**
* 操作ZSet类型的数据,SortedSet(有序集合,根据分数排序从小到大)
*/
@Test
public void testZSet() {
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
// 存数据
zSetOperations.add("myZSet", "a", 10.0);
zSetOperations.add("myZSet", "b", 11.0);
zSetOperations.add("myZSet", "c", 12.0);
zSetOperations.add("myZSet", "a", 13.0); // 覆盖掉上边的a
// 取数据
Set myZSet = zSetOperations.range("myZSet", 0, -1);
for (Object o : myZSet) {
System.out.println(o);
}
System.out.println("==========");
// 修改分数
zSetOperations.incrementScore("myZSet", "b", 20.0); // 为指定元素增加分数
// 取数据
myZSet = zSetOperations.range("myZSet", 0, -1);
for (Object o : myZSet) {
System.out.println(o);
}
System.out.println("==========");
// 删除元素
zSetOperations.remove("myZSet", "a", "b");
// 取数据
myZSet = zSetOperations.range("myZSet", 0, -1);
for (Object o : myZSet) {
System.out.println(o);
}
}
/**
* 通用操作,针对不同的数据类型都可以操作
*/
@Test
public void testCommon(){
// 获取Redis中所有的key
Set keys = redisTemplate.keys("*");
for (Object key : keys) {
System.out.println(key);
}
System.out.println("=============");
// 判断某个key是否存在
Boolean flagExists = redisTemplate.hasKey("myset");
System.out.println(flagExists);
System.out.println("=============");
// 删除指定key
Boolean flagDelete = redisTemplate.delete("myset");
System.out.println(flagDelete);
System.out.println("=============");
// 获取指定key对应的value数据类型
DataType type = redisTemplate.type("myset-java");
System.out.println(type.name());
}
}
项目中碰到的情况
1、缓存短信验证码(登录)
在发送验证码的方法中,将随机生成的验证码缓存到Redis中,并设置有效期5min
// 用redis存储,并且设置有效期5min
redisTemplate.opsForValue().set(phone, code, 5L, TimeUnit.MINUTES);
在后续登录方法中,直接从Redis中获取缓存的验证码
// 从redis中获取验证码
String codeInRedis = redisTemplate.opsForValue().get(phone);
登录成功直接删除Redis中的验证码
// 如果用户登录成功,删除Redis中缓存的验证码
redisTemplate.delete(phone);
2、缓存菜品数据
在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长,用Redis做缓存优化,提高系统性能。
首先想法是用菜品的分类Id作为Key,用当前分类下的菜品集合作为Value。
1、当我们去查询菜品的时候,首先从Redis中去获取菜品的数据,如果有直接返回,如果没有就去查询数据库,之后将查询到的菜品数据以及当前菜品分类的Id存入Redis中。
2、同时要注意在操作分类的新增和更新方法时候,去更新Redis内该分类Key对应的菜品Value值 ,根据分类的Id去清除缓存。
注意:在使用缓存过程中,要注意保证数据库中数据和缓存中数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据。