遍历Redis集群中的所有Key

2023-11-14

1. 背景s

公司有一个外采项目, 数据存储使用 Redis 集群, 数据量在200GB~300GB之间。

2. 需求

由于成本上的考虑, 需要清理一部分满足特定条件的数据。

3. 实现思路

由于是 Redis 集群, 大致可以分为以下步骤:

    1. 根据Redis节点信息, 获取此集群中所有的Redis节点。
    1. 对每个节点, 执行 scan 100 match *** 命令。
    1. 获取key并执行回调方法/特定业务逻辑。
    1. 迭代下一个Redis节点。

4. SCAN 简介

SCAN 是 redis 支持的一个命令, 可用来:

  1. 分页查询当前Redis节点, 对应数据库中的所有Key。
  2. 分页查询匹配特定统配模式的Key。

时间复杂度为 O(N), 主要使用场景是遍历所有Key。

另一个类似的命令是 keys, 但是 keys 命令存在严重的性能问题, 某些环境下管理员会直接禁掉这个命令。

SCAN 命令的语法格式为:

SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]

其中:

  • cursor 表示游标序号, 默认是 0; 每次查询都会返回下一页需要使用的游标序号;
  • MATCH pattern 是简单的模式匹配, 使用通配符 * 表示任意字符;
  • COUNT count 指定每次查询返回的最大数量, 类似于分页查询的 pageSize, 默认值 10;
  • TYPE type 用来可以过滤具体的数据类型, 满足类型的Key才会返回, 某个Key的具体类型可以使用 TYPE xxxKey 来探测。 一般取值包括: string, list, set, zset, hash 以及 stream

示例:

scan 0

scan 0 MATCH cnc:*

scan 0 MATCH *cnc:* COUNT 100

scan 0 MATCH *cnc:* COUNT 100 TYPE zset

此外, SCAN 还有一些变种命令, 用来遍历某个主Key下面对应的集合中的Key。

如果是很庞大的集合, 比如几十万个元素, 没有进行拆分的话, 有些时候可以使用这几个变种命令。

  • Sscan 用来分页遍历 set 类型集合中的子KEY;
  • Hscan 用来分页遍历 hash 类型集合中的子KEY;
  • Zscan 用来分页遍历 zset 类型集合中的子KEY;

至于list数据类型这不需要专门的分页遍历命令, 因为有 lindexlrange 可以很方便进行分页了。

示例:

hscan xxxHashKey 0

5. 用Jedis来实现Key扫描

由于我们的项目中使用了 Jedis 依赖库, 所以直接以这个库为基础。

jedis的依赖库可以到 mvnrepository.com 网站搜索, 该网站目前启动了防刷校验, 如果展示有问题, 刷新页面即可。

例如:

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.4.2</version>
</dependency>

Jedis提供的方法, 命名和Redis命令很类似, 使用起来很方面。

如果直接对 JedisCluster 调用 scan 方法, 则会提示报错信息:

Error: Cluster mode only supports SCAN command
  with MATCH pattern containing hash-tag
    ( curly-brackets enclosed string )

报错代码和详情可以参考: Scan a Redis Cluster

所以需要使用我们前面提到的思路: 挨个遍历Redis节点并执行扫描。

5.1 基础的辅助工具类

我们先创建一个基础的工具类, 用来对相关的逻辑进行简单的组织和封装, 避免代码杂乱。

# 相关依赖附在此处
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import redis.clients.jedis.*;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

// Redis的Key扫描辅助工具类
public class RedisKeyScanHelper {
    // ...
}

5.2 批处理停止开关

由于Key扫描是一个耗时较长的批处理任务, 如果需要中途干预或者终止, 有一个控制开关是比较优雅的做法。 否则只能暴力停机重启了。

    // 增加一个开关, 控制是否停止
    public static AtomicBoolean stopFlag = new AtomicBoolean(false);

执行扫描遍历的代码, 在每个批次执行之前, 可以判断开关状态, 确定退出或者抛异常。

示例代码:

    if (stopFlag.get()) {
        return; // 退出;
        // 可以考虑抛出业务异常:
        // throw new RuntimeException("收到停止信号");
    }

5.3 扫描结果回调函数

由于我们封装了工具类, 所以使用回调方法是一种比较方便的设计方式。

好处是将后续的业务处理逻辑剥离出去, 避免代码耦合在一起。

先定义一个 interface 接口。

    // 扫描结果回调函数
    public interface ScanResultCallBack {
        // 根据需要, 也可以定制对应的方法参数;
        public void process(String key, Jedis jedis);
    }

由于我们扫描Key时, 想要统计一下各个节点的数据量, 所以回调方法的参数有2个:

  • String key 就是扫描到的key;
  • Jedis jedis 是对应Key所在的Redis节点;

从这个代码可以看到, 扫描到对应的Key才会调用这个回调类。

如果需要在其他时机回调, 比如:

  • 扫描到Redis节点
  • 连接Redis成功
  • 集群信息
  • Key遍历
  • 扫描到具体Key类型
  • 扫描到某种类型的某类值
  • 发生某种异常情况时

等等之类的操作, 根据需要定制具体逻辑即可。

5.4 实现单个Redis节点的Key扫描

Jedis 类封装了 scan 命令, 我们直接使用即可。

对应的方法为:

// 在Redis节点内部遍历和扫描
public static void scanRedisNode(Jedis jedis, ScanResultCallBack callBack) {
    // 每次扫描的数量
    final Integer pageSize = 100;
    ScanParams scanParams = new ScanParams()
            //.match("*")
            .count(pageSize);
    // 游标: 直接使用 ScanParams 的常量
    String cursor = ScanParams.SCAN_POINTER_START;
    do {
        if (stopFlag.get()) {
            return; // 退出;
            // 可以考虑抛出业务异常:
            // throw new RuntimeException("收到停止信号");
        }
        // 执行扫描
        ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
        // 获取对应的key-list
        List<String> keys = scanResult.getResult();
        for (String key : keys) {
            // 判空
            if (Objects.isNull(key)) {
                continue;
            }
            if (Objects.isNull(callBack)) {
                continue;
            }
            // 执行回调
            try {
                callBack.process(key, jedis);
            } catch (Exception e) {
                String message = ("执行回调异常: key=: " + key + ";" + e.getMessage());
                System.out.println(message);
                // 根据需要选择是否抛出异常; 或者打印堆栈
                // throw new RuntimeException(message, e);
            }
        }
        //
        // 设置下一次扫描的游标
        cursor = scanResult.getCursor();
        // 只要返回的游标不是起始值0, 就继续执行下一次循环
    } while (!cursor.equals(ScanParams.SCAN_POINTER_START));
}

因为我们使用 static 来声明了静态方法, 所以通过方法入参的方式传入回调函数。

如果是普通方法, 那就可以采用依赖注入之类的方式设置回调字段。 比如常用的

5.5 实现Redis集群的扫描

Redis集群中, 分为主节点(master)和从节点(slave), 所以我们需要判断 Redis 节点的角色.

// 判断 Redis 节点的角色
public static String role(Jedis jedis) {
    try {
        // info replication
        String replicationInfo = jedis.info("replication");
        // 其实可以按行解析;
        // 这里简单粗暴直接判断
        if (replicationInfo.contains("role:master")) {
            // 主节点
            return "master";
        }
        if (replicationInfo.contains("role:slave")) {
            // 从节点
            return "slave";
        }
    } catch (Exception ignore) {
    }
    return "";
}

JedisCluster 是 Jedis 对集群的封装, 构造方法非常多, 开发时可以根据需要选择。

// 扫描 Redis 集群的Key
public static void scanRedisCluster(JedisCluster cluster, ScanResultCallBack callBack) {
    // 获取集群的所有节点
    Map<String, JedisPool> clusterNodes = cluster.getClusterNodes();
    Set<String> keySet = clusterNodes.keySet();
    for (String nodeKey : keySet) {
        JedisPool jedisPool = clusterNodes.get(nodeKey);
        Jedis jedis = jedisPool.getResource();
        System.out.println("scanRedisCluster: 探测到Redis节点: " + hostPort(jedis));
    }
    // 遍历Redis节点
    for (String nodeKey : keySet) {
        if (stopFlag.get()) {
            return; // 退出;
            // 可以考虑抛出业务异常:
            // throw new RuntimeException("收到停止信号");
        }
        JedisPool jedisPool = clusterNodes.get(nodeKey);
        Jedis jedis = jedisPool.getResource();
        // 判断节点角色
        String role = role(jedis);
        if (!"master".equals(role)) {
            System.out.println("scanRedisCluster: 忽略从节点: " + hostPort(jedis) + "; role=" + role);
            continue;
        } else {
            System.out.println("scanRedisCluster: 开始扫描主节点: " + hostPort(jedis) + "; role=" + role);
        }
        // 扫描该节点
        try {
            scanRedisNode(jedis, callBack);
        } catch (Exception e) {
            String message = ("扫描节点异常: jedis=: " + jedis + ";" + e.getMessage());
            System.out.println(message);
            // 根据需要选择是否抛出异常; 或者打印堆栈
            // throw new RuntimeException(message, e);
        }
    }
}

实现逻辑也不复杂, System.out.println 部分的代码可以根据项目, 决定是否使用 logger 输出。

一般来说, 作为批处理, 需要兼容处理掉各种意外情况, 同时也应当保留一定的通知能力, 将异常情况告知外部。

5.6 创建JedisCluster的示例代码

JedisCluster 是 Jedis 对集群的封装, 构造方法非常多, 开发时可以根据具体情况选择。

这里给出创建JedisCluster的2段示例代码。


// 创建Jedis集群
public static JedisCluster createJedisCluster(RedisProperties properties) {
    Set<HostAndPort> jedisClusterNode = new HashSet<>();
    //
    String clientName = properties.getClientName();
    String password = properties.getPassword();
    //
    RedisProperties.Cluster clusterProperties = properties.getCluster();
    List<String> nodeStrList = clusterProperties.getNodes();
    //
    // System.out.println("createJedisCluster: nodeStrList=" + JSON.toJSON(nodeStrList));
    //
    for (String str : nodeStrList) {
        if (StringUtils.isEmpty(str)) {
            continue;
        }
        String host = str;
        int port = 6379;
        if (str.contains(":")) {
            String[] arrays = str.split(":");
            host = arrays[0];
            port = Integer.parseInt(arrays[1]);
        }
        // 其实只要有1个可连接的节点就行;
        HostAndPort hostAndPort = new HostAndPort(host, port);
        jedisClusterNode.add(hostAndPort);
    }
    return createJedisCluster(jedisClusterNode, password, clientName);
}

// 创建Jedis集群
public static JedisCluster createJedisCluster(Set<HostAndPort> nodes, String password, String clientName) {
    //
    int DEFAULT_MAX_ATTEMPTS = 5;
    int DEFAULT_TIMEOUT = 2000;
    //
    int connectionTimeout = DEFAULT_TIMEOUT;
    int soTimeout = DEFAULT_TIMEOUT;
    int maxAttempts = DEFAULT_MAX_ATTEMPTS;
    GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();

    JedisCluster jedisCluster = new JedisCluster(nodes, connectionTimeout,
            soTimeout, maxAttempts, password, clientName, poolConfig);
    return jedisCluster;

    // public JedisCluster(Set<HostAndPort> jedisClusterNode, int connectionTimeout, int soTimeout,
    //      int maxAttempts, String password, String clientName, final GenericObjectPoolConfig poolConfig) {
    //    super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig);
    // }
}

逻辑并不复杂, 关键是使用哪个构造方法。

具体开发时, 根据已有的参数和Redis集群情况, 根据入参选择对应的构造函数即可。

5.7 解析Jedis对应的IP和端口号

因为业务逻辑需要, 创建一个解析方法:

// 反射获取Redis节点的IP和端口号
public static HostAndPort hostPort(Jedis jedis) {
    if (Objects.isNull(jedis)) {
        return null;
    }
    // 获取private 属性时, 需要使用直接定义该字段的类;
    // 当然, 也可以遍历迭代所有超类和接口来查找。
    Class<BinaryJedis> clazzJedis = BinaryJedis.class;
    Class<Connection> clazzClient = Connection.class;

    try {
        // 获取字段
        Field clientField = clazzJedis.getDeclaredField("client");
        Field jedisSocketFactoryField = clazzClient.getDeclaredField("jedisSocketFactory");
        // 临时设置这个字段包装允许访问/读取
        clientField.setAccessible(true);
        jedisSocketFactoryField.setAccessible(true);
        // 反射获取对应的属性
        Client client = (Client) clientField.get(jedis);
        JedisSocketFactory jedisSocketFactory = 
            (JedisSocketFactory) jedisSocketFactoryField.get(client);
        // 拼装 HostAndPort
        String host = jedisSocketFactory.getHost();
        int port = jedisSocketFactory.getPort();
        HostAndPort hostAndPort = new HostAndPort(host, port);
        // HostAndPort 实现了 toString() 方法, 使用很方便.
        return hostAndPort;
    } catch (Exception e) {
        String message = ("解析Jedis的HostAndPort出错; errorMsg: " + e.getMessage());
        System.out.println(message);
        return null;
    }
}

需要其他属性, 也可以采用类似的方法来获取。

5.8 扫描结果回调示例

这里提供一个简单的回调示例, 具体代码可根据需求进行改写。


// 扫描结果回调逻辑实现
public static class ScanResultCallBackImpl implements ScanResultCallBack {
    // 缓存Jedis与IP端口的映射关系
    private Map<Jedis, String> hostMap = new HashMap<>();
    // IP端口与Key数量的简单统计
    private Map<String, AtomicLong> countMap = new HashMap<>();

    // 解析Jedis的IP端口并缓存
    private String parseHostPort(Jedis jedis) {
        if (hostMap.containsKey(jedis)) {
            return hostMap.get(jedis);
        }
        HostAndPort hostAndPort = RedisKeyScanHelper.hostPort(jedis);
        if (Objects.nonNull(hostAndPort)) {
            hostMap.put(jedis, hostAndPort.toString());
            return hostMap.get(jedis);
        }
        return "UNKNOWN";
    }

    // 自增统计
    private long incrementCount(String hostAndPort) {
        AtomicLong counter = countMap.getOrDefault(hostAndPort, new AtomicLong());
        long curCount = counter.incrementAndGet();
        countMap.put(hostAndPort, counter);
        return curCount;
    }

    // 回调入口
    @Override
    public void process(String key, Jedis jedis) {
        String hostAndPort = parseHostPort(jedis);
        // System.out.println("扫描到Key:" + key + "; 所在节点:" + hostAndPort);
        // 计数
        long curCount = incrementCount(hostAndPort);
        // 采样
        if (curCount % 10000L == 1L) {
            String type = jedis.type(key);
            // 一般取值包括: `string`, `list`, `set`, `zset`, `hash` 以及 `stream`
            if ("string".equals(type)) {
                String value = jedis.get(key);
                System.out.println("==回调采样" + curCount + "; 扫描到Key=" + key +
                        "; value=" + value + "; 所在节点: " + hostAndPort);
            } else {
                System.out.println("==回调采样" + curCount + "; 扫描到Key:" + key +
                        "; type=" + type + "; 所在节点: " + hostAndPort);
            }
        }
        // 判断Key满足某种标准;
        if (key.startsWith("cnc:")) {
            // doSomething
            // 比如满足某种特征的Key,
            // 或者满足某种特征的VALUE, 执行某些操作
        }
    }

    // 这里通过 toString() 暴露一些信息
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("ClusterHostList: " + JSON.toJSON(hostMap.values())).append("\n");
        builder.append("countMap: " + JSON.toJSON(countMap)).append("\n");
        return builder.toString();
    }
}

如果不想实现其他方法, 重写 toString() 是一个简单的方法。

5.9 测试代码

我们写一个简单的 main 方法来测试:



public static void main(String[] args) {
    // 只需要传入1个节点的IP和端口即可
    Set<HostAndPort> jedisClusterNode = new HashSet<>();
    jedisClusterNode.add(new HostAndPort("cluster-1.cnc.com", 7000));
    // 密码信息, 没有传 null
    String password = "Your_Password";
    String clientName = "RedisKeyScanner";
    // 创建Jedis集群
    JedisCluster jedisCluster = createJedisCluster(jedisClusterNode, password, clientName);
    // 回调
    ScanResultCallBack callBack = new ScanResultCallBackImpl();
    // 开始扫描
    scanRedisCluster(jedisCluster, callBack);
    // 汇总结果信息
    System.out.println("结果汇总:" + callBack.toString());
}

测试时需要先准备好 Redis 集群, 根据集群信息配置即可。

6. Lettuce 实现代码

TODO 准备研究 Lettuce, 如果读者有参考实现, 欢迎交流探讨。

7. 简单总结

遍历Redis集群中所有Key的步骤并不复杂:

  1. 连接Redis集群
  2. 查询所有节点
  3. 判断节点角色
  4. 扫描Redis节点的所有Key
  5. 回调和统计

参考链接

作者: 铁锚
日期: 2023年06月14日

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

遍历Redis集群中的所有Key 的相关文章

  • Android PhoneGap 插件,UI 选项卡栏,调整 WebView 大小

    我正在创建一个美味的 PhoneGap 插件 希望一旦它能被打开 准备好了 插件基本完成了 我只需要一个漂亮的用户界面 相互作用 简而言之 我想创建一个 本机 android 工具栏组件 如果您实现 PhoneGap UIControls
  • Maven 2:如何将当前项目版本打包在WAR文件中?

    我正在使用 Maven 2 构建我的 Java 项目 并且正在寻找一种向用户呈现 pom xml 当前版本号的方法 例如使用 Servlet 或 JSP 据我所知 最好的方法是 Maven 将版本号作为文本文件打包到 WAR 中 这使我能够
  • Java - 如何将特殊字符放入字符串中

    Java 似乎有很好的字符串处理能力 尽管如此 我还是遇到了最简单的问题 我需要动态字符串 它们在运行时更改 因此字符串类型不是一个好的选择 因为它们是不可变的 所以我使用字符数组 设置起来有点痛苦 但至少它们是可以修改的 我想创建一个字符
  • Hashmap并发问题

    我有一个哈希图 出于速度原因 我希望不需要锁定 假设我不介意过时的数据 同时更新它和访问它会导致任何问题吗 我的访问是获取 而不是迭代 删除是更新的一部分 是的 这会导致重大问题 一个例子是向散列映射添加值时可能发生的情况 这可能会导致表重
  • jvm 次要版本与编译器次要版本

    当运行使用具有相同主要版本但次要版本高于 JVM 的 JDK 编译的类时 JVM 会抛出异常吗 JDK 版本并不重要 类文件格式版本 http blogs oracle com darcy entry source target class
  • 使用 Java 在 WebDriver 中按 Ctrl+F5 刷新浏览器

    我已经使用 java 刷新了 WebDriver 中的浏览器 代码如下 driver navigate refresh 如何使用 Java 在 WebDriver 中按 Ctrl F5 来做到这一点 我认为您可以使用 WebDriver 和
  • 在哪里可以获得有关 Java FitNesse 和 Slim 的一些教程? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 无法使用 json 架构验证器根据预定义的 yaml 文件验证查询参数

    我需要根据预定义的 yaml 文件架构验证查询参数的架构 因此我使用 json 架构验证器 验证如何失败 我正在执行以下步骤 填充参数和相应的架构 final List
  • Java-如何将黑白图像加载到二进制中?

    我在 FSE 模式下使用 Java 和 swing 我想将完全黑白图像加载为二进制格式 最好是二维数组 并将其用于基于掩码的每像素碰撞检测 我什至不知道从哪里开始 过去一个小时我一直在研究 但没有找到任何相关的东西 只需将其读入Buffer
  • 如何将 Observable>> 转换为 Observable>

    我陷入了如何将以下可观察类型转换 转换为我的目标类型的困境 我有以下类型的可观察值 Observable
  • 我想在java中使用XQuery进行Xml处理

    我想用XQuery用于从 java 中的 Xml 获取数据 但我没有得到需要为此添加哪个 Jar 我在谷歌上搜索了很多 但没有得到任何有用的例子 例如我得到以下链接 https docs oracle com database 121 AD
  • 在 Java 中将弯音发送到 MIDI 音序器

    我了解启动和运行 MIDI 音序器的基础知识 并且希望能够在播放过程中增加 减小序列的音高 但弯音是发送到合成器而不是音序器的消息 我尝试将音序器的接收器设置为合成器的发射器 当我发送弯音短消息时 音序器保持相同的音调 但随后合成器以新的弯
  • 带有 OpenId 提供程序的 Java Spring 安全性

    我有一个 spring MVC 应用程序 另一个客户端应用程序想要使用 open id connect 访问我的 spring 应用程序 如何在服务器端实现开放ID提供商 请帮忙 MITREid 连接 OpenID Connect Java
  • 如何在Java媒体框架中学习.wav持续时间?

    我正在尝试使用 java 媒体框架将 mov 文件与 wav 文件合并 因此我需要知道它们的持续时间 我怎样才能做到这一点 任何想法 将不胜感激 您可以使用以下方式了解声音文件的持续时间 即 VitalyVal 的第二种方式 import
  • Java:由 HTTP 连接创建的等待连接线程存活时间很长

    我有一个服务器端代码 用于检查 SOAP 服务是否已启动 代码如下 String response while response length 0 try final URL url new URL DummySoapServiceURL
  • 使用 JAD 反编译 java - 限制

    我正在尝试使用 Java 中的 JAD 反编译几个 jar 文件 我也尝试过 JD GUI 但运气更差 但出现了很多错误 一种类型 易于修复 似乎是内部类 但我也发现了这段代码 static int SWITCH TABLE atp com
  • 公共方法与公共 API

    在干净的代码书中 有一个观点是 公共 API 中的 Javadocs 同样 Effective java 一书也有这样的内容 项目 56 为所有公开的 API 元素编写文档注释 所以这就是我的问题 所有公共方法都被视为公共 API 吗 它们
  • 摩尔斯电码 至 英语

    我现在的问题是让 摩尔斯电码转英语 正常工作 将英语转换为莫尔斯电码的第一部分工作正常 我知道以前已经有人问过这个问题 但我不知道我做错了什么 我知道我需要在某个地方进行拆分 但我只是不确定将其放在代码中的何处 现在 莫尔斯电码到英语的部分
  • 为什么应该首选 Java 类的接口?

    PMD https pmd github io 将举报以下违规行为 ArrayList list new ArrayList 违规行为是 避免使用 ArrayList 等实现类型 而是使用接口 以下行将纠正违规行为 List list ne
  • 如何在不同版本的Google App Engine中使用自定义域名?

    我使用谷歌应用程序引擎作为我的 Android 和 Web 应用程序的服务器 我使用 Android Studio 开发了 Android 应用程序 并使用 Eclipse 开发了 Web 应用程序 我在应用程序引擎中部署了两个版本 第一个

随机推荐

  • Android Kotlin的学习

    1 kotlin简介 Kotlin是一种在Java虚拟机上运行的静态类型编程语言 它也可以被编译成为JavaScript源代码 它主要是由俄罗斯圣彼得堡的JetBrains开发团队所发展出来的编程语言 其名称来自于圣彼得堡附近的科特林岛 3
  • 【CLIP速读篇】Contrastive Language-Image Pretraining

    CLIP速读篇 Contrastive Language Image Pretraining 0 前言 Abstract 1 Introduction and Motivating Work 2 Approach 2 1 Natural L
  • 从元宇宙角度看社交出海产品新体验

    提到社交产品 不可避免的会涉及元宇宙方向 那么元宇宙距离落地还有哪些问题 解决这些问题是否会是新的产品机会 社交作为元宇宙赛道的细分领域之一 如何在未来几年向元宇宙产品发展 打造产品新体验 实现用户增长 本文整理自拍乐云行业解决方案专家奚振
  • 《普林斯顿微积分》读书笔记

    写在前面 并不完整 只有零散的记忆 二 三刷的时候再补充吧 一些初等函数的导数 例如 x n n x n 1 sin x cos x 积分等于反导数 其他 待补充
  • games101 作业3

    遇到的问题 1 项目才打开时无法运行 解决方法 切换成c 17 解决方法引用 Games101 作业3 环境问题 知乎 注 知乎里面的关于越界限制的控制不适用 虽然可以解决部分作业的问题 但是在bump里面依然会出现越界错误 应该用以下大佬
  • VS2022+OpenCV4.6.0+MFC环境配置

    一 环境安装 OpenCV Releases OpenCVhttps opencv org releases VS2022 Visual Studio 面向软件开发人员和 Teams 的 IDE 和代码编辑器Visual Studio 开发
  • 【selenium3+JAVA】界面自动化测试教程(六)——元素查找和操作

    一 前言 元素查找为selenium的基础操作 基本上大部分操作都是基于元素的 故此部分为必须掌握内容 方法包括通过名称 id tagName xpath等方法 下面会详细介绍这些方法 二 元素查找 下面一一介绍这些方法 无论哪种查找方式
  • IC验证工程师工作一周年的体会

    转眼之间自己已经工作一周年了 作为一名验证工程师 这一年里面感觉自己虽然有了一定的成长 但是成长的还是比较缓慢的 接下来从个人的角度说说我现在对从IC验证的一些体会 一 要养成良好的工作习惯 1 自己在工作中发现很多时候都是可以偷懒的 有时
  • Docker网络模式

    目录 Docker 网络 Docker 网络图解 Docker 四种网络模式 Host 模式 Container模式 None模式 Bridge模式 Docker 网络自定义 查看网络列表 自定义网络固定IP 暴露端口 在宿主机环境执行容器
  • 软件测试新手入门小知识点,一定要牢记

    引言 最近有很多朋友来问我做测试难不难 需要注意哪些 接下来就给大家讲讲软件测试新手入门需要了解的小知识点 这些软件测试常识你必须牢记 一 软件测试 软件测试存在的意义 1 发现程序中的错误而执行程序的过程 2 检验产品是否符合用户需求 3
  • Prometheus 监控mysql

    目录 下载安装mysqld exporter 在mysql中创建监控用户并赋权 启动mysqld exporter 添加到系统服务 浏览器访问服务器9104端口 在prometheus定义job来监控mysqld 运行prometheus并
  • Linux系统查看硬盘空间的常用命令!

    在Linux系统中 查看硬盘空间使用情况可以使用命令来完成 其中比较常见的命令有 df和du 那么它们具体如何使用呢 本文为大家详细介绍一下 快来学习吧 查看磁盘空间 df df命令以磁盘分区为单位查看文件系统中磁盘空间的使用情况 选项 h
  • VS2019 + Qt5.12 配置完成后,无法打开 Qt 源文件解决方案(非常实用)

    注 本文主要是解决 VS 无法打开 Qt 源文件问题 关于 VS Qt 配置问题 网上一搜一大堆 各个版本都有 这里就不做详细阐述了 最近自己在使用 VS2019 建立 Qt 工程的时候 遇到了无法打开 Qt 源文件问题 在网上阅读了大量的
  • 每天一个设计模式之 -- 组合模式

    组合模式 组合模式 Composite Pattern 又叫部分整体模式 是用于把一组相似的对象当作一个单一的对象 组合模式依据树形结构来组合对象 用来表示部分以及整体层次 这种类型的设计模式属于结构型模式 它创建了对象组的树形结构 这种模
  • 基于TCP的Socket网络编程

    前言 Socket通信是基于TCP IP协议的通信 在工作和做项目中应用非常广 下面来介绍下Socket网络编程 Socket的介绍 首先 在Socket网络编程中我们要了解两个重要的东西 ip和端口号 一台拥有IP地址的主机可以提供许多服
  • HttpContext在一般程序中无法引用

    刚刚写代码的时候 发现在类库的程序中无法直接使用HttpContext 经过学习发现 只要在引用上右击弹出下面界面 然后点击程序集 直接在搜索框中搜web 然后选用System Web 确定 再在你的cs文件中using System We
  • isolation forest在MATLAB和python上的简单应用

    isolation forest在MATLAB和python上的简单应用 1 问题描述 2 isolation forest 3 MATLAB实现isolation forest 4 python实现isolation fores 5 遇到
  • 【linux学习笔记】红帽Linux 7.8系统在虚拟机上的安装

    目录 一 打开VMware 选择新建虚拟机选项 二 选择自定义 并且点击下一步 三 可以选择默认继续下一步 可以进行更改虚拟机硬件兼容性 四 继续下一步 选择稍后安装系统 五 下一步选择客户操作系统Red Hat Enterprise Li
  • Centos8无法用yum下载

    原因 CentOS 8操作系统版本结束了生命周期 EOL Linux社区已不再维护该操作系统版本 解决方案 在服务器上执行如下代码 1 curl o etc yum repos d CentOS Base repo https mirror
  • 遍历Redis集群中的所有Key

    文章目录 1 背景s 2 需求 3 实现思路 4 SCAN 简介 5 用Jedis来实现Key扫描 5 1 基础的辅助工具类 5 2 批处理停止开关 5 3 扫描结果回调函数 5 4 实现单个Redis节点的Key扫描 5 5 实现Redi