Flink寫入Redis集群 重寫flink-connector-redis包,解決Cluster無法輸入密碼問題

来源:https://www.cnblogs.com/zhuisuidefeng/archive/2022/07/28/16530465.html
-Advertisement-
Play Games

使用flink的時候難免和redis打交道,相信大家都使用過flink-connector-redis來處理,但是當我想要使用RedisSink寫入集群時,發現居然不支持使用密碼,於是有了這篇筆記。 ...


起因:使用flink的時候難免和redis打交道,相信大家都使用過flink-connector-redis來處理,但是當我想要使用RedisSink寫入集群時,發現居然不支持使用密碼,於是有了這篇筆記。

 

事情的經過是這樣的,我準備用Flink往Redis寫入數據,我照常引入flink-connector-redis包

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-redis_2.11</artifactId>
            <version>1.1.5</version>
        </dependency>

然後洋洋灑灑寫下如下代碼:

package org.cube.flink

import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}

import java.net.InetSocketAddress
import java.util.HashSet

/**
 * @Author : Lawrence
 * @Date : 2022/7/24 23:11
 * @Description : Flink結果寫入Redis集群
 * @Version : 1.0.0
 * @Modification_Record:
 * Version  Date       Remark
 * v1.0.0   2022/7/24  First Create
 */
object RedisClusterSink {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)

    // source
    import org.apache.flink.api.scala._
    val source = env.fromElements("1 hadoop","2 spark","3 flink", "4 hive", "5 redis", "6 hbase")

    // process
    val tupleValue = source.map(_.split(" ")).map(x => (x(0), x(1)))

    // redis config
    val builder = new FlinkJedisPoolConfig.Builder
    builder.setHost("cube01").setPort(7001).setPassword("123456")
    val redisConf: FlinkJedisPoolConfig = builder.build()

    // sink
    val redisSink = new RedisSink[(String, String)](redisConf, new MyRedisMapper())

    tupleValue.addSink(redisSink)

    env.execute("RedisClusterSink")
  }
}

class MyRedisMapper extends RedisMapper[(String, String)] {
  override def getCommandDescription: RedisCommandDescription = {
    new RedisCommandDescription(RedisCommand.SET)
  }

  override def getKeyFromData(t: (String, String)): String = t._1

  override def getValueFromData(t: (String, String)): String = t._2
}

然後興高采烈地點擊了運行,控制台卻給了我一抹中國紅,

其中最後一條是這樣說的:

Caused by: redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 9842 192.168.20.132:7003

哦哦,是因為我的Redis是集群模式,

這並難不倒我,

我只需要把FlinkJedisPoolConfig改成FlinkJedisClusterConfig就萬事大吉了。

    // redis config
    val builder = new FlinkJedisClusterConfig.Builder
    val inetSocketAddress = new InetSocketAddress("cube01", 7001)
    val nodeSet = new HashSet[InetSocketAddress]()
    nodeSet.add(inetSocketAddress)
    builder.setNodes(nodeSet).setPassword("123456")
    val redisConf: FlinkJedisClusterConfig = builder.build()

可是,這個類並沒有setPassword方法,事實上它連"password"這個屬性都沒有。

這並難不倒我。

先不設密碼總行了吧?

燃鵝並不行,控制台又給了我一抹中國紅,

他是這樣說的:

Caused by: redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required.

呵呵,這可難不倒我,

我的本能反應是,應該到Maven倉庫中找到新版的flink-connector-redis包。

燃鵝,當我搜索之後發現,這已經是最新版了。

這也難不倒我。 

FlinkJedisPoolConfig不是可以設置密碼嗎?

FlinkJedisClusterConfig不是可以訪問集群嗎?

如果我把他們兩個的代碼整合一下呢?是不是就好了。

於是我本能地把"FlinkJedisClusterConfig"改寫成了"MyFlinkJedisClusterConfig"類,增加了password屬性和對應的get,set方法。

package org.cube.flink;

/**
 * @Author : Lawrence
 * @Date : 2022/7/25 21:14
 * @Description : 包含了password的FlinkJedisClusterConfig
 * @Version : 1.0.0
 * @Modification_Record:
 * Version  Date       Remark
 * v1.0.0   2022/7/25  First Create
 */
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisConfigBase;
import org.apache.flink.util.Preconditions;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Protocol;

import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class MyFlinkJedisClusterConfig extends FlinkJedisConfigBase {
    private static final long serialVersionUID = 1L;
    private final Set<InetSocketAddress> nodes;
    private final int maxRedirections;
    private int soTimeout;
    private String password;

    private MyFlinkJedisClusterConfig(Set<InetSocketAddress> nodes, int connectionTimeout, int soTimeout,
                                      int maxRedirections, int maxTotal, int maxIdle, int minIdle, String password) {
        super(connectionTimeout, maxTotal, maxIdle, minIdle);
        Preconditions.checkNotNull(nodes, "Node information should be presented");
        Preconditions.checkArgument(!nodes.isEmpty(), "Redis cluster hosts should not be empty");
        this.nodes = new HashSet(nodes);
        this.soTimeout = soTimeout;
        this.maxRedirections = maxRedirections;
        this.password = password;
    }

    public Set<HostAndPort> getNodes() {
        Set<HostAndPort> ret = new HashSet();
        Iterator var2 = this.nodes.iterator();

        while(var2.hasNext()) {
            InetSocketAddress node = (InetSocketAddress)var2.next();
            ret.add(new HostAndPort(node.getHostName(), node.getPort()));
        }

        return ret;
    }
    public int getMaxRedirections() {
        return this.maxRedirections;
    }
    public int getSoTimeout() { return this.soTimeout; }
    protected String getPassword() { return this.password; }

    public String toString() {
        return "JedisClusterConfig{nodes=" + this.nodes + ", timeout=" + this.connectionTimeout
                + ", maxRedirections=" + this.maxRedirections + ", maxTotal=" + this.maxTotal
                + ", maxIdle=" + this.maxIdle + ", minIdle=" + this.minIdle + '}';
    }

    public static class Builder {
        private Set<InetSocketAddress> nodes;
        private int timeout = Protocol.DEFAULT_TIMEOUT;
        private int maxRedirections = 5;
        //新增屬性
        private int soTimeout = Protocol.DEFAULT_TIMEOUT;
        private int maxTotal = GenericObjectPoolConfig.DEFAULT_MAX_TOTAL;
        private int maxIdle = GenericObjectPoolConfig.DEFAULT_MAX_IDLE;
        private int minIdle = GenericObjectPoolConfig.DEFAULT_MIN_IDLE;
        //增加的屬性
        private String password;

        public Builder() {
        }

        public MyFlinkJedisClusterConfig.Builder setNodes(Set<InetSocketAddress> nodes) {
            this.nodes = nodes;
            return this;
        }

        public MyFlinkJedisClusterConfig.Builder setTimeout(int timeout) {
            this.timeout = timeout;
            return this;
        }

        public MyFlinkJedisClusterConfig.Builder setSoTimeout(int soTimeout) {
            this.soTimeout = soTimeout;
            return this;
        }

        public MyFlinkJedisClusterConfig.Builder setMaxRedirections(int maxRedirections) {
            this.maxRedirections = maxRedirections;
            return this;
        }

        public MyFlinkJedisClusterConfig.Builder setMaxTotal(int maxTotal) {
            this.maxTotal = maxTotal;
            return this;
        }

        public MyFlinkJedisClusterConfig.Builder setMaxIdle(int maxIdle) {
            this.maxIdle = maxIdle;
            return this;
        }

        public MyFlinkJedisClusterConfig.Builder setMinIdle(int minIdle) {
            this.minIdle = minIdle;
            return this;
        }

        public MyFlinkJedisClusterConfig.Builder setPassword(String password) {
            this.password = password;
            return this;
        }

        public MyFlinkJedisClusterConfig build() {
            return new MyFlinkJedisClusterConfig(this.nodes, this.timeout, this.soTimeout,
                    this.maxRedirections, this.maxTotal, this.maxIdle, this.minIdle, this.password);
        }
    }
}

燃鵝,中國紅卻提醒我:

Caused by: java.lang.IllegalArgumentException: Jedis configuration not found

原來,Flink任務執行的時候會調用RedisSink中的open()方法:

    public void open(Configuration parameters) throws Exception {
        this.redisCommandsContainer = RedisCommandsContainerBuilder.build(this.flinkJedisConfigBase);
    }

而這個方法調用的"RedisCommandsContainerBuilder.build"方法,所使用的參數,依然是舊的FlinkJedisClusterConfig類:

    public static RedisCommandsContainer build(FlinkJedisConfigBase flinkJedisConfigBase)

所以,還得改寫這兩個類:

MyRedisSink:

package org.cube.flink;

/**
 * @Author : Lawrence
 * @Date : 2022/7/25 23:52
 * @Description :
 * @Version : 1.0.0
 * @Modification_Record :
 * Version  Date       Remark
 * v1.0.0   2022/7/25  First Create
 */
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisConfigBase;
import org.apache.flink.streaming.connectors.redis.common.container.RedisCommandsContainer;
import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommand;
import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommandDescription;
import org.apache.flink.streaming.connectors.redis.common.mapper.RedisMapper;
import org.apache.flink.util.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class MyRedisSink<IN> extends RichSinkFunction<IN> {
    private static final long serialVersionUID = 1L;
    private static final Logger LOG = LoggerFactory.getLogger(org.apache.flink.streaming.connectors.redis.RedisSink.class);
    private String additionalKey;
    private RedisMapper<IN> redisSinkMapper;
    private RedisCommand redisCommand;
    private FlinkJedisConfigBase flinkJedisConfigBase;
    private RedisCommandsContainer redisCommandsContainer;

    public MyRedisSink(FlinkJedisConfigBase flinkJedisConfigBase, RedisMapper<IN> redisSinkMapper) {
        Preconditions.checkNotNull(flinkJedisConfigBase, "Redis connection pool config should not be null");
        Preconditions.checkNotNull(redisSinkMapper, "Redis Mapper can not be null");
        Preconditions.checkNotNull(redisSinkMapper.getCommandDescription(), "Redis Mapper data type description can not be null");
        this.flinkJedisConfigBase = flinkJedisConfigBase;
        this.redisSinkMapper = redisSinkMapper;
        RedisCommandDescription redisCommandDescription = redisSinkMapper.getCommandDescription();
        this.redisCommand = redisCommandDescription.getCommand();
        this.additionalKey = redisCommandDescription.getAdditionalKey();
    }

    @Override
    public void invoke(IN input) throws Exception {
        String key = this.redisSinkMapper.getKeyFromData(input);
        String value = this.redisSinkMapper.getValueFromData(input);
        switch(this.redisCommand) {
            case RPUSH:
                this.redisCommandsContainer.rpush(key, value);
                break;
            case LPUSH:
                this.redisCommandsContainer.lpush(key, value);
                break;
            case SADD:
                this.redisCommandsContainer.sadd(key, value);
                break;
            case SET:
                this.redisCommandsContainer.set(key, value);
                break;
            case PFADD:
                this.redisCommandsContainer.pfadd(key, value);
                break;
            case PUBLISH:
                this.redisCommandsContainer.publish(key, value);
                break;
            case ZADD:
                this.redisCommandsContainer.zadd(this.additionalKey, value, key);
                break;
            case HSET:
                this.redisCommandsContainer.hset(this.additionalKey, key, value);
                break;
            default:
                throw new IllegalArgumentException("Cannot process such data type: " + this.redisCommand);
        }

    }

    @Override
    public void open(Configuration parameters) throws Exception {
        this.redisCommandsContainer = MyRedisCommandsContainerBuilder.build(this.flinkJedisConfigBase);
    }

    @Override
    public void close() throws IOException {
        if (this.redisCommandsContainer != null) {
            this.redisCommandsContainer.close();
        }

    }
}

MyRedisCommandsContainerBuilder:

package org.cube.flink;

/**
 * @Author : Lawrence
 * @Date : 2022/7/25 21:30
 * @Description : 包含了password的RedisCommandsContainerBuilder
 * @Version : 1.0.0
 * @Modification_Record :
 * Version  Date       Remark
 * v1.0.0   2022/7/25  First Create
 */
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisConfigBase;
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig;
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisSentinelConfig;
import org.apache.flink.streaming.connectors.redis.common.container.RedisClusterContainer;
import org.apache.flink.streaming.connectors.redis.common.container.RedisCommandsContainer;
import org.apache.flink.streaming.connectors.redis.common.container.RedisContainer;
import org.apache.flink.util.Preconditions;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisSentinelPool;

public class MyRedisCommandsContainerBuilder {
    public MyRedisCommandsContainerBuilder() {
    }

    public static RedisCommandsContainer build(FlinkJedisConfigBase flinkJedisConfigBase) {
        if (flinkJedisConfigBase instanceof FlinkJedisPoolConfig) {
            FlinkJedisPoolConfig flinkJedisPoolConfig = (FlinkJedisPoolConfig)flinkJedisConfigBase;
            return build(flinkJedisPoolConfig);
        } else if (flinkJedisConfigBase instanceof MyFlinkJedisClusterConfig) {
            MyFlinkJedisClusterConfig flinkJedisClusterConfig = (MyFlinkJedisClusterConfig)flinkJedisConfigBase;
            return build(flinkJedisClusterConfig);
        } else if (flinkJedisConfigBase instanceof FlinkJedisSentinelConfig) {
            FlinkJedisSentinelConfig flinkJedisSentinelConfig = (FlinkJedisSentinelConfig)flinkJedisConfigBase;
            return build(flinkJedisSentinelConfig);
        } else {
            throw new IllegalArgumentException("Jedis configuration not found");
        }
    }

    public static RedisCommandsContainer build(FlinkJedisPoolConfig jedisPoolConfig) {
        Preconditions.checkNotNull(jedisPoolConfig, "Redis pool config should not be Null");
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxIdle(jedisPoolConfig.getMaxIdle());
        genericObjectPoolConfig.setMaxTotal(jedisPoolConfig.getMaxTotal());
        genericObjectPoolConfig.setMinIdle(jedisPoolConfig.getMinIdle());
        JedisPool jedisPool = new JedisPool(genericObjectPoolConfig, jedisPoolConfig.getHost(), jedisPoolConfig.getPort()
                , jedisPoolConfig.getConnectionTimeout(), jedisPoolConfig.getPassword(), jedisPoolConfig.getDatabase());
        return new RedisContainer(jedisPool);
    }

    public static RedisCommandsContainer build(MyFlinkJedisClusterConfig jedisClusterConfig) {
        Preconditions.checkNotNull(jedisClusterConfig, "Redis cluster config should not be Null");
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxIdle(jedisClusterConfig.getMaxIdle());
        genericObjectPoolConfig.setMaxTotal(jedisClusterConfig.getMaxTotal());
        genericObjectPoolConfig.setMinIdle(jedisClusterConfig.getMinIdle());
        JedisCluster jedisCluster;
        if (null == jedisClusterConfig.getPassword()) {
            jedisCluster = new JedisCluster(jedisClusterConfig.getNodes(), jedisClusterConfig.getConnectionTimeout(), 
                    jedisClusterConfig.getMaxRedirections(), genericObjectPoolConfig);
        } else
            {
            jedisCluster = new JedisCluster(jedisClusterConfig.getNodes(), jedisClusterConfig.getConnectionTimeout()
                    , jedisClusterConfig.getSoTimeout(), jedisClusterConfig.getMaxRedirections()
                    , jedisClusterConfig.getPassword(), genericObjectPoolConfig);
        }
        return new RedisClusterContainer(jedisCluster);
    }

    public static RedisCommandsContainer build(FlinkJedisSentinelConfig jedisSentinelConfig) {
        Preconditions.checkNotNull(jedisSentinelConfig, "Redis sentinel config should not be Null");
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxIdle(jedisSentinelConfig.getMaxIdle());
        genericObjectPoolConfig.setMaxTotal(jedisSentinelConfig.getMaxTotal());
        genericObjectPoolConfig.setMinIdle(jedisSentinelConfig.getMinIdle());
        JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(jedisSentinelConfig.getMasterName()
                , jedisSentinelConfig.getSentinels(), genericObjectPoolConfig, jedisSentinelConfig.getConnectionTimeout()
                , jedisSentinelConfig.getSoTimeout(), jedisSentinelConfig.getPassword(), jedisSentinelConfig.getDatabase());
        return new RedisContainer(jedisSentinelPool);
    }
}

燃鵝,在重寫"MyRedisCommandsContainerBuilder"類時,你會驚奇地發現,jedisCluster 也不支持密碼。

你可千萬別慣性思維去重新jedisCluster ,

因為這回可真的是版本問題了。

所以這依然難不倒我,

只需要把redis.clients包升級到2.9以上版本即可:

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-redis_2.11</artifactId>
            <version>1.1.5</version>
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

好了,到這裡咱們終於大功告成了。

 

代碼寫完了,但是咱們卻留下一個疑惑,

為什麼這麼簡單的需求卻沒有jar包更新呢?

我只是想把Flink數據寫到帶密碼的Redis集群里,這過分嗎?

這並不過分,那這又是為啥呢?

我想可能是這樣的:

首先,先想一個問題,在流計算中我們往Redis寫的是什麼數據?

通常是一些狀態信息,中間結果。而Flink本身支持狀態、緩存和廣播機制,導致對Redis的需求下降了。

其次,大數據應用實際運行的環境通常是提交到內網的機器上進行的,大家知道大數據集群之間的主機是需要設置免驗證登錄的,單單給Redis設密碼顯得有一點點多餘。

其三,Redis的密碼機制據說是很弱雞的,出於安全考慮,更多地是通過防火牆來限制埠,所以很多Redis集群處於管理方便並沒有設置密碼的。

 其四,出於人類懶惰的本性,發現RedisSink不支持密碼後,最省事的方式,或許是放棄使用密碼。

 

好了該寫的寫了,該想的也想了,差不多可以愉快地結束這一天了。

那麼晚安了,咱們下期再肝。

 

三天後。。。

哈哈哈,最後發現好像還是重造了輪子,bahir發佈了一個非正式版的jar,這是介紹頁:

https://bahir.apache.org/docs/flink/current/flink-streaming-redis/

或者maven直接引入這個包即可:

        <dependency>
            <groupId>org.apache.bahir</groupId>
            <artifactId>flink-connector-redis_2.11</artifactId>
            <version>1.1-SNAPSHOT</version>
        </dependency>

有點小憂傷。

這個包增加了很多類,完善了一些功能,有興趣可以下載下來,比較一下和1.0的不同。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作者:Steven Giesel 翻譯:Alan Wang 校對:李衛涵 – 微軟 MVP 排版:Rani Sun 有什麼比參考包含分步說明和代碼示例的動手教程更好的學習新技術的方式呢?當你完成或 fork 本教程後,你將得到這樣一個應用程式: Steven Giesel 最近發佈了一個由5部分內容 ...
  • 自從2021年2月第20輪公佈的測試以後,一年半後 的2022年7月19日 發佈了 TechEmpower 21輪測試報告:Round 21 results - TechEmpower Framework Benchmarks。Techempower benchmark是包含範圍最廣泛的web框架性 ...
  • 我個人對GRPC是比較感興趣的,最近在玩通過前端調用GRPC。通過前端調用GRPC業界有兩種方式:GRPC Web和GRPC JSON轉碼。 GRPC Web 通過JS或者Blazor WASM調用GRPC,微軟在這方面做的還是很好的,從.NET Core3.0之後就提供了兩種實現GRPC Web的 ...
  • Doxillion是一款多格式文檔轉換器軟體,支持在Microsoft office文件(如doc,docx,xls和xlsx)之間進行轉換,且一次可以轉換數千個文件。Doxillion還支持從WordPerfect,txt,Open office,odt等轉換為pdf,docx等,可以大大的提升你 ...
  • Music Tag Editor 中文版是一款macOS平臺的音頻標簽管理工具,支持UNICODE,軟體帶有PLS/M3U列表生成器,還可以生成XML/TXT/CSV格式的播放列表,通過它用戶可以快速修改音樂文件的tag信息、創建播放列表,另外,軟體自帶播放器,可以播放常見的音頻文件。 詳情:Mus ...
  • PDFelement Pro Mac是一款可以幫助用戶編輯PDF的工具,其設計的功能針對中小型用戶開發,支持常規的編輯、修改、操作,並且可以實現PDF文件轉換功能,您可以將word、Excel等office文件轉換為PDF文件保存,讓您可以擁有一款簡單、高效的PDF辦公軟體。 詳情:Wondersh ...
  • SoundSource 5 for Mac是一款優質的音頻控制軟體,可讓您直接從菜單欄調整輸入,輸出和音效設備以及音量設置。啟用輸入設備的軟播放到您想要的輸出,這樣您就可以聽到通過任何麥克風或其他來源發出的聲音。 詳情:SoundSource 5 for Mac(音頻控制工具) 功能介紹 1、快速訪 ...
  • 上篇文章講到使用MySQL的Explain命令可以分析SQL性能瓶頸,優化SQL查詢,以及查看是否用到了索引。 我們都知道創建索引可以提高查詢效率,但是具體該怎麼創建索引? 哪些欄位適合創建索引? 哪些欄位又不適合創建索引? 本文跟大家一塊學習一下如何創建合適資料庫索引。 ...
一周排行
    -Advertisement-
    Play Games
  • 隨著Aspire發佈preview5的發佈,Microsoft.Extensions.ServiceDiscovery隨之更新, 服務註冊發現這個屬於老掉牙的話題解決什麼問題就不贅述了,這裡主要講講Microsoft.Extensions.ServiceDiscovery(preview5)以及如何 ...
  • 概述:通過使用`SemaphoreSlim`,可以簡單而有效地限制非同步HTTP請求的併發量,確保在任何給定時間內不超過20個網頁同時下載。`ParallelOptions`不適用於非同步操作,但可考慮使用`Parallel.ForEach`,儘管在非同步場景中謹慎使用。 對於併發非同步 I/O 操作的數量 ...
  • 1.Linux上安裝Docken 伺服器系統版本以及內核版本:cat /etc/redhat-release 查看伺服器內核版本:uname -r 安裝依賴包:yum install -y yum-utils device-mapper-persistent-data lvm2 設置阿裡雲鏡像源:y ...
  • 概述:WPF界面綁定和渲染大量數據可能導致性能問題。通過啟用UI虛擬化、非同步載入和數據分頁,可以有效提高界面響應性能。以下是簡單示例演示這些優化方法。 在WPF中,當你嘗試綁定和渲染大量的數據項時,性能問題可能出現。以下是一些可能導致性能慢的原因以及優化方法: UI 虛擬化: WPF提供了虛擬化技術 ...
  • 引言 上一章節介紹了 TDD 的三大法則,今天我們講一下在單元測試中模擬對象的使用。 Fake Fake - Fake 是一個通用術語,可用於描述 stub或 mock 對象。 它是 stub 還是 mock 取決於使用它的上下文。 也就是說,Fake 可以是 stub 或 mock Mock - ...
  • 為.net6在CentOS7上面做準備,先在vmware虛擬機安裝CentOS 7.9 新建CentOS764位的系統 因為CentOS8不更新了,所以安裝7;簡單就一筆帶過了 選擇下載好的操作系統的iso文件,下載地址https://mirrors.aliyun.com/centos/7.9.20 ...
  • 經過前面幾篇的學習,我們瞭解到指令的大概分類,如:參數載入指令,該載入指令以 Ld 開頭,將參數載入到棧中,以便於後續執行操作命令。參數存儲指令,其指令以 St 開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。創建實例指令,其指令以 New 開頭,用於在運行時動態生成並初始化對象。方法調用指... ...
  • LiteDB 是一個輕量級的嵌入式 NoSQL 資料庫,其設計理念與 MongoDB 類似,但它是完全使用 C# 開發的,因此與 C# 應用程式的集成非常順暢。與 SQLite 相比,LiteDB 提供了 NoSQL(即鍵值對)的數據存儲方式,並且是一個開源且免費的項目。它適用於桌面、移動以及 We ...
  • 1 開源解析和拆分文檔 第三方的工具去對文件解析拆分,去將我們的文件內容給提取出來,並將我們的文檔內容去拆分成一個小的chunk。常見的PDF word mark down, JSON、HTML。都可以有很好的一些模塊去把這些文件去進行一個東西去提取。 優勢 支持豐富的文檔類型 每種文檔多樣化選擇 ...
  • OOM是什麼?英文全稱為 OutOfMemoryError(記憶體溢出錯誤)。當程式發生OOM時,如何去定位導致異常的代碼還是挺麻煩的。 要檢查OOM發生的原因,首先需要瞭解各種OOM情況下會報的異常信息。這樣能縮小排查範圍,再結合異常堆棧、heapDump文件、JVM分析工具和業務代碼來判斷具體是哪 ...