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. Python爬虫爬取国家统计局网站【统计用区划和城乡划分代码】并存入MySQL数据库

    国家统计局网站相关分级页面截图 基本思路 爬取每个页面的a标签内容,生成省市两级数据字典,最后合成区县对应的链接,爬取第三层区划代码和名字,结合省市两级名字生成最后的标准. 代码 1 import p ...

  2. C++设计模式 - 门面模式(Facade)

    接口隔离模式 在组件构建过程中,某些接口之间直接的依赖常常会带来很多问题.甚至根本无法实现.采用添加一层间接(稳定)接口,来隔离本来互相紧密关联的接口是一种常见的解决方案. 典型模式 Facade P ...

  3. #根号分治#洛谷 3645 [APIO2015]雅加达的摩天楼

    题目传送门 分析 设 \(d[i][j]\) 表示 所处位置为 \(i\),跳跃能力为 \(j\) 的步数, 若 \(j\leq \sqrt{n}\),这样的状态最多有 \(n\sqrt{n}\) 个 ...

  4. #子序列自动机,vector#洛谷 3500 [POI2010]TES-Intelligence Test

    题目 多组询问查询某个串是否为模式串的子序列 分析 考虑用子序列自动机做,匹配的时候显然选择靠前的,用个vector查询最近的就行了 代码 #include <cstdio> #inclu ...

  5. OpenHarmony携千行百业创新成果亮相HDC.Together 2023

     8月4日-6日,华为开发者大会2023(以下简称"大会")在中国松山湖举办,OpenAtom OpenHarmony(简称"OpenHarmony")隆重参会 ...

  6. Docker学习路线7:构建容器镜像

    容器镜像是可执行的软件包,包括运行应用程序所需的所有内容:代码.运行时.系统工具.库和设置.通过构建自定义镜像,您可以在任何支持Docker的平台上无缝地部署应用程序及其所有依赖项. Dockerfi ...

  7. Ubuntu部署Django一:环境搭建

      前言: Ubuntu系统上部署django,使用的部署方案是 Ubuntu + django + uwsgi + nginx Ubuntu系统版本用的是 ubuntu-20.04.2.0-desk ...

  8. K8S 性能优化-K8S Node 参数调优

    前言 K8S 性能优化系列文章,本文为第四篇:Kubernetes Node 性能优化参数最佳实践. 系列文章: <K8S 性能优化 - OS sysctl 调优> <K8S 性能优 ...

  9. 基于eTS高效开发HarmonyOS课程类应用

    原文:https://mp.weixin.qq.com/s/kU76kB6T1JSqapAfGPGRHQ,点击链接查看更多技术内容. 随着HarmonyOS 3.0 Beta版的发布,API Vers ...

  10. MySQL集群入门(PXC)

    目标: 1.掌握PXC集群MySQL方案的原理: 2.掌握PXC集群的强一致性: 3.掌握PXC集群的高可用方案:硬件要求: 1.Win10x64企业版/linux/MacOS: 2.Docker虚拟 ...