Map是我工作中应用比较多的数据结构之一,主要用来存储一些kv的映射信息,如果是单线程环境下我会优先使用HashMap,但是如果在多线程环境下继续使用HashMap我不确定会不会被我老大打死,为了生命安全考虑我选用了大名鼎鼎的ConcurrentHashMap。

使用背景

笔者负责过一个http推送系统,其职责是定时将生产者者插入到库中的推送任务捞出来根据推送地址进行http推送,推送时需要进行RSA签名,但是加签用的私钥是每个接收者不同的,所以意味着每次在推送时我是需要根据推送者标识(统一叫appId)去获取私钥信息的,很显然每次查库不是个好办法,这时就需要加一层缓存来解决这个问题了。

为什么使用ConcurrentHashMap

最开始我是想用Redis这种集中缓存的,操作简单,而且不用考虑多节点之间数据的一致性,后来跟负责开放平台的同学沟通了一下(接收者是ISV,公私钥信息目前是开放平台同学线下派发的),公私钥信息被改动的频率很小,线上跑了两年多目前还没有ISV提出要更换,基于这一点我决定使用本地缓存,本地缓存的优点是减少了网络IO,性能更好,缺点是多节点之间数据一致性是个问题,如果数据不一致ISV验签失败会返回错误,但推送系统有个设计是如果推送失败了会阶梯式重试,整个重试过程持续12个小时,我只要把本地缓存的过期时间调小就好,最终会拿到正确的私钥信息(后来也想过设计明确的错误码,比如ISV返回SIGN_ERROR时主动刷新本地缓存,但是这涉及到接口协议的变更,最终还是搁置了),我为什么没有选择Ehcache,Guava这种成熟的缓存框架呢?一个原因是不太熟悉,第二个原因是我这个场景比较简单,不想引入一套框架进来。

步骤分解

1.根据appId去本地缓存中查询是否存在;

2.如果存在,判断是否过期,如果不过期直接返回,否则继续执行以下逻辑;

3.删除appId对应的Value

4.查询db;

5.将查询结果放到本地缓存中;

识别问题

上面的五步是一般使用缓存的大体流程,这五步涉及到的一些细节如下:

1.过期数据如何识别

2.过期数据如何删除

3.缓存并发更新如何处理

解决问题

1.如何识别过期数据:往本地缓存塞值的时候同时将过期时间塞进去,这块是将Value做了一层包装,大体结构如下:

public class CacheFutureTask<V> extends FutureTask<V>{
private long expireTime = -1;
private long createTime = -1;
private V result; public CacheFutureTask (Callable<V> call){
super(call);
this.createTime = System.currentTimeMillis();
this.expireTime = this.createTime+ TimeUnit.MINUTES.toMillis(30);//三十分钟过期
} //是否过期
public boolean isExpired(){
return System.currentTimeMillis() > this.expireTime ;
} public CacheFutureTask(Runnable runnable, V result) {
super(runnable, result);
} @Override
public V get() throws InterruptedException, ExecutionException {
return super.get();
} }

2.如何删除过期数据:一般的缓存框架都支持主动删除和惰性删除,主动删除主要是为了尽快释放内存,实现起来有一定复杂度,我这个场景中数据量不大,目前几十条,短期内也不会有大幅增长,所以内存并不会成为瓶颈,基于此我只是实现了惰性删除,获取之后判断下是否过期,如果过期就删除,代码如下;

CacheFutureTask<OpenIsvAppDO> cacheFutureTask = null;

cacheFutureTask = isvAppInfos.get(appId);
if(cacheFutureTask != null && cacheFutureTask.isExpired()){
logger.info("appId:{} configInfo is expired,will load from db",appId);
isvAppInfos.remove(appId);
cacheFutureTask = null;
}

3.如何避免缓存并发更新:缓存存在的意义就是减少db的访问,但是在并发环境下每个线程都有机会去更新缓存,如果不做控制在高并发环境下对db是一种摧残,所以必须要控制并发更新缓存,更新时需要加锁,更新完成释放锁,不过要注意设置合理的超时时间,否则可能会有大量线程等待,严重的时候可能会撑爆jvm内存,这块用到了ConcurrentHashMap的putIfAbsent(K key, V value) 方法来实现单线程更新缓存,使用CacheFutureTask的get(long timeout, TimeUnit unit)方法来做到超时结束等待,代码如下:

CacheFutureTask<OpenIsvAppDO> cacheFutureTask = null;
try {
//缓存惰性删除逻辑
cacheFutureTask = isvAppInfos.get(appId);
if(cacheFutureTask != null && cacheFutureTask.isExpired()){
logger.info("appId:{} configInfo is expire,will load from db",appId);
isvAppInfos.remove(appId);
cacheFutureTask = null;
} //如果本地缓存中不存在appId对应条目,就构造一个CacheFutureTask尝试插入
if(cacheFutureTask == null) {
Callable<OpenIsvAppDO> call = new Callable<OpenIsvAppDO>() { @Override
public OpenIsvAppDO call() throws Exception {
return openIsvAppDAO.selectByAppIdAndStatus(appId, APP_STAUTS_ACTIVE);
}
}; CacheFutureTask<OpenIsvAppDO> futureTask = new CacheFutureTask<OpenIsvAppDO>(call); //尝试插入appId对应缓存条目
cacheFutureTask = isvAppInfos.putIfAbsent(appId,futureTask);
//如果返回为null,说明当前线程拿到了锁,可以执行查询db逻辑
if(cacheFutureTask == null) {
cacheFutureTask = futureTask;
//执行run逻辑,run最终会将操作委派到Callable的call方法
futureTask.run();
}
} //带着超时参数获取Value信息
return cacheFutureTask.get(10, TimeUnit.SECONDS);
}catch(Exception e) {
//如果发生异常,将appId对应的FutureTask从缓存中删除
isvAppInfos.remove(appId);
}

结语

以上就是我用ConcurrentHashMap来实现本地缓存的一个例子,之所以没有用一些成熟的框架是因为我遇到的这个场景比较简单,所以选择自己动手实现,还是那句话,适合的才是最好的,做技术选型的时候要结合实际情况。

来我的公众号与我交流

juc之ConcurrentHashMap在我工作中的实践的更多相关文章

  1. Svn在工作中的实践感悟

    Svn是一款管理项目代码的版本控制系统,是基于集中式的版本控制系统.在工作中,由于实际开发工作的需要,部门是使用Svn来管理日常的项目开发任务.使用这么长时间了,来谈谈对Svn的感悟. 首先,说下工作 ...

  2. 工作中常用到的Java集合类有哪些?

    前言 只有光头才能变强. 文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y Java集合是我认为在Java基础中最最重要的知 ...

  3. Stream流的基本介绍以及在工作中的常用操作(去重、排序以及数学运算等)

    平时工作中,我在处理集合的时候,总是会用到各种流操作,但是往往在处理一些较为复杂的集合时,还是会出现无法灵活运用api的场景,这篇文章的目的,主要是为介绍一些工作中使用流时的常用操作,例如去重.排序和 ...

  4. 随机记录工作中常见的sql用法错误(一)

    没事开始写博客,留下以前工作中常用的笔记,内容不全或者需要补充的可以留言,我只写我常用的. 网上很多类似动软生成器的小工具,这类工具虽然在表关系复杂的时候没什么软用,但是在一些简单的表结构关系还是很方 ...

  5. 工作中常用的js、jquery自定义扩展函数代码片段

    仅记录一些我工作中常用的自定义js函数. 1.获取URL请求参数 //根据URL获取Id function GetQueryString(name) { var reg = new RegExp(&q ...

  6. 工作中那些提高你效率的神器(第二篇)_Listary

    引言 无论是工作还是科研,我们都希望工作既快又好,然而大多数时候却迷失在繁杂的重复劳动中,久久无法摆脱繁杂的事情. 你是不是曾有这样一种想法:如果我有哆啦A梦的口袋,只要拿出神奇道具就可解当下棘手的问 ...

  7. 工作中那些提高你效率的神器(第一篇)_Everything

    引言 无论是工作还是科研,我们都希望工作既快又好,然而大多数时候却迷失在繁杂的重复劳动中,久久无法摆脱繁杂的事情. 你是不是曾有这样一种想法:如果我有哆啦A梦的口袋,只要拿出神奇道具就可解当下棘手的问 ...

  8. Atitit 软件开发中 瓦哈比派的核心含义以及修行方法以及对我们生活与工作中的指导意义

    Atitit 软件开发中 瓦哈比派的核心含义以及修行方法以及对我们生活与工作中的指导意义 首先我们指明,任何一种行动以及教派修行方法都有他的多元化,只看到某一方面,就不能很好的评估利弊,适不适合自己使 ...

  9. C# 工作中遇到的几个问题

    C#  工作中遇到的几个问题 1.将VS2010中的代码编辑器的默认字体“新宋体”改为“微软雅黑”后,代码的注释,很难对齐,特别是用SandCastle Help File Builder生成帮助文档 ...

  10. [工作中的设计模式]解释器模式模式Interpreter

    一.模式解析 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器.客户端可以使用这个解释器来解释这个语言中的句子. 以上是解释器模式的类图,事实上我 ...

随机推荐

  1. linux学习指令与现有环境解决问题笔记

    linux学习指令与现有环境笔记 注意:我将pytorch和cuda安装在了pytorch这个虚拟环境中 pytorch安装及注意问题 注意版本对应,稳定版2.0.1对应cuda11.7,别按错了 按 ...

  2. KingbaseES json操作符

    下表列出了常用的json数据类型操作符: 操作符 操作符右侧数据类型 返回类型 描述 -> int json or jsonb 获得 JSON 数组元素(索引从 0 开始,负整数从末尾开始计) ...

  3. 学习 Tensorflow 的困境与解药

    我构建的预测模型 在过去的一段时间里我抓去了小宇宙内上万条播客节目的首日播放量的数据,并利用这些数据构建了一个用于预测播客节目播放量的模型.包含以下六个输入参数: 节目发布于一周中的哪一天 节目发布于 ...

  4. 前端常用库 CDN

    jQuery 链接: v1.9.1:https://i.mazey.net/lib/jquery/1.9.1/jquery.min.js v2.1.1:https://i.mazey.net/lib/ ...

  5. 【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(2)

    1.问题描述: 开发服务端推送,客户端能收到离线推送,但是推送收到的通知只能从手机顶部下拉看到,无法收到一个顶部的弹框.请问是什么原因? 解决方案: 可能原因一: 消息提醒的方式与消息类别有关,比如: ...

  6. C++调用Python-0:搭建环境

    1.进入到Python安装目录 2.将Python安装目录中的 include 和 libs 文件夹放在 C++项目中 3.设置 附加包含目录 和 附加库目录.附加依赖项(python310_d.li ...

  7. 开源相机管理库Aravis例程学习(一)——单帧采集single-acquisition

    目录 简介 源码 函数说明 arv_camera_new arv_camera_acquisition arv_camera_get_model_name arv_buffer_get_image_w ...

  8. 敲重点!HarmonyOS这些更新将会影响原子化服务上架

    原文:https://mp.weixin.qq.com/s/t-MaHqYiJ3z-QxaIsgWNPA,点击链接查看更多技术内容. 一.引言 随着原子化服务生态的发展,我们的业务诉求也在不断地变化, ...

  9. 基于locust全链路压测系统

    2021年中旬就计划着搭建一套压测系统,大约9月份已经搭建完成,使用至今还是比较稳定了,分享一下搭建思路及过程: 为什么选择Locust呢,因为Locust可以仅需要执行命令就可以完成压测任务,并且集 ...

  10. kkfileview搭建实战

    kkfileview可以与nginx搭建的文件服务器配合实现预览工作,也可以通过自身的文件系统机制免搭建nginx文件服务器来实现预览工作. nginx 创建nginx # 创建初始容器,获得容器内部 ...