Оригинальная ссылка:blog.thinker idea.com/201906/go/from…
Недавно столкнулся с проблемой, что лог бэкап io слишком высокий.Бэкап бизнес лога бэкапится каждые десять минут.Он изначально был написан на Python для сканирования лога бекапа по правилам.Чем больше файлов,тем больше количество файлов, что приводит к серьезной блокировке ввода-вывода в момент каждого резервного копирования, аномально высокой загрузке ЦП и нагрузки.К счастью, скорость резервного копирования высокая, и влияние на бизнес невелико.Эта проблема будет расти с ростом дело.Все очевиднее становится,что за этот промежуток времени метод бекапа оптимизировался,и эффект очень значительный.Я организую статью,чтобы это зафиксировать.
справочная информация
Конфигурация сервера: 4 ядра 8G Диск: 500G
Необходимо загружать каждые десять минут: 18 файлов, около 10G в часы пик.
Для обеспечения надежности бизнес-журналы сначала записываются в файлы на диске, а файлы журналов разбиваются каждые 10 минут, а затем журналы копируются в OSS в первую минуту следующих десяти минут. Служба анализа данных будет извлекать журналы для анализ после завершения резервного копирования.Резервное копирование журнала должно быть эффективным и быстрым, и резервное копирование может быть выполнено в кратчайшие сроки.Как правило, резервное копирование может быть завершено в течение десятков секунд.
Скорость и эффективность резервного копирования не является проблемой, оно достаточно быстрое, но аномальная нагрузка на ЦП и нагрузка, вызванная сильной блокировкой ввода-вывода во время резервного копирования, становится узким местом бизнес-сервисов.В часы пик бизнес-сервисы потребляют только половину системных ресурсов, но при резервном копировании ЦП часто 100%, а iowait может достигать более 70, а простаивающих ресурсов очень мало, поэтому с расширением бизнеса, хотя время резервного копирования журнала очень короткое, оно стало узким местом система.
Решение до и после оптимизации будет подробно описано далее, а тест будет написан на ходу, а для тестирования будет использован 2-ядерный 4G сервер Размер тестового набора данных:
- Количество файлов: 336
- Исходный файл: 96G
- Сжатый файл: 24G
- Схема сжатия: lzo
- Количество горутин: 4
До оптимизации
Процесс резервного копирования журнала перед оптимизацией:
- Сканировать файлы для резервного копирования в соответствии с правилами резервного копирования
- использовать
lzop
команда для сжатия журналов - Загружать сжатые журналы в OSS
Ниже приведена реализация кода. Правила файла резервного копирования здесь больше не включены, а демонстрируется только логика сжатия и загрузки.Программа принимает список файлов, сжимает и загружает список файлов в OSS.
.../pkg/aliyun_oss
Это пакет, основанный на операциях Alibaba Cloud OSS, которые я инкапсулировал сам. Этот путь неверен. Он предназначен только для демонстрации. Если вы хотите запустить следующий код, вам нужно реализовать часть взаимодействия с OSS самостоятельно.
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
"time"
".../pkg/aliyun_oss"
)
func main() {
var oss *aliyun_oss.AliyunOSS
files := os.Args[1:]
if len(files) < 1 {
fmt.Println("请输入要上传的文件")
os.Exit(1)
}
fmt.Printf("待备份文件数量:%d\n", len(files))
startTime := time.Now()
defer func(startTime time.Time) {
fmt.Printf("共耗时:%s\n", time.Now().Sub(startTime).String())
}(startTime)
var wg sync.WaitGroup
n := 4
c := make(chan string)
// 压缩日志
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
defer wg.Done()
for file := range c {
cmd := exec.Command("lzop", file)
cmd.Stderr = &bytes.Buffer{}
err := cmd.Run()
if err != nil {
panic(cmd.Stderr.(*bytes.Buffer).String())
}
}
}()
}
for _, file := range files {
c <- file
}
close(c)
wg.Wait()
fmt.Printf("压缩耗时:%s\n", time.Now().Sub(startTime).String())
// 上传压缩日志
startTime = time.Now()
c = make(chan string)
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
defer wg.Done()
for file := range c {
name := filepath.Base(file)
err := oss.PutObjectFromFile("tmp/"+name+".lzo", file+".lzo")
if err != nil {
panic(err)
}
}
}()
}
for _, file := range files {
c <- file
}
close(c)
wg.Wait()
fmt.Printf("上传耗时:%s\n", time.Now().Sub(startTime).String())
}
Вывод при запуске программы:
待备份文件数量:336
压缩耗时:19m44.125314226s
上传耗时:6m14.929371103s
共耗时:25m59.118002969s
Из бегущих результатов видно, что сжатый файл занимает много времени, а фактическоеiostat
Анализ команд также показал, что потребление ресурсов при сжатии относительно велико.iostat -m -x 5 10000
Команда собирает данные на каждом этапе.
- Перед запуском программы
avg-cpu: %user %nice %system %iowait %steal %idle
2.35 0.00 2.86 0.00 0.00 94.79
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
vdb 0.00 0.60 0.00 0.60 0.00 4.80 16.00 0.00 0.67 0.00 0.67 0.67 0.04
- При сжатии журнала
avg-cpu: %user %nice %system %iowait %steal %idle
10.84 0.00 6.85 80.88 0.00 1.43
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 0.60 0.00 2.40 0.00 8.00 0.00 0.67 0.67 0.00 0.67 0.04
vdb 14.80 5113.80 1087.60 60.60 78123.20 20697.60 172.13 123.17 106.45 106.26 109.87 0.87 100.00
avg-cpu: %user %nice %system %iowait %steal %idle
10.06 0.00 7.19 79.06 0.00 3.70
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 1.60 0.00 103.20 0.00 129.00 0.01 3.62 3.62 0.00 0.50 0.08
vdb 14.20 4981.20 992.80 52.60 79682.40 20135.20 190.97 120.34 112.19 110.60 142.17 0.96 100.00
- При загрузке журналов
avg-cpu: %user %nice %system %iowait %steal %idle
6.98 0.00 7.81 7.71 0.00 77.50
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 13.40 0.00 242.40 0.00 36.18 0.02 1.63 1.63 0.00 0.19 0.26
vdb 0.40 2.40 269.60 1.20 67184.80 14.40 496.30 4.58 15.70 15.77 0.33 1.39 37.74
avg-cpu: %user %nice %system %iowait %steal %idle
7.06 0.00 8.00 4.57 0.00 80.37
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 0.60 0.00 75.20 0.00 250.67 0.00 2.67 2.67 0.00 2.00 0.12
vdb 0.20 0.00 344.80 0.00 65398.40 0.00 379.34 5.66 16.42 16.42 0.00 1.27 43.66
отiostat
В результате выяснилось, что при сжатии программыr_await
а такжеw_await
достигли более ста, иiowait
Гандам80.88%
, почти исчерпал весь ЦП, при загрузкеiowait
Это приемлемо, потому что сжатый файл просто читается, а сжатый файл еще и небольшой.
анализировать проблему
Из вышеприведенных результатов видно, что программа в основном потребляет сжатый лог, а оптимизация также фокусируется на логике сжатия лога.
При сжатии журнал сначала будет сжат вlzo
файл перед загрузкойlzo
В процессе передачи файлов в Alibaba Cloud OSS есть несколько процессов:
- Чтение необработанного файла журнала
- сжатые данные
- записывать
lzo
документ - читать
lzo
документ -
http
отправить прочитанный контент
при сжатииr_await
а такжеw_await
высоки, в основном возникает при чтении необработанных файлов журнала, записиlzo
файл, как его оптимизировать?
Сначала подумайте об исходных требованиях, прочитайте исходный файл -> загрузите данные. Но загрузка исходного файла напрямую, файл относительно большой, передача по сети медленная, а стоимость хранения относительно высока, что мне делать?
В это время мы ожидаем загрузки сжатых файлов, поэтому у нас есть логика перед оптимизацией, которая генерирует промежуточный процесс, то есть с использованиемlzop
команда сжимает файл и создает промежуточный файлlzo
документ.
Необходимо прочитать исходный файл и загрузить данные, тогда процесс сжатия можно оптимизировать, поэтомуr_await
Нет возможности оптимизировать, тогда только оптимизацияw_await
,w_await
Как получилось, именно написатьlzo
генерируется, может быть, а может и не бытьlzo
файл? Что делает этот файл?
Если мы сжимаем поток данных файла, в процессе чтения исходного файла -> загрузка данных загруженный поток данных сжимается в режиме реального времени, а сжатый контент загружается для реализации сжатия при чтении и обработки потока данных. , как промежуточное ПО, поэтому вам не нужно писатьlzo
файл, затемw_await
Он полностью оптимизирован.
lzo
Что делает файл? Я думаю, что потребление сжатия файла может быть сохранено один раз только после неудачной загрузки. Сколько раз загрузка не удалась? Пользуюсь Alibaba Cloud OSS уже несколько лет.За исключением сбоя в интранете ни разу не сталкивался с файлом, который не удалось загрузить.Думаю, этот файл не нужен, а он генерируетlzo
Файлы также должны занимать место на диске и регулярно очищаться, что увеличивает потребление ресурсов и затраты на обслуживание.
Оптимизировано
Согласно предыдущему анализу, давайте посмотрим на процесс резервного копирования файлов после оптимизации:
- читать необработанный журнал
- Сжать поток данных в памяти
- http отправить сжатый контент
Этот процесс экономит два шага, записываяlzo
файл и читатьlzo
файл, не только нетw_await
, четноеr_await
Он также был немного оптимизирован.
План оптимизации определен, но как добитьсяlzo
Как насчет сжатия файлового потока?Github
Проверьте его, чтобы увидеть, есть лиlzo
библиотека алгоритмов сжатия, найденнаяgithub.com/cyberdelia/lzo
, хотя он реализован обращением к библиотеке C, два классических алгоритма (lzo1x_1
а такжеlzo1x_999
) все предоставляют интерфейсы, и кажется, что Go может использовать эту библиотеку напрямую.
Обнаружено, что эта библиотека реализуетio.Reader
а такжеio.Writer
интерфейс,io.Reader
Чтение потока сжатых файлов, вывод распакованных данных,io.Writer
Реализовать входные необработанные данные и записать на входio.Writer
.
Для достижения сжатых потоков данных кажется, что вам нужно использоватьio.Writer
интерфейс, но этот ввод и выводio.Writer
, что может быть сложно, потому что мы читаем файл и получаемio.Reader
, ввод интерфейса http такжеio.Reader
,похоже,нет интерфейса,который можно использовать напрямую,неужели нет возможности его реализовать?Нет,мы можем его сами инкапсулировать.Следующая инкапсуляцияlzo
Метод сжатия потока данных:
package lzo
import (
"bytes"
"io"
"github.com/cyberdelia/lzo"
)
type Reader struct {
r io.Reader
rb []byte
buff *bytes.Buffer
lzo *lzo.Writer
err error
}
func NewReader(r io.Reader) *Reader {
z := &Reader{
r: r,
rb: make([]byte, 256*1024),
buff: bytes.NewBuffer(make([]byte, 0, 256*1024)),
}
z.lzo, _ = lzo.NewWriterLevel(z.buff, lzo.BestSpeed)
return z
}
func (z *Reader) compress() {
if z.err != nil {
return
}
var nr, nw int
nr, z.err = z.r.Read(z.rb)
if z.err == io.EOF {
if err := z.lzo.Close(); err != nil {
z.err = err
}
}
if nr > 0 {
nw, z.err = z.lzo.Write(z.rb[:nr])
if z.err == nil && nr != nw {
z.err = io.ErrShortWrite
}
}
}
func (z *Reader) Read(p []byte) (n int, err error) {
if z.err != nil {
return 0, z.err
}
if z.buff.Len() <= 0 {
z.compress()
}
n, err = z.buff.Read(p)
if err == io.EOF {
err = nil
} else if err != nil {
z.err = err
}
return
}
func (z *Reader) Reset(r io.Reader) {
z.r = r
z.buff.Reset()
z.err = nil
z.lzo, _ = lzo.NewWriterLevel(z.buff, lzo.BestSpeed)
}
Эта библиотека будет постоянно потреблять 512 КБ памяти, что не очень много. Нам нужно создать буфер чтения и буфер сжатия, оба имеют размер 256 КБ. Фактическому сжатому буферу 256 КБ не нужно. сжатые данные будут больше, чем исходные данные.Small, учитывая, что пространство не очень велико, непосредственно выделяйте 256 КБ, чтобы избежать выделения во время выполнения.
Принцип реализации - когда http вводится изio.Reader
(На самом деле это то, что мы инкапсулировали вышеlzo
библиотека), при чтении данных эта библиотека проверяет, пуст ли буфер сжатия, если он пуст, то она будет считывать 256k данных из файла и сжимать их в буфер сжатия, а затем читать данные из буфера сжатия в httpio.Reader
, если в буфере сжатия есть данные, прочитать сжатые данные непосредственно из буфера сжатия.
Это не является потокобезопасным и выделяет фиксированный буфер размером 512 КБ, поэтому также обеспечиваетReset
метод повторного использования этого объекта, чтобы избежать повторного выделения памяти, но необходимо обеспечитьlzo
Доступ к экземплярам объекта может получить только одна горутина, это можно сделать с помощьюsync.Pool
Для обеспечения следующего кода я использую другой метод для обеспечения.
package main
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
".../pkg/aliyun_oss"
".../pkg/lzo"
)
func main() {
var oss *aliyun_oss.AliyunOSS
files := os.Args[1:]
if len(files) < 1 {
fmt.Println("请输入要上传的文件")
os.Exit(1)
}
fmt.Printf("待备份文件数量:%d\n", len(files))
startTime := time.Now()
defer func() {
fmt.Printf("共耗时:%s\n", time.Now().Sub(startTime).String())
}()
var wg sync.WaitGroup
n := 4
c := make(chan string)
// 压缩日志
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
defer wg.Done()
var compress *lzo.Reader
for file := range c {
r, err := os.Open(file)
if err != nil {
panic(err)
}
if compress == nil {
compress = lzo.NewReader(r)
} else {
compress.Reset(r)
}
name := filepath.Base(file)
err = oss.PutObject("tmp/"+name+"1.lzo", compress)
r.Close()
if err != nil {
panic(err)
}
}
}()
}
for _, file := range files {
c <- file
}
close(c)
wg.Wait()
}
Программа выделяет фиксированныйcompress
, когда файл нужно сжать, определяется, создавать его или сбрасывать для достижения эффекта мультиплексирования.
Запуск программы выводит:
待备份文件数量:336
共耗时 18m20.162441931s
Фактическое время на 28% выше, чем до оптимизации, а фактический проходiostat
Анализ команд также показал, что потребление ресурсов также значительно улучшилось.iostat -m -x 5 10000
Команда собирает данные на каждом этапе.
avg-cpu: %user %nice %system %iowait %steal %idle
15.72 0.00 6.58 74.10 0.00 3.60
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
vdb 3.80 3.40 1374.20 1.20 86484.00 18.40 125.79 121.57 87.24 87.32 1.00 0.73 100.00
avg-cpu: %user %nice %system %iowait %steal %idle
26.69 0.00 8.42 64.27 0.00 0.62
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.20 426.80 0.80 9084.80 4.00 42.51 2.69 6.29 6.30 1.00 0.63 26.92
vdb 1.80 0.00 1092.60 0.00 72306.40 0.00 132.36 122.06 108.45 108.45 0.00 0.92 100.02
пройти черезiostat
нашел толькоr_await
,w_await
полностью оптимизирован,iowait
Значительное улучшение, более короткое время выполнения, более эффективное и меньшее время для воздействия на ввод-вывод.
Проблемы, возникающие при оптимизации
первый, кто нашелlzo
Библиотека алгоритмов для тестирования, чтобы убедиться, что сжатие и распаковка не вызывают проблем, иlzop
Совместимость с командами.
В этот период было установлено, что соотношение данных при использовании сжатыхlzop
Сжатых данных намного больше.Прочитав реализацию исходного кода, я не обнаружил никаких проблем.Я попытался настроить размер буфера и обнаружил, что размер сгенерированного сжатого файла значительно улучшился.
Это открытие меня очень смутило.Какого размера соответствующий буфер?Я могу только посмотреть на него.lzop
Я обнаружил, что размер сжатого блока lzop по умолчанию составляет 256 КБ, а максимальный размер блока, поддерживаемый фактическим алгоритмом lzo, составляет 256 КБ, поэтому реализацияlzo
Алгоритм упаковки заключается в создании буфера размером 256 КБ, размер этого буфера равен размеру сжатого блока, рекомендуется не настраивать его при использовании.
Суммировать
После запуска программы загрузка заняла почти полминуты до примерно десяти секунд (эффективность самого языка Go также очень помогает), и нагрузка была значительно улучшена.
До оптимизации при каждом запуске бэкапа лога ЦП часто рвался в таблицу.После оптимизации ЦП увеличился на 20% при бэкапе, что может спокойно справиться с проблемой расширения бизнеса.
Тесты проводились на простаивающей машине, на самом рабочем сервере.w_await
Их будет около 20. Если вы используете твердотельный накопитель, режим полного дуплекса, чтение и запись разделены, то оптимизируйте егоw_await
Помощь бизнесу очень велика, и канал записи бизнес-лога не будет заблокирован.
Конечно, наш сервер представляет собой высокоскоростной облачный диск (механический диск), поскольку физические характеристики механического диска могут быть только полудуплексными, он либо читается, либо пишется, поэтому он оптимизирован.w_await
Это правда, что эффективность будет значительно повышена, но это все равно повлияет на написание бизнес-сервисов.
Перепечатано:
Автор этой статьи: Ци Инь (thinkeridea)
Ссылка на эту статью:blog.thinker idea.com/201906/go/from…
Заявление об авторских правах: все статьи в этом блоге используются, если не указано иное.Соглашение CC BY 4.0 CNсоглашение. Пожалуйста, укажите источник!