2 缓存概述
2.1 缓存本质
系统各级处理速度不匹配,导致利用空间换时间。缓存是提升系统性能的一个简单有效的办法。
2.2 缓存加载时机
启动全量加载
全局有效,使用简单,但导致启动慢。
懒加载
同步使用加载
- 先看缓存是否有数据,没有则从数据库读取
- 读取的数据,先放入
延迟异步加载
从缓存获取数据,不管是否为空直接返回,有如下两种策略:
- 异步,如果为空,则发起一个异步加载的线程,负责加载数据
- 解耦,异步线程负责维护缓存的数据,定期或根据条件触发更新
2.3 缓存的有效性
- 变动频、一致性要求高的数据,不适合用缓存
变化大,意味着内存缓存数据和原始数据库数据之间一直有差异;
一致性要求高,意味着只有使用原始数据,甚至加了事务,才稳妥。
缓存的有效性度量
- 读写比
对数据的写操作导致数据变动,意味着维护成本。N :1。 - 命中率
命中缓存意味着缓存数据被使用,意味着有价值。90%+
计算机科学只存在两个难题:缓存失效和命名。
- Phil Karlton
所以必须综合衡量数据一致性,性能,成本来决定是否引入缓存。
2 缓存介质
从硬件介质上来看,内存和硬盘
从技术上,可以分成内存、硬盘文件、数据库
- 内存
将缓存存储于内存中是最快的选择,无需额外的I/O开销,但是内存的缺点是没有持久化落地物理磁盘,一旦应用异常break down而重新启动,数据很难或者无法复原
- 硬盘
一般来说,很多缓存框架会结合使用内存和硬盘,在内存分配空间满了或是在异常的情况下,可以被动或主动的将内存空间数据持久化到硬盘中,达到释放空间或备份数据的目的。
- 数据库
增加缓存的策略的目的之一就是为了减少数据库的I/O压力。现在使用数据库做缓存介质是不是又回到了老问题上了?其实,数据库也有很多种类型,像那些不支持SQL,只是简单的key-value存储结构的特殊数据库(如BerkeleyDB和Redis),响应速度和吞吐量都远远高于我们常用的关系型数据库等。
3 缓存分类和应用场景
根据缓存与应用的藕合度,分为local cache(本地缓存)和remote cache(分布式缓存)
本地缓存
在业务系统应用中的缓存组件:
- 最大的优点
应用和cache是在同一个JVM进程,请求缓存非常快速,没有过多网络开销(请求 redis 服务器)等,在单体应用不需集群或集群下各节点无需互相通知的数据强一致场景下使用本地缓存较合适
- 缺点
应为缓存跟应用程序耦合,多个应用程序无法直接的共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。
分布式缓存
与应用分离的缓存组件或服务,其最大的优点是自身就是一个独立的应用,与本地应用隔离,多个应用可直接的共享缓存
3.1 本地缓存
3.1.1 编程直接实现缓存
有的场景只需简单的缓存数据的功能,无需关注更多存取、清空策略等深入特性,这时直接编程实现缓存最为简单高效。
成员变量或局部变量实现
public void UseLocalCache(){ //一个本地的缓存变量 Map<String, Object> localCacheStoreMap = new HashMap<String, Object>(); List<Object> infosList = this.getInfoList(); for(Object item:infosList){ if(localCacheStoreMap.containsKey(item)){ //缓存命中 使用缓存数据 // todo } else { // 缓存未命中 I/O获取数据,结果存入缓存 Object valueObject = this.getInfoFromDB(); localCacheStoreMap.put(valueObject.toString(), valueObject); } } } //示例 private List<Object> getInfoList(){ return new ArrayList<Object>(); } //示例数据库I/O获取 private Object getInfoFromDB(){ return new Object(); }
以局部变量map结构缓存部分业务数据,减少频繁的重复数据库I/O操作。缺点仅限于类的自身作用域内,类间无法共享缓存。
静态变量实现
最常用的单例实现静态资源缓存
public class CityUtils { private static final HttpClient httpClient = ServerHolder.createClientWithPool(); private static Map<Integer, String> cityIdNameMap = new HashMap<Integer, String>(); private static Map<Integer, String> districtIdNameMap = new HashMap<Integer, String>(); static { HttpGet get = new HttpGet("http://gis-in.sankuai.com/api/location/city/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); cityIdNameMap.put(itemJo.getInt("id"), itemJo.getString("name")); } } catch (Exception e) { throw new RuntimeException("Init City List Error!", e); } } static { HttpGet get = new HttpGet("http://gis-in.sankuai.com/api/location/district/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); districtIdNameMap.put(itemJo.getInt("id"), itemJo.getString("name")); } } catch (Exception e) { throw new RuntimeException("Init District List Error!", e); } } public static String getCityName(int cityId) { String name = cityIdNameMap.get(cityId); if (name == null) { name = "未知"; } return name; } public static String getDistrictName(int districtId) { String name = districtIdNameMap.get(districtId); if (name == null) { name = "未知"; } return name; } }
O2O业务中常用的城市基础基本信息判断,通过静态变量一次获取缓存内存中,减少频繁的I/O读取
。
静态变量实现类间可共享,进程内可共享,缓存的实时性稍差
。