Menangani Keadaan Perlumbaan: Contoh Praktikal

PHPz
Lepaskan: 2024-07-18 14:47:22
asal
1119 orang telah melayarinya

Dalam kerjaya anda, anda akan menghadapi masalah kucing Schrödinger, situasi yang kadangkala berkesan dan kadangkala tidak. Keadaan perlumbaan adalah salah satu daripada cabaran ini (ya, hanya satu!).

Sepanjang catatan blog ini, saya akan membentangkan contoh dunia sebenar, menunjukkan cara menghasilkan semula masalah dan membincangkan strategi untuk mengendalikan keadaan perlumbaan menggunakan pengasingan transaksi boleh bersiri dan kunci nasihat dengan PostgreSQL.

Diinspirasikan oleh "Merancang Aplikasi Intensif Data," Bab 7 - Transaksi "Tahap Pengasingan Lemah"

Repositori Github dengan Contoh Praktikal

Permohonan itu

Aplikasi ini menguruskan syif atas panggilan untuk doktor di hospital. Untuk memberi tumpuan kepada masalah keadaan perlumbaan, mari kita permudahkan senario kita. Apl kami menyelesaikan sekitar jadual tunggal ini:

CREATE TABLE shifts (
    id SERIAL PRIMARY KEY,
    doctor_name TEXT NOT NULL,
    shift_id INTEGER NOT NULL,
    on_call BOOLEAN NOT NULL DEFAULT FALSE
);
Salin selepas log masuk

Kami mempunyai peraturan perniagaan yang kritikal:

  • Setiap syif mesti sentiasa mempunyai sekurang-kurangnya seorang doktor yang bertugas.

Seperti yang anda duga, melaksanakan API naif boleh membawa kepada senario keadaan perlumbaan. Pertimbangkan situasi hipotesis ini:

Jack dan John kedua-duanya on-call di hospital semasa syif yang sama. Pada masa yang hampir sama, mereka memutuskan untuk meminta cuti. Satu berjaya, tetapi yang lain bergantung pada maklumat lapuk tentang bilangan doktor yang sedang bekerja. Akibatnya, kedua-duanya akhirnya meninggalkan syif mereka, melanggar peraturan perniagaan dan meninggalkan syif tertentu tanpa doktor yang bertugas:

John --BEGIN------doctors on call: 2-------leave on call-----COMMIT--------> (t)
          \                 \                      \             \
           \                 \                      \             \ 
Database ------------------------------------------------------------------> (t)
               /               /                      /              /
              /               /                      /              /
Jack ------BEGIN------doctors on call: 2-----leave on call----COMMIT-------> (t)

Salin selepas log masuk

Menghasilkan semula masalah

Aplikasi ini adalah API mudah yang dilaksanakan di Golang. Lihat repositori GitHub untuk mendapatkan arahan tentang cara menjalankan dan melaksanakan skrip untuk menghasilkan semula senario keadaan perlumbaan ini. Secara ringkasnya, anda perlu:

  1. Mulakan pelayan: yarn nx serve hospital-shift
  2. Jalankan ujian k6 untuk menghasilkan semula senario keadaan perlumbaan: ujian yarn nx hospital-shifts

Ujian cuba untuk membatalkan dua doktor secara serentak, mencapai titik akhir dengan pendekatan berbeza: shiftId=1 menggunakan kunci nasihat, shiftId=2 menggunakan pengasingan transaksi boleh bersiri dan shiftId=3 ialah pelaksanaan naif tanpa kawalan serentak.

Hasil k6 akan mengeluarkan metrik tersuai untuk menunjukkan shiftId yang melanggar peraturan perniagaan:

     ✓ at least one doctor on call for shiftId=1
     ✓ at least one doctor on call for shiftId=2
     ✗ at least one doctor on call for shiftId=3
      ↳  36% — ✓ 123 / ✗ 217
Salin selepas log masuk

Anda memerlukan alatan seperti Yarn, Go, K6 dan Docker, atau anda boleh menggunakan DevBox untuk persediaan kebergantungan repositori yang lebih mudah.

Menangani Keadaan Perlumbaan

Masalah berlaku apabila aplikasi kami membuat keputusan berdasarkan data lapuk. Ini boleh berlaku jika dua urus niaga berjalan hampir serentak dan kedua-duanya cuba membatalkan doktor untuk pertukaran mereka. Satu transaksi berjaya seperti yang diharapkan, tetapi satu lagi, bergantung pada maklumat lapuk, juga berjaya dengan salah. Bagaimanakah kita boleh mengelakkan tingkah laku yang tidak diingini ini? Terdapat beberapa cara untuk mencapai ini, dan saya akan meneroka dua pilihan yang disokong oleh PostgreSQL, walaupun penyelesaian yang serupa boleh ditemui dalam sistem pengurusan pangkalan data lain.

Pengasingan Transaksi Boleh Bersiri

Pengasingan Syot Kilat Boleh Bersiri secara automatik mengesan dan menghalang anomali seperti pencongan tulis yang ditunjukkan oleh aplikasi kami.

Saya tidak akan mendalami teori di sebalik pengasingan transaksi, tetapi ia adalah topik biasa dalam banyak sistem pengurusan pangkalan data yang popular. Anda boleh mencari bahan yang baik dengan mencari pengasingan syot kilat, seperti ini daripada dokumentasi rasmi PostgreSQL tentang pengasingan transaksi. Selain itu, berikut ialah kertas yang mencadangkan penyelesaian ini beberapa tahun yang lalu. Cakap murah, jadi jom tengok kod:

Mula-mula, mulakan transaksi dan tetapkan tahap pengasingan kepada Boleh Bersiri:

    // Init transaction with serializable isolation level
    tx, err := db.BeginTxx(c.Request().Context(),    &sql.TxOptions{
        Isolation: sql.LevelSerializable,
    })
Salin selepas log masuk

Kemudian, teruskan untuk melaksanakan operasi. Dalam kes kami, ia melaksanakan fungsi ini:

CREATE OR REPLACE FUNCTION update_on_call_status_with_serializable_isolation(shift_id_to_update INT, doctor_name_to_update TEXT, on_call_to_update BOOLEAN)
RETURNS VOID AS $$
DECLARE
    on_call_count INT;
BEGIN
    -- Check the current number of doctors on call for this shift
    SELECT COUNT(*) INTO on_call_count FROM shifts s WHERE s.shift_id = shift_id_to_update AND s.on_call = TRUE;

    IF on_call_to_update = FALSE AND on_call_count = 1 THEN
        RAISE EXCEPTION '[SerializableIsolation] Cannot set on_call to FALSE. At least one doctor must be on call for this shiftId: %', shift_id_to_update;
    ELSE
        UPDATE shifts s
        SET on_call = on_call_to_update
        WHERE s.shift_id = shift_id_to_update AND s.doctor_name = doctor_name_to_update;
    END IF;

END;
$$ LANGUAGE plpgsql;
Salin selepas log masuk

Apabila senario yang tidak konsisten berlaku disebabkan pelaksanaan serentak, tahap pengasingan boleh bersiri akan membolehkan satu transaksi berjaya dan akan melancarkan yang lain secara automatik dengan mesej ini, jadi anda boleh mencuba semula dengan selamat:

ERROR:  could not serialize access due to read/write dependencies among transactions
Salin selepas log masuk
  • Anda boleh mendapatkan contoh lengkap dalam fungsi updateWithSerializableIsolation.

Kunci Penasihat

Cara lain untuk memastikan peraturan perniagaan kami dikuatkuasakan ialah dengan mengunci sumber secara eksplisit untuk anjakan tertentu. Kita boleh mencapai ini menggunakan Kunci Nasihat pada peringkat transaksi. Kunci jenis ini dikawal sepenuhnya oleh aplikasi. Anda boleh mendapatkan maklumat lanjut mengenainya di sini.

Adalah penting untuk ambil perhatian bahawa kunci boleh digunakan pada kedua-dua peringkat sesi dan transaksi. Anda boleh meneroka pelbagai fungsi yang terdapat di sini. Dalam kes kami, kami akan menggunakan pg_try_advisory_xact_lock(key bigint) → boolean, yang secara automatik melepaskan kunci selepas komit atau rollback:

BEGIN;

-- Attempt to acquire advisory lock and handle failure with EXCEPTION
    IF NOT pg_try_advisory_xact_lock(shift_id_to_update) THEN
        RAISE EXCEPTION '[AdvisoryLock] Could not acquire advisory lock for shift_id: %', shift_id_to_update;
    END IF;

-- Perform necessary operations

-- Commit will automatically release the lock
COMMIT;
Salin selepas log masuk

Berikut ialah fungsi lengkap yang digunakan dalam aplikasi kami:

-- Function to Manage On Call Status with Advisory Locks, automatic release when the trx commits
CREATE OR REPLACE FUNCTION update_on_call_status_with_advisory_lock(shift_id_to_update INT, doctor_name_to_update TEXT, on_call_to_update BOOLEAN)
RETURNS VOID AS $$
DECLARE
    on_call_count INT;
BEGIN
    -- Attempt to acquire advisory lock and handle failure with NOTICE
    IF NOT pg_try_advisory_xact_lock(shift_id_to_update) THEN
        RAISE EXCEPTION '[AdvisoryLock] Could not acquire advisory lock for shift_id: %', shift_id_to_update;
    END IF;

    -- Check the current number of doctors on call for this shift
    SELECT COUNT(*) INTO on_call_count FROM shifts s WHERE s.shift_id = shift_id_to_update AND s.on_call = TRUE;

    IF on_call_to_update = FALSE AND on_call_count = 1 THEN
        RAISE EXCEPTION '[AdvisoryLock] Cannot set on_call to FALSE. At least one doctor must be on call for this shiftId: %', shift_id_to_update;
    ELSE
        UPDATE shifts s
        SET on_call = on_call_to_update
        WHERE s.shift_id = shift_id_to_update AND s.doctor_name = doctor_name_to_update;
    END IF;
END;
$$ LANGUAGE plpgsql;
Salin selepas log masuk
  • You can find the complete example in the function updateWithAdvisoryLock.

Conclusion

Dealing with race conditions, like the write skew scenario we talked about, can be pretty tricky. There's a ton of research and different ways to solve these problems, so definitely check out some papers and articles if you're curious.

These issues can pop up in real-life situations, like when multiple people try to book the same seat at an event or buy the same spot in a theater. They tend to appear randomly and can be hard to figure out, especially if it's your first time dealing with them.

When you run into race conditions, it's important to look into what solution works best for your specific situation. I might do a benchmark in the future to compare different approaches and give you more insights.

I hope this post has been helpful. Remember, there are tools out there to help with these problems, and you're not alone in facing them!


Dealing with Race Conditions: A Practical Example iamseki / dev-to

Implementations of dev.to blog posts

Atas ialah kandungan terperinci Menangani Keadaan Perlumbaan: Contoh Praktikal. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan