Kafkaマルチスレッド書き込みデータの場合

1.主なアイデア:

  • プロデューサー構成情報をカプセル化する
  • LineNumberReaderを使用して、ファイル内の行の総数と対応する行の開始バイト位置を取得し、それらをマップに格納して、さまざまなスレッドがさまざまな行からKafkaに読み書きできるようにします。
  • Threadクラスを継承し、runメソッドをオーバーライドして、実行します。

2.実装手順

2.1、メッセージインターフェースDbinfo

  • KafkaメッセージオブジェクトKafkaConfiguration
    • Dbinfo:接続パラメータ。手動で渡す必要があります。IP、ポート、トピックなど、別のクラスオブジェクトを記述してください。
    • 他のkafka構成はコンストラクターに配置できます
public interface Dbinfo {
    
    
    //1、分别为获取IP、端口号、主题topic名
    String getIp();
    int getPort();
    String getDbName();
    Map<String,String> getOther();
}
public class KafkaConfiguration implements Dbinfo {
    
    
    //2、配置参数实现类
    private String ip;
    private int port;
    private String dbName;
    
    @Override
    public String getIp() {
    
    
        return this.ip;
    }

    @Override
    public int getPort() {
    
    
        return this.port;
    }

    public void setIp(String ip) {
    
    
        this.ip = ip;
    }

    public void setPort(int port) {
    
    
        this.port = port;
    }

    public void setDbName(String dbName) {
    
    
        this.dbName = dbName;
    }

    @Override
    public String getDbName() {
    
    
        return this.dbName;
    }

    @Override
    public Map<String, String> getOther() {
    
    
        return null;
    }
}
  • mysql
  • オラクル
  • 繰り返す..。

2.2、KafkaConnector

ユーザーに公開されるコアメソッド:sendMessage(String path)

  • コンストラクターは、接続パラメーターと構成パラメーターを取得します
  • LineNumberReaderメソッドを使用してファイルの行情報を計算し、合計行数totalRowと[行番号と行開始位置]のrowSize配列を取得します。
  • クライアントをリンクして、トピックメッセージのパーティション数を取得します
  • 各スレッドの開始バイト位置と行数を計算し、それらをHashMapに格納します
  • sendMessageメソッドで、トピックパーティションの数と同じ数のスレッドを開いて開始します
public class KafkaConnector {
    
    
    //3、链接属性
    private Dbinfo info;
    //总行数
    private int totalRow=0;
    //每行的尺寸
    private List<Long> rowSize = new ArrayList<>();
    //配置属性
    Properties prop = new Properties();

    /**
     * info为用户端输入的参数,其他的此处在初始化里默认,也可以在作为参数让用户设置
     * @param info
     */
    public KafkaConnector(Dbinfo info){
    
    
        this.info = info;
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,info.getIp()+":"+info.getPort());
        prop.put(ProducerConfig.ACKS_CONFIG,"all");
        prop.put(ProducerConfig.RETRIES_CONFIG,"0");
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getTypeName());
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getTypeName());
    }

    /**
     * 9、send方法,用户调用
     * @param path
     * @throws FileNotFoundException
     */
    public void sendMessage(String path)throws FileNotFoundException{
    
    
        //获取文件信息
        getFileInfo(path);
        //获取分区数
        int psize = getTopicPartition();
        //获取每个线程的开始位置和执行行数(不是每个线程对应的分区,但可以这么理解)
        Map<Long, Integer> threadParams = calcPosAndRow(psize);
        int thn = 0;
        //启动每个线程
        for (Long key : threadParams.keySet()) {
    
    
            new CustomkafkaProducer(prop,path,key,threadParams.get(key), info.getDbName(), thn+"").start();
            thn++;
        }
    }

    /**
     * 6、计算每个线程的开始字节位置和行数
     * @return
     */
    private Map<Long,Integer> calcPosAndRow(int partitionNum){
    
    
        //new一个HashMap
        Map<Long,Integer> result = new HashMap<>();
        //计算平均每个分区多少行
        int row = totalRow/partitionNum;
        //每个分区循环
        for (int i = 0; i < partitionNum; i++) {
    
    
            if (i==(partitionNum-1)){
    
    
                //把没除尽的行放到最后一个分区
                //getPos(row*i+1),对应分区的起始行行号,求出起始行第一个字符的位置,以便于线程开始读
                result.put(getPos(row*i+1),row+totalRow%partitionNum);
            }else {
    
    
                result.put(getPos(row*i+1),row);
            }
        }
        return result;
    }

    /**
     * 6.1、输入行号,返回这一行的起始字节位置
     * @param row
     * @return
     */
    private Long getPos(int row){
    
    
        return rowSize.get(row-1)+row-1;
    }


    /**
     * 5、获取分区信息
     * @return
     */
    private int getTopicPartition(){
    
    
        //链接客户端
        AdminClient client = KafkaAdminClient.create(prop);
        //获取对应的topic详细信息
        DescribeTopicsResult result = client.describeTopics(Arrays.asList(info.getDbName()));
        //获取所有的信息,是一个Map结构
        KafkaFuture<Map<String, TopicDescription>> kf = result.all();
        //获取分区数量
        int num = 0;
        try {
    
    
            num = kf.get().get(info.getDbName()).partitions().size();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }
        return num;
    }


    /**
     * 4、计算出文件的行信息,得到总行数totalRow和【行号与行起始位置的】rowSize数组
     * @param path
     * @throws FileNotFoundException
     */
    private void getFileInfo(String path) throws FileNotFoundException {
    
    
        LineNumberReader reader = new LineNumberReader(new FileReader(path));
        try {
    
    
            String str = null;
            int total=0;
            while ((str=reader.readLine())!=null){
    
    
                //每一行的长度为实际长度+1个换行符,而total+=则代表读到这一行时,这一行的开头在全文里是第几个字符
                total+=str.getBytes().length+1;
                //把total(每一行的起始位置)放在list里,对应的下标则为其对应第几行的位置
                rowSize.add(((long) total));
            }
            //算出读的总行数
            totalRow = reader.getLineNumber();
            //把00添加到首位
            rowSize.add(0,0L);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        try {
    
    
            reader.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
}

2.3、CustomkafkaProducer

Threadクラスを継承するには、構成情報にパーティショナー情報を追加する必要があります

  • コンストラクター、キーとしてスレッド名を取得する必要があります
  • 各スレッドの場所
  • データの読み取り
  • kafkaに書き込む
public class CustomkafkaProducer extends Thread{
    
    

    private Properties prop;
    private String path;
    private long pos;
    private int rows;
    private String topic;

    /**
     * 9、构造器还缺一个县城名字
     * @param prop
     * @param path
     * @param pos
     * @param rows
     * @param topic
     * @param threadname
     */
    public CustomkafkaProducer(Properties prop, String path, long pos, int rows, String topic,String threadname) {
    
    
        this.prop = prop;
        this.path = path;
        this.pos = pos;
        this.rows = rows;
        this.topic = topic;
        this.setName(threadname);
    }

    /**
     * 8、线程执行,缺啥就加啥属性,可以通过构造器传
     */
    @Override
    public void run() {
    
    
        prop.put("partitioner.class",SimplePartitioner.class.getTypeName());
        //创建producer对象
        KafkaProducer producer = new KafkaProducer(prop);
        try {
    
    
            RandomAccessFile raf = new RandomAccessFile(new File(path),"r");
            //移位置
            raf.seek(pos);
            for (int i = 0; i < rows; i++) {
    
    
                //读数据
                String ln = new String(raf.readLine().getBytes(StandardCharsets.UTF_8));
                //读完写进kafka里,用线程名字当key
                ProducerRecord pr = new ProducerRecord(topic, Thread.currentThread().getName(), ln);
                producer.send(pr);
            }
            producer.close();
            raf.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
}

2.4。テストアプリ

public static void main( String[] args ) throws FileNotFoundException {
    
    
    //10、测试
    KafkaConfiguration kc = new KafkaConfiguration();
    kc.setDbName("mydemo5");
    kc.setIp("single");
    kc.setPort(9092);
    new KafkaConnector(kc).sendMessage("本地路径");
}

出力は次のとおりです。実行後の10,000個のデータが、複数のスレッドを介してKafkaの各パーティションに書き込まれていることがわかります。

画像-20210330014346833

おすすめ

転載: blog.csdn.net/xiaoxaoyu/article/details/115315577