信頼性と分析NSQメッセージ待ち行列(C)の耐久メッセージ送信の[ジ] diskqueue

NSQは、メッセージを確実にするためにどのように前の主な発言は、成功した消費者向け最終消費者である、おそらくビットを置く永続メッセージが--mem-queue-size0に設定されている、すべてのメッセージがディスクに保存されます。
誰かがいつも言うnsq、永続的な問題をベンチマークテストを行うには、元のコードを読む方法が安心さで、個人的な感情はnsqまだ非常にトリッキーです。nsq独自の実装FIFOキューメッセージファイルゴーdiskqueueがローカルファイルにメッセージを保存することです、それは価値分析どのような彼実装プロセスです。

全体の処理ロジック

go-diskqueue開始gorouting方法は、読み取りおよび書き込みデータioLoop
パラメータに基づいてデータを読み書きすることであろうが、あなたのフローチャートはevernotecid以下の設定:// D2602A6B-6F53-4199-885D-97DFC21CBA3E / appyinxiangcom / 2479854 / ENResource / p1391

この絵は、特に正確ではないioLoopですしてselectいないif else複数の条件がある場合true、実行はランダムに選択されます

nsq evernotecid:// D2602A6B-6F53-4199-885D-97DFC21CBA3E / appyinxiangcom / 2479854 / ENResource / p1383データは、以下のように生成されました

xxxx.diskqueue.meta.dat 未読メッセージ、読み出しおよび格納された位置データの読み出し数のメタデータが格納された長さ
xxxx.diskqueue.编号.dat 4バイトは、メッセージの長さ+メッセージ:保存されたメッセージファイルを格納する各メッセージ
evernotecid:// D2602A6B-6F53-4199-885D -97DFC21CBA3E / appyinxiangcom / 2479854 / ENResource / p1381

パラメータ説明

これらのパラメータの使用を記載し、主要なパラメータや制約の中には、ロジックを処理し、後述します

// diskQueue implements a filesystem backed FIFO queue
type diskQueue struct {
	// run-time state (also persisted to disk)
	// 读取数据的位置    
	readPos      int64
	// 写入数据的位置
	writePos     int64
	// 读取文件的编号    
	readFileNum  int64
	// 写入文件的编号
	writeFileNum int64
	// 未处理的消息总数    
	depth        int64

	// instantiation time metadata
	// 每个文件的大小限制    
	maxBytesPerFile int64 // currently this cannot change once created
	// 每条消息的最小大小限制    
	minMsgSize      int32
	// 每条消息的最大大小限制    
	maxMsgSize      int32
	// 缓存消息有多少条后进行写入    
	syncEvery       int64         // number of writes per fsync
	// 自动写入消息文件的时间间隔    
	syncTimeout     time.Duration // duration of time per fsync
	exitFlag        int32
	needSync        bool

	// keeps track of the position where we have read
	// (but not yet sent over readChan)
	// 下一条消息的位置    
	nextReadPos     int64
	// 下一条消息的文件编号    
	nextReadFileNum int64

	// 读取的文件
	readFile  *os.File
	// 写入的文件    
	writeFile *os.File
	// 读取的buffer    
	reader    *bufio.Reader
	// 写入的buffer    
	writeBuf  bytes.Buffer

	// exposed via ReadChan()
	// 读取数据的channel    
	readChan chan []byte

	//.....
}
复制代码

データ

メタデータ

メタデータが格納されたデータを読み書きするxxxxx.diskqueue.meta.data未処理のメッセージ・ファイル内のコードフィールドの総数として主に使用されるdepthのファイル番号を読み取るためにreadFileNum、読み出しデータの場所readPos
ファイルに書き込まれた数writeFileNumの書き込みデータを場所writePos
、実際のデータを次のように

15
0,22
3,24
复制代码

メタデータ情報の保存

func (d *diskQueue) persistMetaData() error {
	// ...
	fileName := d.metaDataFileName()
	tmpFileName := fmt.Sprintf("%s.%d.tmp", fileName, rand.Int())
	// write to tmp file
	f, err = os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE, 0600)
	// 元数据信息
	_, err = fmt.Fprintf(f, "%d\n%d,%d\n%d,%d\n",
		atomic.LoadInt64(&d.depth),
		d.readFileNum, d.readPos,
		d.writeFileNum, d.writePos)
	// 保存
	f.Sync()
	f.Close()
	// atomically rename
	return os.Rename(tmpFileName, fileName)
}
复制代码

メタデータ情報を取得します。

func (d *diskQueue) retrieveMetaData() error {
	// ...
	fileName := d.metaDataFileName()
	f, err = os.OpenFile(fileName, os.O_RDONLY, 0600)
	// 读取数据并赋值
	var depth int64
	_, err = fmt.Fscanf(f, "%d\n%d,%d\n%d,%d\n",
		&depth,
		&d.readFileNum, &d.readPos,
		&d.writeFileNum, &d.writePos)
	//...
	atomic.StoreInt64(&d.depth, depth)
	d.nextReadFileNum = d.readFileNum
	d.nextReadPos = d.readPos
	return nil
}
复制代码

メッセージデータ

データ書き込み

ioLoop発見された書き込まれたデータは、呼び出すとwriteOne方法を、メッセージがファイルに保存されます

		select {
		// ...
		case dataWrite := <-d.writeChan:
			count++
			d.writeResponseChan <- d.writeOne(dataWrite)
		// ...
复制代码
func (d *diskQueue) writeOne(data []byte) error {
	var err error

	if d.writeFile == nil {
		curFileName := d.fileName(d.writeFileNum)
		d.writeFile, err = os.OpenFile(curFileName, os.O_RDWR|os.O_CREATE, 0600)
		// ...
		if d.writePos > 0 {
			_, err = d.writeFile.Seek(d.writePos, 0)
			// ...
		}
	}

	dataLen := int32(len(data))
	// 判断消息的长度是否合法
	if dataLen < d.minMsgSize || dataLen > d.maxMsgSize {
		return fmt.Errorf("invalid message write size (%d) maxMsgSize=%d", dataLen, d.maxMsgSize)
	}
	d.writeBuf.Reset()
	// 写入4字节的消息长度,以大端序保存
	err = binary.Write(&d.writeBuf, binary.BigEndian, dataLen)
	if err != nil {
		return err
	}
	// 写入消息
	_, err = d.writeBuf.Write(data)
	if err != nil {
		return err
	}

	// 写入到文件
	_, err = d.writeFile.Write(d.writeBuf.Bytes())
	// ...
	// 计算写入位置,消息数量加1
	totalBytes := int64(4 + dataLen)
	d.writePos += totalBytes
	atomic.AddInt64(&d.depth, 1)
	// 如果写入位置大于 单个文件的最大限制, 则持久化文件到硬盘
	if d.writePos > d.maxBytesPerFile {
		d.writeFileNum++
		d.writePos = 0

		// sync every time we start writing to a new file
		err = d.sync()
		// ...
	}
	return err
}

复制代码

メッセージを書き込んだ後、それは現在のファイルのサイズがされているかどうかを判断しますmaxBytesPerFileハードディスクに大きな、永続的なファイルならば、その後、書き込みのため、もう一度新しいファイル番号を開きます。

ときにハードディスクへの持続的なファイル

呼び出しsync()方法は、ハードディスクへの永続的なファイルである、その後、書き込みのため、もう一度新しいファイル番号を開きます。
コール・コールこの方法にはいくつかの場所があります。

  • ファイルに書き込まれた個数に達するsyncEvery初期設定の最大数である時間値を、。呼び出します。sync()
  • syncTimeout 初期化時に同期時間間隔設定した時間間隔がアップしている、と書かれた文書の数が> 0の場合、呼び出しは次のようになりますsync()
  • 上記のあるwriteOne方法は、あなたがメッセージを書き込むした後、現在のファイルサイズがされているかどうかを判断します、maxBytesPerFile大きな、呼び出した場合sync()
  • ファイルを読み込むときは、ファイル全体が読み込まれたときに終了し、ファイルを削除しますとなりますneedSync設定しtrueioLoop呼び出します。sync()
  • あるClose時、呼び出しがsync()
func (d *diskQueue) sync() error {
	if d.writeFile != nil {
		// 把数据 flash到硬盘,关闭文件并设置为 nil
		err := d.writeFile.Sync()
		if err != nil {
			d.writeFile.Close()
			d.writeFile = nil
			return err
		}
	}
	// 保存元数据信息
	err := d.persistMetaData()
	// ...
	d.needSync = false
	return nil
}
复制代码

データの一部を読みます

リードメタデータファイル番号の保持readFileNum位置を、データの読み出しreadPos
diskQueueする方法を公開、によりchannelデータを読み出します

func (d *diskQueue) ReadChan() chan []byte {
	return d.readChan
}
复制代码

ioLoopここで、読取位置に見出される、または位置を書くこと数が少ないリードライトファイル番号、及び読取位置データを読み取り、次のより現在位置に等しく、かつ、外部グローバル変数に配置されているファイルよりも小さい場合dataReadに、読み出しchannel割り当てモニターr = d.readChan誰かが外部メッセージを読み、実行moveForward操作を

func (d *diskQueue) ioLoop() {
	var dataRead []byte
	var err error
	var count int64
	var r chan []byte
	for {
		// ...
		if (d.readFileNum < d.writeFileNum) || (d.readPos < d.writePos) {
			if d.nextReadPos == d.readPos {
				dataRead, err = d.readOne()
				if err != nil {
					d.handleReadError()
					continue
				}
			}
			r = d.readChan
		} else {
			r = nil
		}

		select {
		// ...
		case r <- dataRead:
			count++
			// moveForward sets needSync flag if a file is removed
			d.moveForward()
		// ...
		}
	}

// ...
}

复制代码

readOneファイルから読み取られたメッセージ、4ビットサイズ、及び、特定のメッセージを読み出します。読み取り位置が最大ファイルサイズよりも大きい場合は近くにあります。moveForwardレーンでの操作を削除します

func (d *diskQueue) readOne() ([]byte, error) {
	var err error
	var msgSize int32
	// 如果readFile是nil,打开一个新的
	if d.readFile == nil {
		curFileName := d.fileName(d.readFileNum)
		d.readFile, err = os.OpenFile(curFileName, os.O_RDONLY, 0600)
		// ...
		d.reader = bufio.NewReader(d.readFile)
	}
	err = binary.Read(d.reader, binary.BigEndian, &msgSize)
	// ...
	readBuf := make([]byte, msgSize)
	_, err = io.ReadFull(d.reader, readBuf)
	totalBytes := int64(4 + msgSize)
	// ...
	d.nextReadPos = d.readPos + totalBytes
	d.nextReadFileNum = d.readFileNum
	// 如果读取位置大于最大文件限制,则close。在moveForward里会进行删除操作
	if d.nextReadPos > d.maxBytesPerFile {
		if d.readFile != nil {
			d.readFile.Close()
			d.readFile = nil
		}
		d.nextReadFileNum++
		d.nextReadPos = 0
	}
	return readBuf, nil
}

复制代码

moveForward数字のメソッドのルックスは、次の番号ならば、読み、現在の数が同じでないことが判明し、古いファイルが削除されます。

func (d *diskQueue) moveForward() {
	oldReadFileNum := d.readFileNum
	d.readFileNum = d.nextReadFileNum
	d.readPos = d.nextReadPos
	depth := atomic.AddInt64(&d.depth, -1)

	// see if we need to clean up the old file
	if oldReadFileNum != d.nextReadFileNum {
		// sync every time we start reading from a new file
		d.needSync = true

		fn := d.fileName(oldReadFileNum)
		err := os.Remove(fn)
		// ...
	}
	d.checkTailCorruption(depth)

复制代码

おすすめ

転載: juejin.im/post/5dce6baef265da0bf175d2b1