github.com/zhiqiangxu/util@v0.0.0-20230112053021-0a7aee056cd5/diskqueue/meta.go (about)

     1  package diskqueue
     2  
     3  import (
     4  	"encoding/binary"
     5  	"sync"
     6  	"unsafe"
     7  
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/zhiqiangxu/util"
    12  	"github.com/zhiqiangxu/util/logger"
    13  	"github.com/zhiqiangxu/util/mapped"
    14  	"go.uber.org/zap"
    15  )
    16  
    17  type queueMetaROInterface interface {
    18  	NumFiles() int
    19  	Stat() QueueMeta
    20  	FileMeta(idx int) FileMeta
    21  }
    22  
    23  type queueMetaInterface interface {
    24  	queueMetaROInterface
    25  	Init() error
    26  	AddFile(f FileMeta)
    27  	UpdateFileStat(idx, n int, endOffset, endTime int64)
    28  	LocateFile(readOffset int64) int
    29  	UpdateMinValidIndex(minValidIndex uint32)
    30  	Sync() error
    31  	Close() error
    32  }
    33  
    34  var _ queueMetaInterface = (*queueMeta)(nil)
    35  
    36  const (
    37  	maxSizeForMeta = 1024 * 1024
    38  )
    39  
    40  var (
    41  	reservedHeaderSize = util.Max(256 /*should be enough*/, int(unsafe.Sizeof(QueueMeta{})))
    42  )
    43  
    44  // FileMeta for a single file
    45  type FileMeta struct {
    46  	StartOffset int64 // inclusive
    47  	EndOffset   int64 // exclusive
    48  	StartTime   int64
    49  	EndTime     int64
    50  	MsgCount    uint64
    51  }
    52  
    53  // QueueMeta for the queue
    54  type QueueMeta struct {
    55  	FileCount     uint32
    56  	MinValidIndex uint32
    57  }
    58  
    59  type queueMeta struct {
    60  	mu          sync.RWMutex
    61  	conf        *Conf
    62  	mappedFile  *mapped.File
    63  	mappedBytes []byte
    64  }
    65  
    66  // NewQueueMeta is ctor for queueMeta
    67  func newQueueMeta(conf *Conf) *queueMeta {
    68  	return &queueMeta{conf: conf}
    69  }
    70  
    71  const (
    72  	metaFile = "qm"
    73  )
    74  
    75  // Init either load or creates the meta file
    76  func (m *queueMeta) Init() (err error) {
    77  	path := filepath.Join(m.conf.Directory, metaFile)
    78  	if _, err := os.Stat(path); os.IsNotExist(err) {
    79  		m.mappedFile, err = mapped.CreateFile(path, maxSizeForMeta, true, nil)
    80  	} else {
    81  		m.mappedFile, err = mapped.OpenFile(path, maxSizeForMeta, os.O_RDWR, true, nil)
    82  	}
    83  	if err != nil {
    84  		return
    85  	}
    86  
    87  	err = m.mappedFile.MLock()
    88  	if err != nil {
    89  		return
    90  	}
    91  	m.mappedBytes = m.mappedFile.MappedBytes()
    92  
    93  	return nil
    94  }
    95  
    96  func (m *queueMeta) NumFiles() int {
    97  	m.mu.RLock()
    98  	defer m.mu.RUnlock()
    99  
   100  	return int(binary.BigEndian.Uint32(m.mappedBytes))
   101  }
   102  
   103  func (m *queueMeta) Stat() QueueMeta {
   104  	m.mu.RLock()
   105  	defer m.mu.RUnlock()
   106  
   107  	return QueueMeta{FileCount: binary.BigEndian.Uint32(m.mappedBytes), MinValidIndex: binary.BigEndian.Uint32(m.mappedBytes[4:])}
   108  }
   109  
   110  func (m *queueMeta) FileMeta(idx int) (fm FileMeta) {
   111  	m.mu.RLock()
   112  	defer m.mu.RUnlock()
   113  
   114  	nFiles := int(binary.BigEndian.Uint32(m.mappedBytes))
   115  	if idx >= nFiles {
   116  		logger.Instance().Fatal("FileMeta idx over size", zap.Int("idx", idx), zap.Int("nFiles", nFiles))
   117  	}
   118  
   119  	offset := reservedHeaderSize + int(unsafe.Sizeof(FileMeta{}))*idx
   120  	startOffset := int64(binary.BigEndian.Uint64(m.mappedBytes[offset:]))
   121  	endOffset := int64(binary.BigEndian.Uint64(m.mappedBytes[offset+8:]))
   122  	startTime := int64(binary.BigEndian.Uint64(m.mappedBytes[offset+16:]))
   123  	endTime := int64(binary.BigEndian.Uint64(m.mappedBytes[offset+24:]))
   124  	msgCount := binary.BigEndian.Uint64(m.mappedBytes[offset+32:])
   125  
   126  	fm = FileMeta{StartOffset: startOffset, EndOffset: endOffset, StartTime: startTime, EndTime: endTime, MsgCount: msgCount}
   127  	return
   128  
   129  }
   130  
   131  func (m *queueMeta) AddFile(f FileMeta) {
   132  	m.mu.Lock()
   133  	defer m.mu.Unlock()
   134  
   135  	nFiles := binary.BigEndian.Uint32(m.mappedBytes)
   136  	binary.BigEndian.PutUint32(m.mappedBytes, nFiles+1)
   137  	offset := reservedHeaderSize + int(unsafe.Sizeof(FileMeta{}))*int(nFiles)
   138  	binary.BigEndian.PutUint64(m.mappedBytes[offset:], uint64(f.StartOffset))
   139  	binary.BigEndian.PutUint64(m.mappedBytes[offset+8:], uint64(f.EndOffset))
   140  	binary.BigEndian.PutUint64(m.mappedBytes[offset+16:], uint64(f.StartTime))
   141  	binary.BigEndian.PutUint64(m.mappedBytes[offset+24:], uint64(f.EndTime))
   142  
   143  }
   144  
   145  func (m *queueMeta) UpdateFileStat(idx, n int, endOffset, endTime int64) {
   146  	m.mu.Lock()
   147  	defer m.mu.Unlock()
   148  
   149  	nFiles := int(binary.BigEndian.Uint32(m.mappedBytes))
   150  	if idx >= nFiles {
   151  		logger.Instance().Fatal("UpdateFileStat idx over size", zap.Int("idx", idx), zap.Int("nFiles", nFiles))
   152  	}
   153  
   154  	offset := reservedHeaderSize + int(unsafe.Sizeof(FileMeta{}))*idx
   155  	endOffset0 := int64(binary.BigEndian.Uint64(m.mappedBytes[offset+8:]))
   156  	startTime0 := int64(binary.BigEndian.Uint64(m.mappedBytes[offset+16:]))
   157  	endTime0 := int64(binary.BigEndian.Uint64(m.mappedBytes[offset+24:]))
   158  	msgCount0 := binary.BigEndian.Uint64(m.mappedBytes[offset+32:])
   159  
   160  	if endOffset > endOffset0 {
   161  		binary.BigEndian.PutUint64(m.mappedBytes[offset+8:], uint64(endOffset))
   162  	}
   163  
   164  	if endTime < startTime0 {
   165  		binary.BigEndian.PutUint64(m.mappedBytes[offset+16:], uint64(endTime))
   166  	}
   167  
   168  	if endTime > endTime0 {
   169  		binary.BigEndian.PutUint64(m.mappedBytes[offset+24:], uint64(endTime))
   170  	}
   171  
   172  	binary.BigEndian.PutUint64(m.mappedBytes[offset+32:], msgCount0+uint64(n))
   173  
   174  	return
   175  
   176  }
   177  
   178  func (m *queueMeta) LocateFile(readOffset int64) int {
   179  
   180  	m.mu.RLock()
   181  	defer m.mu.RUnlock()
   182  
   183  	start := int(binary.BigEndian.Uint32(m.mappedBytes[4:]))
   184  	end := int(binary.BigEndian.Uint32(m.mappedBytes) - 1)
   185  
   186  	// 二分查找
   187  	for start <= end {
   188  		target := start + (end-start)/2 // 防止溢出
   189  		offset := reservedHeaderSize + int(unsafe.Sizeof(FileMeta{}))*target
   190  		startOffset := int64(binary.BigEndian.Uint64(m.mappedBytes[offset:]))
   191  		endOffset := int64(binary.BigEndian.Uint64(m.mappedBytes[offset+8:]))
   192  		switch {
   193  		case startOffset <= readOffset && endOffset > readOffset:
   194  			return target
   195  		case readOffset < startOffset:
   196  			end = target - 1
   197  		case readOffset >= endOffset:
   198  			start = target + 1
   199  		}
   200  	}
   201  
   202  	return -1
   203  
   204  }
   205  
   206  func (m *queueMeta) UpdateMinValidIndex(minValidIndex uint32) {
   207  	m.mu.Lock()
   208  	binary.BigEndian.PutUint32(m.mappedBytes[4:], minValidIndex)
   209  	m.mu.Unlock()
   210  }
   211  
   212  func (m *queueMeta) Sync() error {
   213  	return m.mappedFile.Sync()
   214  }
   215  
   216  func (m *queueMeta) Close() error {
   217  	m.mappedBytes = nil
   218  	return m.mappedFile.Close()
   219  }