Flume-transaction机制源码分析

news2024/7/2 3:25:19

一、整体流程

在这里插入图片描述
FileChannel主要是由WAL预写日志和内存队列FlumeEventQueue组成。
在这里插入图片描述

二、Transaction

public interface Transaction {
  // 描述transaction状态
  enum TransactionState { Started, Committed, RolledBack, Closed }

  void begin();
  
  void commit();

  void rollback();

  void close();
}

在这里插入图片描述

三、Put()

Put事务发生在source往channel写入数据

// source收集到events后,交由channel处理
getChannelProcessor().processEventBatch(events);
  public void processEventBatch(List<Event> events) {
    Preconditions.checkNotNull(events, "Event list must not be null");

    events = interceptorChain.intercept(events);
    //必需通道
    Map<Channel, List<Event>> reqChannelQueue =
        new LinkedHashMap<Channel, List<Event>>();
    //可选通道,可选通道是指在定义 SinkGroup 时可以选择添加的通道,用于将数据从 SinkGroup 中的一个 Sink 传输到另一个 Sink
    //没有配置可选通道可忽略。
    Map<Channel, List<Event>> optChannelQueue =
        new LinkedHashMap<Channel, List<Event>>();

    for (Event event : events) {
      List<Channel> reqChannels = selector.getRequiredChannels(event);

      for (Channel ch : reqChannels) {
        List<Event> eventQueue = reqChannelQueue.get(ch);
        if (eventQueue == null) {
          eventQueue = new ArrayList<Event>();
          reqChannelQueue.put(ch, eventQueue);
        }
        eventQueue.add(event);
      }

      List<Channel> optChannels = selector.getOptionalChannels(event);

      for (Channel ch : optChannels) {
        List<Event> eventQueue = optChannelQueue.get(ch);
        if (eventQueue == null) {
          eventQueue = new ArrayList<Event>();
          optChannelQueue.put(ch, eventQueue);
        }

        eventQueue.add(event);
      }
    }

    // Process required channels
    for (Channel reqChannel : reqChannelQueue.keySet()) {
      Transaction tx = reqChannel.getTransaction();
      Preconditions.checkNotNull(tx, "Transaction object must not be null");
      try {
        tx.begin();

        List<Event> batch = reqChannelQueue.get(reqChannel);

        for (Event event : batch) {
          //进入必选通道,调用put()
          reqChannel.put(event);
        }

        tx.commit();
      } catch (Throwable t) {
        tx.rollback();
        if (t instanceof Error) {
          LOG.error("Error while writing to required channel: " + reqChannel, t);
          throw (Error) t;
        } else if (t instanceof ChannelException) {
          throw (ChannelException) t;
        } else {
          throw new ChannelException("Unable to put batch on required " +
              "channel: " + reqChannel, t);
        }
      } finally {
        if (tx != null) {
          tx.close();
        }
      }
    }

    // Process optional channels
    for (Channel optChannel : optChannelQueue.keySet()) {
      Transaction tx = optChannel.getTransaction();
      Preconditions.checkNotNull(tx, "Transaction object must not be null");
      try {
        tx.begin();

        List<Event> batch = optChannelQueue.get(optChannel);

        for (Event event : batch) {
          optChannel.put(event);
        }

        tx.commit();
      } catch (Throwable t) {
        tx.rollback();
        LOG.error("Unable to put batch on optional channel: " + optChannel, t);
        if (t instanceof Error) {
          throw (Error) t;
        }
      } finally {
        if (tx != null) {
          tx.close();
        }
      }
    }
  }
  //org.apache.flume.channel.BasicChannelSemantics.java
  public void put(Event event) throws ChannelException {
    BasicTransactionSemantics transaction = currentTransaction.get();
    Preconditions.checkState(transaction != null,
        "No transaction exists for this thread");
    //调用transaction对象put()
    transaction.put(event);
  }
  //org.apache.flume.channel.BasicTransactionSemantics.java
  protected void put(Event event) {
    Preconditions.checkState(Thread.currentThread().getId() == initialThreadId,
        "put() called from different thread than getTransaction()!");
    Preconditions.checkState(state.equals(State.OPEN),
        "put() called when transaction is %s!", state);
    Preconditions.checkArgument(event != null,
        "put() called with null event!");

    try {
      doPut(event);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new ChannelException(e.toString(), e);
    }
  }
// org.apache.flume.channel.file.FileBackedTransaction.java
protected void doPut(Event event) throws InterruptedException {
      channelCounter.incrementEventPutAttemptCount();
      if (putList.remainingCapacity() == 0) {
        throw new ChannelException("Put queue for FileBackedTransaction " +
            "of capacity " + putList.size() + " full, consider " +
            "committing more frequently, increasing capacity or " +
            "increasing thread count. " + channelNameDescriptor);
      }
      // this does not need to be in the critical section as it does not
      // modify the structure of the log or queue.
      if (!queueRemaining.tryAcquire(keepAlive, TimeUnit.SECONDS)) {
        throw new ChannelFullException("The channel has reached it's capacity. "
            + "This might be the result of a sink on the channel having too "
            + "low of batch size, a downstream system running slower than "
            + "normal, or that the channel capacity is just too low. "
            + channelNameDescriptor);
      }
      boolean success = false;
      //doTake,也会获取该锁,所以doTake和doPut只能操作一个,无法同时操作
      log.lockShared();
      try {
        // transactionID在前面创建transaction对象的时候定义的
        // 将put事件写入WAL日志,并将数据持久化到磁盘文件
        FlumeEventPointer ptr = log.put(transactionID, event);
        // 将event pointer放到内存队列putList
        Preconditions.checkState(putList.offer(ptr), "putList offer failed "
            + channelNameDescriptor);
        // 将event也放到inflightPuts中,这是临时数据,因为还没有提交
        // 如果还没有提交就直接放到FlumeEventQueue,那么将提前暴露给sink。
        queue.addWithoutCommit(ptr, transactionID);
        success = true;
      } catch (IOException e) {
        channelCounter.incrementEventPutErrorCount();
        throw new ChannelException("Put failed due to IO error "
            + channelNameDescriptor, e);
      } finally {
        log.unlockShared();
        if (!success) {
          // release slot obtained in the case
          // the put fails for any reason
          queueRemaining.release();
        }
      }
    }
  // org.apache.flume.channel.file.FlumeEventQueue.java
  synchronized void addWithoutCommit(FlumeEventPointer e, long transactionID) {
    inflightPuts.addEvent(transactionID, e.toLong());
  }
  
  public void addEvent(Long transactionID, Long pointer) {
      // event放到inflightEvents
      inflightEvents.put(transactionID, pointer);
      inflightFileIDs.put(transactionID,
          FlumeEventPointer.fromLong(pointer).getFileID());
      syncRequired = true;
    }

InflightPuts 是实际正在传输中的事件集合,Flume 使用 InflightPuts 来跟踪正在传输的事件,只有事务提交了,InflightPuts 里才会清空这次事务的所有pointer数据。InflightPuts 和Flume的checkpoint机制密切相关。

先来看下Flume如何将event持久化到磁盘,将写到"log-"前缀的文件中。log.put(transactionID, event):

// org.apache.flume.channel.file.Log.java
FlumeEventPointer put(long transactionID, Event event)
      throws IOException {
    Preconditions.checkState(open, "Log is closed");
    FlumeEvent flumeEvent = new FlumeEvent(
        event.getHeaders(), event.getBody());
    //封装Put操作,WAL日志会记录四种操作,分别是Put,Take,Commit和Rollback
    Put put = new Put(transactionID, WriteOrderOracle.next(), flumeEvent);
    ByteBuffer buffer = TransactionEventRecord.toByteBuffer(put);
    //选择数据目录的数据文件,比如log-1
    int logFileIndex = nextLogWriter(transactionID);
    long usableSpace = logFiles.get(logFileIndex).getUsableSpace();
    long requiredSpace = minimumRequiredSpace + buffer.limit();
    if (usableSpace <= requiredSpace) {
      throw new IOException("Usable space exhausted, only " + usableSpace +
          " bytes remaining, required " + requiredSpace + " bytes");
    }
    boolean error = true;
    try {
      try {
        // Put事件写入WAL日志文件,Event也就持久化到文件了
        // logFileIndex就是数据文件ID,比如log-1文件
        FlumeEventPointer ptr = logFiles.get(logFileIndex).put(buffer);
        error = false;
        return ptr;
      } catch (LogFileRetryableIOException e) {
        if (!open) {
          throw e;
        }
        roll(logFileIndex, buffer);
        FlumeEventPointer ptr = logFiles.get(logFileIndex).put(buffer);
        error = false;
        return ptr;
      }
    } finally {
      if (error && open) {
        roll(logFileIndex);
      }
    }
  }

再来看下commit,tx.commit():

  // org.apache.flume.channel.BasicTransactionSemantics.java
  public void commit() {
    Preconditions.checkState(Thread.currentThread().getId() == initialThreadId,
        "commit() called from different thread than getTransaction()!");
    Preconditions.checkState(state.equals(State.OPEN),
        "commit() called when transaction is %s!", state);

    try {
      doCommit();
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new ChannelException(e.toString(), e);
    }
    // 修改transaction状态
    state = State.COMPLETED;
  }
// org.apache.flume.channel.file.FileChannel.FileBackedTransaction
protected void doCommit() throws InterruptedException {
      int puts = putList.size();
      int takes = takeList.size();
      if (puts > 0) {
        Preconditions.checkState(takes == 0, "nonzero puts and takes "
            + channelNameDescriptor);
        log.lockShared();
        try {
          // commit操作写入WAL日志
          log.commitPut(transactionID);
          channelCounter.addToEventPutSuccessCount(puts);
          synchronized (queue) {
            while (!putList.isEmpty()) {
              // 将putList的event pointer放到transaction的queue中
              // channel的容量就是channel transaction的queue的容量
              if (!queue.addTail(putList.removeFirst())) {
                StringBuilder msg = new StringBuilder();
                msg.append("Queue add failed, this shouldn't be able to ");
                msg.append("happen. A portion of the transaction has been ");
                msg.append("added to the queue but the remaining portion ");
                msg.append("cannot be added. Those messages will be consumed ");
                msg.append("despite this transaction failing. Please report.");
                msg.append(channelNameDescriptor);
                LOG.error(msg.toString());
                Preconditions.checkState(false, msg.toString());
              }
            }
            //清除事务ID
            //清空inflightPuts
            queue.completeTransaction(transactionID);
          }
        } catch (IOException e) {
          throw new ChannelException("Commit failed due to IO error "
              + channelNameDescriptor, e);
        } finally {
          log.unlockShared();
        }

      } else if (takes > 0) {
        //省略代码
        //......
      }
      //清空
      putList.clear();
      takeList.clear();
      channelCounter.setChannelSize(queue.getSize());
    }
  // org.apache.flume.channel.file.FlumeEventQueue
  synchronized void completeTransaction(long transactionID) {
    //清空inflightPuts
    if (!inflightPuts.completeTransaction(transactionID)) {
      inflightTakes.completeTransaction(transactionID);
    }
  }
    // org.apache.flume.channel.file.FlumeEventQueue.InflightEventWrapper
    // inflightPuts和inflightTakes都是InflightEventWrapper的实例对象
    public boolean completeTransaction(Long transactionID) {
      if (!inflightEvents.containsKey(transactionID)) {
        return false;
      }
      //清除inflightPuts的事务ID
      //清空inflightPuts的inflightEvents
      inflightEvents.removeAll(transactionID);
      inflightFileIDs.removeAll(transactionID);
      syncRequired = true;
      return true;
    }

内存队列putList中的event pointer放入到transaction的queue中,并清空了inflightPuts,说明当前put事务已经提交成功了

四、Take()

Take事务发生在sink从channel拿取数据。

public Status process() throws EventDeliveryException {
    Status result = Status.READY;
    Channel channel = getChannel();
    Transaction transaction = channel.getTransaction();
    Event event = null;

    try {
      transaction.begin();
      //从channel拿取数据
      event = channel.take();
      ......
      transaction.commit();
    } catch (Exception ex) {
      transaction.rollback();
      throw new EventDeliveryException("Failed to process transaction" , ex);
    } finally {
      transaction.close();
    }

    return result;
  }
 // org.apache.flume.channel.BasicTransactionSemantics.java
  protected Event take() {
    Preconditions.checkState(Thread.currentThread().getId() == initialThreadId,
        "take() called from different thread than getTransaction()!");
    Preconditions.checkState(state.equals(State.OPEN),
        "take() called when transaction is %s!", state);

    try {
      return doTake();
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      return null;
    }
  }
// org.apache.flume.channel.file.FileChannel.FileBackedTransaction
protected Event doTake() throws InterruptedException {
      channelCounter.incrementEventTakeAttemptCount();
      if (takeList.remainingCapacity() == 0) {
        throw new ChannelException("Take list for FileBackedTransaction, capacity " +
            takeList.size() + " full, consider committing more frequently, " +
            "increasing capacity, or increasing thread count. "
            + channelNameDescriptor);
      }
      log.lockShared();
      /*
       * 1. Take an event which is in the queue.
       * 2. If getting that event does not throw NoopRecordException,
       *    then return it.
       * 3. Else try to retrieve the next event from the queue
       * 4. Repeat 2 and 3 until queue is empty or an event is returned.
       */

      try {
        while (true) {
          // 从FlumeEventQueue中取出event pointer
          // 并将event pointer放到inflightTakes
          FlumeEventPointer ptr = queue.removeHead(transactionID);
          if (ptr == null) {
            return null;
          } else {
            try {
              // first add to takeList so that if write to disk
              // fails rollback actually does it's work
              //首先将pointer放入takeList中
              Preconditions.checkState(takeList.offer(ptr),
                  "takeList offer failed "
                      + channelNameDescriptor);
              // take操作写入WAL日志
              log.take(transactionID, ptr); // write take to disk
              //根据pointer从持久化文件中获取event
              Event event = log.get(ptr);
              return event;
            } catch (IOException e) {
              channelCounter.incrementEventTakeErrorCount();
              throw new ChannelException("Take failed due to IO error "
                  + channelNameDescriptor, e);
            } catch (NoopRecordException e) {
              LOG.warn("Corrupt record replaced by File Channel Integrity " +
                  "tool found. Will retrieve next event", e);
              takeList.remove(ptr);
            } catch (CorruptEventException ex) {
              channelCounter.incrementEventTakeErrorCount();
              if (fsyncPerTransaction) {
                throw new ChannelException(ex);
              }
              LOG.warn("Corrupt record found. Event will be " +
                  "skipped, and next event will be read.", ex);
              takeList.remove(ptr);
            }
          }
        }
      } finally {
        log.unlockShared();
      }
    }

看一下queue.removeHead(transactionID)

synchronized FlumeEventPointer removeHead(long transactionID) {
    if (backingStore.getSize() == 0) {
      return null;
    }

    long value = remove(0, transactionID);
    Preconditions.checkState(value != EMPTY, "Empty value "
        + channelNameDescriptor);

    FlumeEventPointer ptr = FlumeEventPointer.fromLong(value);
    backingStore.decrementFileID(ptr.getFileID());
    return ptr;
  }
protected synchronized long remove(int index, long transactionID) {
    if (index < 0 || index > backingStore.getSize() - 1) {
      throw new IndexOutOfBoundsException("index = " + index
          + ", queueSize " + backingStore.getSize() + " " + channelNameDescriptor);
    }
    copyCount++;
    long start = System.currentTimeMillis();
    long value = get(index);
    if (queueSet != null) {
      queueSet.remove(value);
    }
    //if txn id = 0, we are recovering from a crash.
    if (transactionID != 0) {
      //将queue的event pointer加入到inflightTakes中
      inflightTakes.addEvent(transactionID, value);
    }
    if (index > backingStore.getSize() / 2) {
      // Move tail part to left
      for (int i = index; i < backingStore.getSize() - 1; i++) {
        long rightValue = get(i + 1);
        set(i, rightValue);
      }
      set(backingStore.getSize() - 1, EMPTY);
    } else {
      // Move head part to right
      for (int i = index - 1; i >= 0; i--) {
        long leftValue = get(i);
        set(i + 1, leftValue);
      }
      set(0, EMPTY);
      backingStore.setHead(backingStore.getHead() + 1);
      if (backingStore.getHead() == backingStore.getCapacity()) {
        backingStore.setHead(0);
      }
    }
    backingStore.setSize(backingStore.getSize() - 1);
    copyTime += System.currentTimeMillis() - start;
    return value;
  }

拿到event后,再来看commit事务:

protected void doCommit() throws InterruptedException {
      int puts = putList.size();
      int takes = takeList.size();
      if (puts > 0) {
        //省略代码
        //......
      } else if (takes > 0) {
        log.lockShared();
        try {
          log.commitTake(transactionID);
          //清除事务ID
          //清空inflightTakes
          queue.completeTransaction(transactionID);
          channelCounter.addToEventTakeSuccessCount(takes);
        } catch (IOException e) {
          throw new ChannelException("Commit failed due to IO error "
              + channelNameDescriptor, e);
        } finally {
          log.unlockShared();
        }
        queueRemaining.release(takes);
      }
      putList.clear();
      takeList.clear();
      channelCounter.setChannelSize(queue.getSize());
    }
  // org.apache.flume.channel.file.FlumeEventQueue
  synchronized void completeTransaction(long transactionID) {
    if (!inflightPuts.completeTransaction(transactionID)) {
      //清空inflightTakes
      inflightTakes.completeTransaction(transactionID);
    }
  }

如果take事务提交,则从transaction的queue中取出了event pointer,并清空inflightTakes和takeList

transaction.rollback()

protected void doRollback() throws InterruptedException {
      int puts = putList.size();
      int takes = takeList.size();
      log.lockShared();
      try {
        if (takes > 0) {
          Preconditions.checkState(puts == 0, "nonzero puts and takes "
              + channelNameDescriptor);
          synchronized (queue) {
            while (!takeList.isEmpty()) {
              // 把takeList中的数据放回到transaction的queue中
              Preconditions.checkState(queue.addHead(takeList.removeLast()),
                  "Queue add failed, this shouldn't be able to happen "
                      + channelNameDescriptor);
            }
          }
        }
        putList.clear();
        takeList.clear();
        //清除事务ID
        //清空inflightTakes
        queue.completeTransaction(transactionID);
        channelCounter.setChannelSize(queue.getSize());
        //rollback操作写入WAL日志
        log.rollback(transactionID);
      } catch (IOException e) {
        throw new ChannelException("Commit failed due to IO error "
            + channelNameDescriptor, e);
      } finally {
        log.unlockShared();
        // since rollback is being called, puts will never make it on
        // to the queue and we need to be sure to release the resources
        queueRemaining.release(puts);
      }
    }

take事务回滚时,takeList中的数据重新放回到transaction的queue中,并清空这次事务的inflightTakes

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1533195.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用户中心项目(数据库表设计 + 用户注册后端)

文章目录 1.数据库表设计1.IDEA连接MySQL1.选择database&#xff0c;添加数据源2.填写信息&#xff0c;然后点击测试连接3.查找指定数据库4.查看某个表的DDL5.新建查询 2.删除测试的user表3.创建一个新的user表4.创建user表 2.注册功能1.换了台电脑&#xff0c;重新打开后端项目…

深度学习-2.9梯度不稳定和Glorot条件

梯度不稳定和Glorot条件 一、梯度消失和梯度爆炸 对于神经网络这个复杂系统来说&#xff0c;在模型训练过程中&#xff0c;一个最基础、同时也最常见的问题&#xff0c;就是梯度消失和梯度爆炸。 我们知道&#xff0c;神经网络在进行反向传播的过程中&#xff0c;各参数层的梯…

Cesium for Unreal注意事项

一、Cesium for Unreal使用WGS84坐标系统 原因&#xff1a;在百度、高德、谷歌拾取的坐标经纬度设置在Cesium for Unreal项目中时位置不准确是因为这些厂商使用的坐标系不一样。高德是GCJ02&#xff0c;百度是在GCJ02的基础上再加密&#xff0c;谷歌是WGS84就是原始gps坐标&am…

蓝桥杯Python B组练习——完美的代价

一、题目 问题描述   回文串&#xff0c;是一种特殊的字符串&#xff0c;它从左往右读和从右往左读是一样的。小龙龙认为回文串才是完美的。现在给你一个串&#xff0c;它不一定是回文的&#xff0c;请你计算最少的交换次数使得该串变成一个完美的回文串。   交换的定义是…

分布式游戏服务器

1、概念介绍 分布式游戏服务器是一种专门为在线游戏设计的大型系统架构。这种架构通过将游戏服务器分散部署到多台计算机&#xff08;节点&#xff09;上&#xff0c;实现了数据的分散存储和计算任务的并行处理。每个节点都负责处理一部分游戏逻辑和玩家请求&#xff0c;通过高…

环境安装篇 之 安装kubevela

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 环境安装 系列文章&#xff0c;介绍 oam规范标准实施项目 kubevela 的安装详细步骤kubevela 官方安装文档&#xff1a;https://kubevela.io/zh/docs/installation/kubernetes/ 1.CentOS 安装kubevela 1.1.前提…

GIS设计与开发的学习笔记

目录 一、简答题 1.GeoDatabase数据模型结构类型与四种关系。 2.组件式GIS的基本思想是什么&#xff1f; 3.请简述创建空间书签的实现逻辑。 4.请问与地理要素编辑相关的类有哪些&#xff1f;&#xff08;列举至少五个类&#xff09; 5.利用ArcGIS Engine提供的栅格运算工…

【LabVIEW FPGA入门】局部变量和全局变量

局部变量 无法访问某前面板对象或需要在程序框图节点之间传递数据时&#xff0c;可创建前面板对象的局部变量。创建局部变量后&#xff0c;局部变量仅仅出现在程序框图上&#xff0c;而不在前面板上。 局部变量可对前面板上的输入控件或显示件进行数据读写。写入局部变量相当于…

借还款管理神器,高效记录借还款信息,让财务明细不再遗漏

在快节奏的现代生活中&#xff0c;借还款管理成为我们日常财务处理的重要一环。无论是个人生活还是企业运营&#xff0c;都需要一个高效、准确、便捷的方式来记录和追踪借还款信息。传统的记账方式往往容易出错、繁琐且耗时&#xff0c;难以满足现代人的需求。现在&#xff0c;…

数据库系统概论(超详解!!!) 第四节 关系数据库标准语言SQL(Ⅰ)

1.SQL概述 SQL&#xff08;Structured Query Language&#xff09;结构化查询语言&#xff0c;是关系数据库的标准语言 SQL是一个通用的、功能极强的关系数据库语言 SQL的动词 基本概念 基本表 &#xff1a;本身独立存在的表&#xff1b; SQL中一个关系就对应一个基本表&am…

一、初识 Web3

瑾以此系列文章&#xff0c;献给那些出于好奇并且想要学习这方面知识的开发者们 在多数时间里&#xff0c;我们对 web3 的理解是非常模糊的 就好比提及什么是 web1 以及 web2&#xff0c;相关概念的解释是&#xff1a; 1. 从 Web3 的开始 Web3&#xff0c;也被称为Web3.0&…

飞腾+FPGA+AI电力行业智能数据采集与分析网闸解决方案

行业痛点: 安全物联网闸在监控平台中的具体作用&#xff1a;35KV变电站是煤矿的动力核心&#xff0c;采矿人员上下井、煤炭提升输送、矿井通风等核心设备均依靠变电站提供电源。监控中心及时掌握变电站的运行状态对煤矿的安全生产非常重要。如若外部通过监控网络来控制变电站会…

ByteMD - 掘金社区 MarkDown 编辑器的免费开源的版本,可以在 Vue / React / Svelte 中使用

各位元宵节快乐&#xff0c;今天推荐一款字节跳动旗下掘金社区官方出品的 Markdown 编辑器 JS 开发库。 ByteMD 是一个用于 web 开发的 Markdown 编辑器 JavaScript 库&#xff0c;是字节跳动&#xff08;也就是掘金社区&#xff09;出品的 Markdown 格式的富文本编辑器&#…

区域规划(Regional Planning)的学习笔记

目录 一、概念题 1.区域的概念、类型、特性 2.区域分析的概念、主要内容 3.自然环境、自然资源的概念 4.区域自然资源评价的内容 5.可持续发展理论定义 6.经济增长、经济结构定义 7.产业结构概念 8.人口增长分析的含义、指标 9.技术进步概念、类型 10.技术进步对区域…

小侃威胁情报

0x01 什么是情报 百度百科释义&#xff1a; 情报“有情有报告的信息”&#xff0c;学者从情报搜集的手段来给其下定义&#xff0c;说情报是通过秘密手段搜集来的、关于敌对方外交军事政治经济科技等信息。还有学者从情报处理的流程来给其下定义&#xff0c;认为情报是被传递、整…

Vue响应式原理全解析

前言 大家好&#xff0c;我是程序员蒿里行。浅浅记录一下面试中的高频问题&#xff0c;请你谈一下Vue响应式原理。 必备前置知识&#xff0c;​​Vue2​​官方文档中​​深入响应式原理​​​及​​Vue3​​官方文档中​​深入响应式系统​​。 什么是响应式 响应式本质是当…

[隐私计算实训营学习笔记] 第1讲 数据要素流通

信任四基石 数据的分级分类 技术信任&#xff1a;全链路审计、闭环完成的数据可信流通体系 技术信任&#xff1a;开启数据密态时代 数据可流通的基础设施&#xff1a;密态天空计算

react ant design radio group, 自定义modal样式,radio样式

需求&#xff1a; modal 里面需要一个list 列表&#xff0c;列表有单选框&#xff0c;并且可以确认。 遇到的问题&#xff1a;自定义modal的样式&#xff0c;修改radio/ radio group 的样式 设计图如下&#xff1a; 代码&#xff1a; return (<Modaltitle"Duplica…

7.PWM开发SG90(手把手教会)

简介 PWM&#xff0c;英文名Pulse Width Modulation&#xff0c;是脉冲宽度调制缩写&#xff0c;它是通过对一系列脉冲的宽度进 行调制&#xff0c;等效出所需要的波形&#xff08;包含形状以及幅值&#xff09;&#xff0c;对模拟信号电平进行数字编码&#xff0c;也就是说通…

Transformer的前世今生 day02(神经网络语言模型、词向量)

神经网络语言模型 使用神经网络的方法&#xff0c;去完成语言模型的两个问题&#xff0c;下图为两层感知机的神经网络语言模型&#xff1a; 假设词典V内有五个词&#xff1a;“判断”、“这个”、“词”、“的”、“词性”&#xff0c;且要输出P(w_next | “判断”、“这个”、…