1、redis发布订阅模式
Redis中的list结构可以作为队列来满足一些生产消费的业务场景。实际上Redis还提供了发布/订阅(publish/subscribe)模式来实现类似的生产消费的功能。
1.1 list与发布/订阅的不同
- list中的任务或消息无法被重复消费,消息被一个消费者pop 掉以后,其他消费者就获取不到了这个消息了。而发布/订阅模式中可以有多个订阅者消费同一个消息。
- list可以保存任务或消息,直到客户端连接之后才消费掉。但发布/订阅模式中订阅者无法获取到订阅之前的历史消息,由于这个缺陷,在一些严格的生产消费场景下,建议还是用MQ的主题模式。
1.2 什么是发布订阅模式
发布/订阅模式模式包含两种角色:发布者和订阅者。订阅者可以订阅一个或多个通道(channel),而发布者可以向指定的通道(channel)中发送消息,所有此通道的订阅者都会收到消息。
1.3 发布订阅实现
打开两个客户端,一个客户端订阅信道,一个客户端发送消息,另一个客户端则会收到消息,效果如下:


2、redis和SpringBoot整合
2.1 jedis测试
1、需要修改redis配置文件,设置redis.conf中注释掉bind 127.0.0.1 ,然后 protected-mode no 2、引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
3、测试demo
public class JedisDemo1 {
public static void main(String[] args) {
//创建Jedis对象
Jedis jedis = new Jedis("具体ip",6379);
//测试
String value = jedis.ping();
System.out.println(value);
jedis.close();
}
}
2、SpringBooot整合redis
1、引入依赖
<!-- 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>
<version>2.6.0</version>
</dependency>
2、 application.yml配置redis配置
# redis配置
redis:
# Redis服务器地址
host: 具体ip
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password: 具体密码
# Redis数据库索引(默认为0)
database: 1
jedis:
pool:
# 连接池中的最小空闲连接
max-idle: 8
# 连接池中的最大空闲连接
min-idle: 0
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
# 连接超时时间(毫秒)
timeout: 60000
3、 使用ObjectMapper 需要导入依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>
4、添加redis的配置类
/**
* 功能说明: redis配置类
*
*
* @author yangxu
* @date 2022/4/3
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
// 改为自己要操作的对象模式
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// Object 如何序列化的问题
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key: String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash: String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// Object: Jsckson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hashObject: Jsckson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
// 其与的类型设置同理
template.afterPropertiesSet();
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);
// 配置序列化(解决乱码的问题),过期时间3600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(3600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();
return cacheManager;
}
}
5、测试 1、测试demo
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String testRedis() {
String name = "xuzai";
//设置值到redis
redisTemplate.opsForValue().set("name",name);
//从redis获取值
String username = (String)redisTemplate.opsForValue().get("name");
return username;
}
}
2、测试效果

3、redis事务
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
MULTI,EXEC,DISCARD,WATCH 四个命令是 Redis 事务的四个基础命令。其中:
# MULTI,告诉 Redis 服务器开启一个事务。注意,只是开启,而不是执行 。
# EXEC,告诉 Redis 开始执行事务 。
# DISCARD,告诉 Redis 取消事务。
# WATCH,监视某一个键值对,它的作用是在事务执行之前如果监视的键值被修改,事务会被取消。
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。 组队的过程中可以通过discard来放弃组队。


3.1 事务的错误处理
组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。

如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

3.2 WATCH key [key2]...
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
3.3 unwatch
取消 WATCH 命令对所有 key 的监视。 如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。
3.4 Redis事务小结
①Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败 ②事务的三个阶段: 开启:以MULTI开始一个事务 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到待执行的事务队列里面 执行:由EXEC命令触发事务 ③事务的三个特性: a、单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 b、没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题 c、不保证原子性:有时redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。
评论