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 }