一、为什么要使用缓存

缓存的读写性能是介于内存与硬盘/数据库之间的,适用于查询频繁,更新较少或者不更新的数据。

内存、分布式缓存、数据库三者的读性能大概是这样的。

1、直接读内存数据,耗时0ms。如:ecache缓存

2、读redis里面的数据,耗时5ms。如:redis、memecache缓存

3、读数据库数据,耗时35ms。如:mysql等

比较下来,

  • 从内存读写数据,性能最好,但是有一些缺点:占用系统内存,可扩展性差,维护里面的缓存数据比较麻烦。但是也有极端应用场景,就是对读性能要求极致的,本人就遇到了,人脸比对数据,数据量庞大,事先还得预热,如果是集群,还得借助mq做广播。
  • 分布式缓存,应用广泛,是提高系统tps/qps、并发性能最直接有效的手段,但是需要处理好数据库与缓存的一致性问题。

二、缓存一致性解决方案(针对分布式缓存)

1、预热型

缓存预热,即在服务上线之前就把数据加载进缓存的方式,这种方式适用于数据量较大,缓存时需要耗费一定时间的,平时数据不需要更新的。

为了把缓存预热与缓存的使用解耦,独立出一个专门进行缓存预热的“缓存预热系统”,把共用一个分布式缓存的服务,如:订单系统,库存系统共用一个redis,把这两个系统需要的缓存预先加载到redis中。

流程,如图:




redis 高并发 push redis 高并发读写 查询慢_缓存


2、实时型(比较灵活)

实时查询缓存,如果不存在,则查询数据库并返回,同时加载数据进入缓存。适用于单体小数据量数据缓存,平时也有可能需要对数据库进行更新操作的。

策略:

1) 更新: 先把数据存到数据库中,成功后,再让缓存失效。

更新流程,如图:


redis 高并发 push redis 高并发读写 查询慢_缓存_02


2)命中:应用程序从缓存中取数据,取到后返回。

3)失效:应用程序先从缓存取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。(这个是重点)

查询缓存,2)、3)流程,如图:


redis 高并发 push redis 高并发读写 查询慢_redis 缓存list_03


实时型缓存一致性,源码解读:

1)带进程内同步锁的redis缓存读写操作框架类,抽象基类,如下:

package com.study.service;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantReadWriteLock;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Jedis;/** * 带进程内同步锁的redis缓存读写操作框架类
 * 引用该类防止大量线程同时请求时造成同时回源,增加系统负担。
 * 基本流程:
 * 1) 读取 -(未找到)-> 回源 -> 写入 -> 返回
 * 2) 读取 -(找到)-> 返回 * * @param  输出类型 * @param  读写的输入参数类型 */public abstract class SyncCacheWorker { protected static final int DEFAULT_CACHE_TIME = 24 * 60 * 60; private static final int LOCK_EXPIRE_TIME = 10; private static final long SLEEP_TIME = 100; private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); @Autowired protected RedisCacheUtil redisCacheUtil;//这里redis版本不一样,大家可以自行决定 /** * 读Redis缓存的实现 * * @param z 输入参数 * @return */ protected T read(Z z) { throw new IllegalArgumentException("read 方法未定义"); } /** * 批量读redis缓存的实现 * * @param z 输入参数 * @return 输出读取结果 */ protected List batchRead(List z) { throw new IllegalArgumentException("batchRead 方法未定义"); } /** * 回源并写Redis缓存的实现 * * @param z 输入参数 * @return 输出回源结果 */ protected T write(Z z) { throw new IllegalArgumentException("write 方法未定义"); } /** * 批量回源并写Redis缓存的实现 * * @param z 输入参数 * @return 输出回源结果 */ protected List batchWrite(List z, List pos) { throw new IllegalArgumentException("batchWrite 方法未定义"); } /** * 单条回源后,立即执行方法入口 * * @param t 输入对象 * @return 输出对象 */ protected T beforeReturn(T t) { return t; } /** * 批量回源后,立即执行方法入口 * * @param list 输入对象 * @return 输出对象 */ protected List beforeBatchReturn(List list) { return list; } /** * 执行批量查询的方法 * * @param list 输入参数 * @return 结果 */ @SuppressWarnings("all") public List batchFind(List list) { if (list == null || list.isEmpty()) { return new ArrayList(); } boolean lockedFail = false; do { if (lockedFail) { lockedFail = false; try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException ex) { return Collections.emptyList(); } } r.lock(); try { List tList = batchRead(list); List posList = nullPos(tList); if (!posList.isEmpty()) { r.unlock(); w.lock(); try { tList = batchRead(list); posList = nullPos(tList); if (!posList.isEmpty()) { if (acquireLock()) { try { List tmpList = batchWrite(list, posList); for (int i = 0; i < posList.size(); i++) { int pos = posList.get(i); tList.set(pos, tmpList.get(i)); } } finally { releaseLock(); } } else { lockedFail = true; continue; } } } finally { r.lock(); w.unlock(); } } return beforeBatchReturn(tList); } finally { r.unlock(); } } while (true); } /** * 执行单条查询的方法 * * @param z 输入参数 * @return 结果 */ @SuppressWarnings("all") public T find(Z z) { if (z == null) { return null; } boolean lockedFail = false; do { if (lockedFail) { lockedFail = false; try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException ex) { return null; } } r.lock(); try { T t = read(z); if (t == null) { r.unlock(); w.lock(); try { t = read(z); if (t == null) { if (acquireLock()) { try { t = write(z); } finally { releaseLock(); } } else { lockedFail = true; continue; } } } finally { r.lock(); w.unlock(); } } return beforeReturn(t); } finally { r.unlock(); } } while (true); } /** * 计算列表中为null的各元素位置 * * @param t 列表 * @return null元素位置列表 */ private List nullPos(List t) { List posList = new ArrayList(); for (int i = 0; i < t.size(); i++) { if (t.get(i) == null) { posList.add(i); } } return posList; } private Long releaseLock() { return redisCacheUtil.getJedisClient().execute(new JedisAction() { @Override public Long doAction(Jedis jedis) { return jedis.del(getLockKey()); } }); } private boolean acquireLock() { return redisCacheUtil.getJedisClient().execute(new JedisAction() { @Override public Boolean doAction(Jedis jedis) { String ret = jedis.set(getLockKey(), "lock