首页 > 其他分享 >缓存设计

缓存设计

时间:2024-02-27 17:37:02浏览次数:12  
标签:map 缓存 数据库 key println 设计 public

分流:到达服务之前,减少服务处理的请求数。

并发:到达服务之后,提升服务处理的请求数。

缓存设计

导流:将原本复杂的操作请求(sql 大堆),引导到简单的请求上。前人栽树后人乘凉。

缓存:空间换时间的一个做法。

redis, memcached,localcache guava,客户端缓存

user_info_xxxx : 姓名,年龄,xxx。getKey 内存操作

select * from user where id = xxx。 硬盘IO

缓存的收益

成本,收益。

读、写。

位置:介于 请求方和提供方之间。

收益:节省了响应时间。

成本:

kv

计算key的时间,查询key的时间,转换值的时间。命中率P。

所有数据的查询时间=计算key的时间+查询key的时间+转换值的时间+(1-p) * 原始查询时间

计算key的时间+查询key的时间+转换值的时间+(1-p) * 原始查询时间 <<<< 所有数据 的原始查询时间

适合:耗时特别长的查询(复杂sql),读多写少。

缓存键的设计

kv

单向函数:给定输入,很容易,很快能计算出结果,但是给你结果,很难计算出输入。

正向快速,逆向困难,输入敏感,冲突避免。

sha-256

冲突的概率 极低。

查询key的速度 取决于:物理位置 (内存,硬盘)。

值:

序列化

对象

总结:

无碰撞。高效生成。高效查询,高效转换。

上面所有:都被中间件提供的api 封装了。

实际中:前缀业务关键信息后缀。 公司统一制定规范。

user_order_xxxx:

user+order+xxxx:

user-order-xxxx:

$

缓存的更新机制

被动更新

调用方 暂存方(缓存) 数据提供方

被动:有效期到后,再次写入。

1。客户端 查数据,缓存中没有,从提供方获取,写入缓存(有一个过期时间t)。

2。在t内,所有的查询,都由缓存提供。所有的写,直接写数据库。

3。当 缓存数据 t 到点了,缓存 数据 变没有。后面的查询,回到了第1步。

适合:对数据准确性和实时性要求不高的场景。比如:商品 关注的人数。

主动更新

主动:被其他操作直接填充。

数据库:更新数据库

缓存:更新缓存,删除缓存

保持一个定量,考虑围绕它的变量,这样才不会有异常的遗漏。

更新缓存,更新数据库

数据不一致的风险比较高,所以一般不采用。

更新数据库,更新缓存

一般也不采用。

请求被阻塞,

业务要求:修改数据库,然后经过大量的计算,才能得出缓存的值。浪费了性能。如果缓存还没用,更浪费。

删除缓存,为了节省计算时间。

删除缓存,更新数据库

一般不采用,因为大概率 读比写快。

延时双删,休眠多久,系统吞吐量下降。

昨天被高德一个面试问:说,你这个延时双删有这么几步操作。如果其中某一步失败了这么办?

删除缓存

更新数据库:事务,回滚就OK。

第二次删除缓存

重试删除:当你前面的操作,无法回滚时,为了保证后续数据的一致性,

(最便宜的做法)硬着头皮往前走,重试。

借用中间件:消息队列,重发消息。

系统外订阅:canal。binlog。

二次删除key,和我们的业务代码解耦。

更新数据库,删除缓存

经常采用的方式。

cache-aside模式。

异常流程:

前提:缓存无数据。数据库有数据。

A:查询,B:更新

A查缓存,无数据,去读数据库,旧值。-----查

B更新数据库 新值

B删除缓存

A 将旧值写入缓存。

脏数据。

就是说这个方案也有问题?这次是读的速度慢了?

读比写慢 概率很低,极低。

缓存无数据。

如果非要解决,延时双删。再删除一次。

Read/Write Through

程序启动时,将数据库 的数据, 放到缓存中,不能等启动完成,再放缓存中。

Write Behind

降低了写操作的时间,提高了系统吞吐量。

双写一致性。

缓存清理机制

如何提升缓存命中率:尽可能多的缓存。所有数据都放缓存,命中率 100%。

我们需要用有限的缓存空间,发挥最大的作用。

如何判断 一个数据 在未来被访问的次数呢?

读的时间频繁:当清理一个数据的时候,发现,它一直被访问,那我就认为他 马上的未来,也会被访问。

写入时间的时间节点。

我是问代码怎么实现:当清理数据时,发现他一直被访问。

读一次,记录一次 ,时间。阈值。

读:

getKey, k =0 ttl 1min , incr

if(!getK > 1){

delete k

}

时效性清理

给缓存设置一个过期时间,到期 缓存 自动 清理。

缓存中的数据 有 一个 生存时间:ttl。过期时间。set k v ex 10 s

set cookie   过期时间。

k v 10s

定时任务轮询。delete

自动清理机制: cookie redis expire .。(本质:轮询)

数目阈值式清理机制

判断缓存中的缓存的数量 达到一定值 ,对缓存进行清理。

阈值:根据自己的业务来定。1g,1m,1024个, 800 80%。

采取什么策略去清理:

fifo: 先进先出

package com.example.cachetest;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 数据阈值式清理
 */
public class CacheThresholdTest {
    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<>();
        for (int i = 0; i < 4; i++) {
            setCache(queue,""+i);
        }

    }

    public static void setCache(Queue<String> queue,String cache){
        int size = queue.size();
        if (size >= 3){
            queue.poll();
        }
        queue.add(cache);
        System.out.println("缓存中的值如下:");
        for (String q: queue) {
            System.out.println(q);
        }
    }
}

random:随机

lru:规律:

LinkedHashMap 套。fifo,lru。

map:存 键值对。

顺序:插入顺序 fifo,访问顺序 lru。

removeEldestEntry。

package com.example.cachetest;

import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.Map;
public class TestCache {
    @Test
    public void testLinkedHashMap() {
        // 在介绍accessOrder属性的时候说accessOrder设置为false时,按照插入顺序,设置为true时,按照访问顺序
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(5, 0.75F, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                //当LinkHashMap的容量大于等于5的时候,再插入就移除旧的元素
                return this.size() >= 5;
            }
        };
        map.put("1", "bb");
        map.put("2", "dd");
        map.put("3", "ff");
        map.put("4", "hh");

        System.out.println("原始顺序:");
        print(map);
        map.get("2");
        System.out.println("2 最近访问:");
        print(map);

        map.get("3");
        System.out.println("3 最近访问:");
        print(map);


        map.put("5","oo");
        System.out.println("加元素");
        print(map);
    }
 
    void print(LinkedHashMap<String, String> source) {
        source.keySet().iterator().forEachRemaining(System.out::println);
    }
}   

实现:k v。 map 一台服务器上能用。redis。

软引用清理

用空间换时间的模块。尽量用空间,用以提高缓存 命中率p。

适时的释放空间,gc。

识别出要清理的缓存,然后清除。

gc root引用。

强:哪怕自己oom,不清理。(不用)

软:当空间不足的时候,会被回收。√。

空间不足时,进行缓存清理。软引用。

把值 放到 SoftReference 包装中。

package com.example.cachetest;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.*;

/**
 * 软引用 缓存 实验
 */
public class CacheSoftReferenceTest {
    public static void main(String[] args) throws InterruptedException {

        soft();
    }
    static void soft(){
        // 缓存
        Map<Integer, SoftRefedStudent> map = new HashMap<Integer, SoftRefedStudent>();
        ReferenceQueue<Student> queue = new ReferenceQueue<Student>();
        int i = 0;
        while (i < 10000000) {
            Student p = new Student();
            map.put(i, new SoftRefedStudent(i, p, queue));
            //p = null;
            SoftRefedStudent pollref = (SoftRefedStudent) queue.poll();
            if (pollref != null) {//找出被软引用回收的对象
                //以key为标志,从map中移除
                System.out.println("回收"+pollref.key);
                map.remove(pollref.key);

                System.out.println(i+"新一轮================================================");
                Iterator<Map.Entry<Integer, SoftRefedStudent>> iterator = map.entrySet().iterator();
                while (iterator.hasNext()){
                    Map.Entry entry = iterator.next();
                    if ((int)entry.getKey() == pollref.key){
                        System.out.println("见鬼了");
                    }
                }
                System.out.println(i+"新一轮================================================");


            }
            i++;
        }
        System.out.println("done");
    }
}


class Student{
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

class SoftRefedStudent extends SoftReference<Student> {
    public int key;
    // 第3个参数叫做ReferenceQueue,是用来存储封装的待回收Reference对象的
    /**
     * 当Student对象被回收后,SoftRefedStudent对象会被加入到queue中。
     */
    public SoftRefedStudent(int key, Student referent, ReferenceQueue<Student> q) {
        super(referent, q);
        this.key = key;
    }
}

缓存清理机制总结

时效式清理+数目阈值:防止:短期内,密集查询,导致缓存空间的急剧增大。

--自己的完整思路。

lru+软引用:保证热数据,最大限度的提高 缓存命中率,p。

不建议:仅仅使用 软引用。因为我们失去了对它的控制。

目的:提高缓存命中率,节省空间,=》提升性能。

缓存风险概述

在系统中,每增加一个环节,就多一份风险。用是不得已。

缓存穿透

缓存中没有,数据库也没有。

方案:在第一次调用的时候,数据提供方返回一个空值,将空值放到缓存中。

缓存雪崩

大量缓存突然失效,导致大量的请求,倾泻到数据库上,引起数据库压力骤增。

时效式清理:批量缓存,统一时间到期。缓存ttl=(固定时间,结合业务)+随机时间。

软引用清理:某个时间点,空间突然紧张,常用的缓存用强引用,不常用的用软引用。

缓存击穿

高频率的缓存,突然失效,大量请求倾泻到数据库上。

lru:

read write through or write behind.更新机制:无所谓。数据永远留在缓存当中。

缓存预热

read write through or write behind

预热:高频访问的,提前准备。

计价规则,提前加载到缓存中。

系统启动前:加载缓存,不让缓存统一时间过期。

电商系统:热门商品,提前加入缓存。网约车中,计价规则提前加入缓存。

热门数据,加到缓存。

缓存风险的总结

遇到风险,分析原因,解决之。

原因:更新机制,清理机制。

标签:map,缓存,数据库,key,println,设计,public
From: https://www.cnblogs.com/fylh/p/18037341

相关文章

  • 设计模式--观察者(Observer)模式
    目录概念概念观察者模式(ObserverPattern)是C++中常用的一种设计模式,它定义了对象间的一种一对多的依赖关系。在这种模式中,当一个对象(被观察者,Subject)的状态发生改变时,所有依赖于它的对象(观察者,Observer)都会收到通知并自动更新。这种模式的结构通常包括四个部分:抽象主题(Subject......
  • 【进阶篇】使用 Redis 实现分布式缓存的全过程思考(一)
    目录前言一、关于缓存二、基本数据结构三、缓存注解3.1自定义注解3.2定义切点(拦截器)3.3AOP实现3.4使用示例四、数据一致性4.1缓存更新策略4.2缓存读写过程五、高可用5.1缓存穿透5.2缓存击穿5.3缓存雪崩5.4Redis集群六、文章小结前言写在前面,让我们从3个问题开始今天的文章:......
  • 接口隔离原则(设计模式)
    定义“Clientsshouldnotbeforcedtodependuponinterfacesthattheydonotuse”个人认为接口隔离原则,和单一原则有点像。一个接口实现一个功能。 不过,你应该已经发现,接口隔离原则跟单一职责原则有点类似,不过稍微还是有点区别。单一职责原则针对的是模块、类、接口......
  • 项目开发中 Redis 缓存和数据库一致性问题及解决方案
    引入Redis缓存提高性能如果公司的项目业务处于起步阶段,流量非常小,那无论是读请求还是写请求,直接操作数据库即可,这时架构模型是这样的:但随着业务量的增长,你的项目业务请求量越来越大,这时如果每次都从数据库中读数据,那肯定会有性能问题。这个阶段通常的做法是,引入缓存来提高读性......
  • 解析Spring中的循环依赖问题:初探三级缓存
    什么是循环依赖?这个情况很简单,即A对象依赖B对象,同时B对象也依赖A对象,让我们来简单看一下。//A依赖了BclassA{publicBb;}//B依赖了AclassB{publicAa;}这种循环依赖可能会引发问题吗?在没有考虑Spring框架的情况下,循环依赖并不会带来问题,因为对象之间相互依赖......
  • 里斯替换原则 (设计模式)
    定义IfSisasubtypeofT,thenobjectsoftypeTmaybereplacedwithobjectsoftypeS,withoutbreakingtheprogram。Functionsthatusepointersofreferencestobaseclassesmustbeabletouseobjectsofderivedclasseswithoutknowingit。子类能够替......
  • 【系统设计】笔记10 GFS
    分布式系统谷歌三剑客DistributedFileSystem(GoogleFIleSystem)如何有效存储数据?Nosql底层需要一个文件系统Bigtable=Nosqldatabase怎么链接底层存储和上层数据MapReduce怎么快速处理数据 GFSC++GoogleHDFSJavaYahoo scenario用户写入一......
  • 单一职责 (设计模式)
    定义一个类或者模块只复杂完成一个职责。也就是说,不要设计大而全的类,要设计力度小,功能单一的类。一个类包含两个以上和业务不相干的功能,应该将他拆分多个功能更加单一,粒度更加细化的类。 比如一个类力既含有订单的一些操作,又包含用户的一些操作,而订单和用户是两个独立的业务领......
  • 【性能测试】Redis中的缓存雪崩、缓存击穿、缓存穿透问题详解
    一.什么是缓存雪崩当我们提到缓存系统中的问题,缓存雪崩是一个经常被讨论的话题。缓存雪崩是指在某一时刻发生大量的缓存失效,导致瞬间大量的请求直接打到了数据库,可能会导致数据库瞬间压力过大甚至宕机。尤其在高并发的系统中,这种情况会导致连锁反应,整个系统可能会崩溃。1.......
  • Vue框架设计:性能权衡的艺术
    “框架设计里到处都体现了权衡的艺术。”当我们设计一个框架的时候,框架本身的各个模块之间并不是相互独立的,而是相互关联、相互制约的。因此作为框架设计者,一定要对框架的定位和方向拥有全局的把控,这样才能做好后续的模块设计和拆分。同样,作为学习者,我们在学习框架的时候,也应......