redis与springboot整合使用
一、项目配置与使用
1、引入的依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
</parent>
<dependencies>
<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>
<!-- jackson 用于redis配置序列化使用,因为没引入mvc模块,所以手动引入了下 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、配置文件
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
#password: 123456
lettuce:
pool:
max-idle: 8
min-idle: 8
max-active: 8
max-wait: 100
3、编写配置类与实体类,实体类必须实现序列化接口并有get set方法,否则会序列化出错,序列化器有俩选择,它们的区别在于存储时的值
GenericJackson2JsonRedisSerializer :存储的是{“@class”:“com.xp.User”,“name”:“徐鹏”,“age”:24},该对象可以反序列化为我们的实体类对象,缺点就是体积大了一些。
Jackson2JsonRedisSerializer :存储的是{“name”:“徐鹏”,“age”:24},推荐用这种,使用时转为JSON对象使用
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
//设置序列化工具 - 有两种
//GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
public class User implements Serializable {
private String name;
private int age;
public User(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
4、编写测试代码
@SpringBootTest
class RedisDemoApplicationTests {
// 配置了redis的序列化器后使用
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// key和值都是字符串可以用这个
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void test1(){
ValueOperations valueOperations = stringRedisTemplate.opsForValue();
valueOperations.set("key1","v1");
Object key1 = valueOperations.get("key1");
System.out.println((String) key1);//v1
}
@Test
void test2() throws Exception {
User u = new User("徐鹏", 24);
redisTemplate.opsForValue().set("key2",u);
Object key2 = redisTemplate.opsForValue().get("key2");
System.out.println(String.valueOf(key2));//{name=徐鹏, age=24}
}
}
5、截图效果
二、监听过期key
1、监听过期key需要修改redis配置文件
notify-keyspace-events Ex
2、修改上图代码配置类,新增如下配置
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
@Component
public class RedisListener extends KeyExpirationEventMessageListener {
public RedisListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = message.toString();
System.out.println("接收到了过期key" + expiredKey);//key2
String topic = new String(pattern);
System.out.println("渠道名称是" + topic);//keyevent@*__:expired
if(expiredKey.startsWith("user:")){
//业务逻辑
}else if(expiredKey.startsWith("order:")){
//业务逻辑
}
}
}
3、修改测试代码
@Test
voidtest2() throws Exception {
User u = new User("徐鹏", 24);
redisTemplate.opsForValue().set("key2",u);
// 5秒后key过期
redisTemplate.expire("key2",5, TimeUnit.SECONDS);
// 休眠10秒,让key过期监听器能触发
Thread.sleep(10000);
System.out.println("====进程结束=====");
}
三、redis实现分布式主键
解释:雪花算法,下图生成的是一个long类型的正数。long类型占用62位,为了保证是正正数,所以第一位一定是0。
获取当前秒数(用31位就可以装下),然后向左位移32位,会形成一个首位是0,后面31位是原本秒值所占的位,后面都是0。redis通过指定日期自增,或取出一个当天的唯一数字。防止冲突。
实际上相当每秒有四十多亿个key组成,而且redis是命令执行时单线程的,不用担心同一秒获取到了一样的自增数字
这个是当前秒,用32位就可以装下
01100011000101100101100111111111
左位移32位后
01100011000101100101100111111111 00000000 00000000 00000000 00000000
假设redis取出了1,它的二进制为就是如下
00000000 00000000 00000000 00000001
此时让他与秒值左位移使用|运算符号得出了
01100011000101100101100111111111 00000000 00000000 00000000 00000001
@Test
//void test3(String keyPrefix) {
void test3() {
String keyPrefix = "userid";
// 1.获取当前秒数 转换为二进制(不用数了是31位),1100011000101100101100111111111
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
System.out.println(Long.toBinaryString(nowSecond));
// 2.生成序列号自增长,按天自增
String yyyyMMdd = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
Long increment = redisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + yyyyMMdd, 0);
System.out.println(increment);
// 3.组装key,7139993210994032640
System.out.println(nowSecond << 32 | increment);
}
四、发布和订阅
项目启动时监听指定通道信息,当有信息时会触发onMessage(),可以做一些自己的业务逻辑。
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 订阅指定通道
container.addMessageListener(new MessageListener(){
@Override
public void onMessage(Message message, byte[] pattern) {
System.out.println(message.toString());
// 自己的业务逻辑................
}
}, new PatternTopic("channel_01"));
return container;
}
/**
* 测试类新增代码如下
*/
@Test
void test4() {
redisTemplate.convertAndSend("channel_01","我是发送的消息");
try {
Thread.sleep(10000);
}catch (Exception e){
}
}
五、分布式锁Redisson
因为redis自身执行命令是单线程的,我们可以通过setnx命令来实现分布式锁,但是这把锁是不可重入的。也就是递归行为的方法,因为自己持有锁了再次尝试获取会失败,导致线程死锁。
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
分布式锁的大致实现思路:
1.把原本的string类型替换为hash类型,
2.hash类型里面存储两个字段,一个是持有锁的线程名称,一个是计数器,还要设置锁的过期时间
3.使用时尝试添加数据,如果已存在,获取出来判断是否是当前线程持有锁,是的话,计数器+1,重置锁的过期时间
4.每次业务方法执行完时,先判断锁的计数器,如果为1直接删除key即可,大于1表示是重复进入方法,只需要将计数器-1即可
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
// 配置
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
// 配置的是单节点redis
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456");
return Redisson.create(config);
}
}
// 基本使用
@RestController
public class RedissonController {
@Resource
private RedissonClient redissonClient;
@RequestMapping("/test")
public void test() throws InterruptedException {
// 获取锁(可重入),指定锁的名称
RLock lock = redissonClient.getLock("test1");
// 尝试获取锁,锁的最大等待时间(期间会重试),自动释放时间,时间单位
boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
// 判断释放获取成功
if(isLock){
try {
System.out.println("执行业务");
}finally {
lock.unlock();
}
}
}
}