精华内容
下载资源
问答
  • 查找一个不严格的小于一个值的节点,就是当二叉树中存在这个节点的时候。直接查找出来,当二叉树中不存在这个节点的时候查找比这个节点小的,但是在所有小于所查节点中的最大节点。这个程序中使用了栈进行中序遍历。...


    查找一个不严格的小于一个值的节点,就是当二叉树中存在这个节点的时候。直接查找出来,当二叉树中不存在这个节点的时候查找比这个节点小的,但是在所有小于所查节点中的最大节点。这个程序中使用了栈进行中序遍历。代码如下:

     

    typedef struct BiTNode{
    	int data;
    	
    	struct BiTNode *lchild,*rchild;
    }*BiTree;
    struct SqList
    {
    	int elem[200];//将二叉树的节点数值存放到这个数组中,暂时定义数组长度为200
    	int length;
    };
    
    //时间复杂度为O(1),怎么找出一个栈里的最大元素 ,在这里用顺序栈表示,不用链栈
    #define  MAX_SIZE 100 //定义初始栈的大小
    struct Stack
    {
    	int data; //存放整形数据数据,没什么作用
    	char character;//存放字符,这两个没什么作用
    	BiTNode *Tree_Node;
    	
    	
    };
    struct qStack
    {
    	int i;//计算栈中所存数据的个数
    	int stack_size;//顺序栈的大小
    	Stack *top;
    	Stack *base;
    };
    
    
    
    //顺序栈
    void IniqStack(qStack &s)
    {
    
    	s.i=0;
    	s.base=s.top=(Stack*)malloc(sizeof(Stack)*MAX_SIZE);//
    	s.stack_size=MAX_SIZE;
    }
    void Push_Tree(qStack &s,BiTNode *p)
    {
    	if (s.top-s.base>=s.stack_size)
    	{
    		s.base=(Stack*)realloc(s.base,(s.stack_size+10));//增加十个空间
    		s.top++;//顶部上移
    		s.stack_size+=10;
    	}
    
    	s.top->Tree_Node=p;
    	s.i++;
    	s.top++;
    }
    void Pop_Tree(qStack &s,BiTNode *&p)
    {
    	if (s.i==0)
    	{
    		printf("栈为空\n");
    	}
    	s.top--;
    	s.i--;
    	p=s.top->Tree_Node;
    	s.top->Tree_Node=NULL;
    }
    bool StackEmpty(qStack s) //判断栈是否为空,
    {
    	if (s.base==s.top)
    	{
    		return true;
    	}
    	else
    	{
    		return false;
    	}
    }
    ///
    //二叉树查找不严格小于一个值的最大值
    void Serch_point(BiTNode *head,int data,BiTNode *&p,qStack s)
    {
    	int temp;
    	//BiTNode *p;
    	p=head;
    	temp=p->data; //假定temp的最初值是根节点的大小
    	while(p||!StackEmpty(s)) //采用中序遍历,
    	{
    		if (p)
    		{
    			Push_Tree(s,p);
    			p=p->lchild;
    		}
    		else
    		{
    			Pop_Tree(s,p);
    			if (data==p->data)
    			{
    				return; //跳出循环,进入返回p节点
    			}else if ((p->data<data)&&(p->data>temp)) //如果在二叉树不存在data,则寻找最大的节点,temp记录小于data的最大节点;
    			{
    				temp=p->data;
    			}
    
    			p=p->rchild;
    		}
    	}
    	Serch_point(head,temp,p,s);//递归调用寻找最大的值,
     if (p->data>data) //如果最后返回的值大于data的值,则说明。data比所有的节点都要小。则p返回null
     {
      p=NULL;
      return;
     }
    }
    
    
    
    展开全文
  • WebSphere节点同步

    2018-11-02 09:58:29
    错误现象: 1、 启动应用的时候特别慢,报“可能已经启动成功,但没有在预定的时间启动...系统管理下的节点状态不对,同步节点后仍然显示未同步。 4、 部署新应用后启动时,会报 [12-4-11 20:08:07:127 CST] 000000...

    错误现象:

    1、
    启动应用的时候特别慢,报“可能已经启动成功,但没有在预定的时间启动完成,详情请参考日志xxx”。
    2、 
    “企业级应用程序”下应用的状态好像不对,在WebSphere企业应用程序中启动起来的应用在这里仍然是“红X”状态。
    3、
    系统管理下的节点状态不对,同步节点后仍然显示未同步。
    4、
    部署新应用后启动时,会报
    [12-4-11 20:08:07:127 CST] 0000002b DefaultTokenP I   HMGR0149E: 尝试打开到核心组 DefaultCoreGroup 的连接被拒绝。发送进程的名称为 fqztestapCell01\fqztestapCellManager01\dmgr 且 IP 地址为 /172.18.251.23。本地进程中的全局安全性为 Disabled。发送进程中的全局安全性为 Enabled。接收到的标记以 ?0G??????+?Qe?? 开头。异常为 <null>。
    [12-4-11 20:20:40:736 CST] 00000017 AdminHelper   A   ADMN1009I: 尝试启动 rews_message_parse 应用程序。
    [12-4-11 20:20:40:740 CST] 00000017 ApplicationMg W   WSVR0215W: 启动应用程序 rews_message_parse 失败。尚未安装该应用程序。
    [12-4-11 20:42:21:326 CST] 00000016 AdminHelper   A   ADMN1009I: 尝试启动 rews_message_parse 应用程序。
    [12-4-11 20:42:21:330 CST] 00000016 ApplicationMg W   WSVR0215W: 启动应用程序 rews_message_parse 失败。尚未安装该应用程序。

    但实际部署的整个过程中,日志没有报错且控制台上提示部署成功。

    ----------------

    很遗憾,做开发这么久,对WAS却是如此陌生,最近老碰到WAS节点不同步问题,导致所有应用都不能正常部署,总结了以下这个解决办法,分享于此。。。

    ------------
    Was控制台节点显示同步状态不正常,无法完成同步,问题解决如下:
    一、
    切换到bin目录下,执行相应命令,依次停止 server 、 node 、dmgr(严格按照此顺序)
    /opt/IBM/WebSphere/AppServer/profiles/AppSrv02/bin/stopServer.sh  server1 --servername

    /opt/IBM/WebSphere/AppServer/profiles/AppSrv02/bin/stopNode.sh

    /opt/IBM/WebSphere/AppServer/bin/stopManager.sh

    二、删除 wstemp, temp 和 config/temp 文件夹下面的临时文件
    /opt/IBM/WebSphere/AppServer/profiles/Dmgr01/temp、wstemp、tranlog目录下内容删除。
    /opt/IBM/WebSphere/AppServer/profiles/Dmgr01/config/temp目录下内容删除。

    四、同步节点:
    ##同步节点信息
    切换到“/opt/IBM/WebSphere/AppServer/profiles/AppSrv02/bin/”下:
    执行 :syncNode.sh fqztestap 8879 -username appname -password 000000

    ------------
    其中 “fqztestap”是was所部署在的主机名,linux/unix 下用“uname -all”获取
    比如我执行 “uname -all ”后,显示:
    Linux fqztestap 2.6.18-128.el5 #1 SMP Wed Dec 17 11:41:38 EST 2008 x86_64 x86_64 x86_64 GNU/Linux
    第二项即为主机名
    ------------

    五、依次启动 dmgr、node、server (严格按照此顺序)

    /opt/IBM/WebSphere/AppServer/bin/startManager.sh

    /opt/IBM/WebSphere/AppServer/profiles/AppSrv02/bin/startNode.sh

    /opt/IBM/WebSphere/AppServer/profiles/AppSrv02/bin/startServer.sh  server1 --servername

    展开全文
  • Hadoop源码分析——数据节点写数据1

    千次阅读 2016-06-22 20:59:42
    即使不考虑数据节点出错后的故障处理,文件写入也是HDFS中最复杂的流程。本章以创建一个新文件并向文件中写入数据,然后关闭文件为例,分析客户端写文件时系统各节点的配合,如下图所示。 客户端调用...

    即使不考虑数据节点出错后的故障处理,文件写入也是HDFS中最复杂的流程。本章以创建一个新文件并向文件中写入数据,然后关闭文件为例,分析客户端写文件时系统各节点的配合,如下图所示。
    这里写图片描述
    客户端调用DistributedFileSystem的create()方法创建文件,上图的步骤1,这时,DistributedFileSystem创建DFSOutputStream,并由远程过程调用,让名字节点执行同名方法,在文件系统的命名空间中创建一个新文件。名字节点创建新文件时,需要执行各种各样的检查,如名字节点处于正常工作状态,被创建的文件不存在,客户端有在父目录中创建文件的权限等。这些检查都通过以后,名字节点会构造一个新文件,并记录创建操作到编辑日志edits中。远程方法调用结束后,DistributedFileSystem将该DFSOutputStream对象包裹在FSDataOutputStream实例中,返回给客户端。
    在上图的步骤3客户端写入数据时,由于create()调用创建了一个空文件,所以,DFSOutputStream实例首先需要向名字节点申请数据块,addBlock()方法成功执行后,返回一个LocatedBlock对象。该对象包含了新数据块的数据块标识和版本号,同时,它的成员变量LocatedBlock.locs提供了数据流管道的信息,通过上述信息,DFSOutputStream就可以和数据节点联系,通过写数据接口建立数据流管道。客户端写入FSDataOutputStream流中的数据,被分成一个一个的文件包,放入DFSOutputStream对象的内部队列。该队列中的文件包最后打包成数据包,发往数据流管道,流经管道上的各个数据节点,并持久化。确认包,上图步骤6逆流而上,从数据流管道依次发往客户端,当客户端收到应答时,它将对应的包从内部队列移除。
    DFSOutputStream在写完一个数据块后,数据流管道上的节点,会通过和名字节点的DatanodeProtocol远程接口的blockReceived()方法,向名字节点提交数据块。如果数据队列中还有等待输出的数据,DFSOutputStream对象需要再次调用addBlock()方法,为文件添加新的数据块。
    客户端完成数据的写入后,调用close()方法关闭流,步骤8。关闭意味着客户端不会再往流中写入数据,所以,当DFSOutputStream数据队列中的文件包都收到应答后,就可以使用ClinetProtocol.complete()方法通知名字节点关闭文件,完成一次正常的写文件流程。

    写数据
    流式接口的写数据实现远比读数据复杂。客户端写HDFS文件数据的操作码为80,请求包含如下主要字段:
    blockId(数据块ID):写数据的数据块标识,数据节点通过它定位数据块。
    generationStamp(版本号):数据块的版本号,用于进行版本检查。
    pipelineSize(数据流管道的大小):参与到写过程的所有数据节点的个数。
    isRecovery(是否是数据恢复过程):这个写操作是不是错误恢复过程中的一部分。
    clientName(客户端名字):发起写请求的客户端名字,可能为空。
    hasSrcDataNode(源信息标记):写请求是否携带源信息,如果是true,则包含源信息。
    srcDataNode(源信息,可选):类型为DatanodeInfo,包含发起写请求的数据节点信息。
    numTargets(数据目标列表大小):当前数据节点还有多少个下游数据推送目标。
    targets(数据目标列表):当前数据节点的下游数据推送目标列表。
    accessToken(访问令牌):与安全特性相关,不讨论。
    checksum(数据校验信息):类型为DataChecksum,包含了后续写数据数据包的校验方式。
    这里写图片描述

    与org.apache.hadoop.hdfs.server.datanode.DataXceiver.java.readBlock类似,上述字段在writeBlock()入口中读取,并保存在对应的方法变量中,然后,构造数据块接收器org.apache.hadoop.hdfs.server.datanode.BlockReceiver对象,

     /**
       * Write a block to disk.
       * 
       * @param in The stream to read from
       * @throws IOException
       */
      private void writeBlock(DataInputStream in) throws IOException {
        DatanodeInfo srcDataNode = null;
        LOG.debug("writeBlock receive buf size " + s.getReceiveBufferSize() +
                  " tcp no delay " + s.getTcpNoDelay());
        //
        // Read in the header
        //
        Block block = new Block(in.readLong(), 
            dataXceiverServer.estimateBlockSize, in.readLong());
        LOG.info("Receiving " + block + " src: " + remoteAddress + " dest: "
            + localAddress);
        int pipelineSize = in.readInt(); // num of datanodes in entire pipeline
        boolean isRecovery = in.readBoolean(); // is this part of recovery?
        String client = Text.readString(in); // working on behalf of this client
        boolean hasSrcDataNode = in.readBoolean(); // is src node info present
        if (hasSrcDataNode) {
          srcDataNode = new DatanodeInfo();
          srcDataNode.readFields(in);
        }
        int numTargets = in.readInt();
        if (numTargets < 0) {
          throw new IOException("Mislabelled incoming datastream.");
        }
        DatanodeInfo targets[] = new DatanodeInfo[numTargets];
        for (int i = 0; i < targets.length; i++) {
          DatanodeInfo tmp = new DatanodeInfo();
          tmp.readFields(in);
          targets[i] = tmp;
        }
        Token<BlockTokenIdentifier> accessToken = new Token<BlockTokenIdentifier>();
        accessToken.readFields(in);
        DataOutputStream replyOut = null;   // stream to prev target
        replyOut = new DataOutputStream(
                       NetUtils.getOutputStream(s, datanode.socketWriteTimeout));
        if (datanode.isBlockTokenEnabled) {
          try {
            datanode.blockTokenSecretManager.checkAccess(accessToken, null, block, 
                BlockTokenSecretManager.AccessMode.WRITE);
          } catch (InvalidToken e) {
            try {
              if (client.length() != 0) {
                replyOut.writeShort((short)DataTransferProtocol.OP_STATUS_ERROR_ACCESS_TOKEN);
                Text.writeString(replyOut, datanode.dnRegistration.getName());
                replyOut.flush();
              }
              throw new IOException("Access token verification failed, for client "
                  + remoteAddress + " for OP_WRITE_BLOCK for " + block);
            } finally {
              IOUtils.closeStream(replyOut);
            }
          }
        }
    
        DataOutputStream mirrorOut = null;  // stream to next target
        DataInputStream mirrorIn = null;    // reply from next target
        Socket mirrorSock = null;           // socket to next target
        BlockReceiver blockReceiver = null; // responsible for data handling
        String mirrorNode = null;           // the name:port of next target
        String firstBadLink = "";           // first datanode that failed in connection setup
        short mirrorInStatus = (short)DataTransferProtocol.OP_STATUS_SUCCESS;
        try {
          // open a block receiver and check if the block does not exist
          blockReceiver = new BlockReceiver(block, in, 
              s.getRemoteSocketAddress().toString(),
              s.getLocalSocketAddress().toString(),
              isRecovery, client, srcDataNode, datanode);
    
          //
          // Open network conn to backup machine, if 
          // appropriate
          //
          if (targets.length > 0) {
            InetSocketAddress mirrorTarget = null;
            // Connect to backup machine
            final String mirrorAddrString = 
              targets[0].getName(connectToDnViaHostname);
            mirrorNode = targets[0].getName();
            mirrorTarget = NetUtils.createSocketAddr(mirrorAddrString);
            mirrorSock = datanode.newSocket();
            try {
              int timeoutValue = datanode.socketTimeout +
                                 (HdfsConstants.READ_TIMEOUT_EXTENSION * numTargets);
              int writeTimeout = datanode.socketWriteTimeout + 
                                 (HdfsConstants.WRITE_TIMEOUT_EXTENSION * numTargets);
              LOG.debug("Connecting to " + mirrorAddrString);
              NetUtils.connect(mirrorSock, mirrorTarget, timeoutValue);
              mirrorSock.setSoTimeout(timeoutValue);
              mirrorSock.setSendBufferSize(DEFAULT_DATA_SOCKET_SIZE);
              mirrorOut = new DataOutputStream(
                 new BufferedOutputStream(
                             NetUtils.getOutputStream(mirrorSock, writeTimeout),
                             SMALL_BUFFER_SIZE));
              mirrorIn = new DataInputStream(NetUtils.getInputStream(mirrorSock));
    
              // Write header: Copied from DFSClient.java!
              mirrorOut.writeShort( DataTransferProtocol.DATA_TRANSFER_VERSION );
              mirrorOut.write( DataTransferProtocol.OP_WRITE_BLOCK );
              mirrorOut.writeLong( block.getBlockId() );
              mirrorOut.writeLong( block.getGenerationStamp() );
              mirrorOut.writeInt( pipelineSize );
              mirrorOut.writeBoolean( isRecovery );
              Text.writeString( mirrorOut, client );
              mirrorOut.writeBoolean(hasSrcDataNode);
              if (hasSrcDataNode) { // pass src node information
                srcDataNode.write(mirrorOut);
              }
              mirrorOut.writeInt( targets.length - 1 );
              for ( int i = 1; i < targets.length; i++ ) {
                targets[i].write( mirrorOut );
              }
              accessToken.write(mirrorOut);
    
              blockReceiver.writeChecksumHeader(mirrorOut);
              mirrorOut.flush();
    
              // read connect ack (only for clients, not for replication req)
              if (client.length() != 0) {
                mirrorInStatus = mirrorIn.readShort();
                firstBadLink = Text.readString(mirrorIn);
                if (LOG.isDebugEnabled() || mirrorInStatus != DataTransferProtocol.OP_STATUS_SUCCESS) {
                  LOG.info("Datanode " + targets.length +
                           " got response for connect ack " +
                           " from downstream datanode with firstbadlink as " +
                           firstBadLink);
                }
              }
    
            } catch (IOException e) {
              if (client.length() != 0) {
                replyOut.writeShort((short)DataTransferProtocol.OP_STATUS_ERROR);
                Text.writeString(replyOut, mirrorNode);
                replyOut.flush();
              }
              IOUtils.closeStream(mirrorOut);
              mirrorOut = null;
              IOUtils.closeStream(mirrorIn);
              mirrorIn = null;
              IOUtils.closeSocket(mirrorSock);
              mirrorSock = null;
              if (client.length() > 0) {
                throw e;
              } else {
                LOG.info(datanode.dnRegistration + ":Exception transfering " +
                         block + " to mirror " + mirrorNode +
                         "- continuing without the mirror\n" +
                         StringUtils.stringifyException(e));
              }
            }
          }
    
          // send connect ack back to source (only for clients)
          if (client.length() != 0) {
            if (LOG.isDebugEnabled() || mirrorInStatus != DataTransferProtocol.OP_STATUS_SUCCESS) {
              LOG.info("Datanode " + targets.length +
                       " forwarding connect ack to upstream firstbadlink is " +
                       firstBadLink);
            }
            replyOut.writeShort(mirrorInStatus);
            Text.writeString(replyOut, firstBadLink);
            replyOut.flush();
          }
    
          // receive the block and mirror to the next target
          String mirrorAddr = (mirrorSock == null) ? null : mirrorNode;
          blockReceiver.receiveBlock(mirrorOut, mirrorIn, replyOut,
                                     mirrorAddr, null, targets.length);
    
          // if this write is for a replication request (and not
          // from a client), then confirm block. For client-writes,
          // the block is finalized in the PacketResponder.
          if (client.length() == 0) {
            datanode.notifyNamenodeReceivedBlock(block, DataNode.EMPTY_DEL_HINT);
            LOG.info("Received " + block + " src: " + remoteAddress + " dest: "
                + localAddress + " size " + block.getNumBytes());
          }
    
          if (datanode.blockScanner != null) {
            datanode.blockScanner.addBlock(block);
          }
    
        } catch (IOException ioe) {
          LOG.info("writeBlock " + block + " received exception " + ioe);
          throw ioe;
        } finally {
          // close all opened streams
          IOUtils.closeStream(mirrorOut);
          IOUtils.closeStream(mirrorIn);
          IOUtils.closeStream(replyOut);
          IOUtils.closeSocket(mirrorSock);
          IOUtils.closeStream(blockReceiver);
        }
      }

    读取数据包头的代码

    //
        // Read in the header
        //
        Block block = new Block(in.readLong(), 
            dataXceiverServer.estimateBlockSize, in.readLong());
        LOG.info("Receiving " + block + " src: " + remoteAddress + " dest: "
            + localAddress);
        int pipelineSize = in.readInt(); // num of datanodes in entire pipeline
        boolean isRecovery = in.readBoolean(); // is this part of recovery?
        String client = Text.readString(in); // working on behalf of this client
        boolean hasSrcDataNode = in.readBoolean(); // is src node info present
        if (hasSrcDataNode) {
          srcDataNode = new DatanodeInfo();
          srcDataNode.readFields(in);
        }
        int numTargets = in.readInt();
        if (numTargets < 0) {
          throw new IOException("Mislabelled incoming datastream.");
        }
        DatanodeInfo targets[] = new DatanodeInfo[numTargets];
        for (int i = 0; i < targets.length; i++) {
          DatanodeInfo tmp = new DatanodeInfo();
          tmp.readFields(in);
          targets[i] = tmp;
        }
        Token<BlockTokenIdentifier> accessToken = new Token<BlockTokenIdentifier>();
        accessToken.readFields(in);

    构造BlockReceiver对象的代码

      blockReceiver = new BlockReceiver(block, in, 
              s.getRemoteSocketAddress().toString(),
              s.getLocalSocketAddress().toString(),
              isRecovery, client, srcDataNode, datanode);

    在BlockReceiver的构造函数中,会为写数据块和校验信息文件打开输出数据流,使用的是FSDataset.writeToBlock()方法,在完成一系列检查后,它返回到数据块文件和校验文件的输出流。

    BlockReceiver(Block block, DataInputStream in, String inAddr,
                    String myAddr, boolean isRecovery, String clientName, 
                    DatanodeInfo srcDataNode, DataNode datanode) throws IOException {
        try{
          this.block = block;
          this.in = in;
          this.inAddr = inAddr;
          this.myAddr = myAddr;
          this.isRecovery = isRecovery;
          this.clientName = clientName;
          this.offsetInBlock = 0;
          this.srcDataNode = srcDataNode;
          this.datanode = datanode;
          this.checksum = DataChecksum.newDataChecksum(in);
          this.bytesPerChecksum = checksum.getBytesPerChecksum();
          this.checksumSize = checksum.getChecksumSize();
          this.dropCacheBehindWrites = datanode.shouldDropCacheBehindWrites();
          this.syncBehindWrites = datanode.shouldSyncBehindWrites();
          //
          // Open local disk out
          //
          streams = datanode.data.writeToBlock(block, isRecovery,
                                  clientName == null || clientName.length() == 0);
          this.finalized = false;
          if (streams != null) {
            this.out = streams.dataOut;
            this.cout = streams.checksumOut;
            if (out instanceof FileOutputStream) {
              this.outFd = ((FileOutputStream) out).getFD();
            } else {
              LOG.warn("Could not get file descriptor for outputstream of class "
                  + out.getClass());
            }
            this.checksumOut = new DataOutputStream(new BufferedOutputStream(
                                                      streams.checksumOut,
                                                      SMALL_BUFFER_SIZE));
            // If this block is for appends, then remove it from periodic
            // validation.
            if (datanode.blockScanner != null && isRecovery) {
              datanode.blockScanner.deleteBlock(block);
            }
          }
        } catch (BlockAlreadyExistsException bae) {
          throw bae;
        } catch(IOException ioe) {
          IOUtils.closeStream(this);
          cleanupBlock();
    
          // check if there is a disk error
          IOException cause = FSDataset.getCauseIfDiskError(ioe);
          DataNode.LOG.warn("IOException in BlockReceiver constructor. Cause is ",
              cause);
    
          if (cause != null) { // possible disk error
            ioe = cause;
            datanode.checkDiskError(ioe); // may throw an exception here
          }
    
          throw ioe;
        }
      }

    接下来,DataXceiver.writeBlock()将根据请求信息建立数据流管道。
    数据流管道中,顺流的是HDFS的文件数据(下图的粗箭头方向),而写操作的确认包会逆流而上,所以,这里需要两个Socket对象。其中,对象s用于和管道上游通信,它的输入和输出流分别是in和replyOut;往下游的Socket对象是mirrorSock,关联了输出流mirrorOut和输入流mirrorIn。
    这里写图片描述
    如果当前数据节点不是数据管道的最末端,writeBlock()方法就会使用数据目标列表的第一项,建立到下一个数据节点的Socket连接,连接建立后,通过输出流mirrorOut,往下一个数据节点发起写请求,除了数据目标列表大小和数据目标列表字段会相应变化以外,其他字段和上游读到的请求信息是一致的(严格地说,这里不涉及请求的数据校验信息,即上面讨论的接收发送字段只包含请求前面的10个字段)。
    代码如下

     // Write header: Copied from DFSClient.java!
              mirrorOut.writeShort( DataTransferProtocol.DATA_TRANSFER_VERSION );
              mirrorOut.write( DataTransferProtocol.OP_WRITE_BLOCK );
              mirrorOut.writeLong( block.getBlockId() );
              mirrorOut.writeLong( block.getGenerationStamp() );
              mirrorOut.writeInt( pipelineSize );
              mirrorOut.writeBoolean( isRecovery );
              Text.writeString( mirrorOut, client );
              mirrorOut.writeBoolean(hasSrcDataNode);
              if (hasSrcDataNode) { // pass src node information
                srcDataNode.write(mirrorOut);
              }
              mirrorOut.writeInt( targets.length - 1 );
              for ( int i = 1; i < targets.length; i++ ) {
                targets[i].write( mirrorOut );
              }
              accessToken.write(mirrorOut);
    
              blockReceiver.writeChecksumHeader(mirrorOut);
              mirrorOut.flush();

    往下一个数据节点的写请求发送以后,writeBlock()会等待请求的应答,这是一个同步的过程,由于数据流中会有多个数据节点,所以建立数据流管道会花比较长的时间,这也是HDFS不适合用于低延迟数据访问场景的原因之一。
    应答包含的内容:返回码和附加信息。当返回码是DataTransferProtocol.OP_STATUS_ERROR,即出错时,附加信息提供了流中第一个出错的数据节点地址信息(主机名和端口)。方法变量mirrorInStatus和firstBadLink用于保存返回码和附加信息。writeBlock()通过从流mirrorIn中读取返回码和附加信息,等待下游节点的应答。

      // read connect ack (only for clients, not for replication req)
              if (client.length() != 0) {
                mirrorInStatus = mirrorIn.readShort();
                firstBadLink = Text.readString(mirrorIn);
                if (LOG.isDebugEnabled() || mirrorInStatus != DataTransferProtocol.OP_STATUS_SUCCESS) {
                  LOG.info("Datanode " + targets.length +
                           " got response for connect ack " +
                           " from downstream datanode with firstbadlink as " +
                           firstBadLink);
                }
              }

    顺利完成各类初始化任务,写应答

      // send connect ack back to source (only for clients)
          if (client.length() != 0) {
            if (LOG.isDebugEnabled() || mirrorInStatus != DataTransferProtocol.OP_STATUS_SUCCESS) {
              LOG.info("Datanode " + targets.length +
                       " forwarding connect ack to upstream firstbadlink is " +
                       firstBadLink);
            }
            replyOut.writeShort(mirrorInStatus);
            Text.writeString(replyOut, firstBadLink);
            replyOut.flush();
          }

    DataXceiver委托BlockReceiver.receiveBlock()处理写数据的数据包。

     // receive the block and mirror to the next target
          String mirrorAddr = (mirrorSock == null) ? null : mirrorNode;
          blockReceiver.receiveBlock(mirrorOut, mirrorIn, replyOut,
                                     mirrorAddr, null, targets.length);
    

    成功处理完这些数据包以后,调用DataNode.notifyNamenodeReceivedBlock()通知名字节点。

     // if this write is for a replication request (and not
          // from a client), then confirm block. For client-writes,
          // the block is finalized in the PacketResponder.
          if (client.length() == 0) {
            datanode.notifyNamenodeReceivedBlock(block, DataNode.EMPTY_DEL_HINT);
            LOG.info("Received " + block + " src: " + remoteAddress + " dest: "
                + localAddress + " size " + block.getNumBytes());
          }
    
          if (datanode.blockScanner != null) {
            datanode.blockScanner.addBlock(block);
          }

    最后,writeBlock()会关闭到上下游的输入/输出流,完成一次写数据请求

       // close all opened streams
          IOUtils.closeStream(mirrorOut);
          IOUtils.closeStream(mirrorIn);
          IOUtils.closeStream(replyOut);
          IOUtils.closeSocket(mirrorSock);
          IOUtils.closeStream(blockReceiver);

    PacketResponder线程
    当BlockReceiver处理客户端的写数据请求时,方法receiveBlock()接收数据包,校验数据并保存到本地的数据块文件和校验信息文件中,如果节点处于数据流管道的中间,它还需要向下一个数据节点转发数据包。同时,数据节点还需要从下游接收数据包确认,并向上游转发。为此,数据块接收器引入了PacketResponder线程,它和BlockReceiver所在的线程(下面称为BlockReceiver线程)一起工作,分别用于从下游接收应答和从上游接收数据。
    org.apache.hadoop.hdfs.server.datanode.BlockReceiver.java中的receiveBlock方法

    void receiveBlock(
          DataOutputStream mirrOut, // output to next datanode
          DataInputStream mirrIn,   // input from next datanode
          DataOutputStream replyOut,  // output to previous datanode
          String mirrAddr, DataTransferThrottler throttlerArg,
          int numTargets) throws IOException {
    
          mirrorOut = mirrOut;
          mirrorAddr = mirrAddr;
          throttler = throttlerArg;
    
        try {
          // write data chunk header
          if (!finalized) {
            BlockMetadataHeader.writeHeader(checksumOut, checksum);
          }
          if (clientName.length() > 0) {
            responder = new Daemon(datanode.threadGroup, 
                                   new PacketResponder(this, block, mirrIn, 
                                                       replyOut, numTargets,
                                                       Thread.currentThread()));
            responder.start(); // start thread to processes reponses
          }
    
          /* 
           * Receive until packet length is zero.
           */
          while (receivePacket() > 0) {}
    
          // flush the mirror out
          if (mirrorOut != null) {
            try {
              mirrorOut.writeInt(0); // mark the end of the block
              mirrorOut.flush();
            } catch (IOException e) {
              handleMirrorOutError(e);
            }
          }
    
          // wait for all outstanding packet responses. And then
          // indicate responder to gracefully shutdown.
          if (responder != null) {
            ((PacketResponder)responder.getRunnable()).close();
          }
    
          // if this write is for a replication request (and not
          // from a client), then finalize block. For client-writes, 
          // the block is finalized in the PacketResponder.
          if (clientName.length() == 0) {
            // close the block/crc files
            close();
    
            // Finalize the block. Does this fsync()?
            block.setNumBytes(offsetInBlock);
            datanode.data.finalizeBlock(block);
            datanode.myMetrics.incrBlocksWritten();
          }
    
        } catch (IOException ioe) {
          LOG.info("Exception in receiveBlock for " + block + " " + ioe);
          IOUtils.closeStream(this);
          if (responder != null) {
            responder.interrupt();
          }
          cleanupBlock();
          throw ioe;
        } finally {
          if (responder != null) {
            try {
              responder.join();
            } catch (InterruptedException e) {
              throw new IOException("Interrupted receiveBlock");
            }
            responder = null;
          }
        }
      }

    PacketResponder线程从下游数据节点接收确认,并在合适的时候,往上游发送。这里的“合适”包括两个条件:
    1、当前数据节点已经顺利处理完该数据包。
    2、(数据节点处于管道的中间时)当前数据节点收到下游数据节点的数据包确认。
    这两个条件都满足,意味着当前数据节点和数据流管道后续数据节点都完成了对某个数据包的处理。

    由于当前节点由BlockReceiver线程处理数据包,所以,它必须将处理结果通过某种机制,通知到PacketResponder线程,并由PacketResponder线程进行进一步的处理。

    /**
       * Processed responses from downstream datanodes in the pipeline
       * and sends back replies to the originator.
       */
      class PacketResponder implements Runnable, FSConstants {   
    
        //packet waiting for ack
        private LinkedList<Packet> ackQueue = new LinkedList<Packet>(); 
        private volatile boolean running = true;
        private Block block;
        DataInputStream mirrorIn;   // input from downstream datanode
        DataOutputStream replyOut;  // output to upstream datanode
        private int numTargets;     // number of downstream datanodes including myself
        private BlockReceiver receiver; // The owner of this responder.
        private Thread receiverThread; // the thread that spawns this responder
    
        public String toString() {
          return "PacketResponder " + numTargets + " for " + this.block;
        }
    
        PacketResponder(BlockReceiver receiver, Block b, DataInputStream in, 
                        DataOutputStream out, int numTargets,
                        Thread receiverThread) {
          this.receiver = receiver;
          this.block = b;
          mirrorIn = in;
          replyOut = out;
          this.numTargets = numTargets;
          this.receiverThread = receiverThread;
        }
    
        /**
         * enqueue the seqno that is still be to acked by the downstream datanode.
         * @param seqno
         * @param lastPacketInBlock
         */
        synchronized void enqueue(long seqno, boolean lastPacketInBlock) {
          if (running) {
            LOG.debug("PacketResponder " + numTargets + " adding seqno " + seqno +
                      " to ack queue.");
            ackQueue.addLast(new Packet(seqno, lastPacketInBlock));
            notifyAll();
          }
        }
    
        /**
         * wait for all pending packets to be acked. Then shutdown thread.
         */
        synchronized void close() {
          while (running && ackQueue.size() != 0 && datanode.shouldRun) {
            try {
              wait();
            } catch (InterruptedException e) {
              running = false;
            }
          }
          LOG.debug("PacketResponder " + numTargets +
                   " for block " + block + " Closing down.");
          running = false;
          notifyAll();
        }
    
        /**
         * Thread to process incoming acks.
         * @see java.lang.Runnable#run()
         */
        public void run() {
          boolean lastPacketInBlock = false;
          boolean isInterrupted = false;
          final long startTime = ClientTraceLog.isInfoEnabled() ? System.nanoTime() : 0;
          while (running && datanode.shouldRun && !lastPacketInBlock) {
    
            try {
              /**
               * Sequence number -2 is a special value that is used when
               * a DN fails to read an ack from a downstream. In this case,
               * it needs to tell the client that there's been an error downstream
               * but has no valid sequence number to use. Thus, -2 is used
               * as an UNKNOWN value.
               */
              long expected = PipelineAck.UNKOWN_SEQNO;
              long seqno = PipelineAck.UNKOWN_SEQNO;;
    
              PipelineAck ack = new PipelineAck();
              boolean localMirrorError = mirrorError;
              try { 
                Packet pkt = null;
                synchronized (this) {
                  // wait for a packet to arrive
                  while (running && datanode.shouldRun && ackQueue.size() == 0) {
                    if (LOG.isDebugEnabled()) {
                      LOG.debug("PacketResponder " + numTargets + 
                                " seqno = " + seqno +
                                " for block " + block +
                                " waiting for local datanode to finish write.");
                      }
                      wait();
                    }
                    if (!running || !datanode.shouldRun) {
                      break;
                    }
                    pkt = ackQueue.removeFirst();
                    expected = pkt.seqno;
                    notifyAll();
                  }
                  // receive an ack if DN is not the last one in the pipeline
                  if (numTargets > 0 && !localMirrorError) {
                    // read an ack from downstream datanode
                    ack.readFields(mirrorIn);
                    if (LOG.isDebugEnabled()) {
                      LOG.debug("PacketResponder " + numTargets + 
                          " for block " + block + " got " + ack);
                    }
                    seqno = ack.getSeqno();
                    // verify seqno
                    if (seqno != expected) {
                      throw new IOException("PacketResponder " + numTargets +
                          " for block " + block +
                          " expected seqno:" + expected +
                          " received:" + seqno);
                    }
                  }
                  lastPacketInBlock = pkt.lastPacketInBlock;
                } catch (InterruptedException ine) {
                  isInterrupted = true;
                } catch (IOException ioe) {
                  if (Thread.interrupted()) {
                    isInterrupted = true;
                  } else {
                    // continue to run even if can not read from mirror
                    // notify client of the error
                    // and wait for the client to shut down the pipeline
                    mirrorError = true;
                    LOG.info("PacketResponder " + block + " " + numTargets +
                        " Exception " + StringUtils.stringifyException(ioe));
                  }
                }
    
                if (Thread.interrupted() || isInterrupted) {
                  /* The receiver thread cancelled this thread. 
                   * We could also check any other status updates from the 
                   * receiver thread (e.g. if it is ok to write to replyOut). 
                   * It is prudent to not send any more status back to the client
                   * because this datanode has a problem. The upstream datanode
                   * will detect that this datanode is bad, and rightly so.
                   */
                  LOG.info("PacketResponder " + block +  " " + numTargets +
                           " : Thread is interrupted.");
                  break;
                }
    
                // If this is the last packet in block, then close block
                // file and finalize the block before responding success
                if (lastPacketInBlock && !receiver.finalized) {
                  receiver.close();
                  final long endTime = ClientTraceLog.isInfoEnabled() ? System.nanoTime() : 0;
                  block.setNumBytes(receiver.offsetInBlock);
                  datanode.data.finalizeBlock(block);
                  datanode.myMetrics.incrBlocksWritten();
                  datanode.notifyNamenodeReceivedBlock(block, 
                      DataNode.EMPTY_DEL_HINT);
                  if (ClientTraceLog.isInfoEnabled() &&
                      receiver.clientName.length() > 0) {
                    long offset = 0;
                    ClientTraceLog.info(String.format(DN_CLIENTTRACE_FORMAT,
                          receiver.inAddr, receiver.myAddr, block.getNumBytes(), 
                          "HDFS_WRITE", receiver.clientName, offset, 
                          datanode.dnRegistration.getStorageID(), block, endTime-startTime));
                  } else {
                    LOG.info("Received " + block + " of size " + block.getNumBytes() +
                             " from " + receiver.inAddr);
                  }
                }
    
                // construct my ack message
                short[] replies = null;
                if (mirrorError) { // no ack is read
                    replies = new short[2];
                    replies[0] = DataTransferProtocol.OP_STATUS_SUCCESS;
                    replies[1] = DataTransferProtocol.OP_STATUS_ERROR;
                } else {
                    short ackLen = numTargets == 0 ? 0 : ack.getNumOfReplies();
                    replies = new short[1+ackLen];
                    replies[0] = DataTransferProtocol.OP_STATUS_SUCCESS;
                    for (int i=0; i<ackLen; i++) {
                        replies[i+1] = ack.getReply(i);
                    }
                }
                PipelineAck replyAck = new PipelineAck(expected, replies);
    
                // send my ack back to upstream datanode
                replyAck.write(replyOut);
                replyOut.flush();
                if (LOG.isDebugEnabled()) {
                  LOG.debug("PacketResponder " + numTargets +
                            " for block " + block +
                            " responded an ack: " + replyAck);
                }
            } catch (Throwable e) {
              LOG.warn("IOException in BlockReceiver.run(): ", e);
              if (running) {
                LOG.info("PacketResponder " + block + " " + numTargets + 
                         " Exception " + StringUtils.stringifyException(e));
                running = false;
              }
              if (!Thread.interrupted()) { // error not caused by interruption
                receiverThread.interrupt();
              }
            }
          }
          LOG.info("PacketResponder " + numTargets + " for " + block +
              " terminating");
        }
      }

    org.apache.hadoop.hdfs.server.datanode.BlockReceiver.java中的内部类
    PacketResponder中的成员变量ackQueue,保存了BlockReceiver线程已经处理的写请求数据包。BlockReceiver.receivePacket()方法每处理完一个数据包,就通过PacketResponder.enqueue()将对应信息(定义在内部类BlockReceiver.Packet中,包括数据包的序列号和是否是最后一个数据包两个字段)放入队列ackQueue中,队列ackQueue中的信息由PacketResponder.run()方法处理,这是一个典型的生产者-消费者模型。

    PacketResponder.run()是一个大方法,它的处理过程明显的分为两个部分:等待上述两个条件满足,以及条件满足后的处理。其中,第一部分需要通过Java的同步工具wait(),等待ackQueue中的数据,这里wait()等待的是enqueue()方法中notifyAll()的通知。如果ackQueue有数据,则获取第一个记录,接下来,如果当前数据节点位于数据流管道的中间,那么,在流mirrorIn上读取下游的确认,如果顺利读取到下游的响应,表明第一步处理已经完成,代码如下

     while (running && datanode.shouldRun && !lastPacketInBlock) {
    
            try {
              /**
               * Sequence number -2 is a special value that is used when
               * a DN fails to read an ack from a downstream. In this case,
               * it needs to tell the client that there's been an error downstream
               * but has no valid sequence number to use. Thus, -2 is used
               * as an UNKNOWN value.
               */
              long expected = PipelineAck.UNKOWN_SEQNO;
              long seqno = PipelineAck.UNKOWN_SEQNO;;
    
              PipelineAck ack = new PipelineAck();
              boolean localMirrorError = mirrorError;
              try { 
                Packet pkt = null;
                synchronized (this) {
                  // wait for a packet to arrive
                  while (running && datanode.shouldRun && ackQueue.size() == 0) {
                    if (LOG.isDebugEnabled()) {
                      LOG.debug("PacketResponder " + numTargets + 
                                " seqno = " + seqno +
                                " for block " + block +
                                " waiting for local datanode to finish write.");
                      }
                      wait();
                    }
                    if (!running || !datanode.shouldRun) {
                      break;
                    }
                    pkt = ackQueue.removeFirst();
                    expected = pkt.seqno;
                    notifyAll();
                  }
                  // receive an ack if DN is not the last one in the pipeline
                  if (numTargets > 0 && !localMirrorError) {
                    // read an ack from downstream datanode
                    ack.readFields(mirrorIn);
                    if (LOG.isDebugEnabled()) {
                      LOG.debug("PacketResponder " + numTargets + 
                          " for block " + block + " got " + ack);
                    }
                    seqno = ack.getSeqno();
                    // verify seqno
                    if (seqno != expected) {
                      throw new IOException("PacketResponder " + numTargets +
                          " for block " + block +
                          " expected seqno:" + expected +
                          " received:" + seqno);
                    }
                  }
                  lastPacketInBlock = pkt.lastPacketInBlock;
                } catch (InterruptedException ine) {
                  isInterrupted = true;
                } catch (IOException ioe) {
                  if (Thread.interrupted()) {
                    isInterrupted = true;
                  } else {
                    // continue to run even if can not read from mirror
                    // notify client of the error
                    // and wait for the client to shut down the pipeline
                    mirrorError = true;
                    LOG.info("PacketResponder " + block + " " + numTargets +
                        " Exception " + StringUtils.stringifyException(ioe));
                  }
                }
    
                if (Thread.interrupted() || isInterrupted) {
                  /* The receiver thread cancelled this thread. 
                   * We could also check any other status updates from the 
                   * receiver thread (e.g. if it is ok to write to replyOut). 
                   * It is prudent to not send any more status back to the client
                   * because this datanode has a problem. The upstream datanode
                   * will detect that this datanode is bad, and rightly so.
                   */
                  LOG.info("PacketResponder " + block +  " " + numTargets +
                           " : Thread is interrupted.");
                  break;
                }

    如果处理的是整个写请求最后一个数据包的确认,这时,需要执行如下附加步骤:关闭PacketResponder所属的数据块接收器对象,设置数据块长度,使用FSDataset.finalizeBlock()方法提交数据块,最后利用notifyNamenodeReceivedBlock()通知名字节点,本节点完成了一个数据块的接收。代码如下:

      // If this is the last packet in block, then close block
                // file and finalize the block before responding success
                if (lastPacketInBlock && !receiver.finalized) {
                  receiver.close();
                  final long endTime = ClientTraceLog.isInfoEnabled() ? System.nanoTime() : 0;
                  block.setNumBytes(receiver.offsetInBlock);
                  datanode.data.finalizeBlock(block);
                  datanode.myMetrics.incrBlocksWritten();
                  datanode.notifyNamenodeReceivedBlock(block, 
                      DataNode.EMPTY_DEL_HINT);
                  if (ClientTraceLog.isInfoEnabled() &&
                      receiver.clientName.length() > 0) {
                    long offset = 0;
                    ClientTraceLog.info(String.format(DN_CLIENTTRACE_FORMAT,
                          receiver.inAddr, receiver.myAddr, block.getNumBytes(), 
                          "HDFS_WRITE", receiver.clientName, offset, 
                          datanode.dnRegistration.getStorageID(), block, endTime-startTime));
                  } else {
                    LOG.info("Received " + block + " of size " + block.getNumBytes() +
                             " from " + receiver.inAddr);
                  }
                }

    无论当前处理的是否是最后一个数据包,也无论当前数据节点是否是管道的最后一个节点,确认包都需要往上游发送,代码如下

      // construct my ack message
                short[] replies = null;
                if (mirrorError) { // no ack is read
                    replies = new short[2];
                    replies[0] = DataTransferProtocol.OP_STATUS_SUCCESS;
                    replies[1] = DataTransferProtocol.OP_STATUS_ERROR;
                } else {
                    short ackLen = numTargets == 0 ? 0 : ack.getNumOfReplies();
                    replies = new short[1+ackLen];
                    replies[0] = DataTransferProtocol.OP_STATUS_SUCCESS;
                    for (int i=0; i<ackLen; i++) {
                        replies[i+1] = ack.getReply(i);
                    }
                }
                PipelineAck replyAck = new PipelineAck(expected, replies);
    
                // send my ack back to upstream datanode
                replyAck.write(replyOut);
                replyOut.flush();
                if (LOG.isDebugEnabled()) {
                  LOG.debug("PacketResponder " + numTargets +
                            " for block " + block +
                            " responded an ack: " + replyAck);
                }

    当客户端最终接收到确认包时,它可以断定数据流管道上的所有数据节点已经接收到对应的数据包。

    这一篇就分析到这,文章篇幅实在是太长了,下一篇分析数据包接收,org.apache.hadoop.hdfs.server.datanode.BlockReceiver.java中的receiveBlock方法。。。

    展开全文
  • 技术评审节点

    千次阅读 2016-12-27 12:45:04
    产品开发中,TR是技术评审节点。 . 在工作中,我们经常可以听到以下的声音:  “我们不进行评审,是因为我们项目比较特殊,没有时间……”。  “我们的项目已经进行了测试,不需要再进行评审了”。  ...
    产品开发中,TR是技术评审节点。 .

    在工作中,我们经常可以听到以下的声音:

      “我们不进行评审,是因为我们项目比较特殊,没有时间……”。

      “我们的项目已经进行了测试,不需要再进行评审了”。

      “评审都是在走过场,没有效果……”。

      业界公认评审是质量控制最有效的手段之一,但评审在很多公司却没能很好地实施,甚至没有实施,公司也未能从中获益。一方面因为员工不清楚评审的目的、评审和测试的区别,认为评审只不过是除了测试以外的锦上添花的过场。另一方面也因为许多公司制定的评审流程流于形式,缺乏可操作性,也未对员工进行评审流程的培训,未能在评审流程执行过程中提供适当的指导和监督。

    Why-为什么要技术评审?

      测试无疑是质量控制最常用的方法之一,因此很多公司认为对产品进行了测试就万事大吉了。而评审是一种在产品开发过程中尽早发现缺陷的手段。根据IBM的统计数据显示:大多数企业的产品开发中,2/3的缺陷都是在需求和设计阶段引入的。因此,通过评审尽早发现的缺陷的修复成本远低于在产品开发后期测试中发现的缺陷的修复成本。

      缺乏技术评审,或未严格进行技术评审的后果往往会导致测试阶段发生缺陷的“井喷”,开发人员不得不拼命加班“救火”,而最终由于缺陷越来越多,产品上市时间也所剩无几,不得不遗憾地放弃——产品只能带着缺陷发布给客户,听天由命了。

      案例:某产品由于未经严格评审,而匆促上市,结果发现设计指标不符合规格书要求,设计中未考虑工程和维护的问题,产品质量问题多多,生产的单板直通率低,生产效率不高,结果开发工作重新回炉,导致客户投诉不断,用户怨声载道,严重影响用户关系和公司产品形象;导致所有开发人员全部出去救火,开发周期大大加长,开发投入增加,库存积压占用资金。
    评审的目的在于:越早发现问题,总体成本越低,因此要评审,评审,再评审!等到测试已经太迟了!

    What-什么是技术评审?

      测试和技术评审都是有效的质量控制手段,但也有明显的区别。

      类似地,技术评审和测试的目的都是为了寻找缺陷,寻找缺陷的目标不是证明它是正确的,而是证明产品不能工作。

      测试是在产品运行时进行的动态分析,测试的对象为原型、中间产品和最终产品。相对地,评审是一种静态分析,评审对象通常是技术文档、计划、测试用例和测试数据、测试结果等。

    How- 如何做好技术评审?

    1. 技术评审常见的问题
      许多公司虽然执行了技术评审,但却未能从中获益,这往往是因为以下的原因导致的:

      ◆ 没有评审计划,没有充分的准备
      ◆ 专家选择不合适
      ◆ 评审会议偏离主题和重点,过多争论占用大量时间
      ◆ 没有使用Checklist作为指导
      ◆ 问题修改后跟踪不力……

      由此可见,评审效率不高的原因主要是因为缺乏可操作的评审规程、评审执行和跟踪不力导致的。因此,针对不同类型的工作产品,应制定包括多种评审类型的规程,并借助检查单的使用来提高评审的可操作性。

    1. 常见的技术评审的类型
      常见的技术评审包括了走查(Walkthrough)、轮查(Pass Around)、正式的同行评审(Peer Reviews)等。

      1) 走查(Walkthrough):是大名鼎鼎的面向对象方法学的开发者之一Yourdon 定义的方法,它由作者启动和主持评审,作者向评审者展示文档。优点是启动快,成本低,缺点是容易被作者误导过程。

      2) 轮查(Pass Around):作者向评审者作简要介绍,但不参加评审过程;评审者独立进行评审,并记录发现结果、准备报告。

      3) 同行评审:评审者与作者是地位平等的同行/专家,而不是领导对员工的评价;是最为结构化的评审方法;可以作为同行之间学习和分享经验的机会。

    1. 同行评审简介
      在软件CMM中首次提出了同行评审(Peer Reviews)这个概念,它的目标是在产品开发过程中尽早发现缺陷,从而以较低的成本尽早解决缺陷。这种方法借鉴了IBM的范根检查法(Fagan Inspection)的优点,是一种结构化的正式的评审方法。

      同行评审有明确的角色定义:

      ◆ 协调员(Moderator):保证评审按流程进行。
      ◆ 朗读者(Reader):评审的技术领导,把焦点放在有争议的问题方面。
      ◆ 记录员(Recorder):负责记录缺陷。
      ◆ 评审员(Reviewer):负责发现缺陷,除了作者外,所有的其他角色都可以担任评审员。
      ◆ 作者(Author):负责修正缺陷。

      同行评审通常包括六个步骤:制定计划、召开准备会议、评审人员独立预审、召开评审会议、返工、跟进返工结果。各个步骤的活动说明如下:

      1) 计划:选择参与者;准备检查单。

      2) 准备会:分配各参与人员的角色;作者对产品作概要介绍。

      3) 个人预审:评审者研究评审文档,使用检查单寻找缺陷,记录发现结果。

      4) 评审会议:读者阅读评审文档,评审员发现缺陷,对有争议的问题进行讨论;作者一般
        保持沉 默,除非读者要求对产品作解释。

      5) 返工:作者修正错误。

      6) 跟进:检查修正工作的进展;分析错误原因;分析评审过程,补充完善检查单。

    附:做好技术评审的小贴士

      ◆ 不因为时间紧迫和缺少预算而省略评审
      ◆ 评审前充分准备和沟通
      ◆ 安排合理的预审时间以便评审人员阅读评审材料
      ◆ 技术评审应当“就是论事”,不要把评审会开成“批斗会”,不要打击有失误的开发人
        员的工作积极性,更不准搞人身攻击,如挖苦、讽刺等
      ◆ 评审人员的职责是发现工作成果中的缺陷,并帮助开发人员给出消除缺陷的办法,而不
        是替开发人员消除缺陷
      ◆ 把技术评审作为交流、提高的机会
      ◆ 记录评审中出现的问题,跟踪改进
      ◆ 定期改进技术评审检查单,把检查单作为持续改进的重要载体
      ◆ 评审者必须是领域内的专家

    When-常见的技术评审点举例

      在对工作产品建立基线之前进行评审,目的是发现缺陷。

    产品开发中,TR是技术评审节点。


    下面是某产品的技术评审点,供参考:
    TR1——概念阶段技术评审点:产品需求和概念技术评审(业务需求评审)
    TR2——计划阶段技术评审点1:需求分解和需求规格评审(功能需求评审,产品级规格)
    TR3——计划阶段技术评审点2:总体方案评审(系统设计,架构设计,概要设计)
    TR4——开发阶段技术评审点1:模块/系统评审(详细设计,BBFV测试结果)
    TR4A——开发阶段技术评审点2:原形机的质量SDV结果和初始产品的准备情况
    TR5——开发阶段技术评审点3:初始产品的质量(SIT结果)(SIT Alpha测试技术评审)
    TR6——验证阶段技术评审点:发布评审(SVT Beta测试、制造系统验证等)

    SDV就是system design verification,即系统设计验证
    BBFV就是building block fuction verification,即编译模块功能验证
    SIT就是system integration testing,即系统集成测试
    SVT就是system verification testing,系统验证测试
    展开全文
  • 统计完全二叉树的节点

    千次阅读 2018-07-23 01:03:11
     统计一个二叉树的节点最简单的方法当然是遍历一次,但是这样的时间复杂度是严格的O(n),不满足要求。要更快,自然要在完全二叉树的性质上下功夫。  完全二叉树的最后一层,节点一定是从左向右紧密排列的。由此...
  • 集群的各个节点名词解释

    千次阅读 2018-09-09 15:30:27
    zookeeper:主从架构  leader:集群当中的主... 节点模型: 永久节点 临时节点 序列化节点 可以组合成四类节点 永久节点 临时节点 永久序列化节点 永久临时节点  临时节点:一旦客户端断开连接,临时节点消失  ...
  • 5节点Hadoop HA集群搭建

    千次阅读 2020-08-29 17:11:24
    5节点hadoop-2.7.3 HA集群搭建 一、集群规划 共5个节点,主机名分别是node-01、node-02、node-03、node-04、node-05 初始启动集群,node-01上运行active namenode即主namenode;node-02上运行standby namenode即从...
  • Nginx对后端节点健康检查

    千次阅读 2017-05-10 16:04:13
    严格来说,nginx自带是没有针对负载均衡后端节点的健康检查的,但是可以通过默认自带的ngx_http_proxy_module模块和ngx_http_upstream_module模块中的相关指令来完成当后端节点出现故障时,自动切换到健康节点来提供...
  • 法定数量是通过严格意义上的多数节点来表示的。在集合体中,可以包含一个节点,但它不是一个高可用和可靠的系统。如果在集合体中有两个节点,那么这两个节点都必须已经启动并让服务正常运行,因为两个节点中的一个并...
  • 索引节点和inode的指针结构浅析

    千次阅读 2016-07-01 23:19:18
    的文件系统对象的属性可包括操纵元数据(例如,修改,JVSANTEN的访问,修改时间),以及雇主和权限数据(例如组ID,用户ID,权限)。 目录是分配给索引节点名称的列表。该目录包含自己,其母公司,及其每个孩子的...
  • 区块链轻节点和SPV

    千次阅读 2019-07-07 13:15:11
    参考: ... ... 为什么会有SPV: ...在比特币整个生态圈里,大部分都是普通用户,即只有基本的比特币投资及消费支付需要的用户,他们可能没有矿机,没有高端配置的电脑,那么他们是否也要运行一个全节点程序呢?...
  • IAR最新全套教程: ...2.EWSTM8系列教程02_新建基础软件工程 3.EWSTM8系列教程03_主窗口、工具栏的概述 ...6.EWSTM8系列教程06_工程节点选项配置(一) 7.EWSTM8系列教程07_工程节点选项配置(二) 8...
  • Nginx后端节点健康检查

    千次阅读 2015-11-24 17:27:15
    公司前一段对业务线上的nginx做了整理,重点就是对nginx上负载均衡器的后端节点做健康检查。目前,nginx对后端节点健康检查的方式主要有3种,这里列出: 1、ngx_http_proxy_module 模块和ngx_http_upstream_module...
  • 水下无线传感器网络节点定位算法

    千次阅读 2017-06-10 17:31:27
    目前存在着大量的基于水下的无线传感器网络节点定位算法,根据采集或处理数据方式的不同,可将这些定位算法划分为以下几种类型: ...典型的测距算法主要有四种:基于信号到达时间((time of arrival,TOA) 、信
  • jQuery中DOM节点删除、复制、替换、

    千次阅读 2017-05-15 23:45:11
    1DOM节点删除之empty()的基本用法 要移除页面上节点是开发者常见的操作,jQuery提供了几种不同的方法用来处理这个问题,这里我们开仔细了解下empty方法 empty 顾名思义,清空方法,但是与删除又有点不...
  • WAS 节点不同步解决办法一

    千次阅读 2018-09-30 16:43:37
    错误现象: 1、 启动应用的时候特别慢,报“可能已经启动成功,但没有在预定的时间启动...系统管理下的节点状态不对,同步节点后仍然显示未同步。 4、 部署新应用后启动时,会报 [12-4-11 20:08:07:127 CST] 000000...
  • WAS 节点不同步解决办法

    千次阅读 2014-08-19 20:11:08
    错误现象: 1、 启动应用的时候特别慢,报“可能已经启动成功,但没有在预定的时间启动完成,...系统管理下的节点状态不对,同步节点后仍然显示未同步。 4、 部署新应用后启动时,会报 [12-4-11 20:08:07:127 CST
  • Hyperledger Fabric基础之Peer节点

    千次阅读 2018-08-19 17:15:38
    Hyperledger Fabric基础之Peer节点 参考 https://hyperledger-fabric.readthedocs.io/en/release-1.2/peers/peers.html http://www.javatree.cn/news/5129ebda1ada4e93ab2fcc393fc79184 先复习下区块链网络关于...
  • gp5.0.0源码安装部署3节点

    千次阅读 2017-06-01 18:12:07
    gp版本版本:5.0.0 gp节点规划: 1个master,3个segment的集群(采用相同配置)
  • Nginx--后端节点健康检查

    千次阅读 2016-04-27 20:41:48
    公司前一段对业务线上的nginx做了整理,重点就是对nginx上负载均衡器的后端节点做健康检查。目前,nginx对后端节点健康检查的方式主要有3种,这里列出: 1 2 3 4 5 6 1、ngx...
  • zookeeper_节点数据版本号问题

    千次阅读 2019-01-08 20:51:00
    更新节点数据的方法: 同步方法:Stat setData(final String path, byte data[], int version) 异步方法:void setData(final String path, byte data[], int version, StatCallback cb, Object ctx) 参数...
  • 汽车CAN总线设计规范对于CAN节点的输入电容有着严格的规定,每个节点不允许添加过多容性器件,否则节点组合到一起后,会导致总线波形畸变,通讯错误增加。具体如表 1所示。为汽车测试标准GMW3122中的输入电容标准 ...
  • 对于分布式中间件缓存的节点同步其实还是很好处理的,应用服务器集群都是向中间件缓存操作缓存数据,只需要保证缓存中间件节点的数据一致性即可保证缓存数据一致性。当然,对于不同的缓存中间件,节点数据同步机制也...
  • DOM将文档看作一颗树,其中的每个成分都是对象,这些对象都看作是节点。可以将文档的一切都理解成节点对象。 DOM的组成部分介绍:文档、对象和模型 Document(文档):当用浏览器打开一个页面文档时,这个文档就会被...
  • 所以在图网络上按照一个搜索的方法生成节点序列,这个节点的序列可以对应到自然语言的一个句子,后面我们通过Wodrd2Vec的框架,将节点embedding为一个向量。所以对于做network embedding的时候,这个生成节点序列的...
  • 为了避免X某个设备因为自身原因(例如硬件损坏)导致无法正确收发报文而不断的破坏总线的数据帧,从而影响其它正常节点通信,CAN网络具有严格的错误诊断功能,CAN通用规范中规定每个CAN控制器中有一个发送错误计数器...
  • 这就需要经常对ALV树进行调整,由于平衡二叉树对其子树的限制太严格,因而进行插入或者删除时经常需要对树进行调整,而且插入时需要调整的子树可能就是树本身,这就需要较长的时间来查找需要调整的子树的根节点;...
  • 公共节点是必然的选择。应用开发者依赖公共节点相对于传统APP开发依赖云计算,云存储。DAPP依赖的就是区块链节点和IPFS节点,而开发者自己维护这些节点又难免有中心化之嫌。区块链运营者依赖公共节点,一个公共节点...
  • 人工神经网络有两个重要的超参数,用于控制网络的体系结构或拓扑:层数和每个隐藏层中的节点数。配置网络时,必须指定这些参数的值。 为你的特定预测建模问题配置这些超参数的最可靠方法是通过强大的测试工具系统...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 113,637
精华内容 45,454
关键字:

严格按照时间节点