作者:ChenZhen
本人不常看网站消息,有问题通过下面的方式联系:
- 邮箱:1583296383@qq.com
- vx: ChenZhen_7
我的个人博客地址:https://www.chenzhen.space/🌐
版权:本文为博主的原创文章,本文版权归作者所有,转载请附上原文出处链接及本声明。📝
如果对你有帮助,请给一个小小的star⭐🙏
最近在学习Redis,这个是我做的笔记,另外推荐黑马Redis的学习视频,比硅谷讲的更详细,而且实战很多)
黑马程序员Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+redis实战
安装
大多数企业都是基于Linux服务器来部署项目,而且Redis官方也没有提供Windows版本的安装包。因此我们会基于Linux系统来安装Redis.
下载安装包: 链接:https://pan.baidu.com/s/1VCHAwvVqFuhNTibQl6bd0Q?pwd=zb47 提取码:zb47
依赖库
Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖:
CentOS
下:
yum install -y gcc tcl
Ubuntu
下:
apt install gcc
上传安装包并解压
然后将提供的Redis安装包上传到虚拟机的任意目录:
例如,我放到了/opt 目录:
在该目录下解压缩:
tar -xzf redis-6.2.6.tar.gz
解压后,进入redis目录:
cd redis-6.2.6
运行编译命令:
make && make install
如果没有出错,应该就安装成功了。
默认的安装路径是在 /usr/local/bin
目录下:
该目录已经默认配置到环境变量,因此可以在任意目录下运行这些命令。其中:
- redis-cli:是redis提供的命令行客户端
- redis-server:是redis的服务端启动脚本
- redis-sentinel:是redis的哨兵启动脚本
启动
redis的启动方式有很多种,例如:
- 默认启动
- 指定配置启动
- 开机自启
默认启动
安装完成后,在任意目录输入redis-server命令即可启动Redis:
redis-server
这种启动属于前台启动
,会阻塞整个会话窗口,窗口关闭或者按下CTRL + C
则Redis停止。不推荐使用。如图:
指定配置启动
如果要让Redis以后台
方式启动,则必须修改Redis配置文件,就在我们之前解压的redis安装包下
cd /opt/redis-6.2.6
名字叫redis.conf
:
我们先将这个配置文件备份一份:
cp redis.conf redis.conf.bck
然后修改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 123321
#保护模式
protected-mode no
Redis的其它常见配置:
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
启动Redis:
# 进入redis安装目录
cd /opt/redis-6.2.6
# 运行指令redis-server,后面跟上你的redis.conf配置文件路径,如果在当前目录直接写文件名
redis-server redis.conf
查看后台进程,redis服务成功启动
ps -ef|grep redis
开放端口
要让外部能够成功连接上Redis,还要开放防火墙的6379端口
firewall-cmd --permanent --add-port=6379/tcp
permanent
:表示永久生效,即使机器重启
查看防火墙状态
firewall-cmd --list -all
6379端口已经开放
停止服务:
-
提供密码进行身份验证:
如果您在 Redis 配置文件中启用了密码(也称为认证密码或密钥),您需要使用
AUTH
命令提供密码进行身份验证。假设您的密码是 "your_password",则可以执行以下步骤:a. 打开 Redis 命令行客户端:
redis-cli
b. 使用
AUTH
命令提供密码进行身份验证:AUTH your_password
如果密码正确,Redis 将接受身份验证,并允许您执行其他命令。
-
检查密码或身份验证配置:
如果您无法确定密码或身份验证配置,可以检查 Redis 配置文件(通常是
redis.conf
)以查找密码设置。打开配置文件,查找类似于以下行的配置项:requirepass your_password
确保
requirepass
设置的密码与您尝试使用AUTH
命令提供的密码匹配。
开机自启
我们也可以通过配置来实现开机自启。
首先,新建一个系统服务文件:
vi /etc/systemd/system/redis.service
内容如下:
[Unit]
Description=redis-server
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/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
Redis桌面客户端
安装完成Redis,我们就可以操作Redis,实现数据的CRUD了。这需要用到Redis客户端,包括:
- 命令行客户端
- 图形化桌面客户端
- 编程客户端
.Redis命令行客户端
Redis安装完成后就自带了命令行客户端:redis-cli,使用方式如下:
redis-cli [options] [commonds]
其中常见的options有:
-h 127.0.0.1
:指定要连接的redis节点的IP地址,默认是127.0.0.1-p 6379
:指定要连接的redis节点的端口,默认是6379-a 123321
:指定redis的访问密码
其中的commonds就是Redis的操作命令,例如:
ping
:与redis服务端做心跳测试,服务端正常会返回pong
不指定commond时,会进入redis-cli
的交互控制台:
图形化桌面客户端
GitHub上的大神编写了Redis的图形化桌面客户端,地址:https://github.com/uglide/RedisDesktopManager
不过该仓库提供的是RedisDesktopManager的源码,并未提供windows安装包。
在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases
引入相关依赖
SpringData是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实现
引入下面的依赖
<!-- spring boot整合redis场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
配置文件
spring:
redis:
host: 192.168.43.128 #Redis服务器的Ip
port: 6379
#password: 123456
lettuce:
pool:
max-active: 8 # 最大连接
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: 100 # 连接等待时间
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
测试
package com.chenzhen.springboot_redis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("age", "年龄");
Object age = redisTemplate.opsForValue().get("age");
System.out.println("age = " + age);
}
}
成功获取到值
序列化问题
当你以为已经成功利用springboot操作Redis的时候
但是当你查询Redis数据库会发现在Redis中存入的键值对和你输入的键值对并不一样 开头都加上了一堆乱码,这是为什么呢?
原因是springboot提供的操控Redis的方法的形参都是Object类型,需要经过序列化成为字符串才能存入Redis,而如果不指定那么会使用默认的jdk的序列化器,就会出现问题
这个时候呢我们需要配置一个配置类,指定springboot的序列化方式
RedisTemplate
默认使用的是JdkSerializationRedisSerializer
来进行存储
StringRedisTemplate
默认使用的是StringRedisSerializer
来进行存储
我们需要将对象存储的 key
使用 StringRedisSerializer
来进行序列号存储
对对象的 value
使用 Jackson2JsonRedisSerializer
来进行序列号存储 。
我们可以通过 往容器里注入 redisTemplate (用户自定义redisTemplate了,系统将不再提供默认的 redisTemplate)
直接复制即可
package com.chenzhen.springboot_redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* @author ChenZhen
* @Description
* @create 2022/10/6 20:15
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
//变双冒号为单冒号
.computePrefixWith(name -> name+":")
//设置过期时间600秒
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
在这里还需要引入一个依赖,这样才不会报错
这个依赖已经在springboot的web模块里面有了,但是这边我没有引入web的启动器,所以我单独引入这个依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
重新设置key-value'
查看redis数据库,发现已经成功存入,并且没有乱码
而且存入对象时会自动转化成json格式保存到redis数据库中
并且获取的时候,会自动将json格式转换为java对象
其实到这里已经可以结束了,但是尽管JSON的序列化方式可以满足我们的需求,却依然存在一些问题,如图:
为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销。
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。
当需要存储Java对象时,手动完成对象的序列化和反序列化。
StringRedisTemplate
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:
如果你使用的是手动序列化的方法,则之前配置的RedisTemplate就不需要了
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testStringTemplate() throws JsonProcessingException {
// 准备对象
User user = new User("虎哥", 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);
}
你可以直接编写springboot提供的API来操作缓存,当然你也可以使用更方便的注解方式。
使用注解操作
缓存介绍
Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。
其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方法上的,其核心思想是,当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存在缓存中。
@EnableCaching开启缓存注解的使用
1、开启基于注解的缓存,使用 @EnableCaching 标识在 SpringBoot 的主启动类上。
@SpringBootApplication
@EnableCaching//开启基于缓存的注解
public class SpringbootRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedisApplication.class, args);
}
}
(1)@Cacheable
根据方法对其返回值进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不 存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
属性值如下:
-
cacheNames / value(必填):用来指定缓存组件的名字,value和cacheNames作用一样,二者必须选一个,可以是数组的方式,支持指定多个缓存。
-
key :缓存数据时使用的 key,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写)
-
keyGenerator :key 的生成器。 key 和 keyGenerator 二选一使用
-
cacheManager :可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。当我们添加进redis相关的启动器之后, SpringBoot会使用RedisCacheConfigratioin 当做生效的自动配置类进行缓存相关的自动装配,容器中使用的缓存管理器是RedisCacheManager , 这个缓存管理器创建的Cache为 RedisCache , 进而操控redis进行数据的缓存
-
condition :可以用来指定符合条件的情况下才缓存
-
unless :否定缓存,与condition相对,当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)
-
sync :是否使用异步模式。默认是方法执行完,以同步的方式将方法返回的结果存在缓存中。
一个小案例,这里注意必须将注解标注方法的类交给spring管理,也就是加上要@Bean、@Service等标签,否则会失效
这里的#id就是spEL表达式,代表从方法的形参取id的值
直接编写测试方法运行 成功将返回值写入缓存。
小知识点:redis key 键名称中的冒号——命名空间层次的表示
redis中key的命名,用 冒号:分隔不同的层次命名空间,如:userId:12345,表示ID为12345的用户
如果表示某个对象的字段,也用冒号分隔,如:userId:123456:name,表示ID为12345的用户名
如果某个对象有字段的字段,用.连接。如user:id12345:friend.id。表示ID为12345的用户的朋友的ID
spEL 编写 key
前面说过,缓存的 key 支持使用 spEL 表达式去编写,下面总结一下使用 spEL 去编写 key 可以用的一些元数据:
@Cacheable(value="user",key="123") 这个注释的意思是,当调用这个方法的时候,会从缓存中查询键为user:123的值,如果没有,则执行实际的方法(即查询数据库),并将方法的返回结果存入缓存中。如果找到了,则直接返回缓存中的值,不会执行实际的方法(即查询数据库)
(2)@CachePut
和 @Cacheable 不同的是,使用该注解标志的方法,它每次都会触发真实方法的调用,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取数据,而不需要再去查询数据库。一般用在新增方法上。
@CachePut 注释,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新
大部分属性值与Cacheable类似
(3)@CacheEvict
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上,大部分属性值与*@Cacheable*相似,
- allEntries:清空所有缓存,默认为false
- beforeInvocation:是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
(4)@Caching
有时候会遇到一个方法需要多个注解的时候,我们可以用**@Caching**这个注解,同时设置多个缓存的信息设置,它能够为一个方法提供多个缓存配置
@Caching(
cacheable = {
@Cacheable(value = "name",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.email"),
}
)
@Caching(evict = {
@CacheEvict(value = "pageIndex",allEntries = true),
@CacheEvict(value = "recommendList",allEntries = true),
@CacheEvict(value = "blog",key = "#blog.id")
})