Apa yang harus saya lakukan ketika penguncian optimis tidak berfungsi?


11

Saya memiliki skenario berikut ini:

  1. Seorang pengguna membuat permintaan GET ke /projects/1dan menerima ETag .
  2. Pengguna membuat permintaan PUT/projects/1 dengan ETag dari langkah # 1.
  3. Pengguna membuat permintaan PUT lain untuk /projects/1menggunakan ETag dari langkah # 1.

Biasanya, permintaan PUT kedua akan menerima respons 412, karena ETag sekarang basi - permintaan PUT pertama memodifikasi sumber daya, sehingga ETag tidak cocok lagi.

Tetapi bagaimana jika dua permintaan PUT dikirim pada waktu yang sama (atau tepatnya satu demi satu)? Permintaan PUT pertama tidak punya waktu untuk memproses dan memperbarui sumber daya sebelum PUT # 2 tiba, yang menyebabkan PUT # 2 menimpa PUT # 1. Inti dari penguncian optimis adalah agar hal itu tidak terjadi ...


3
Atomisasi operasi Anda dalam transaksi tingkat bisnis, seperti yang dijelaskan Esben di bawah ini.
Robert Harvey

Apa yang akan terjadi jika saya melakukan atomisasi operasi saya menggunakan transaksi? PUT # 2 tidak akan diproses sampai PUT # 1 diproses sepenuhnya?
maximedupre

7
Menjadi pesimis?
jpmc26

nah inilah gunanya mengunci.
Fattie

Benar, tentu saja Put # 2 tidak boleh diproses - mereka seharusnya unik.
Fattie

Jawaban:


21

Mekanisme ETag hanya menetapkan protokol komunikasi untuk penguncian optimis. Merupakan tanggung jawab layanan aplikasi untuk menerapkan mekanisme untuk mendeteksi pembaruan bersamaan untuk menegakkan kunci optimis.

Dalam aplikasi tipikal yang menggunakan database, Anda biasanya akan melakukan ini dengan membuka transaksi saat memproses permintaan PUT. Anda biasanya membaca keadaan database yang ada di dalam transaksi itu (untuk mendapatkan kunci baca), memeriksa validitas Etag Anda, dan menimpa data (dengan cara yang akan menyebabkan konflik penulisan ketika ada transaksi bersamaan yang tidak kompatibel), lalu komit. Jika Anda mengatur transaksi dengan benar, maka salah satu komit akan gagal karena mereka berdua akan mencoba memperbarui data yang sama secara bersamaan. Anda kemudian dapat menggunakan kegagalan transaksi ini untuk mengembalikan 412 atau mencoba kembali permintaan, jika itu masuk akal untuk aplikasi tersebut.


Cara server saat ini mengimplementasikan mekanisme untuk mendeteksi pembaruan bersamaan adalah dengan membandingkan hash sumber daya. Server juga menggunakan transaksi untuk semua operasi, tapi saya tidak mendapatkan kunci apa pun, yang mungkin menjadi penyebab masalah. Namun dalam contoh Anda, bagaimana bisa ada kesalahan di salah satu komit jika transaksi menggunakan kunci? Transaksi kedua harus ditunda ketika membaca keadaan, sampai transaksi pertama selesai.
maximedupre

1
@maximedupre: jika Anda menggunakan transaksi, Anda memiliki semacam kunci, meskipun itu mungkin kunci implisit (kunci tersebut diperoleh secara otomatis ketika Anda membaca / memperbarui bidang daripada diminta secara eksplisit). Mekanisme yang saya jelaskan di atas dapat diimplementasikan menggunakan penguncian implisit saja. Sebagai pertanyaan Anda yang lain, itu tergantung pada basis data yang Anda gunakan, tetapi banyak basis data modern menggunakan MVCC (multi-concurrency control) untuk memungkinkan banyak pembaca dan penulis bekerja di bidang yang sama tanpa harus saling menghalangi.
Lie Ryan

1
Peringatan: di banyak DBMS (PostgreSQL, Oracle, SQL Server, dll.), Tingkat isolasi transaksi default adalah "read berkomitmen", di mana pendekatan Anda tidak cukup untuk mencegah kondisi balapan OP. Dalam DMBS seperti itu, Anda dapat memperbaikinya dengan memasukkan AND ETag = ...dalam klausa UPDATEpernyataan Anda WHERE, dan memeriksa jumlah baris yang diperbarui sesudahnya. (Atau dengan menggunakan tingkat isolasi transaksi yang lebih ketat, tapi saya tidak benar-benar merekomendasikan hal itu.)
ruakh

1
@ruakh: itu tergantung pada bagaimana Anda menulis permintaan Anda, ya tingkat isolasi default tidak memberikan perilaku seperti itu secara otomatis untuk semua pertanyaan, tetapi sering kali mungkin untuk menyusun transaksi Anda dengan cara yang akan cukup untuk menerapkan penguncian yang optimis. Dalam kebanyakan kasus, jika konsistensi transaksi penting dalam aplikasi, saya akan merekomendasikan pembacaan berulang sebagai tingkat isolasi standar; dalam database yang menggunakan MVCC, overhead dari readable repeat cukup minim dan menyederhanakan aplikasi secara signifikan.
Lie Ryan

1
@ruakh: kelemahan utama dari pembacaan berulang adalah Anda harus siap untuk mencoba lagi atau gagal jika ada transaksi bersamaan. Ini biasanya merupakan masalah, tetapi aplikasi yang menyediakan penguncian Optimis sebagai strategi konkurensi akan tetap memerlukan penanganan ini, jadi peta kegagalan baca yang diulang secara alami ke kegagalan penguncian yang optimis dan ini tidak akan benar-benar menambah kekurangan baru.
Lie Ryan

13

Anda harus menjalankan pasangan berikut secara atom:

  • memeriksa tag untuk validitas (yaitu yang terbaru)
  • memperbarui sumber daya (yang termasuk memperbarui tag-nya)

Yang lain menyebut ini sebagai transaksi - tetapi pada dasarnya, eksekusi atom dari kedua operasi ini adalah apa yang mencegah satu dari menimpa yang lain dengan kecelakaan waktu; tanpa ini Anda memiliki kondisi balapan, seperti yang Anda perhatikan.

Ini masih dianggap penguncian optimis, jika Anda melihat gambaran besar: bahwa sumber daya itu sendiri tidak dikunci oleh pembacaan awal (GET) oleh setiap Pengguna atau setiap Pengguna yang melihat data, apakah dengan maksud memperbarui atau tidak.

Beberapa perilaku atom diperlukan, tetapi ini terjadi dalam satu permintaan (PUT) alih-alih mencoba untuk mengunci beberapa interaksi jaringan; ini adalah penguncian optimis: objek tidak dikunci oleh GET namun masih dapat diperbarui dengan aman oleh PUT.

Ada juga banyak cara untuk mencapai eksekusi atom dari kedua operasi ini - mengunci sumber daya bukanlah satu-satunya pilihan; misalnya, utas ringan atau kunci objek mungkin cukup dan tergantung pada arsitektur aplikasi dan konteks eksekusi Anda.


4
+1 untuk mencatat bahwa atomiklah yang penting. Bergantung pada sumber daya mendasar yang diperbarui, ini dapat dilakukan tanpa transaksi atau penguncian. Misalnya, perbandingan-dan-pertukaran atom dari sumber daya dalam-memori, atau sumber-acara dari data yang ada.
Aaron M. Eshbach

@ AaronM.Eshbach, setuju, dan terima kasih telah memanggil mereka.
Erik Eidt

1

Ada di pengembang aplikasi untuk benar-benar memeriksa E-Tag dan memberikan logika itu. Bukanlah keajaiban bahwa server web melakukannya untuk Anda karena hanya tahu cara menghitung E-Tagtajuk untuk konten statis. Jadi mari kita ambil skenario Anda di atas dan uraikan bagaimana interaksi seharusnya terjadi.

GET /projects/1

Server menerima permintaan, menentukan E-Tag untuk versi catatan ini, mengembalikannya dengan konten yang sebenarnya.

200 - OK
E-Tag: "412"
Content-Type: application/json
{modified: false}

Karena klien sekarang memiliki nilai E-Tag, ia dapat menyertakannya dengan PUTpermintaan:

PUT /projects/1
If-Match: "412"
Content-Type: application/json
{modified: true}

Pada titik ini aplikasi Anda harus melakukan hal berikut:

  • Verifikasi bahwa E-Tag masih benar: "412" == "412"?
  • Jika demikian, lakukan pembaruan dan hitung E-Tag baru

Kirim respons sukses.

204 No Content
E-Tag: "543"

Jika permintaan lain datang dan mencoba melakukan yang PUTserupa dengan permintaan di atas, kali kedua kode server Anda mengevaluasinya, Anda bertanggung jawab untuk memberikan pesan kesalahan.

  • Verifikasi E-Tag masih benar: "412"! = "543"

Pada kegagalan, kirim respons kegagalan.

412 Precondition Failed

Ini adalah kode yang harus Anda tulis. E-Tag sebenarnya bisa berupa teks apa saja (dalam batas yang ditentukan dalam spesifikasi HTTP). Tidak harus berupa angka. Ini bisa menjadi nilai hash juga.


Ini bukan notasi HTTP standar yang Anda gunakan di sini. Di HTTP patuh standar, Anda hanya menggunakan ETag di header respons. Anda tidak pernah mengirim ETag di header permintaan, tetapi alih-alih menggunakan nilai ETag yang diperoleh sebelumnya di header If-Match atau If-None-Match di header permintaan.
Lie Ryan

-2

Sebagai pelengkap jawaban lainnya, saya akan memposting salah satu kutipan terbaik dalam dokumentasi ZeroMQ yang dengan setia menjelaskan masalah mendasar:

Untuk membuat program MT yang benar-benar sempurna (dan maksud saya benar-benar), kita tidak perlu mutex, kunci, atau bentuk lain dari komunikasi antar-thread kecuali pesan yang dikirim melalui soket ZeroMQ.

Dengan "program MT sempurna", maksud saya kode yang mudah ditulis dan dipahami, yang bekerja dengan pendekatan desain yang sama dalam bahasa pemrograman apa pun, dan pada sistem operasi apa pun, dan yang mengukur berbagai jumlah CPU dengan status tunggu nol dan tanpa titik hasil yang semakin berkurang.

Jika Anda telah menghabiskan bertahun-tahun mempelajari trik untuk membuat kode MT Anda berfungsi sama sekali, apalagi dengan cepat, dengan kunci dan semafor dan bagian-bagian penting, Anda akan merasa jijik ketika Anda menyadari bahwa itu semua sia-sia. Jika ada satu pelajaran yang telah kita pelajari dari 30+ tahun pemrograman bersamaan, itu adalah: jangan berbagi status. Ini seperti dua pemabuk yang mencoba berbagi bir. Tidak masalah jika mereka adalah teman baik. Cepat atau lambat, mereka akan bertengkar. Dan semakin banyak pemabuk yang Anda tambahkan ke meja, semakin banyak mereka saling bertarung memperebutkan bir. Mayoritas tragis dari aplikasi MT terlihat seperti perkelahian di bar mabuk.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.