golang如何truncate記錄檔

來源:互聯網
上載者:User

有時候我們產生的記錄檔很大,如果檔案過大,或者磁碟空間有限,那麼我們就需要把記錄檔改小一點。
(當然實際應用情境中,一般採用log rotate的方式實現多個記錄檔,定期把舊的記錄檔挪走或者刪除)

現在我們考慮如何把記錄檔變小的情境。

首先想到的是檔案truncate,遺憾的是truncate是把新的內容刪掉,而不是把舊的內容刪掉;因為通常我們的做法是要把舊的日誌內容刪除,而保留最新的日誌內容。

方法1:拷貝檔案,然後把前半部分刪除,保留新的後半部分

$ wc -l logfile$ tail -n {LINENUM}/2 logfile > logfile2$ mv logfile2 logfile

缺點是需要額外的臨時磁碟空間(logfile2),保留新拷貝的檔案

方法2:把記錄檔讀入記憶體,截去前半部分,把後半部分重新寫會檔案。

缺點也是要建立一個臨時檔案,或者把整個檔案內容一次性讀入記憶體。

下面是一個實現建立臨時檔案的例子:
(如果採用整個檔案讀入記憶體,那麼可以重複覆蓋使用原來的讀入檔案,不用建立臨時檔案):

package mainimport (    "bufio"    "log"    "io"    "os")func ReadLine(r *bufio.Reader, w *bufio.Writer) error {    for i := 0; ; i++ {        line, err := r.ReadString('\n')        if i >= 12554364/2 {  // this number is hard-codes just for example use            if _,err := w.WriteString(line); err != nil {                log.Fatal(err)            }        }        if err != nil {            if err == io.EOF {                return nil            }            return err        }    }    return nil}func main() {    fin, err := os.Open("data.in")    if err != nil {        log.Fatal(err)    }    defer fin.Close()    fout, err := os.Create("data.dat")    if err != nil {        log.Fatal(err)    }    defer fout.Close()    reader := bufio.NewReader(fin)    writer := bufio.NewWriter(fout)    if err := ReadLine(reader, writer); err != nil {        log.Fatal(err)    }    writer.Flush()}

運行:
對於1G大小的記錄檔,在我的Linux上執行時間大約是:

$ go build && time ./mainreal    0m6.972suser    0m4.668ssys     0m2.055s

方法3:也是利用檔案,把檔案mmap映射到記憶體,然後使用copy函數覆蓋記憶體。

缺點是,mmap動作記錄檔案不能過大。

下面是一個實現的代碼例子:

package mainimport (    "fmt"    "log"    "os"    "syscall")// Truncate file to half size, with line aligned.func truncateText(f *os.File) error {    fi, err := f.Stat()    if err != nil {        return err    }    mem, err := syscall.Mmap(int(f.Fd()), 0, int(fi.Size()), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)    if err != nil {        return err    }    size := fi.Size()    if size <= 1 {        // Don't need to truncate file if it's too small        return nil    }    trun := size/2 - 1    for ; trun < fi.Size(); trun ++ {        if mem[trun] == '\n' {            break        }    }    //fmt.Printf("size=%d, trun=%d\n", size, trun)    if trun >= size - 1 {        trun = size/2    } else {        trun = trun + 1    }    // Overwrite file content    copy(mem[0:], mem[trun:])        err = syscall.Munmap(mem)    if err != nil {        return err    }    // truncate file    f.Truncate(fi.Size() - trun)    // reset file offset    f.Seek(trun,0)    return nil}func main() {    path := "data.dat"    f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)    if err != nil {        fmt.Println("Cannot create file")        log.Fatal(err)    }    err = truncateText(f)    if err != nil {        fmt.Println("Cannot truncateLog file")        log.Fatal(err)    }    f.Write([]byte("ABCD"))    f.Close()}

函數truncateText的功能就是把檔案截取到原來的一半大小,並保持按行對齊;分如下幾步:

  1. 先把檔案對應syscall.Mmap 到記憶體。
  2. 尋找到中間行的位置。
  3. 然後用內建函數copy,把中間行以後的挪動到檔案開始位置。
  4. 修改檔案的大小file.Truncate
  5. 重設檔案的位移指標offset
  6. 取消檔案對應syscall.Munmap

運行:
對於1G大小的記錄檔,在我的Linux上執行時間大約是:

$ go build && time ./mainreal    0m1.861suser    0m0.335ssys     0m0.650s
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.