Rumah  >  Artikel  >  pangkalan data  >  Fahami operasi atom redis dalam sepuluh minit

Fahami operasi atom redis dalam sepuluh minit

WBOY
WBOYke hadapan
2022-02-17 18:27:194304semak imbas

Artikel ini membawakan anda pengetahuan yang berkaitan tentang operasi atom redis Untuk memastikan ketepatan akses serentak, Redis menyediakan dua kaedah, iaitu mengunci dan operasi atom.

Fahami operasi atom redis dalam sepuluh minit

operasi atom redis

Apabila kami menggunakan Redis, kami pasti akan menghadapi masalah akses serentak, contohnya, jika berbilang pengguna membuat pesanan pada masa yang sama , inventori produk yang dicache dalam Redis akan dikemas kini secara serentak. Sebaik sahaja terdapat operasi tulis serentak, data akan diubah suai Jika kami tidak mengawal permintaan tulis serentak, data mungkin diperbetulkan, menjejaskan penggunaan biasa perniagaan (contohnya, ralat data inventori membawa kepada penempatan pesanan yang tidak normal).

Untuk memastikan ketepatan akses serentak, Redis menyediakan dua kaedah, iaitu mengunci dan operasi atom.

Mengunci adalah kaedah biasa Sebelum membaca data, pelanggan perlu mendapatkan kunci terlebih dahulu, jika tidak, operasi tidak boleh dilakukan. Apabila pelanggan memperoleh kunci, ia akan memegang kunci sehingga pelanggan melengkapkan kemas kini data dan kemudian melepaskan kunci.
Nampaknya penyelesaian yang baik, tetapi sebenarnya terdapat dua masalah di sini: satu ialah jika terdapat banyak operasi mengunci, ia akan mengurangkan prestasi akses serentak sistem; anda perlu menggunakan kunci yang diedarkan, dan pelaksanaan kunci yang diedarkan adalah rumit dan memerlukan sistem storan tambahan untuk menyediakan operasi mengunci dan membuka kunci saya akan memperkenalkannya kepada anda dalam pelajaran seterusnya.

Operasi atom ialah satu lagi cara untuk menyediakan kawalan akses serentak. Operasi atom merujuk kepada operasi yang mengekalkan atomicity semasa pelaksanaan, dan tidak memerlukan kunci tambahan apabila melaksanakan operasi atom, dengan itu mencapai operasi tanpa kunci. Dengan cara ini, kawalan konkurensi boleh dijamin dan kesan ke atas prestasi konkurensi sistem dapat dikurangkan.

Apakah yang perlu dikawal semasa akses serentak?
Apa yang kami panggil kawalan akses serentak merujuk kepada mengawal proses berbilang pelanggan yang mengakses dan mengendalikan data yang sama untuk memastikan bahawa operasi yang dihantar oleh mana-mana pelanggan adalah saling eksklusif apabila dilaksanakan pada contoh Redis. Sebagai contoh, semasa operasi capaian klien A sedang dilaksanakan, operasi klien B tidak boleh dilaksanakan dan mesti menunggu sehingga operasi A selesai sebelum ia boleh dilaksanakan.

Operasi yang sepadan dengan kawalan akses serentak adalah terutamanya operasi pengubahsuaian data. Apabila pelanggan perlu mengubah suai data, proses asas dibahagikan kepada dua langkah:

  1. Klien membaca data secara setempat dan mengubah suainya secara setempat
  2. Selepas pelanggan mengubah suai data , dan kemudian tulis semula kepada Redis.

Kami memanggil proses ini operasi "Baca-Ubahsuai-Tulis" (Baca-Ubahsuai-Tulis, dirujuk sebagai operasi RMW). Apabila berbilang pelanggan melakukan operasi RMW pada data yang sama, kami perlu membenarkan kod yang terlibat dalam operasi RMW dilaksanakan secara atom. Kod operasi RMW yang mengakses data yang sama dipanggil kod bahagian kritikal.

Walau bagaimanapun, apabila berbilang pelanggan melaksanakan kod bahagian kritikal secara serentak, akan terdapat beberapa masalah yang berpotensi Seterusnya, saya akan menggunakan contoh berbilang pelanggan mengemas kini inventori produk untuk menerangkan.

Mari kita lihat kod bahagian kritikal dahulu. Andaikan bahawa pelanggan ingin memotong 1 daripada inventori produk Kod pseudo adalah seperti berikut:

current = GET(id)
current--
SET(id, current)

Seperti yang anda lihat, pelanggan akan membaca inventori semasa produk daripada Redis terlebih dahulu. id produk. Nilai semasa (bersamaan dengan Baca), kemudian pelanggan mengurangkan nilai inventori sebanyak 1 (bersamaan dengan Ubah Suai), dan kemudian menulis nilai inventori kembali kepada Redis (bersamaan dengan Tulis). Apabila berbilang pelanggan melaksanakan kod ini, ini ialah kod bahagian kritikal.

Jika kami tidak mempunyai mekanisme kawalan ke atas pelaksanaan kod bahagian kritikal, ralat kemas kini data akan berlaku. Dalam contoh tadi, dengan mengandaikan terdapat dua pelanggan A dan B, melaksanakan kod bahagian kritikal pada masa yang sama, ralat akan berlaku Anda boleh melihat gambar di bawah.
Fahami operasi atom redis dalam sepuluh minit

Seperti yang anda lihat, pelanggan A membaca nilai inventori 10 dan menolak 1 pada t1 Pada t2, pelanggan A belum lagi menulis nilai inventori yang ditolak 9. Kembali ke Redis, dan pada masa ini, pelanggan B membaca nilai inventori 10, yang juga ditolak dengan 1, dan nilai inventori yang direkodkan oleh B juga ialah 9. Apabila t3, A menulis semula nilai inventori 9 kepada Redis, dan apabila t4, B juga menulis semula nilai inventori 9.

Jika diproses mengikut logik yang betul, pelanggan A dan B masing-masing membuat potongan daripada nilai inventori, dan nilai inventori hendaklah 8. Oleh itu, nilai inventori di sini jelas dikemas kini secara tidak betul.

Sebab fenomena ini ialah pelanggan dalam kod bahagian kritikal melibatkan tiga operasi: membaca data, mengemas kini data dan menulis balik data, dan ketiga-tiga operasi ini tidak saling eksklusif semasa pelaksanaan, berbilang pelanggan ubah suai berdasarkan nilai awal yang sama, bukannya ubah suai berdasarkan nilai yang diubah suai oleh pelanggan sebelumnya.

Untuk memastikan ketepatan pengubahsuaian data serentak, kami boleh menggunakan kunci untuk menukar operasi selari kepada operasi bersiri dan operasi bersiri adalah saling eksklusif. Selepas pelanggan memegang kunci, pelanggan lain hanya boleh menunggu sehingga kunci dilepaskan sebelum mereka boleh mengambil kunci dan membuat pengubahsuaian.

Pseudokod berikut menunjukkan penggunaan kunci untuk mengawal pelaksanaan kod bahagian kritikal, anda boleh lihat.

LOCK()
current = GET(id)
current--
SET(id, current)
UNLOCK()

虽然加锁保证了互斥性,但是加锁也会导致系统并发性能降低。

如下图所示,当客户端 A 加锁执行操作时,客户端 B、C 就需要等待。A 释放锁后,假设 B 拿到锁,那么 C 还需要继续等待,所以,t1 时段内只有 A 能访问共享数据,t2 时段内只有 B 能访问共享数据,系统的并发性能当然就下降了。
Fahami operasi atom redis dalam sepuluh minit

和加锁类似,原子操作也能实现并发控制,但是原子操作对系统并发性能的影响较小,接下来,我们就来了解下 Redis 中的原子操作。

Redis 的两种原子操作方法

为了实现并发控制要求的临界区代码互斥执行,Redis 的原子操作采用了两种方法:

  1. 把多个操作在 Redis 中实现成一个操作,也就是单命令操作;
  2. 把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本。

我们先来看下 Redis 本身的单命令操作。

Redis 是使用单线程来串行处理客户端的请求操作命令的,所以,当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的。当然,Redis 的快照生成、AOF 重写这些操作,可以使用后台线程或者是子进程执行,也就是和主线程的操作并行执行。不过,这些操作只是读取数据,不会修改数据,所以,我们并不需要对它们做并发控制。

你可能也注意到了,虽然 Redis 的单个命令操作可以原子性地执行,但是在实际应用中,数据修改时可能包含多个操作,至少包括读数据、数据增减、写回数据三个操作,这显然就不是单个命令操作了,那该怎么办呢?

别担心,Redis 提供了 INCR/DECR 命令,把这三个操作转变为一个原子操作了。INCR/DECR 命令可以对数据进行增值 / 减值操作,而且它们本身就是单个命令操作,Redis 在执行它们时,本身就具有互斥性。

比如说,在刚才的库存扣减例子中,客户端可以使用下面的代码,直接完成对商品 id 的库存值减 1 操作。即使有多个客户端执行下面的代码,也不用担心出现库存值扣减错误的问题。

DECR id

所以,如果我们执行的 RMW 操作是对数据进行增减值的话,Redis 提供的原子操作 INCR 和 DECR 可以直接帮助我们进行并发控制。

但是,如果我们要执行的操作不是简单地增减数据,而是有更加复杂的判断逻辑或者是其他操作,那么,Redis 的单命令操作已经无法保证多个操作的互斥执行了。所以,这个时候,我们需要使用第二个方法,也就是 Lua 脚本。

Redis 会把整个 Lua 脚本作为一个整体执行,在执行的过程中不会被其他命令打断,从而保证了 Lua 脚本中操作的原子性。如果我们有多个操作要执行,但是又无法用 INCR/DECR 这种命令操作来实现,就可以把这些要执行的操作编写到一个 Lua 脚本中。
然后,我们可以使用 Redis 的 EVAL 命令来执行脚本。这样一来,这些操作在执行时就具有了互斥性。

再举个例子,具体解释下 Lua 的使用。
当一个业务应用的访问用户增加时,我们有时需要限制某个客户端在一定时间范围内的访问次数,比如爆款商品的购买限流、社交网络中的每分钟点赞次数限制等。

那该怎么限制呢?我们可以把客户端 IP 作为 key,把客户端的访问次数作为 value,保存到 Redis 中。客户端每访问一次后,我们就用 INCR 增加访问次数。

不过,在这种场景下,客户端限流其实同时包含了对访问次数和时间范围的限制,例如每分钟的访问次数不能超过 20。所以,我们可以在客户端第一次访问时,给对应键值对设置过期时间,例如设置为 60s 后过期。同时,在客户端每次访问时,我们读取客户端当前的访问次数,如果次数超过阈值,就报错,限制客户端再次访问。你可以看下下面的这段代码,它实现了对客户端每分钟访问次数不超过 20 次的限制。

//获取ip对应的访问次数
current = GET(ip)
//如果超过访问次数超过20次,则报错
IF current != NULL AND current > 20 THEN
    ERROR "exceed 20 accesses per second"
ELSE
    //如果访问次数不足20次,增加一次访问计数
    value = INCR(ip)
    //如果是第一次访问,将键值对的过期时间设置为60s后
    IF value == 1 THEN
        EXPIRE(ip,60)
    END
    //执行其他操作
    DO THINGS
END

可以看到,在这个例子中,我们已经使用了 INCR 来原子性地增加计数。但是,客户端限流的逻辑不只有计数,还包括访问次数判断和过期时间设置。

对于这些操作,我们同样需要保证它们的原子性。否则,如果客户端使用多线程访问,访问次数初始值为 0,第一个线程执行了 INCR(ip) 操作后,第二个线程紧接着也执行了 INCR(ip),此时,ip 对应的访问次数就被增加到了 2,我们就无法再对这个 ip 设置过期时间了。这样就会导致,这个 ip 对应的客户端访问次数达到 20 次之后,就无法再进行访问了。即使过了 60s,也不能再继续访问,显然不符合业务要求。

所以,这个例子中的操作无法用 Redis 单个命令来实现,此时,我们就可以使用 Lua 脚本来保证并发控制。我们可以把访问次数加 1、判断访问次数是否为 1,以及设置过期时间这三个操作写入一个 Lua 脚本,如下所示:

local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then
    redis.call("expire",KEYS[1],60)
end

假设我们编写的脚本名称为 lua.script,我们接着就可以使用 Redis 客户端,带上 eval 选项,来执行该脚本。脚本所需的参数将通过以下命令中的 keys 和 args 进行传递。

redis-cli  --eval lua.script  keys , args

这样一来,访问次数加 1、判断访问次数是否为 1,以及设置过期时间这三个操作就可以原子性地执行了。即使客户端有多个线程同时执行这个脚本,Redis 也会依次串行执行脚本代码,避免了并发操作带来的数据错误。

推荐学习:《Redis视频教程》、《2022最新redis面试题大全及答案

Atas ialah kandungan terperinci Fahami operasi atom redis dalam sepuluh minit. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:csdn.net. Jika ada pelanggaran, sila hubungi admin@php.cn Padam