缓存小结

目录
  • 1 缓存介绍和分类

    • 1.本地缓存
  • 2 缓存使用注意事项

    • 2.1 数据更新先操作缓存还是数据库
    • 2.2 并发更新大坑
  • 3 手写LRU缓存

1 缓存介绍和分类
1.1 介绍

用户请求 -> 界面 -> 网络转发 -> 应用服务 —> 数据服务

  • 应用服务器资源是有限的,且技术变革是缓慢的,数据库每秒能接受的请求次数也是有限的
    • 目的:加速数据访问,提高减轻数据库压力,利用有限的资源来提供尽可能大的吞吐量

    • 场景:短时间内相同数据需要查询多次且数据更新不频繁

    • 特征:

      • 1.命中率;命中数/请求数
      • 2.最大元素;可以存放的最大数量,如果超过最大数量则会触发清空策略
      • 3.清空策略;
        • LRU:最近最少使用:数据实效性
        • FIFO 先进先出:高频数据
        • LFU 最少使用:优先保证热点数据的有效性
1.2 分类
  • 存储介质

    • 内存
    • 硬盘
    • 数据库
  • 与应用耦合情况

    • 1.本地缓存,存在同一个进程内,访问快,无网络开销。与应用耦合,不同节点应用无法共享
      如HashMap

    • 2 分布式缓存,与应用隔离,独立的应用,部署多点,多个不同节点应用都可以访问到。如redis memcache

本地缓存说明:
public class UseLocalCache {
        //声明一个成员/局部变量
        Map<String, Object> localCacheMap = new HashMap<String, Object>();

        public Object put(String key,Object value){
            Object o = localCacheMap.put(key,value);
            return o;
        }
        
        public Object get(String key){
            Object o = localCacheMap.get(key);
            if(o == null) {
                o = getInfoFromDB(key);
            }
            return o;
        }
        //示例数据库IO获取
        private Object getInfoFromDB(String key) {
            return new Object();
        }
    }
public class UseLocalCache {
        private static final HttpClient httpClient = ServerHolder.createClientWithPool();
        //声明一个静态变量
        private static Map<Integer, String> orgCodeMap = new HashMap<Integer, String>();

        static {
            HttpGet get = new HttpGet("http://fudata.cn/api/org/code/all");
            BaseAuthorizationUtils.generateAuthAndDateHeader(get,
                    BaseAuthorizationUtils.CLIENT_TO_REQUEST_MDC,
                    BaseAuthorizationUtils.SECRET_TO_REQUEST_MDC);
            try {
                String resultStr = httpClient.execute(get, new BasicResponseHandler());
                JSONObject resultJo = new JSONObject(resultStr);
                JSONArray dataJa = resultJo.getJSONArray("data");
                for (int i = 0; i < dataJa.length(); i++) {
                    JSONObject itemJo = dataJa.getJSONObject(i);
                    orgCodeMap.put(itemJo.getInt("code"), itemJo.getString("name"));
                }
            } catch (Exception e) {
                throw new RuntimeException("Init Org code Error!", e);
            }
        }
        

        public static String getOrgName(int orgCode) {
            String name = orgCodeMap.get(orgCode);
            if (name == null) {
                name = "未知";
            }
            return name;
        }
        
    }

本地缓存框架

  • Guava Cache

  • Ehcache

画外音:引入缓存就是为了减少数据库IO操作次数,加速数据访问。

  • 2 缓存使用注意事项
    • 2.1 先操作缓存还是先操作数据库

      • 读操作:先读缓存,缓存命中则返回。缓存不命中,尝试从数据库里读取再加载到缓存中,方便下次读取
      • 写操作:
        • 【写写并发有问题】先操作数据库,再更新缓存
        • 【写写并发有问题】先更新缓存,再操作数据库
        • 【读写并发有问题】先淘汰缓存,再操作数据库
        • 先操作数据库,再淘汰缓存
    • 2.2 并发更新的大坑

      • 场景:对接三方数据源把token放入缓存,需要每次带上token去调用接口,并设置失效时间,如果缓存失效时服务节点会请求三方token接口,再把获取到的token放入缓存中。
graph LR
serviceA-- 1 --> redis 
serviceB-- 2 -->redis
serviceA-- 3 --> 三方 
serviceB-- 4 --> 三方

(1)serviceA取旧token,访问接口,发现token过期;

(2)serviceB并发请求,取旧token,访问接口,也发现token过期;

(3)serviceA去申请新token1;

(4)serviceB并发申请新token2(此时token1会过期);

(5)serviceA把token1放入缓存,带着过期的token1去请求接口发现token1已过期,又去申请新的token3(此时token2会过期)

(6)serviceB把token2放入缓存,带着过期的token2去请求接口发现token2已过期,又去申请新的token4(此时token3会过期)


高并发请求导致相互失效。

  • 解决1

graph LR
serviceA-- 1 --> redis
serviceB-- 2 -->redis
tokenService – 异步定期更新 -->redis
back --> tokenService

>  线上serviceA和serviceA只从缓存读取token
> 
> 更新token异步,tokenService定期更新token,避免并发更新
> 使用tokenServiceback保证token更新高可用,tokenService挂了,back顶上
> 
- 解决2
    - redis分布式锁
    ```java
    public String get(key) {
      String value = redis.get(key);
      //代表缓存值过期
      if (value == null) { 
          //设置1min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
          if (redis.setnx(key_mutex, 1, 1 * 60) == 1) {  //代表设置成功
               value = getRemoteToken();
               redis.set(key, value, expire_secs);
               redis.del(key_mutex);
              } else { 
              //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
                      sleep(50);
                       //重试
                      get(key); 
              }
          } else {
              return value;      
          }
  }
    ```
          


- 3 手写实现LRU缓存



graph LR
NodeA-- next–>NodeB
NodeB-- pre–>NodeA
NodeB-- next–>NodeC
NodeC-- pre–>NodeB



```java
package demo.lru;

import java.util.HashMap;
import java.util.Map;

public class LinkNodeLru {
    private Node head;
    private Node tail;
    Map<Integer, Node> map;
    private int capacity;

    LinkNodeLru(int capacity) {
        this.capacity = capacity;
        //定义头结点,不存值,作用方便结点的插入和删除
        //双链表,head始终指向头结点,tail始终指向最后一个结点
        this.tail = this.head = new Node(-2, -2);
        //map,键是结点的属性key,值是指向结点的指针
        //map作用保证了O(1)时间内实现get与put操作
        this.map = new HashMap<>();
    }

    private int get(int key) {
        Node node = map.get(key);
        if (node == null) {
            return -1;
        }
        //1.当前node节点引用变更
        node.pre.next = node.next;
        if (node.next == null) {
            this.tail = tail.pre;
        } else {
            node.next.pre = node.pre;
        }
        //2.将node放置在head节点之后(访问节点前移)
        node.next = head.next;
        node.pre = head;
        if (node.next == null) {
            this.tail = node;
        } else {
            node.next.pre = node;
        }
        head.next = node;
        return node.val;
    }

    private Node put(int key, int val) {
        Node node = map.get(key);
        if (node != null) {
            //1.改变当前node的值
            node.val = val;
            //2.当前node节点引用变更
            node.pre.next = node.next;
            if (node.next == null) {
                this.tail = tail.pre;
            } else {
                node.next.pre = node.pre;
            }
            //3.将node放置在head节点之后(访问节点前移)
            node.next = head.next;
            node.pre = head;
            if (node.next == null) {
                this.tail = node;
            } else {
                node.next.pre = node;
            }
            head.next = node;
        } else {
            node = new Node(key, val);
            if (map.size() == 0) {
                this.tail = node;
                node.pre = head;
                node.next = null;
                head.next = node;
            } else {
                node.pre = head;
                node.next = head.next;
                node.next.pre = node;
                head.next = node;
            }
            if (map.size() >= capacity) {
                map.remove(tail.key);
                this.tail = tail.pre;
                tail.next = null;
            }
        }
        return this.map.put(key, node);
    }


    class Node {
        private int key;
        private int val;
        private Node pre;
        private Node next;

        Node(int key, int val) {
            //定义双链表的结点
            this.key = key;
            this.val = val;
            this.pre = this.next = null;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "key=" + key +
                    ", val=" + val +
                    '}';
        }
    }


    public static void main(String[] args) {
        LinkNodeLru linkNodeLru = new LinkNodeLru(1);
        linkNodeLru.put(1, 2);
        linkNodeLru.put(2, 3);
        System.out.println(linkNodeLru.map.size());
        System.out.println(linkNodeLru.map);
        //1
        //{2=Node{key=2, val=3}}
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
hibernate 3中的缓存小结 2.2. 一级缓存的管理: 当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的list()、iterate()或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象。 2.3. 二级缓存的管理: 2.3.1. Hibernate的二级缓存策略的一般过程如下: 1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。 2) 把获得的所有数据对象根据ID放入到第二级缓存中。 3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。 4) 删除、更新、增加数据的时候,同时更新缓存。   Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。 2.3.2. 什么样的数据适合存放到第二级缓存中? 1 很少被修改的数据 2 不是很重要的数据,允许出现偶尔并发的数据 3 不会被并发访问的数据 4 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。 2.3.3. 不适合存放到第二级缓存的数据? 1 经常被修改的数据 2 财务数据,绝对不允许出现并发 3 与其他应用共享的数据。 2.3.4. 常用的缓存插件 Hibernater 的二级缓存是一个插件,下面是几种常用的缓存插件: l EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。 l OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。 l SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。 l JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。 2.3.5. 配置二级缓存的主要步骤: 1) 选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。 2) 选择合适的缓存插件,然后编辑该插件的配置文件。 2.4. 使用EhCache配置二级缓存: 2.4.1. 配置准备: 1) 把ehcache-1.2.3.jar加入到当前应用的classpath中。 2) 在hibernate.cfg.xml文件中加入EhCache缓存插件的提供类。 <!--配置缓存插件 --> <property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property> 3) 挎贝ehcache.xml文件到类路径(项目工程的src目录下),这个文件在Hibernate安装目录的etc下。 2.4.2. 配置步骤: Hibernate允许在类和集合的粒度上设置第二级缓存。在映射文件中,<class>和<set>元素都有一个<cache>子元素,这个子元素用来配置二级缓存。 示例:以category(产品类别)和product(产品)的映射为例: 1) 修改要配置缓存的那个持久化类的对象关系映射文件: Category.hbm.xml <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="org.qiujy.domain.cachedemo.Category" table="categories"> <!— 配置缓存,必须紧跟在class元素后面 对缓存中的Category对象采用读写型的并发访问策略 --> <cache usage="read-write"/> <id name="id" type="java.lang.Long"> <column name="id" /> <generator class="native" /> </id> <!-- 配置版本号,必须紧跟在id元素后面 --> <version name="version" column="version" type="java.lang.Long" /> <property name="name" type="java.lang.String"> <column name="name" length="32" not-null="true"/> </property> <property name="description" type="java.lang.String"> <column name="description" length="255"/> </property> <set name="products" table="products" cascade="all" inverse="true"> <!-- Hibernate只会缓存对象的简单属性的值, 要缓存集合属性,必须在集合元素中也加入<cache>子元素 而Hibernate仅仅是把与当前持久对象关联的对象的OID存放到缓存中。 如果希望把整个关联的对象的所有数据都存入缓存, 则要在相应关联的对象的映射文件中配置<cache>元素 --> <cache usage="read-write"/> <key column="categoryId" not-null="true"/> <one-to-many class="org.qiujy.domain.cachedemo.Product"/> </set> </class> </hibernate-mapping>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值