如何实现设备组缓存的正确清除?——基于心跳请求和心跳响应的解决方案

目录

在设备组关闭后,如何保证缓存中的设备组信息能够正确清除?本文将介绍如何通过前端实现设备组心跳检测和缓存清除,以及通过后端实现缓存清除的逻辑来解决该问题。我们还将详细讨论如何利用心跳请求和心跳响应来实现设备组缓存的正确清除,并提供基于Vue和SpringBoot的代码示例。

一、问题描述

在开发设备管理系统时,我们经常需要保证设备组在关闭后能够从缓存中正确删除,以避免占用过多的系统资源。

示例:若依前后端分离框架,如果用户使用当前设备组,那么当前设备组会被写进缓存里,然后被占用,其他用户则不能使用该设备组;如果用户退出当前设备组,那么将从缓存里删掉该设备,但是很难保证的情况是,如果用户突然关闭浏览器,或者不正常关闭页面、退出帐号,都不能正常从缓存里删除该设备组,如何保证不管怎么样退出,都能从缓存中删掉该设备组?

二、问题分析

前端使用一个定时器,每隔5秒向后端发送请求,告知后端当前设备组是否还在使用中。后端使用一个DEVICE_GROUP_KEY + id来保存设备组是否被占用的状态,当用户加入设备组时,将该设备组的状态设置为占用,并设定过期时间为10秒;当用户退出设备组时,从DEVICE_GROUP_KEY + id中删除该设备组的状态。如果后端收到了一段时间内没有收到定时器请求的设备组,就会自动将该设备组从DEVICE_GROUP_KEY + id中删除。

当用户正常退出设备组时,前端会清除定时器并向后端发送请求,告知后端该设备组已经退出使用。如果用户异常退出设备组,则后端会在一段时间后自动删除该设备组。

三、解决方案

你的前端代码看起来已经调用了后端接口将设备组放入缓存中了。如果你想实现定时向后端发送请求,告知后端该设备组是否还在使用中,可以使用setInterval函数创建一个定时器,每隔一定时间向后端发送请求,告知后端该设备组仍在使用中。

前端

deviceInfo是预选设备组,currentDeviceInfo是当前设备组,deviceGroupKeys是缓存中的设备组,代码示例如下:

html 复制代码
<template>
  <div>
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="60px">
      <el-form-item label="设备组" prop="group">
        <el-select v-model="deviceGroup" placeholder="请选择" @change="selectDeviceGroup">
          <!-- 选择deviceGroupList -->
          <el-option v-for="item in deviceGroupList" :key="item.deviceGroup" :label='"第" + item.deviceGroup + "组"'
            :value="item.deviceGroup" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-circle-check" size="mini" @click="joinDeviceGroup">确定</el-button>
        <el-button icon="el-icon-remove-outline" size="mini" type="info" @click="leaveDeviceGroup">退出当前设备组</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
js 复制代码
    /** 加入设备组 */
	async joinDeviceGroup(){
	//查询预选的设备组是否被占用
	if (this.deviceGroupKeys.includes(`${this.deviceInfo.deviceGroup}`)) {
	        this.$message({
	          message: "该设备组已被使用",
	          type: "warning",
	        });
	        return;
	      }
	      //预选设备组没有被占用,如果当前设备组被自己占用,则将其从缓存中删除
	      if (this.currentDeviceInfo.deviceGroup !== "") {
	        await deleteDeviceGroupKey(this.currentDeviceInfo.deviceGroup);
	        clearInterval(this.timer);
	      }
	      //否则将deviceInfo预选设备组放入currentDeviceInfo当前设备组
	      this.currentDeviceInfo = JSON.parse(JSON.stringify(this.deviceInfo)) || null;
	      // 将currentDeviceInfo中的deviceGroup放入缓存,用setDeviceGroupKey方法
	      await setDeviceGroupKey(this.currentDeviceInfo.deviceGroup);
	      // 开启定时器
	      this.setInterval();
	}
js 复制代码
    /** 定义一个定时器,每隔5秒钟,调用一次sendDeviceHeartbeat方法 */
    setInterval() {
      // 如果currentDeviceInfo.deviceGroup为空,则停止定时器
      if (this.currentDeviceInfo.deviceGroup == "") {
        clearInterval(this.timer);
      } else {
        this.timer = setInterval(() => {
          this.sendDeviceHeartbeat();
        }, 5000);
      }
    },

    // 发送心跳请求的函数
    sendDeviceHeartbeat() {
      // 如果this.currentDeviceInfo.deviceGroup为空,则停止定时器
      if (this.currentDeviceInfo.deviceGroup == "") {
        clearInterval(this.timer);
      }
      // 发送请求deviceHeartBeat
    deviceHeartBeat(this.currentDeviceInfo.deviceGroup).then((response) => {
        // console.log(response);
        if (response === 0) {
          // 心跳成功,设备组仍在使用中
        } else {
          // 心跳失败,设备组已经退出使用
          this.$message({
            message: "设备组已经退出使用",
            type: "warning",
          });
          clearInterval(this.timer);
        }
      });
    },

然后在用户正常退出设备组时,清除定时器并向后端发送请求,告知后端该设备组已经退出使用。代码示例如下:

js 复制代码
leaveDeviceGroup() {
      if (this.currentDeviceInfo.deviceGroup != "") {
        deleteDeviceGroupKey(this.currentDeviceInfo.deviceGroup).then((response) => {
          //清空currentDeviceInfo
          this.currentDeviceInfo = {
            deviceGroup: "",
          };
        });
      }
      // 停止定时器
      clearInterval(this.timer);
}

计时器应该在用户正常退出设备组和关闭页面时被清除。在Vue中,可以通过在beforeDestroy()生命周期钩子中清除计时器,例如:

js 复制代码
beforeDestroy() {
  clearInterval(this.timer);
}

这里假设你的计时器是通过setInterval()创建的,并将其存储在Vue实例的timer属性中。当Vue实例被销毁时,beforeDestroy()生命周期钩子会被调用,此时可以清除计时器。

后端

获取缓存中deviceGroup所有的key

java 复制代码
    /**
     * 获取缓存中deviceGroup所有的key
     */
    @GetMapping("/getDeviceGroupKeys")
    public List<Integer> getDeviceGroupKeys() {
        //将redis中device_group的的基本对象列表,使用redisCache.keys()方法获取
        String[] keys = redisCache.keys(CacheConstants.DEVICE_GROUP_KEY + "*").toArray(new String[0]);
        //将keys中的值,去掉前缀,只保留1,2,3,4,5
        List<Integer> list = new ArrayList<>();
        for (String key : keys) {
            list.add(Integer.parseInt(key.substring(CacheConstants.DEVICE_GROUP_KEY.length())));
        }
        //将list从小到大排序
        list.sort((o1, o2) -> o1 - o2);
        return list;
    }

实现将设备组放入缓存

java 复制代码
    /**
     * 将设备组放入缓存
     */
    @GetMapping(value = "/setDeviceGroupKey/{id}")
    public String setDeviceGroupKey(@PathVariable("id") Integer id) {
        redisCache.setCacheObject(CacheConstants.DEVICE_GROUP_KEY + id, "true", 10, TimeUnit.SECONDS);
        return redisCache.hasKey(CacheConstants.DEVICE_GROUP_KEY + id) ? "true" : "false";
    }

在用户正常退出设备组时,你可以实现一个deleteDeviceGroupKey接口,用于从缓存中删除该设备组。代码示例如下:

java 复制代码
    /**
     * 将设备组从缓存中删除
     */
    @GetMapping("/deleteDeviceGroupKey/{id}")
    public String deleteDeviceGroupKey(@PathVariable("id") Integer id) {
        redisCache.deleteObject(CacheConstants.DEVICE_GROUP_KEY + id);
        return redisCache.hasKey(CacheConstants.DEVICE_GROUP_KEY + id) ? "false" : "true";
    }

你可以实现一个deviceHeartBeat接口,用于更新设备组在缓存中的存活时间。代码示例如下:

java 复制代码
    /**
     * 检查设备组是否还在使用,心跳请求处理接口
     */
    @PostMapping(value = "/deviceHeartBeat/{id}")
    public String deviceHeartBeat(@PathVariable("id") Integer id) {
        // 检查设备组是否存在于缓存中
        if (!redisCache.hasKey(CacheConstants.DEVICE_GROUP_KEY + id)) {
            // 设备组不存在,返回心跳失败
            return "Device group not found!";
        } else {
            // 更新设备组的心跳时间
            redisCache.expire(CacheConstants.DEVICE_GROUP_KEY + id, 10, TimeUnit.SECONDS);
            // 返回心跳成功
            return "Heartbeat successfully!";
        }
    }

如果用户异常退出设备组,你可以在后端实现一个定时任务,定时检查缓存中的设备组是否过期,如果过期则删除该设备组。代码示例如下:

java 复制代码
	/**
	 * 定时任务:删除过期的设备组,每隔10秒检查一次缓存中的设备组是否超时
	 */
    @Scheduled(fixedDelay = 10000)
    public void checkDeviceGroupKey() {
        // 获取当前时间
        Date now = new Date();
  		Set<Object> deviceGroupKeys = redisCache.keys(CacheConstants.DEVICE_GROUP_KEY + "*");
        // 遍历缓存中的设备组DEVICE_GROUP_KEY,检查是否超时
        for (Object deviceGroupKey : deviceGroupKeys) {
            // 获取缓存中的设备组
            String key = (String) deviceGroupKey;
            // 如果缓存中的设备组存在
            if (redisCache.hasKey(key)) {
                // 获取缓存中的设备组的最后一次心跳时间
                Date lastHeartBeatTime = redisCache.getCacheObject(key);
                // 计算当前时间和最后一次心跳时间的差值
                long diff = now.getTime() - lastHeartBeatTime.getTime();
                // 如果差值大于10秒,说明设备组已经超时,将设备组从缓存中删除
                if (diff > 10000) {
                    redisCache.deleteObject(key);
                }
            }
        }
    }

也可以限制哪几个设备组需要被清除

java 复制代码
    /**
     * 定时任务:删除过期的设备组,每隔10秒检查一次缓存中的设备组是否超时
     */
    @Scheduled(fixedDelay = 10000)
    public void checkDeviceGroupKey() {
        // 获取当前时间
        Date now = new Date();

        // 遍历缓存中的设备组DEVICE_GROUP_KEY,检查是否超时
        for (int i = 1; i <= 16; i++) {
            // 获取缓存中的设备组
            String key = CacheConstants.DEVICE_GROUP_KEY + i;
            // 如果缓存中的设备组存在
            if (redisCache.hasKey(key)) {
                // 获取缓存中的设备组的最后一次心跳时间
                Date lastHeartBeatTime = redisCache.getCacheObject(key);
                // 计算当前时间和最后一次心跳时间的差值
                long diff = now.getTime() - lastHeartBeatTime.getTime();
                // 如果差值大于10秒,说明设备组已经超时,将设备组从缓存中删除
                if (diff > 10000) {
                    redisCache.deleteObject(key);
                }
            }
        }
    }

后端缓存时间设置为10秒钟,前端每隔5秒向后端发送请求,那么在正常情况下,如果前端正常关闭,后端会在10秒钟后自动清除该设备组的缓存。

如果前端异常关闭,那么后端会在10秒钟后检测到该设备组的心跳信号已经停止,然后自动清除该设备组的缓存。

因此,这种方法可以保证在大多数情况下能够及时清除缓存,但是仍然可能存在一些极端情况导致缓存无法及时清除,比如网络故障等。

相关推荐
小小李程序员6 小时前
LRU缓存
java·spring·缓存
Clown957 小时前
go-zero(十) 数据缓存和Redis使用
redis·缓存·golang
CopyLower11 小时前
深入理解 MyBatis 的缓存机制:一级缓存与二级缓存
spring·缓存·mybatis
nbsaas-boot15 小时前
秒杀系统三层架构设计:缓存、消息队列与数据库
数据库·缓存
Beekeeper&&P...17 小时前
HttpServletRequest req和前端的关系,req.getParameter详细解释,req.getParameter和前端的关系
前端·状态模式
NiNg_1_23418 小时前
Redis中的zset底层实现
数据库·redis·缓存
白茶等风1213818 小时前
Unity 设计模式-状态模式(State Pattern)详解
设计模式·状态模式
zzzhpzhpzzz19 小时前
设计模式——状态模式
设计模式·状态模式
无敌岩雀19 小时前
C++设计模式行为模式———状态模式
c++·设计模式·状态模式
冧轩在努力21 小时前
【redis 】string类型详解
数据库·redis·缓存