问题现象

表面现象是系统登录突然失效,排查原因发现,使用redis查询用户信息异常,从而定位到redis问题

if (PassWord.equals(dbPassWord)) {
        map.put("rtn", 1);
        map.put("value", validUser);
        session.setAttribute("username", user.getUsername());                                       
        redisWarehouseControlUtil.addObjectData(user.getUsername(),user.getUsername(),30);
}

排查原因

我的redis使用的是华为云的redis分布式缓存服务,所以在问题排查方面,我们可以结合华为云提供的丰富的分析诊断工具来辅助排查解决问题。

1、问题定位到redis上,登陆redis服务器,发现服务器内存使用率100%。

2、使用华为云的性能监控功能,查询指定时段的内存使用率信息。发现“内存利用率”指标持续接近100%。查询内存使用率超过95%的时间段内,“已逐出的键数量”和“命令最大时延”,均呈现显著上升趋势,表明存在内存不足的问题。

当内存不足时,可能导致Key频繁被逐出、响应时间上升、QPS(每秒访问次数)不稳定等问题,基本上redis服务已经瘫痪。

3、先使用实例诊断功能,大体分析一下可能得问题原因:主要还是内存占用过高问题。

4、使用华为云的缓存分析功能,执行大Key扫描,发现另一个项目的ErrorMeterData key,他是一个list队列,竟然存储数据占了2.6G,还有两个key存储数据占用了几百M,就是这三个key把服务器的内存占满了。

5、分析查找原因:去代码中查找ErrorMeterData key对应的功能,找到了问题所在,这个key存储的是解析出现异常的数据队列,但问题是,开发这个功能的同事,并没有给这个key设置过期时间也没有对这个异常数据队列的数据进行其他处理就一直存在这个队列中,随着时间的增长,以及异常数据的日复一日的不断累加,会导致存储数据太多,终于内存被占满。这是一个非常严重的bug。

问题就出在:redisMeterDataUtil.AddErrorMeterDataList(baseMessage);这一步

private void MeterDataExtractProcess()  {
    boolean rtn = false;
    while (!needClose) {
      // 普通表数据的解析
      try {
        BaseMessage baseMessage = redisMeterDataUtil.getMeterData();
        if (baseMessage != null) {
          if (!baseMessage.getFunctionCode().equals("") && baseMessage.getFunctionCode() != null) {
            switch (baseMessage.getFunctionCode()) {

                // 温控面板解析 --主动上传 批量
              case FunctionCode.UploadTcStateData:
                UploadTcStateDataMessage tcStateDataMessage =
                    new UploadTcStateDataMessage(baseMessage);
                rtn = tcStateDataService.addUploadTcStateDataDataMessage(tcStateDataMessage);
                break;
                //  //根据表号读取,单条    温控面板解析
              case FunctionCode.getTcStateData:
                ReplyTcStateDataMessage replyTcStateDataMessage =
                    new ReplyTcStateDataMessage(baseMessage);
                rtn = tcStateDataService.addTcStateDataMessage(replyTcStateDataMessage);
                break;

              default:
                break;
            }

            if (rtn == false) {
              // 解析方法存储失败,将数据添加到错误队列
              redisMeterDataUtil.AddErrorMeterDataList(baseMessage);
            }
          } else {
            // 若队列数据为空,则线程休眠1s后继续执行
            ThreadSleep(1000);
            continue;
          }
        }
      } catch (Exception e) {
          logger.error("MeterDataExtractServer--表数据redis解析出错"+e.getMessage());
          ThreadSleep(1000);
      }
public void AddErrorMeterDataList(BaseMessage baseMessage) {
        addData(ErrorMeterDataListSign, baseMessage);
    }


private void addData(String type, Object data) {
        String key = type;
        redisTemplate.opsForList().leftPush(key, data);

    }

6、如果你没有使用华为云或者阿里云的专门的redis服务,而是自己在服务器搭建的Redis服务。那么排查问题的步骤和方法,大体可以分为几步:

  1. 查询诊断服务的CPU、内存、硬盘、网络等是否正常
  2. 查看日志分析异常问题
  3. 如果是内存占满问题,则可以在Redis-cli客户端连接实例后,执行大key扫描命令或者执行过期key扫描(过期key扫描会对键空间进行Redis的scan扫描,释放内存中已过期但是由于惰性删除机制而没有释放的内存空间),并查看key的内存占用情况。并对内存占用过大的key进行处理。

如果你想扫描Redis实例中的大key,你可以使用SCAN命令结合TYPE命令来获取每个键的类型,并根据键的类型获取其大小。

以下是一个示例的命令:

bash复制代码

redis-cli SCAN 0 MATCH * COUNT 1000 | while read key; do type=$(redis-cli TYPE $key); size=$(redis-cli -c GET $key | wc -c); echo "$key: $type, Size: $size"; done

这个命令将使用SCAN命令迭代整个数据库,并对每个键执行TYPE命令来获取键的类型。然后,对于字符串类型的键,使用GET命令获取其值,并使用wc -c命令计算其长度。最后,将键、类型和大小输出到终端。

另外,如果你想查看Redis实例的output buffer占用情况,你可以使用CONFIG GET output-buffer-limit命令来获取output buffer的配置信息。该命令将返回output buffer的配置参数,包括类型、大小和阈值。

请注意,上述命令中的redis-cli -c GET $key是用于获取字符串类型的键的大小。对于其他类型的键,你可能需要使用其他命令或方法来获取其大小。

处理措施

1、为内存占用过大的key设置过期时间,这样数据就不会一直存储在队列中

(1)比较紧急想要恢复redis,且队列中的数据不重要,则可以直接链接redis,执行命令

EXPIRE key seconds:设置键的过期时间(以秒为单位),过期后键将被自动删除。

或者

DEL key:删除指定键

(2)在代码中为key设置过期时间

/**
     * 设置设备缓存过期时间(分钟)
     * @param type 设备分类
     */
    private void setExpireTime(String type,int cacheTime) {
        String key = type;
        redisTemplate.expire(key,cacheTime,TimeUnit.MINUTES);
    }

/**
     * 设置表数据缓存失效时间list集合
     */
    public void setMeterInfoExpire() {
        setExpireTime(MeterDataListSign,deviceCacheTime);
    }

2、业务逻辑上将这个异常数据队列的数据,重新返回处理队列,设置返回次数,如果超过三次以上,还是没有被正常队列处理掉,则将异常数据持久化,并删除redis中的该异常数据。

我的实际业务中,异常数据没有重回队列处理的必要了,所以我的业务代码中,直接不在用redis队列存储异常数据,而是直接将异常数据持久化存储到mongodb中。

if (rtn == false) {
     // 解析方法存储失败,将数据添加到错误队列----不再存在redis,直接持久化存储到mongodb
     //redisMeterDataUtil.AddErrorMeterDataList(baseMessage);
     tcErrorMessageHistoryUtil.addMessage(baseMessage);
}

3、设置key的过期时间后,过了一段时间内存恢复正常

总结

在使用redis的对象或者list队列等实例时,要记得给key设置过期时间,避免数据一直堆积无法释放。对于重要的异常数据队列的数据,要进行业务处理:重回队列或数据持久化。

相关文章

Redis高并发分布锁实战

Redis分布式锁自己去实现可能会出现几个问题没有在finally显示释放锁,当客户端挂掉了,锁没有被及时删除,这样会导致死锁问题,它这个是需要我们显示的释放锁假如此时我们设置过期时间,但是我们用的是同一个key,就可能出现下一个线程删除上一个线程的锁,但是上一个线程还没有执行完,它这个需要key是不能重复的假如我们既设置了过期时间也指定了不同的key,此时可能因为网络延迟出现上一个线程删除下一个线程的锁,也就是说业务执行的时间超过了锁过期的时间,它这个需要一个锁续命的功能。

Redis是否为单线程?

在深入讨论Redis是否为单线程之前,我们先来了解一下Redis的基本架构。Redis采用了基于内存的数据存储方式,数据存储在内存中,并通过持久化机制将数据定期写入磁盘。客户端:与Redis进行通信的应用程序。Server:负责处理客户端请求、执行命令和管理数据。数据结构:Redis支持多种数据结构,如字符串、列表、哈希表等。事件处理器:用于处理网络事件和命令请求。

MySQL中的高级查询

通过条件查询可以查询到符合条件的数据,但如同要实现对字段的值进行计算、根据一个或多个字段对查询结果进行分组等操作时,就需要使用更高级的查询,MySQL提供了聚合函数、分组查询、排序查询、限量查询、内置函数以实现更复杂的查询需求。接下来将针对这些高级查询的知识进行讲解。

ubuntu20.04安装实时内核补丁PREEMPT_RT

下载实时内核补丁,我下载patch-5.15.148-rt74.patch.sign和patch-5.15.148-rt74.patch.xz。通过以下指令看具体报错并输出日志到make.log:make -j1 deb-pkg 2>&1 | tee ~/make.log。比较幸运没遇到问题,重启进入后,启动页面没有变化,还是进入ubuntu,但是查看内核版本已经自动变到5.15.148。我下载linux-5.15.148.tar.xz和linux-5.15.148.tar.sign。

【Vue3】使用ref与reactive创建响应式对象

先来简单介绍一下ref,它可以定义响应式的变量let xxx = ref(初始值)。**返回值:**一个RefImpl的实例对象,简称ref对象或refref对象的value属性是响应式的。JSxxx.value,但模板中不需要.value,直接使用即可。对于let name = ref('张三')来说,name不是响应式的,name.value是响应式的。下面我们看一看上图红框中代表的意思是,我们哪里需要响应就在哪个里面导入上述代码即可。

如何设置页面恢复运行事件触发回调

由于 Android 原生的 resume 和 pause 事件不能区分是压后台导致还是页面切换导致,所以 pageResume 和 pagePause 事件是通过 JSAPI 调用记录回调的,仅适用于同一个 session 内 Window 之间的互相切换。当一个 WebView 界面重新回到栈顶时,例如从后台被唤起、锁屏界面恢复、从下个页面回退,会触发页面恢复运行(resume)事件。如果这个界面是通过 popWindow 或 popTo 到达,且传递了 data 参数,则此页可以获取到这些参数。

日常遇到Maven出现依赖版本/缓存问题通用思路。

如果怀疑是本地仓库中缓存的依赖有问题,可以手动删除本地仓库(默认位置在用户的.m2/repository目录下),但这是一个较为极端的做法,因为这会删除所有项目的所有本地依赖,之后Maven将不得不重新下载这些依赖。针对于这样的问题 首先我们的第一思路 就是怀疑到是缓存的问题,那么我在这里去描述一下 我们遇到这类通用类的问题如何解决。检查项目的pom.xml文件,确认依赖声明正确无误,没有冲突的版本号或不正确的依赖范围。版本问题导致的,但是我确认过了一下的一些操作 依然没有解决我的问题。

mysql中文首字母排序查询

MySQL中的排序涉及到字符集和排序规则。默认情况下,MySQL按照ASCII码对字符进行排序,数字>字母>中文。但是,特殊字符(非字母、数字、中文)的排序需要一些额外处理。匹配到非字母数字中文的内容,做排序,字母数字中文为null,排序优先级最高,排在上面。为什么用HEX()函数做十六进制编码?因为中文用常规的正则不能匹配到结果。试过SUBSTRING、LEFT等,都不能完美实现多中文的首字母排序。为什么要把字母数字中文放在一起匹配?因为处理复杂度会更高。这样可以处理更复杂的排序需求。

什么是tomcat?tomcat是干什么用的?

Tomcat是一个开源的、轻量级的应用服务器,是Apache软件基金会的一个项目。它实现了Java Servlet、JavaServer Pages(JSP)和Java Expression Language(EL)等Java技术,用于支持在Java平台上运行的动态Web应用程序。AJP是用于Apache服务器与Tomcat之间进行通信的协议,通常用于将动态生成的内容传递给Apache服务器进行处理。它能够运行Servlet和JSP,提供了一个环境,使得开发者能够构建和运行基于Java的Web应用。

C# winfrom中excel文件导入导出

在C#交流群里,看到很多小伙伴在excel数据导入导出到C#界面上存在疑惑,所以今天专门做了这个主题,希望大家有所收获!环境:win10+vs2017界面:主要以演示为主,所以没有做优化,然后主界面上添加两个按钮,分别命名为ExportExcel和ImportExcel,添加两个dataGridView,分别是dataGridView1和dataGridView2然后在窗体加载程序中给dataGr...

使用redis-insight连接到服务器上的redis数据库

我们现在虽然安装好了redis数据库,但是外界是连接不到的,我们需要打破这个限制!设置完之后,可以按以下图的命令查看,redis的密码是不是起作用了。的更改,并退出编辑器。在网上下载好redis-insight的客户端,打开。默认情况下,它可能被设置为只监听本地连接,如。这允许在没有进行身份验证的情况下接受外部连接。(3)为了增强安全性,强烈建议设置访问密码。三、使用redis-insight连接数据库。1.查找redis的配置文件。指令,并确保将其设置为。替换为你自己的强密码。

Java 与 JavaScript 的区别与联系

Java 和 JavaScript 两种编程语言在软件开发中扮演着重要的角色。尽管它们都以“Java”命名,但实际上它们是完全不同的语言,各有其独特的特点和用途。本文将深入探讨 Java 和 JavaScript 的区别与联系,帮助大家更好地理解它们在编程世界中的作用。

C语言中的作用域与生命周期

但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在自己所在的源文件内部使用了,其他源文件,即使声明了,也是无法正常使用的。结论:static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。extern 是用来声明外部符号的,如果一个全局的符号在A文件中定义的,在B文件中想使用,就可以使用extern进行声明,然后使用。全局变量的生命周期是:整个程序的生命周期。

Python和Java的区别(不断更新)

运行效率:一般来说,Java的运行效率要高于Python,这主要是因为Java是编译型语言,其代码在执行前会进行预编译,而Python是解释型语言,边解释边执行。而Python没有类似的强大虚拟机,但它的核心是可以很方便地使用C语言函数或C++库,这使得Python可以轻松地与底层硬件进行交互。**类型系统:**Java是一种静态类型语言,所有变量需要先声明(类型)才能使用,且类型在编译时就已经确定。总的来说,Python和Java各有其优势和特点,选择哪种语言取决于具体的项目需求、开发环境以及个人偏好。

服务器与电脑的区别?

服务器是指一种专门提供计算和存储资源、运行特定软件服务的物理或虚拟计算机。服务器主要用于接受和处理来自客户端(如个人电脑、手机等)的请求,并向客户端提供所需的服务或数据。服务器在网络环境中扮演着中心节点的角色,负责存储和管理数据、提供网络服务、处理计算任务等。

windows下ngnix自启动(借助工具winSw)

在windows下安装nginx后,不想每次都手动启动。本文记录下windows下ngnix自启动(借助工具winSw)的操作流程提示:以下是本篇文章正文内容,下面案例可供参考本文记录下windows下ngnix自启动(借助工具winSw)的操作流程。

synchronized 和 Lock 有什么区别?synchronized 和 ReentrantLock 区别是什么?说一下 atomic 的原理?

例如,AtomicInteger 的 incrementAndGet() 方法就是通过 CAS 操作实现的,它首先尝试原子地将共享变量加 1,如果操作成功,则返回新的值,否则重试直到操作成功为止。CAS 操作的原理是,当 V 的值等于 A 时,将 V 的值更新为 B,否则什么也不做。synchronized 和 Lock 都是 Java 中用于实现线程同步的关键字/类库,它们都能够提供对共享资源的安全访问和防止数据竞争的功能,但是在实现方式、特性、适用场景等方面存在一些差异。

数据湖Paimon入门指南

如果用户建表时指定'merge-engine' = 'partial-update',那么就会使用部分更新表引擎,可以做到多个 Flink 流任务去更新同一张表,每条流任务只更新一张表的部分列,最终实现一行完整的数据的更新,对于需要拉宽表的业务场景,partial-update 非常适合此场景,而且构建宽表的操作也相对简单。这种方式的成本相对较高,同时官方不建议这样使用,因为下游任务在 State 中存储一份全量的数据,即每条数据以及其变更记录都需要保存在状态中。流式查询将不断产生最新的更改。

基于SQL数据库的大模型RAG实现

检索增强生成 (RAG) 涉及从外部数据库获取当前或上下文相关信息,并在请求大型语言模型 (LLM) 生成响应时将其呈现给大型语言模型 (LLM) 的过程。这种方法有效地解决了生成不正确或误导性信息的问题。你能够存储专有业务数据或全局知识,并使你的应用程序能够在响应生成阶段为 LLM 检索此数据。

MySQL运行在docker容器中会损失多少性能

自从使用docker以来,就经常听说MySQL数据库最好别运行在容器中,性能会损失很多。一些之前没使用过容器的同事,对数据库运行在容器中也是忌讳莫深,甚至只要数据库跑在容器中出现性能问题时,首先就把问题推到容器上。

Kafka常见生产问题详解

比如,在原有Topic下,可以调整Producer的分区策略,让Producer将后续的消息更多的发送到新增的Partition里,这样可以让各个Partition上的消息能够趋于平衡。思路是可行的,但是重试的次数,发送消息的数量等都是需要考虑的问题。PageCache缓存中的消息是断电即丢失的。因为如果业务逻辑异步进行,而消费者已经同步提交了Offset,那么如果业务逻辑执行过程中出现了异常,失败了,那么Broker端已经接收到了消费者的应答,后续就不会再重新推送消息,这样就造成了业务层面的消息丢失。
返回
顶部