话不多说先贴代码

/**
* 缓存工具
*/
public class ConcurrentHashMapCacheUtils{ /**
* 当前缓存个数
*/
public static Integer CURRENT_SIZE = 0; /**
* 时间一分钟
*/
static final Long ONE_MINUTE = 60 * 1000L; /**
* 缓存超时
*/
private static final Long TTL_TIME = 60 * 1000L; /**
* 缓存对象
*/
private static final ConcurrentHashMap<String, CacheObj> CACHE_OBJECT_MAP = new ConcurrentHashMap<>(); /**
* 清理过期缓存是否在运行
*/
private static volatile Boolean CLEAN_THREAD_IS_RUN = false; /**
* 设置缓存
*/
public static void setCache(String cacheKey, String cacheValue, long cacheTime) {
Long ttlTime = null;
if (cacheTime <= 0L) {
if (cacheTime == -1L) {
ttlTime = -1L;
} else {
return;
}
}
CURRENT_SIZE = CURRENT_SIZE + 1;
if (ttlTime == null) {
ttlTime = System.currentTimeMillis() + cacheTime;
}
CacheObj cacheObj = new CacheObj(cacheValue, ttlTime);
CACHE_OBJECT_MAP.put(cacheKey, cacheObj);
} /**
* 设置缓存
*/
public static void setCache(String cacheKey, String cacheValue) {
setCache(cacheKey, cacheValue, TTL_TIME);
} public static long getCurrentSize(){
return CACHE_OBJECT_MAP.mappingCount();
} public static List<String> getRecentApp(){
List<String> list = new ArrayList<>(16);
for (String key:CACHE_OBJECT_MAP.keySet()){
list.add(key);
}
return list;
} /**
* 获取缓存
*/
public static String getCache(String cacheKey) {
startCleanThread();
if (checkCache(cacheKey)) {
return CACHE_OBJECT_MAP.get(cacheKey).getCacheValue();
}
return null;
} /**
* 删除某个缓存
*/
public static void deleteCache(String cacheKey) {
Object cacheValue = CACHE_OBJECT_MAP.remove(cacheKey);
if (cacheValue != null) {
CURRENT_SIZE = CURRENT_SIZE - 1;
}
}
/**
* 判断缓存在不在,过没过期
*/
private static boolean checkCache(String cacheKey) {
CacheObj cacheObj = CACHE_OBJECT_MAP.get(cacheKey);
if (cacheObj == null) {
return false;
}
if (cacheObj.getTtlTime() == -1L) {
return true;
}
if (cacheObj.getTtlTime() < System.currentTimeMillis()) {
deleteCache(cacheKey);
return false;
}
return true;
} /**
* 删除过期的缓存
*/
static void deleteTimeOut() {
List<String> deleteKeyList = new LinkedList<>();
for(Map.Entry<String, CacheObj> entry : CACHE_OBJECT_MAP.entrySet()) {
if (entry.getValue().getTtlTime() < System.currentTimeMillis() && entry.getValue().getTtlTime() != -1L) {
deleteKeyList.add(entry.getKey());
}
}
for (String deleteKey : deleteKeyList) {
deleteCache(deleteKey);
}
} /**
* 设置清理线程的运行状态为正在运行
*/
static void setCleanThreadRun() {
CLEAN_THREAD_IS_RUN = true;
} /**
* 开启清理过期缓存的线程
*/
private static void startCleanThread() {
if (!CLEAN_THREAD_IS_RUN) {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNamePrefix("clean-cache-pool-").build();
ThreadPoolExecutor cleanThreadPool = new ThreadPoolExecutor(
8,
16,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(8),
namedThreadFactory
);
cleanThreadPool.execute(new CleanTimeOutThread()); } } } class CacheObj {
/**
* 缓存对象
*/
private String cacheValue;
/**
* 缓存过期时间
*/
private Long ttlTime; CacheObj(String cacheValue, Long ttlTime) {
this.cacheValue = cacheValue;
this.ttlTime = ttlTime;
} String getCacheValue() {
return cacheValue;
} Long getTtlTime() {
return ttlTime;
} @Override
public String toString() {
return "CacheObj {" +
"cacheValue = " + cacheValue +
", ttlTime = " + ttlTime +
'}';
}
} /**
* 每一分钟清理一次过期缓存
*/
class CleanTimeOutThread implements Runnable{ private static Logger logger = LoggerFactory.getLogger(CleanTimeOutThread.class); @Override
public void run() {
ConcurrentHashMapCacheUtils.setCleanThreadRun();
while (true) {
ConcurrentHashMapCacheUtils.deleteTimeOut();
try {
Thread.sleep(ConcurrentHashMapCacheUtils.ONE_MINUTE);
} catch (InterruptedException e) {
logger.error("Time-out Cache has not been cleaned!{}", e.getMessage());
}
if(1==2){
break;
}
}
} }

  

1、背景

在公司对某个开源组件的使用中,频繁出现客户端无法请求到数据的情况,经排查是发生了并发数过大数据库性能瓶颈的情况。

于是有了对服务端的优化喝如下的思考。

2、设计思考

2.1、是否选择缓存

直接查询DB还是添加缓存,这个取决于系统的并发数,如果系统并发数数据库性能足以支持,则无使用缓存的必要。

如果选择使用缓存,则需要面对的一个风险是:

服务启动/重启的瞬间会出现大量对于数据库的请求,容易发生缓存的击穿/雪崩情况。

关于这种情况我做了专门的优化来避免出现缓存击穿/雪崩,这一段的代码后面优化后再上。

2.2、缓存种类的选择

2.2.1、Java内存

优点:

  • 速度快

  • 无额外网络开销

  • 系统复杂度低

缺点:

  • 受限于热点数据数量,对应用内存大小有要求

  • 大量缓存同时失效会发生雪崩导致服务性能瞬间下降

  • 存在击穿风险

  • 多实例存在缓存一致性问题,可能出现对一条数据的重复查询

2.2.2、redis

优点:

  • 支持大量数据缓存,扩展性好

  • 在多实例时不需要考虑缓存一致性问题

缺点:

  • 系统依赖redis,如果redis不可用会导致系统不可用

  • 存在击穿风险

Java实现本地小数据量缓存尝试与实践&设计思考的更多相关文章

  1. Win环境下Oracle小数据量数据库的物理备份

    Win环境下Oracle小数据量数据库的物理备份 环境:Windows + Oracle 单实例 数据量:小于20G 重点:需要规划好备份的路径,建议备份文件和数据库文件分别存在不同的存储上. 1.开 ...

  2. day06 内存地址 小数据池缓存机制

    1. 内存相关 示例一 v1=[11,22,33] v2=[11,22,33] #值相等 内存地址不等 v1=11 v2=11 #按理说内存地址应该不等,但是python为了优化使其内存地址相等 v1 ...

  3. java 导出Excel 大数据量,自己经验总结!

    出处: http://lyjilu.iteye.com/ 分析导出实现代码,XLSX支持: /** * 生成<span style="white-space: normal; back ...

  4. 【Easyexcel】java导入导出超大数据量的xlsx文件 解决方法

    解决方法: 使用easyexcel解决超大数据量的导入导出xlsx文件 easyexcel最大支持行数 1048576. 官网地址: https://alibaba-easyexcel.github. ...

  5. windows 系统下,小数据量Oracle用户物理备份

    环境:windows Server 2003 oracle 10g,系统间备份 目标系统创建共享文件,原系统挂载共享目录 写批处理脚本,用任务计划定时调用 Rem * 由于系统实时性要求不是很高,数据 ...

  6. 小数据量的Key-Value查找类的实现

    平时写程序时经常要把一些Key与Value保存起来,但一般数据量都不大,故不想用TStringHash来做.而用TStringList来做,还要写一个"=",挺别扭!而且数据类型还 ...

  7. java 导出Excel 大数据量,自己经验总结!(二)

    在上一次的基础上加上了样式,以及中文列名 package com.tommy.fundation.util; import java.io.OutputStream; import java.util ...

  8. hihocoder #1062 : 最近公共祖先·一(小数据量 map+set模拟+标记检查 *【模板】思路 )

    #1062 : 最近公共祖先·一 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在 ...

  9. poj 1679 The Unique MST 【次小生成树+100的小数据量】

    题目地址:http://poj.org/problem?id=1679 2 3 3 1 2 1 2 3 2 3 1 3 4 4 1 2 2 2 3 2 3 4 2 4 1 2 Sample Outpu ...

随机推荐

  1. 多测师讲解自动化--rf关键字--断言(下)_高级讲师肖sir

    断言: 1.1Page Should Contain Maximize Browser Window sleep 2 Comment Page Should Contain hao123 #断言当前页 ...

  2. java基础语句翻译

    public static void main(String[] args) { System.out.println("人生中的第一个代码-----"); } } package ...

  3. Java常见的一些经典面试题(附答案解析)

    前言: 我想每个程序员比较头疼的事情都是:工作拧螺丝,面试造火箭吧.但是又必须经历这个过程,尤其是弄不清面试官问的问题,如果你准备的不是很充分,会导致面试的时候手足无措.今天这篇文章是从已工作5年的程 ...

  4. linux wget指定下载目录和重命名

    当我们在使用wget命令下载文件时,通常会需要将文件下载到指定的目录,这时就可以使用 -P 参数来指定目录,如果指定的目录不存在,则会自动创建. 示例: p.p1 { margin: 0; font: ...

  5. nginx安全: 配置http基本验证(Basic Auth)(nginx 1.18.0)

    一,http基本验证的作用: 1,http基本身份验证会从浏览器弹出登录窗口, 简单明了,容易理解, 对于面向终端用户的前台来说,不够友好, 但对于内部员工操作的后台还是很有用,通常作为一层安全措施应 ...

  6. .NET Core开源任务调度平台ScheduleMaster上新了

    ScheduleMaster上一次比较大的更新还是在6月份,转眼已经快过去4个月了,这段时间比较忙,中间只更新过一次修复了几个小bug.要总结这次更新的话,必须要用"千呼万唤始出来" ...

  7. Redis入门之认识redis(一)

    第1章 非关系型数据库 1.1 NoSQL数据库概述 1) NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL",泛指非关系型的数据库. NoSQL 不 ...

  8. 安卓日常开发和逆向中常用的shell命令与非shell命令

    简述shell 命令与 非shell命令区别 shell命令不用先adb shell进入界面执行 非shell命令必须要 adb shell进入界面执行 基础非shell命令 1.安装app adb ...

  9. WSL2 + Docker + IDEA 开发到发布一步到位

    摘要:本文主要介绍了如何用WSL2.Docker.IDEA将Java应用从开发到发布一步到位. 上次介绍了如何在Windows(WSL2) Linux子系统中搭建搭建Docker环境,这次将利用上次搭 ...

  10. Camera2使用surface支持

    surfaceview是运行在子线程,可以为相机提供不断的刷新 public class MainActivity extends AppCompatActivity { public void on ...