redis提供了redis列表这种轻量级的队列订阅监听服务,
redis队列的特点
1相对于kafka等队列消息服务,redis队列由于是基于内存操作,所以速度更快
2处理的数据量不如kafka大
3在一些异常场景下可能会丢失消息。
springboot实现redis队列订阅监听
pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
redis队列消息发布类RedisSender
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
/**
* 生产者
*/
@Service
public class RedisSender {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//向通道发送消息的方法
public void sendChannelMess(String channel, String message) {
stringRedisTemplate.convertAndSend(channel, message);
}
}
redis队列订阅监听类RedisSubListenerConfig
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import java.util.concurrent.*;
@Configuration
public class RedisSubListenerConfig {
//初始化监听器
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic("seckill"));
/**
* 如果不定义线程池,每一次消费都会创建一个线程,如果业务层面不做限制,就会导致秒杀超卖
*/
ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat("redis-listener-pool-%d").build();
Executor executor = new ThreadPoolExecutor(
1,
1,
5L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
factory);
container.setTaskExecutor(executor);
container.setSubscriptionExecutor(Executors.newFixedThreadPool(1));
return container;
}
//利用反射来创建监听到消息之后的执行方法
@Bean
MessageListenerAdapter listenerAdapter(RedisConsumer redisReceiver) {
return new MessageListenerAdapter(redisReceiver, "receiveMessage");
}
//使用默认的工厂初始化redis操作模板
@Bean
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
redis队列消费者类RedisConsumer
import com.seckilldemo.entity.Result;
import com.seckilldemo.redis.RedisUtil;
import com.seckilldemo.service.ISeckillService;
import com.seckilldemo.util.SeckillStatEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 消费者
*/
@Service
public class RedisConsumer {
private static final Logger logger = LoggerFactory.getLogger(RedisConsumer.class);
@Autowired
private ISeckillService seckillService;
@Autowired
private RedisUtil redisUtil;
public void receiveMessage(String message) {
Thread th=Thread.currentThread();
System.out.println("Tread name:"+th.getName());
//收到通道的消息之后执行秒杀操作(超卖)
String[] array = message.split(";");
if(redisUtil.getValue(array[0])==null){//control层已经判断了,其实这里不需要再判断了
Result result = seckillService.startSeckil(Long.parseLong(array[0]), Long.parseLong(array[1]));
if(result.equals(Result.ok(SeckillStatEnum.SUCCESS))){
logger.info("秒杀成功");
}else{
logger.info("秒杀失败");
redisUtil.cacheValue(array[0], "ok");//秒杀结束
}
}else{
logger.info("秒杀失败");
}
}
}
controller
@PostMapping("/startRedisQueue")
public Result startRedisQueue(long seckillId,long userId){
try {
redisUtil.cacheValue(seckillId+"", null);//秒杀结束
if(redisUtil.getValue(seckillId+"")==null){
//思考如何返回给用户信息ws
redisSender.sendChannelMess("seckill",seckillId+";"+userId);
return Result.ok();
}else{
return Result.error();
}
}catch (Exception e){
System.out.println(e);
}
return Result.error();
}
配置文件application.properties
# Redis
# 数据库索引(默认为0)
spring.redis.database=0
# 服务器地址 变更为自己的
spring.redis.host=111.229.94.187
# 服务器连接端口
spring.redis.port=6379
# 服务器连接密码(默认为空)如果有变更为自己的
spring.redis.password=redis123
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=-1
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=100
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=3000ms