Rumah  >  Artikel  >  pangkalan data  >  Perkongsian teknik konkurensi tinggi menggunakan Redis dan cache tempatan

Perkongsian teknik konkurensi tinggi menggunakan Redis dan cache tempatan

WBOY
WBOYke hadapan
2022-11-02 17:35:482409semak imbas

Artikel ini membawa anda pengetahuan yang berkaitan tentang Redis Ia terutamanya memperkenalkan kemahiran penggunaan cache yang diedarkan dan cache setempat, termasuk pengenalan kepada jenis cache, pelbagai senario penggunaan dan Cara menggunakannya, dan akhirnya. kes praktikal akan diberikan. Mari kita lihat.

Pembelajaran yang disyorkan: Tutorial video Redis

Seperti yang kita sedia maklum, tujuan utama caching adalah untuk mempercepatkan akses dan melegakan tekanan pangkalan data. Cache yang paling biasa digunakan ialah cache yang diedarkan, seperti redis Apabila berhadapan dengan kebanyakan senario bersamaan atau situasi di mana trafik beberapa syarikat kecil dan sederhana tidak begitu tinggi, menggunakan redis pada asasnya boleh menyelesaikan masalah. Walau bagaimanapun, dalam kes trafik tinggi, anda mungkin perlu menggunakan cache setempat, seperti LoadingCache jambu batu dan sumber terbuka ReloadableCache Kuaishou.

Senario penggunaan tiga cache

Bahagian ini akan memperkenalkan senario penggunaan dan had redis, seperti LoadingCache jambu batu dan sumber terbuka Kuaishou ReloadableCache Melalui pengenalan dalam bahagian ini, anda boleh mengetahui caranya untuk menggunakannya. Cache yang manakah harus digunakan dalam senario perniagaan dan sebabnya.

Senario penggunaan dan pengehadan Redis

Jika kita secara meluas bercakap tentang masa untuk menggunakan redis, maka ia secara semula jadi akan digunakan di tempat yang bilangan lawatan pengguna terlalu tinggi, sekali gus mempercepatkan akses dan mengurangkan tekanan pangkalan data. Jika dipecahkan, ia boleh dibahagikan kepada masalah nod tunggal dan masalah nod bukan tunggal.

Jika halaman mempunyai bilangan lawatan pengguna yang tinggi, tetapi mereka tidak mengakses sumber yang sama. Sebagai contoh, halaman butiran pengguna mempunyai bilangan lawatan yang agak tinggi, tetapi data setiap pengguna adalah berbeza Dalam kes ini, jelas bahawa hanya cache yang diedarkan boleh digunakan Jika redis digunakan, kuncinya adalah unik pengguna kunci, dan nilainya ialah maklumat pengguna.

Pecahan cache yang disebabkan oleh redis.

Tetapi satu perkara yang perlu diambil perhatian ialah masa tamat tempoh mesti ditetapkan, dan ia tidak boleh ditetapkan untuk tamat tempoh pada masa yang sama. Sebagai contoh, jika pengguna mempunyai halaman aktiviti, halaman aktiviti boleh melihat data anugerah pengguna semasa aktiviti Orang yang cuai boleh menetapkan titik masa tamat data pengguna ke penghujung aktiviti, yang akan

Isu tunggal (Panas)

Masalah nod tunggal merujuk kepada masalah konkurensi satu nod redis, kerana kunci yang sama akan jatuh pada nod yang sama kelompok redis, jadi jika kuncinya adalah Jika bilangan lawatan terlalu tinggi, maka nod redis ini akan mempunyai risiko serentak, dan kunci ini dipanggil kunci panas.

Jika semua pengguna mengakses sumber yang sama, contohnya, halaman utama Apl Xiao Ai memaparkan kandungan yang sama kepada semua pengguna (peringkat awal), dan pelayan mengembalikan json besar yang sama kepada h5, jelas ia mesti digunakan untuk cache. Pertama, kami mempertimbangkan sama ada ia boleh digunakan untuk menggunakan redis Memandangkan redis mempunyai masalah titik tunggal, jika trafik terlalu besar, maka semua permintaan pengguna akan mencapai nod redis yang sama, dan adalah perlu untuk menilai sama ada nod boleh bertahan. aliran yang begitu besar. Peraturan kami ialah jika qps satu nod mencapai seribu tahap, masalah satu titik mesti diselesaikan (walaupun redis mendakwa dapat menahan qps seratus ribu tahap), cara yang paling biasa ialah menggunakan cache tempatan . Jelas sekali, trafik pada halaman utama apl Xiaoai adalah kurang daripada 100, jadi tiada masalah menggunakan redis.

Senario penggunaan dan had LoadingCache

Untuk masalah kunci panas yang disebutkan di atas, pendekatan kami yang paling langsung ialah menggunakan cache setempat, seperti LoadingCache jambu batu yang paling anda kenali, tetapi gunakan cache tempatan Cache diperlukan untuk dapat menerima sejumlah data kotor, kerana jika anda mengemas kini halaman utama, cache tempatan tidak akan dikemas kini Ia hanya akan memuat semula cache mengikut dasar tamat tempoh tertentu, tetapi dalam kami senario ia baik sepenuhnya, kerana sebaik sahaja halaman utama ditolak di latar belakang, ia tidak akan diubah lagi. Walaupun ia berubah, tiada masalah Anda boleh menetapkan tamat tempoh tulis kepada setengah jam, dan muat semula cache selepas setengah jam Kami boleh menerima data kotor dalam tempoh yang singkat.

Pecahan cache yang disebabkan oleh LoadingCache

Walaupun cache setempat sangat berkaitan dengan mesin, walaupun tahap kod ditulis untuk tamat tempoh dalam masa setengah jam, kerana setiap mesin Masa permulaan yang berbeza membawa kepada masa pemuatan cache yang berbeza dan masa tamat tempoh yang berbeza Oleh itu, semua permintaan pada mesin tidak akan meminta pangkalan data selepas cache tamat pada masa yang sama. Walau bagaimanapun, penembusan cache juga akan berlaku untuk satu mesin Jika terdapat 10 mesin, setiap satu dengan 1,000 qps, selagi satu cache tamat tempoh, 1,000 permintaan ini boleh memukul pangkalan data pada masa yang sama. Masalah seperti ini sebenarnya lebih mudah untuk diselesaikan, tetapi ia mudah untuk diabaikan, iaitu, apabila menyediakan LoadingCache, gunakan kaedah load-miss LoadingCache dan bukannya menilai terus cache.getIfPresent()== null dan kemudian meminta. db; yang pertama akan menambah mesin maya Kunci lapisan memastikan bahawa hanya satu permintaan pergi ke pangkalan data, dengan itu menyelesaikan masalah ini dengan sempurna.

Walau bagaimanapun, jika terdapat keperluan masa nyata yang tinggi, seperti aktiviti yang kerap untuk satu tempoh masa, saya ingin memastikan halaman aktiviti boleh dikemas kini dalam hampir masa nyata, iaitu selepas operasi mengkonfigurasi maklumat aktiviti di latar belakang, ia perlu Memaparkan maklumat aktiviti yang dikonfigurasikan dalam hampir masa nyata di sebelah C, menggunakan LoadingCache pastinya tidak mencukupi.

Senario penggunaan dan pengehadan ReloadableCache

Untuk masalah masa nyata yang disebutkan di atas yang tidak dapat diselesaikan oleh LoadingCache, anda boleh mempertimbangkan untuk menggunakan ReloadableCache, iaitu rangka kerja cache tempatan yang bersumberkan Kuaishou ialah ia menyokong berbilang mesin pada masa yang sama Kemas kini cache Katakan kita mengubah suai maklumat halaman utama, dan kemudian permintaan itu memukul mesin A. Pada masa ini, ReloadableCache dimuat semula, dan kemudian ia akan menghantar pemberitahuan mesin yang mendengar nod zk yang sama akan mengemas kini cache selepas menerima pemberitahuan. Keperluan umum untuk menggunakan cache ini adalah untuk memuatkan keseluruhan jumlah data ke dalam cache setempat, jadi jika jumlah data terlalu besar, ia pasti akan memberi tekanan pada gc, dan ia tidak boleh digunakan dalam kes ini. Memandangkan halaman utama Xiao Ai mempunyai status, dan secara amnya hanya terdapat dua status dalam talian, anda boleh menggunakan ReloadableCache untuk memuatkan hanya halaman utama status dalam talian.

Ringkasan

Pada ketika ini, tiga jenis cache pada asasnya telah diperkenalkan Berikut adalah ringkasan:

  • Untuk akses data bukan hotspot, seperti. data berdimensi pengguna, terus Hanya gunakan redis;
  • Untuk akses data panas, jika trafik tidak terlalu tinggi, gunakan redis tanpa berfikir
  • Untuk data panas, jika data kotor dibenarkan untuk tempoh masa tertentu, gunakan LoadingCache sudah memadai;
  • Walaupun apa-apa jenis cache tempatan mempunyai penguncian tahap mesin maya untuk menyelesaikan masalah kerosakan, kemalangan mungkin sentiasa berlaku dalam cara yang tidak dijangka untuk berada di bahagian yang selamat, anda boleh menggunakan cache dua peringkat, iaitu, cache tempatan redis db .
Pengenalan ringkas tentang penggunaan cache

Saya tidak akan mengatakan lebih lanjut mengenai penggunaan redis di sini Saya percaya ramai orang lebih mengenali penggunaan api daripada saya

LoadingCache Use

Ini disediakan oleh jambu dalam talian, tetapi ada dua perkara yang perlu diperhatikan

Jika anda ingin menggunakan load-miss, sama ada gunakan

; Atau gunakan

semasa menggunakan binaan Pada masa ini, anda boleh menggunakan get() secara langsung. Selain itu, disyorkan untuk menggunakan load-miss dan bukannya menyemak pangkalan data apabila getIfPresent==null, yang boleh menyebabkan kerosakan cache

    Gunakan load-miss kerana ia selamat untuk thread. Apabila berbilang rangkaian panggilan diterima, hanya satu utas akan menanyakan db, dan utas lain perlu menunggu, yang bermaksud ia selamat untuk benang.
  • V get(K key, Callabled1336e9e686742fb22c1860557381905 loader)build(CacheLoader2ffe6bce707a67f84d4df961dbeb183a loader)
  • Penggunaan reloadableCache
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(1000L)
                .expireAfterAccess(Duration.ofHours(1L)) // 多久不访问就过期
                .expireAfterWrite(Duration.ofHours(1L))  // 多久这个key没修改就过期
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        // 数据装载方式,一般就是loadDB
                        return key + " world";
                    }
                });
String value = cache.get("hello"); // 返回hello world
Import kebergantungan pihak ketiga

Anda perlu membaca dokumentasi, jika tidak tidak akan berfungsi, jika anda berminat Tidak mengapa untuk menulis satu.

Masalah pecahan/penembusan/avalanche cache biasa
<dependency>
  <groupId>com.github.phantomthief</groupId>
  <artifactId>zknotify-cache</artifactId>
  <version>0.1.22</version>
</dependency>

Ketiga-tiga ini benar-benar masalah kekal dan anda benar-benar perlu mempertimbangkannya jika trafik adalah besar.

public interface ReloadableCache<T> extends Supplier<T> {

    /**
     * 获取缓存数据
     */
    @Override
    T get();

    /**
     * 通知全局缓存更新
     * 注意:如果本地缓存没有初始化,本方法并不会初始化本地缓存并重新加载
     *
     * 如果需要初始化本地缓存,请先调用 {@link ReloadableCache#get()}
     */
    void reload();

    /**
     * 更新本地缓存的本地副本
     * 注意:如果本地缓存没有初始化,本方法并不会初始化并刷新本地的缓存
     *
     * 如果需要初始化本地缓存,请先调用 {@link ReloadableCache#get()}
     */
    void reloadLocal();
}

Pecahan cache

Ringkasnya, cache gagal, menyebabkan sejumlah besar permintaan mencecah pangkalan data pada masa yang sama. Banyak penyelesaian telah diberikan di atas untuk masalah pecahan cache.

Sebagai contoh, gunakan cache setempat

Cache tempatan menggunakan kaedah terlepas beban

    Gunakan perkhidmatan pihak ketiga untuk memuatkan cache
  • 1.2 dan Seperti yang saya katakan, terutamanya lihat 3. Jika perniagaan bersedia menggunakan redis tetapi tidak boleh menggunakan cache setempat, contohnya, jumlah data terlalu besar dan keperluan masa nyata agak tinggi. Kemudian apabila cache gagal, anda perlu mencari jalan untuk memastikan bahawa hanya sebilangan kecil permintaan melanda pangkalan data. Adalah wajar untuk memikirkan menggunakan kunci yang diedarkan, yang boleh dilaksanakan secara teori, tetapi sebenarnya terdapat bahaya tersembunyi. Kami percaya bahawa ramai orang menggunakan redis lua untuk melaksanakan kunci yang diedarkan kami, dan melakukan latihan penggiliran sementara itu akan menyebabkan redis menjadi bahaya tersembunyi dan menduduki terlalu banyak perniagaan sebenarnya meningkatkan kerumitan memperkenalkan kunci yang diedarkan Prinsip kami adalah untuk tidak menggunakannya jika kami boleh.
  • Jadi bolehkah kami mereka bentuk perkhidmatan RPC yang serupa dengan kunci yang diedarkan tetapi lebih dipercayai? Apabila memanggil kaedah get, perkhidmatan rpc ini memastikan bahawa kekunci yang sama dipukul ke nod yang sama, dan menggunakan disegerakkan untuk mengunci, dan kemudian melengkapkan pemuatan data. Kuaishou menyediakan rangka kerja yang dipanggil cacheSetter. Versi ringkas disediakan di bawah, dan ia mudah dilaksanakan dengan menulisnya sendiri.

Pelaksanaan perniagaan

import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;

/**
 * @Description 分布式加载缓存的rpc服务,如果部署了多台机器那么调用端最好使用id做一致性hash保证相同id的请求打到同一台机器。
 **/
public abstract class AbstractCacheSetterService implements CacheSetterService {

    private final ConcurrentMap<String, CountDownLatch> loadCache = new ConcurrentHashMap<>();

    private final Object lock = new Object();

    @Override
    public void load(Collection<String> needLoadIds) {
        if (CollectionUtils.isEmpty(needLoadIds)) {
            return;
        }
        CountDownLatch latch;
        Collection<CountDownLatch> loadingLatchList;
        synchronized (lock) {
            loadingLatchList = excludeLoadingIds(needLoadIds);

            needLoadIds = Collections.unmodifiableCollection(needLoadIds);

            latch = saveLatch(needLoadIds);
        }
        System.out.println("needLoadIds:" + needLoadIds);
        try {
            if (CollectionUtils.isNotEmpty(needLoadIds)) {
                loadCache(needLoadIds);
            }
        } finally {
            release(needLoadIds, latch);
            block(loadingLatchList);
        }

    }

    /**
     * 加锁
     * @param loadingLatchList 需要加锁的id对应的CountDownLatch
     */
    protected void block(Collection<CountDownLatch> loadingLatchList) {
        if (CollectionUtils.isEmpty(loadingLatchList)) {
            return;
        }
        System.out.println("block:" + loadingLatchList);
        loadingLatchList.forEach(l -> {
            try {
                l.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 释放锁
     * @param needLoadIds 需要释放锁的id集合
     * @param latch 通过该CountDownLatch来释放锁
     */
    private void release(Collection<String> needLoadIds, CountDownLatch latch) {
        if (CollectionUtils.isEmpty(needLoadIds)) {
            return;
        }
        synchronized (lock) {
            needLoadIds.forEach(id -> loadCache.remove(id));
        }
        if (latch != null) {
            latch.countDown();
        }
    }

    /**
     * 加载缓存,比如根据id从db查询数据,然后设置到redis中
     * @param needLoadIds 加载缓存的id集合
     */
    protected abstract void loadCache(Collection<String> needLoadIds);

    /**
     * 对需要加载缓存的id绑定CountDownLatch,后续相同的id请求来了从map中找到CountDownLatch,并且await,直到该线程加载完了缓存
     * @param needLoadIds 能够正在去加载缓存的id集合
     * @return 公用的CountDownLatch
     */
    protected CountDownLatch saveLatch(Collection<String> needLoadIds) {
        if (CollectionUtils.isEmpty(needLoadIds)) {
            return null;
        }
        CountDownLatch latch = new CountDownLatch(1);
        needLoadIds.forEach(loadId -> loadCache.put(loadId, latch));
        System.out.println("loadCache:" + loadCache);
        return latch;
    }

    /**
     * 哪些id正在加载数据,此时持有相同id的线程需要等待
     * @param ids 需要加载缓存的id集合
     * @return 正在加载的id所对应的CountDownLatch集合
     */
    private Collection<CountDownLatch> excludeLoadingIds(Collection<String> ids) {
        List<CountDownLatch> loadingLatchList = Lists.newArrayList();
        Iterator<String> iterator = ids.iterator();
        while (iterator.hasNext()) {
            String id = iterator.next();
            CountDownLatch latch = loadCache.get(id);
            if (latch != null) {
                loadingLatchList.add(latch);
                iterator.remove();
            }
        }
        System.out.println("loadingLatchList:" + loadingLatchList);
        return loadingLatchList;
    }
}
Penembusan cache

import java.util.Collection;
public class BizCacheSetterRpcService extends AbstractCacheSetterService {
    @Override
    protected void loadCache(Collection<String> needLoadIds) {
        // 读取db进行处理
   	// 设置缓存
    }
}
Ringkasnya, data yang diminta tidak wujud dalam pangkalan data, mengakibatkan dalam permintaan tidak sah Punch melalui pangkalan data.

Penyelesaian juga sangat mudah Kaedah mendapatkan data daripada db (getByKey(K key)) mesti memberikan nilai lalai.

Sebagai contoh, saya mempunyai kumpulan hadiah dengan had atas 1W Apabila pengguna menyelesaikan tugasan, saya akan menghantar wang kepadanya dan merekodkannya menggunakan redis dan log masuk ke dalam jadual baki jumlah kumpulan hadiah dalam masa nyata pada halaman tugasan Pada permulaan tugasan, adalah jelas bahawa jumlah kumpulan hadiah kekal tidak berubah Tiada rekod amaun yang dikeluarkan dalam redis dan db, yang membawa kepada keperluan untuk menyemak db setiap kali Dalam kes ini, jika data tidak dijumpai dari db, nilai harus dicache.

Cache avalanche

Ini bermakna sebilangan besar kegagalan cache telah mencecah db Sudah tentu, mereka mesti menjadi jenis cache perniagaan masalah dengan penulisan kod. Anda boleh memecahkan masa tamat ketidaksahihan cache dan jangan biarkan ia gagal secara berpusat.

Pembelajaran yang disyorkan: Tutorial video Redis

Atas ialah kandungan terperinci Perkongsian teknik konkurensi tinggi menggunakan Redis dan cache tempatan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

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