SpringBoot|Redis|redisson|订阅|定时任务

一、SpringBoot引入Redis

1、引入Redis

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>

2、配置yml

spring:
  redis:
  	open: true
    host: localhost
    port: 6379
    password: 123456
    timeout: 6000ms # 连接超时时长(毫秒)
    jedis:
      pool:
        max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms  # 连接池最大阻塞等待时间(使用负值表示没有限制)
        min-idle: 0 # 连接池中的最小空闲连接
        max-idle: 8 # 连接池中的最大空闲连接
    database: 0

3、配置redis

/**
 * Redis配置
 *
 * @author Mark sunlightcs@gmail.com
 */
@Configuration
public class RedisConfig {
    @Autowired
    private RedisConnectionFactory factory;

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }

    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    @Bean
    public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }
}

4、RedisUtil类

@Component
public class RedisUtils {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private ValueOperations<String, String> valueOperations;
    @Autowired
    private HashOperations<String, String, Object> hashOperations;
    @Autowired
    private ListOperations<String, Object> listOperations;
    @Autowired
    private SetOperations<String, Object> setOperations;
    @Autowired
    private ZSetOperations<String, Object> zSetOperations;
    /**  默认过期时长,单位:秒 */
    public final static long DEFAULT_EXPIRE = 60 * 60 * 24;
    /**  不设置过期时长 */
    public final static long NOT_EXPIRE = -1;

    public void set(String key, Object value, long expire){
        valueOperations.set(key, toJson(value));
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public void set(String key, Object value){
        set(key, value, DEFAULT_EXPIRE);
    }

    public <T> T get(String key, Class<T> clazz, long expire) {
        String value = valueOperations.get(key);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value == null ? null : fromJson(value, clazz);
    }

    public <T> T get(String key, Class<T> clazz) {
        return get(key, clazz, NOT_EXPIRE);
    }

    public String get(String key, long expire) {
        String value = valueOperations.get(key);
        if(expire != NOT_EXPIRE){
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value;
    }

    public String get(String key) {
        return get(key, NOT_EXPIRE);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * Object转成JSON数据
     */
    private String toJson(Object object){
        if(object instanceof Integer || object instanceof Long || object instanceof Float ||
                object instanceof Double || object instanceof Boolean || object instanceof String){
            return String.valueOf(object);
        }
        return JSON.toJSONString(object);
    }

    /**
     * JSON数据,转成Object
     */
    private <T> T fromJson(String json, Class<T> clazz){
        return JSON.parseObject(json,clazz);
    }
}

5、Redis切面处理类

/**
 * Redis切面处理类
 *
 * @author Mark sunlightcs@gmail.com
 */
@Aspect
@Configuration
public class RedisAspect {
    private Logger logger = LoggerFactory.getLogger(getClass());
    //是否开启redis缓存  true开启   false关闭
    @Value("${spring.redis.open: false}")
    private boolean open;

    @Around("execution(* cn.amoqi.springboot.redis.util.RedisUtils.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        if(open){
            try{
                result = point.proceed();
            }catch (Exception e){
                logger.error("redis error", e);
                throw new RuntimeException("Redis服务异常");
            }
        }
        return result;
    }
}

二、引入redisson

1、注解

        <!-- redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.10.4</version>
        </dependency>

2、配置Redisson

2.1 简单配置

    //简单使用
    //@Bean
    public RedissonClient redissonClient(@Value("${spring.redis.host}") String host,@Value("${spring.redis.port}") String  port,
                                         @Value("${spring.redis.password}") String password){
        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer();
        singleServerConfig.setAddress("redis://"+host+":"+port);
        if(StringUtils.hasText(password)){
            singleServerConfig.setPassword(password);
        }
        return Redisson.create(config);
    }

2.2 配置文件配置

在resource里放入redisson.yaml

singleServerConfig:
    idleConnectionTimeout: 10000
    connectTimeout: 10000
    timeout: 3000
    retryAttempts: 3
    retryInterval: 1500
    password:
    subscriptionsPerConnection: 5
    clientName: null
    address: "redis://127.0.0.1:6379"
    subscriptionConnectionMinimumIdleSize: 1
    subscriptionConnectionPoolSize: 50
    connectionMinimumIdleSize: 24
    connectionPoolSize: 64
    database: 0
    dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.client.codec.StringCodec> {}
transportMode: "NIO"

在RedisConfig配置

    @Value("${spring.profiles.active:}")
    private String activeProfile;

    private String getConfigPath() {
        if(!StringUtils.hasText(activeProfile)){return "classpath:redisson.yaml";}
        String[] profiles = activeProfile.split(",");
        Set<String> profileSet = Arrays.stream(profiles).collect(Collectors.toSet());
        if (profileSet.contains("test")) {
            return "classpath:redisson-test.yaml";
        }
        if (profileSet.contains("prod")) {
            return "classpath:redisson-prod.yaml";
        }
        return "classpath:redisson.yaml";
    }

    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(RedissonClient.class)
    public RedissonClient redissonClient() throws IOException {
        String configPath = getConfigPath();
        File configFile = ResourceUtils.getFile(configPath);
        Config config = Config.fromYAML(configFile);
        return Redisson.create(config);
    }
    

2.3 集群配置

在resource里放入redisson.yaml,在RedisConfig配置和2.2一样即可

clusterServersConfig:
  idleConnectionTimeout: 10000
  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
  failedSlaveReconnectionInterval: 3000
  failedSlaveCheckInterval: 60000
  password: null
  subscriptionsPerConnection: 5
  clientName: null
  loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
  subscriptionConnectionMinimumIdleSize: 1
  subscriptionConnectionPoolSize: 50
  slaveConnectionMinimumIdleSize: 24
  slaveConnectionPoolSize: 64
  masterConnectionMinimumIdleSize: 24
  masterConnectionPoolSize: 64
  readMode: "MASTER"
  subscriptionMode: "MASTER"
  nodeAddresses:
    - "redis://192.168.80.140:6379"
    - "redis://192.168.80.141:6379"
    - "redis://192.168.80.142:6379"
  scanInterval: 1000
  pingConnectionInterval: 0
  keepAlive: false
  tcpNoDelay: false
threads: 16
nettyThreads: 32
codec: !<org.redisson.client.codec.StringCodec> {}
transportMode: "NIO"

3、使用

3.1 加锁使用

    /**
     * 测试redission锁
     * @return
     */
    @RequestMapping("/redissionClient")
    public String init(){
        RLock lock = redissonClient.getLock("name");
        System.out.println(Thread.currentThread().getId());
        try {
            int i = 0;
            System.out.println(Thread.currentThread());
            /*waitTime:尝试获取锁的时间,leaseTime:释放锁的时间 TimeUnit:时间的单位*/
            while (!lock.tryLock(50,50002, TimeUnit.MILLISECONDS)){
                i++;
                lock = redissonClient.getLock("name"+i);
            }
            System.out.println("获取到lock"+lock.getName());
            System.out.println("执行业务");
            try {
                boolean locked = lock.isLocked();
                System.out.println("是否正在锁:"+locked);
                TimeUnit.SECONDS.sleep(5);
                locked = lock.isLocked();
                System.out.println("是否正在锁:"+locked);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println("释放锁");
            if(lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
        return "777777";
    }

3.2 定时任务应用

工具类

@Service
public class RedisDelayedQueue {
    /**
     * 任务回调监听
     *
     * @param <T>
     */
    public abstract static class TaskEventListener<T> {
        /**
         * 执行方法
         *
         * @param t
         */
        public abstract void invoke(T t);
    }
 
    @Autowired
    RedissonClient redissonClient;
 
    /**
     * 添加队列
     *
     * @param t        DTO传输类
     * @param delay    时间数量
     * @param timeUnit 时间单位
     * @param <T>      泛型
     */
    public <T> void addQueue(T t, long delay, TimeUnit timeUnit) {
        RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(t.getClass().getName());
        RDelayedQueue<T> delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue);
        delayedQueue.offer(t, delay, timeUnit);
        delayedQueue.destroy();
    }
 
    /**
     * 获取队列
     *
     * @param zClass            DTO泛型
     * @param taskEventListener 任务回调监听
     * @param <T>               泛型
     * @return
     */
    public <T> void run(Class zClass, TaskEventListener taskEventListener) {
        RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(zClass.getName());
        //由于此线程需要常驻,可以新建线程,不用交给线程池管理
        ((Runnable) () -> {
            while (true) {
                try {
                    T t = blockingFairQueue.poll(10,TimeUnit.SECONDS);
                    if(t!=null){
                        taskEventListener.invoke(t);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).run();
    }
    /**
     * 获取队列,使用java8进行处理
     *
     * @param zClass            DTO泛型
     * @param consumer 消费者方法
     * @param <T>               泛型
     * @return
     */
    public <T> void run(Class zClass, Consumer consumer) {
        RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(zClass.getName());
        //由于此线程需要常驻,可以新建线程,不用交给线程池管理
        ((Runnable) () -> {
            while (true) {
                try {
                    T t = blockingFairQueue.poll(10,TimeUnit.SECONDS);
                    if(t!=null){
                        consumer.accept(t);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).run();
    }
}

放入线程池里或者启动一个线程进行消费

    final RedissonClient redissonClient;

    final TaskExecutor taskExecutor;

    public IndexController(RedissonClient redissonClient,TaskExecutor taskExecutor) {
        this.redissonClient = redissonClient;
        this.taskExecutor = taskExecutor;
    }
    
	@PostConstruct
    public void initTask(){
        /*RedisDelayedQueue.TaskEventListener<Student> taskEventListener = new RedisDelayedQueue.TaskEventListener<Student>() {
            @Override
            public void invoke(Student taskBodyDTO) {
                //这里调用你延迟之后的代码
                log.info("执行...." + taskBodyDTO.getName() + "===" + taskBodyDTO.getAge());
            }
        };*/
        /*实现线程池进行处理*/
        //taskExecutor.execute(()->redisDelayedQueue.run(Student.class, taskEventListener));
        /*新建一个线程进行处理*/
        //new Thread(()->redisDelayedQueue.run(Student.class, taskEventListener)).start();

        /*使用java8函数的方法*/
        new Thread(()->redisDelayedQueue.run(Student.class,x-> System.out.println("执行...." + JSON.toJSONString(x) + "==="))).start();
    }

使用

    /**
     * 测试Redis定时任务
     * @return
     */
    @RequestMapping("/task")
    public String taskTest(){
        Student student1 = new Student("zhangsan",20);
        redisDelayedQueue.addQueue(student1,20L,TimeUnit.SECONDS);

        Student student2 = new Student("zhangsan",21);
        redisDelayedQueue.addQueue(student2,21L,TimeUnit.SECONDS);

        Student student3 = new Student("zhangsan",22);
        redisDelayedQueue.addQueue(student3,22L,TimeUnit.SECONDS);

        Student student4 = new Student("zhangsan",23);
        redisDelayedQueue.addQueue(student4,23L,TimeUnit.SECONDS);
        return "task";
    }

3.3 发布订阅的使用

配置订阅类:

@Configuration
public class MessageConfiguration {

    private RedisConnectionFactory redisConnectionFactory;
    private MessageSubscriber messageSubscriber;

    @Autowired
    public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory) {
        this.redisConnectionFactory = redisConnectionFactory;
    }

    @Autowired
    public void setMessageSubscriber(MessageSubscriber messageSubscriber) {
        this.messageSubscriber = messageSubscriber;
    }

    @Bean
    public RedisMessageListenerContainer redisContainer() {
        RedisMessageListenerContainer container
            = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        container.addMessageListener(messageSubscriber, topic());
        return container;
    }

    private Topic topic() {
        return new ChannelTopic("topic");
    }

}
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Set;

@Slf4j
@Service
public class MessageSubscriber implements MessageListener {

    private final Object lock = new Object();
    private ObjectMapper objectMapper;


    public MessageSubscriber() {
        this.objectMapper = new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
            .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
    }

    /**
     * Callback for processing received objects through Redis.
     *
     * @param message message must not be {@literal null}.
     * @param pattern pattern matching the channel (if specified) - can be {@literal null}.
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        try {
            String msg = objectMapper.readValue(message.getBody(), String.class);
            System.out.println("收到订阅消息:"+msg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

发布类接口:

/**
 * 消息发布
 *
 * @author jeff
 * created on 2020/9/11 11:16
 */
public interface MessagePublisher {

    /**
     * 发送消息
     *
     * @param message
     */
    void send(String message);

    /**
     * 发送消息
     *
     * @param channel
     * @param message
     */
    void send(String channel, String message);

}

发布类实现:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class MessagePublisherImpl implements MessagePublisher {

    private ObjectMapper objectMapper;
    private StringRedisTemplate redisTemplate;

    public MessagePublisherImpl() {
        this.objectMapper = new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
            .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
    }

    @Autowired
    public void setRedisTemplate(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 发送消息
     *
     * @param message
     */
    @Override
    public void send(String message) {
        send("topic", message);
    }

    /**
     * 发送消息
     *
     * @param channel
     * @param message
     */
    @Override
    public void send(String channel, String message) {
        try {
            String messageStr = objectMapper.writeValueAsString(message);
            redisTemplate.convertAndSend(channel, messageStr);
        } catch (JsonProcessingException e) {
            log.error("cannot push message to channel " + channel + ", message: " + message, e);
        }
    }
}

结语

一名四年工作经验的程序猿,目前从事物流行业的工作,有自己的小破网站amoqi.cn。欢迎大家关注公众号【CoderQi】,一起来交流JAVA知识,包括但不限于SpringBoot+微服务,更有奇奇学习过程中学习的视频、面试资料和专业书籍等免费放送,希望大家能够喜欢。
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是刘奇奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值