Profiling. Melacak keadaan lingkungan pertempuran menggunakan Redis, ClickHouse, dan Grafana



kira-kira. latensi / waktu.



Mungkin semua orang menghadapi tugas membuat kode profil dalam produksi. Xhprof Facebook melakukan pekerjaan ini dengan baik. Profil Anda, misalnya, 1/1000 permintaan dan lihat gambarnya saat ini. Setelah setiap rilis, produk akan berjalan dan mengatakan "itu lebih baik dan lebih cepat sebelum rilis." Anda tidak memiliki data historis dan Anda tidak dapat membuktikan apa pun. Bagaimana jika Anda bisa?



Belum lama berselang, kami menulis ulang bagian kode yang bermasalah dan mengharapkan peningkatan kinerja yang kuat. Kami menulis pengujian unit, melakukan pengujian beban, tetapi bagaimana kode berperilaku di bawah pemuatan langsung? Bagaimanapun, kami tahu bahwa pengujian beban tidak selalu menampilkan data nyata, dan setelah penerapan, Anda perlu segera mendapatkan umpan balik dari kode Anda. Jika Anda mengumpulkan data, maka setelah rilis, Anda hanya perlu 10-15 menit untuk memahami situasi di lingkungan pertempuran.





kira-kira. latensi / waktu. (1) terapkan, (2) kembalikan



Tumpukan



Untuk tugas kami, kami mengambil database ClickHouse kolom (disingkat kx). Kecepatan, skalabilitas linier, kompresi data, dan tidak ada kebuntuan adalah alasan utama pilihan ini. Sekarang ini adalah salah satu basis utama dalam proyek tersebut.



Di versi pertama, kami menulis pesan ke antrian, dan sudah oleh konsumen kami menulisnya ke ClickHouse. Penundaan mencapai 3-4 jam (ya, ClickHouse lambat untuk memasukkan satu per satucatatan). Waktu berlalu dan itu perlu untuk mengubah sesuatu. Tidak ada gunanya menanggapi pemberitahuan dengan penundaan seperti itu. Kemudian kami menulis perintah mahkota yang memilih jumlah pesan yang diperlukan dari antrian dan mengirim batch ke database, lalu menandainya diproses dalam antrian. Beberapa bulan pertama semuanya baik-baik saja, sampai masalah dimulai di sini. Ada terlalu banyak acara, data duplikat mulai muncul di database, antrian tidak digunakan untuk tujuan yang dimaksudkan (mereka menjadi database), dan perintah mahkota berhenti menangani perekaman di ClickHouse. Selama waktu ini, beberapa lusin tabel lagi ditambahkan ke proyek, yang harus ditulis dalam batch dalam kx. Kecepatan pemrosesan menurun. Solusinya sesederhana dan secepat mungkin. Ini mendorong kami untuk menulis kode dengan daftar di redis. Idenya adalah ini: kami menulis pesan di akhir daftar,Dengan perintah mahkota, kami membentuk paket dan mengirimkannya ke antrian. Kemudian konsumen mengurai antrian dan menulis banyak pesan ke dalam kx.



Kami memiliki : ClickHouse, Redis, dan antrian (apa saja - rabbitmq, kafka, beanstalkd ...)



Redis dan daftar



Sampai waktu tertentu, Redis digunakan sebagai cache, tapi itu berubah. Basis memiliki fungsi yang sangat besar, dan untuk tugas kita hanya diperlukan 3 perintah: rpush , lrange dan ltrim .



Kami akan menggunakan perintah rpush untuk menulis data ke akhir daftar. Pada perintah crown, baca data menggunakan lrange dan kirim ke antrian, jika kita berhasil mengirim ke antrian, maka kita perlu menghapus data yang dipilih menggunakan ltrim.



Dari teori ke praktek. Mari buat daftar sederhana.







Kami memiliki daftar tiga pesan, mari tambahkan sedikit lagi ...







Pesan baru ditambahkan ke akhir daftar. Menggunakan perintah lrange, pilih batch (biarkan = 5 pesan).







Selanjutnya, kami mengirim paket ke antrian. Sekarang Anda perlu menghapus bundel ini dari Redis agar tidak mengirimnya lagi.







Ada algoritma, mari kita turun ke implementasi.



Penerapan



Mari kita mulai dengan tabel ClickHouse. Saya tidak terlalu repot dan mendefinisikan semuanya dalam tipe String .



create table profile_logs
(
    hostname   String, //  ,  
    project    String, //  
    version    String, //  
    userId     Nullable(String),
    sessionId  Nullable(String),
    requestId  String, //       
    requestIp  String, // ip 
    eventName  String, //  
    target     String, // URL
    latency    Float32, //   (latency=endTime - beginTime)
    memoryPeak Int32,
    date       Date,
    created    DateTime
)
    engine = MergeTree(date, (date, project, eventName), 8192);




Acaranya akan seperti ini:

{
  "hostname": "debian-fsn1-2",
  "project": "habr",
  "version": "7.19.1",
  "userId": null,
  "sessionId": "Vv6ahLm0ZMrpOIMCZeJKEU0CTukTGM3bz0XVrM70",
  "requestId": "9c73b19b973ca460",
  "requestIp": "46.229.168.146",
  "eventName": "app:init",
  "target": "/",
  "latency": 0.01384348869323730,
  "memoryPeak": 2097152,
  "date": "2020-07-13",
  "created": "2020-07-13 13:59:02"
}


Strukturnya ditentukan. Untuk menghitung latensi, kita membutuhkan jangka waktu. Kami menunjukkan dengan tepat menggunakan fungsi mikrotime :



$beginTime = microtime(true);
//    
$latency = microtime(true) - $beginTime;


Untuk menyederhanakan implementasinya, kita akan menggunakan framework laravel dan library laravel-entry . Tambahkan model (tabel profile_logs):



class ProfileLog extends \Bavix\Entry\Models\Entry
{

    protected $fillable = [
        'hostname',
        'project',
        'version',
        'userId',
        'sessionId',
        'requestId',
        'requestIp',
        'eventName',
        'target',
        'latency',
        'memoryPeak',
        'date',
        'created',
    ];

    protected $casts = [
        'date' => 'date:Y-m-d',
        'created' => 'datetime:Y-m-d H:i:s',
    ];

}


Mari tulis metode centang (saya membuat layanan ProfileLogService ) yang akan menulis pesan ke Redis. Kami mendapatkan waktu saat ini (beginTime kami) dan menuliskannya ke variabel $ currentTime:



$currentTime = \microtime(true);


Jika centang untuk suatu peristiwa dipanggil untuk pertama kalinya, tuliskan ke array tick dan akhiri metode:



 if (empty($this->ticks[$eventName])) {
    $this->ticks[$eventName] = $currentTime;
    return;
}


Jika centang dipanggil lagi, maka kami menulis pesan ke Redis menggunakan metode rpush:



$tickTime = $this->ticks[$eventName];
unset($this->ticks[$eventName]);
Redis::rpush('events:profile_logs', \json_encode([
    'hostname' => \gethostname(),
    'project' => 'habr',
    'version' => \app()->version(),
    'userId' => Auth::id(),
    'sessionId' => \session()->getId(),
    'requestId' => \bin2hex(\random_bytes(8)),
    'requestIp' => \request()->getClientIp(),
    'eventName' => $eventName,
    'target' => \request()->getRequestUri(),
    'latency' => $currentTime - $tickTime,
    'memoryPeak' => \memory_get_usage(true),
    'date' => $tickTime,
    'created' => $tickTime,
]));


Variabel $ this-> ticks tidak statis. Anda perlu mendaftarkan layanan sebagai singleton.



$this->app->singleton(ProfileLogService::class);


Ukuran batch ( $ batchSize ) dapat dikonfigurasi, disarankan untuk menentukan nilai kecil (misalnya, 10.000 item). Jika muncul masalah (misalnya, ClickHouse tidak tersedia), antrian akan mulai gagal, dan Anda perlu men-debug data.



Mari kita tulis perintah mahkota:



$batchSize = 10000;
$key = 'events:profile_logs'
do {
    $bulkData = Redis::lrange($key, 0, \max($batchSize - 1, 0));
    $count = \count($bulkData);
    if ($count) {
        //     json,   decode
        foreach ($bulkData as $itemKey => $itemValue) {
            $bulkData[$itemKey] = \json_decode($itemValue, true);
        }

        //       ch
        \dispatch(new BulkWriter($bulkData));
        //    redis
        Redis::ltrim($key, $count, -1);
    }
} while ($count >= $batchSize);


Anda dapat segera menulis data ke ClickHouse, tetapi masalahnya terletak pada fakta bahwa kronor berfungsi dalam mode utas tunggal. Oleh karena itu, kita akan pergi ke arah lain - dengan perintah kita akan membentuk bundel dan mengirimkannya ke antrian untuk perekaman multithread berikutnya di ClickHouse. Jumlah konsumen dapat diatur - ini akan mempercepat pengiriman pesan.



Mari kita lanjutkan ke menulis konsumen:



class BulkWriter implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $bulkData;

    public function __construct(array $bulkData)
    {
        $this->bulkData = $bulkData;
    }

    public function handle(): void
    {
            ProfileLog::insert($this->bulkData);
        }
    }
}


Jadi, pembentukan paket, pengiriman ke antrian dan konsumen dikembangkan - Anda dapat mulai membuat profil:



app(ProfileLogService::class)->tick('post::paginate');
$posts = Post::query()->paginate();
$response = view('posts', \compact('posts'));
app(ProfileLogService::class)->tick('post::paginate');
return $response;


Jika semuanya dilakukan dengan benar, maka data harus ada di Redis. Kami akan mengacaukan perintah mahkota dan mengirim paket ke antrian, dan konsumen akan memasukkannya ke dalam database.







Data di database. Anda dapat membuat grafik.



Grafana



Sekarang mari beralih ke presentasi data secara grafis, yang merupakan elemen kunci dari artikel ini. Anda perlu menginstal grafana . Mari lewati proses instalasi untuk rakitan seperti debain, Anda dapat menggunakan tautan ke dokumentasi . Biasanya, langkah penginstalan bermuara pada apt install grafana .



Di ArchLinux, penginstalannya terlihat seperti ini:



yaourt -S grafana
sudo systemctl start grafana


Layanan telah dimulai. URL: http: // localhost: 3000



Sekarang Anda perlu menginstal plugin sumber data ClickHouse :



sudo grafana-cli plugins install vertamedia-clickhouse-datasource


Jika Anda telah menginstal grafana 7+, maka ClickHouse tidak akan berfungsi. Anda perlu melakukan perubahan pada konfigurasi:



sudo vi /etc/grafana.ini


Mari temukan barisnya:



;allow_loading_unsigned_plugins =


Mari kita ganti dengan yang ini:



allow_loading_unsigned_plugins=vertamedia-clickhouse-datasource


Mari simpan dan mulai ulang layanan:



sudo systemctl restart grafana


Selesai. Sekarang kita bisa pergi ke grafana .

Login: admin / kata sandi: admin secara default.







Setelah otorisasi berhasil, klik roda gigi. Di jendela popup yang terbuka, pilih Sumber Data, tambahkan koneksi ke ClickHouse.







Kami mengisi konfigurasi kx. Klik pada tombol "Save & Test", kami mendapatkan pesan tentang koneksi yang sukses.



Sekarang mari tambahkan dasbor baru:







Tambahkan panel:







Pilih basis dan kolom yang sesuai untuk bekerja dengan tanggal:







Mari beralih ke kueri:







Kami mendapat grafik, tetapi saya ingin yang spesifik. Mari kita cetak latensi rata - rata yang membulatkan tanggal-dengan-waktu ke awal interval lima menit :







Sekarang data yang dipilih ditampilkan pada grafik, kita bisa fokus padanya. Untuk lansiran, konfigurasikan pemicu, kelompokkan menurut peristiwa, dan lainnya.







Profiler sama sekali bukan pengganti alat: xhprof (facebook) , xhprof (tideways) , liveprof dari (Badoo) . Dan hanya melengkapi mereka.



Semua kode sumber ada di github - model profiler , layanan , BulkWriteCommand , BulkWriterJob dan middleware ( 1 , 2 ).



Menginstal paket:



composer req bavix/laravel-prof


Menyiapkan koneksi (config / database.php), tambahkan clickhouse:




'bavix::clickhouse' => [
    'driver' => 'bavix::clickhouse',
    'host' => env('CH_HOST'),
    'port' => env('CH_PORT'),
    'database' => env('CH_DATABASE'),
    'username' => env('CH_USERNAME'),
    'password' => env('CH_PASSWORD'),
],


Awal pekerjaan:



use Bavix\Prof\Services\ProfileLogService;
// ...
app(ProfileLogService::class)->tick('event-name');
// 
app(ProfileLogService::class)->tick('event-name');


Untuk mengirim batch ke antrian, Anda perlu menambahkan perintah ke cron:



* * * * * php /var/www/site.com/artisan entry:bulk


Anda juga perlu menjalankan konsumen:



php artisan queue:work --sleep=3 --tries=3


Direkomendasikan untuk mengkonfigurasi supervisor . Config (5 konsumen):



[program:bulk_write]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/site.com/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=5
redirect_stderr=true
stopwaitsecs=3600


UPD:



1. ClickHouse secara native dapat menarik data dari antrian kafka . Terima kasih,sdm



All Articles