SpringBoot系列(二十二)Redis数据库

在电商系统中,因为存有预支付的情况
所以库存的扣减
客户下单-->延迟支付-->支付成功/支付不成功
所以就存在预扣除你库存的情况,如果取消支付的话,就需要归还库存。关于归还库存,需明白
1、怎么归还库存
2、什么时间节点归还库存
3、谁来触发归还库存的操作

方法一:主动轮询
使用定时器的方式,每1s轮询,查看到过期的订单就归还库存
1、JAVA层面:Timer
2、Linux层面:Corntab
3、定时任务框架:Quartz

常用的队列:
RocketMQ、RabbitMQ、kafka
方法二、延迟消息队列:
Redis、RocketMQ

下面开始讲解Redis

一、windows安装Redis

参考:https://blog.csdn.net/qq_52253798/article/details/122204459

安装和配置Redis

1、在GitHub中下载安装包redis

https://github.com/MicrosoftArchive/redis/releases

2、直接下载最新版本,选择.msi格式的安装版本(另外一种.zip通过命令安装)

JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图
JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图1
JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图2

3、设置Redis的密码

打开安装目录,windows的话找到“ redis.windows-service.conf ”,linux的话“redis.conf”

找到 requirepass。如图设置密码

JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图3

进入计算机服务中(右键计算机–>管理–>服务和应用程序–>服务),再在右侧找到Redis名称的服务,查看启动情况。如未启动,则手动启动之。正常情况下,服务应该正常启动并运行了,但是因为前面修改过配置文件,需要重启服务,切记。

使用服务前需要先通过密码验证。输入“auth 123456”并回车(123456是之前设定的密码)。返回提示OK表示验证通过

JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图4

Redis常用操作

  • 设置指定 key 的值 set key_name fox
  • 获取指定 key 的值 get key_name
  • 查看所有key(key 正则匹配) keys *
  • 删除指定key的值 del key_name

Redis键空间通知

Redis键空间通知是指Redis收到事件的影响(del、expired等)发布一个通知。我们可以通过发布订阅的方式来实现延迟消息队列。

例如 使用set key :
1、set name fox
2、del name
此时的键空间通知
key-space--->del
key-event--->name

1、修改配置文件

windows的话找到“ redis.windows-service.conf ”,linux的话“redis.conf” 加入“notify-keyspace-events Ex

JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图5

2、然后我们重启redis服务

JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图6

进入控制台,获得 CONFIG GET notify-keyspace-events

JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图7

3、控制台进入Redis数据库设置监听(订阅监听过期事物)

shell> SUBSCRIBE __keyevent@0__:expired

JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图8

打开新的控制台订阅键过期事件

SETEX KEY_NAME TIMEOUT VALUE

redis1> SUBSCRIBE __keyevent@0__:expired
1) "subscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
# redis2> SETEX greeting 1 "hello world"
# 等待1秒后:
1) "message"
2) "__keyevent@0__:expired"
3) "greeting"

打开新的控制台订阅所有事件

redis1> PSUBSCRIBE __key*@*__:*
1) "psubscribe"
2) "__key*@*__:*"
3) (integer) 1
# redis2> SET greeting "hello world"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyspace@0__:greeting"
4) "set"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyevent@0__:set"
4) "greeting"

SpringBoot引用Redis

在pom.xml中添加引用

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在配置文件中设置参数。Redis一共有15个数据库,根据实际情况链接。此处使用到0号数据库

spring:
  redis:
    localhost: localhost
    port: 6379
    database: 0
    password: 123456
    listen-pattern: __keyevent@0__:expired
JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图9

创建公共处理类 RedisUtils

在项目中创建package,并命名manager,在下级创建一个package,并命名为redis,并创建 RedisUtils 工具类

package com.fcors.fcors.manager.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisUtils {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 读取缓存
     *
     * @param key
     * @return
     */
    public String get(final String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 写入缓存
     */
    public boolean set(final String key, String value) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存,并设置过期时间
     *
     * @param key
     * @param value
     * @param timeout
     * @param unit
     * @return
     */
    public boolean set(final String key, String value, long timeout, TimeUnit unit) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().set(key, value, timeout, unit);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 更新缓存
     */
    public boolean getAndSet(final String key, String value) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().getAndSet(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 删除缓存
     */
    public boolean delete(final String key) {
        boolean result = false;
        try {
            redisTemplate.delete(key);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

SpringBoot设置键空间通知,实现延时消息队列。

1、设置监听规则

spring:
  redis:
    localhost: localhost
    port: 6379
    database: 0
    password: 123456
    listen-pattern: __keyevent@0__:expired

2、创建类实现消息监听TopicMessageListener

package com.fcors.fcors.manager.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;

@Component
public class TopicMessageListener implements MessageListener {


    @Override
    public void onMessage(Message message, byte[] bytes) {
        byte[] body = message.getBody();
        byte[] channel = message.getChannel();

        String expiredKey = new String(body);
        String topic = new String(channel);
        System.out.println("Redis延迟消息队列接收内容");
        System.out.println("expiredKey"+expiredKey);
        System.out.println("topic"+topic);

    }
}

3、设置MessageListenerConfiguration

package com.fcors.fcors.manager.redis;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;

@Configuration
public class MessageListenerConfiguration {
    @Value("${spring.redis.listen-pattern}")
    public String pattern;

    @Bean
    public RedisMessageListenerContainer listenerContainer(RedisConnectionFactory redisConnection) {
//        return new TopicMessageListener()
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnection);
        /*设置监听主题 */
        Topic topic = new PatternTopic(this.pattern);
        /*添加监听处理 和 监听主题*/
        container.addMessageListener(new TopicMessageListener(), topic);
        return container;
    }
}

4、在TestService处插入超时

   @Autowired
    private RedisUtils redisUtils;
    public long invoiceCount = 0;

    public String TestRedis(){
        String a = "test";
        /*写入缓存*/
        redisUtils.set("fox_key","fox_value");
        /*读取缓存*/
        String Redis_value = redisUtils.get("fox_key");
        System.out.println("获得设置的fox_value"+Redis_value);
        /*更新缓存*/
        Boolean IsSuccess = redisUtils.getAndSet("fox_key","fox_value1");
        System.out.println("是否成功更新"+IsSuccess);

        Boolean IsSuccess2 = redisUtils.getAndSet("fox_key2","fox_value2");
        System.out.println("是否成功更新"+IsSuccess2);

        /*设置超时*/
        Boolean IsSuccess3 =  redisUtils.set("TimeOut","Timeout7Second",7, TimeUnit.SECONDS);
        System.out.println("是否成功插入超时缓存"+IsSuccess2);
        
        return a;

    }

最终执行效果

JAVA、基础技术、技术与框架SpringBoot系列(二十二)Redis数据库插图10

结合优惠券实战案例:

考虑到优惠券和库存扣减归还,我们在Redis的key规则orderId,couponId,userId

1、创建OrderMessageBO

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class OrderMessageBO {
    private Long orderId;
    private Long couponId;
    private Long userId;
    private String message;

    public OrderMessageBO(String message) {
        this.message = message;
        this.parseId(message);
    }

    private void parseId(String message) {
        String[] temp = message.split(",");
        this.userId = Long.valueOf(temp[0]);
        this.orderId = Long.valueOf(temp[1]);
        this.couponId = Long.valueOf(temp[2]);
    }

}

2、创建类实现消息监听TopicMessageListener

package com.fcors.fcors.manager.redis;

import com.fcors.fcors.bo.OrderMessageBO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;

@Component

public class TopicMessageListenerNew implements MessageListener {

    @Autowired
    private static ApplicationEventPublisher publisher;

    @Autowired
    public static void setPublisher(ApplicationEventPublisher publisher) {
        TopicMessageListenerNew.publisher = publisher;
    }

    @Override
    public void onMessage(Message message, byte[] bytes) {
        byte[] body = message.getBody();
        byte[] channel = message.getChannel();

        String expiredKey = new String(body);
        String topic = new String(channel);

        OrderMessageBO messageBO = new OrderMessageBO(expiredKey);
        TopicMessageListenerNew.publisher.publishEvent(messageBO);


    }
}

3、接收监听返回信息在service

    @EventListener
    @Transactional
    public void RedisMessageBack(OrderMessageBO bo){
        Long couponId = bo.getCouponId();
        Long uid = bo.getUserId();

    }

下面附上一个SpringBoot的消息发布订阅

https://juejin.cn/post/7078481193133408270

redis普通消息队列

https://blog.csdn.net/weixin_41359273/article/details/120681977