本地缓存的原理及技术选型参考

转载自 本地缓存的原理及技术选型参考

互联网架构设计的五大要素:高性能、高可用、可伸缩性、可扩展性、安全。


如何做到高性能、高可用,缓存是一大助力。


我们知道,绝大部分的时候,读数据写数据符合二八定律。并且读数据中,百分之二十是数据被经常读取(热数据)。那么我们解决这百分之二十的数据的方法就可以取得很好的一个性能。

缓存分类

从很多互联网架构设计中可以看到,从用户在浏览器上输入网址开始,经历了太多的缓存。我大概列举一下: 


1. 输入网址后,查询浏览器缓存 

2. 查询浏览器dns缓存 

3. 查询操作系统dns缓存 

4. 请求dns服务器,查询dns服务器缓存 

5. 获得ip,静态资源走cdn缓存。动态数据走服务器

6. 如果配置了页面缓存,走页面缓存 

7. 如果配置了本地缓存(localcache),走本地缓存 

8. 如果配置了分布式缓存(如redis等等),走分布式缓存 

9. 数据库操作,数据库缓存

这里,我想主要讲讲2种localcache的选择,为什么要引入localcache,可以用《互联网架构的三板斧》中的一句话来说明:

数据访问热点,比如Detail中对某些热点商品的访问度非常高,即使是Tair缓存这种Cache本身也有瓶颈问题,一旦请求量达到单机极限也会存在热点保护问题。


有时看起来好像很容易解决,比如说做好限流就行,但你想想一旦某个热点触发了一台机器的限流阀值,那么这台机器Cache的数据都将无效,进而间接导致Cache被击穿,请求落地应用层数据库出现雪崩现象。


这类问题需要与具体Cache产品结合才能有比较好的解决方案,这里提供一个通用的解决思路,就是在Cache的client端做本地Localcache,当发现热点数据时直接Cache在client里,而不要请求到Cache的Server。

还有一个原因是,在做秒杀的时候,我可以在每一台应用服务器中设置一个有失效时间的商品剩余数量的计数器,以达到尽可能在调用链前面拦截非有效请求。

分布式缓存

如何部署分布式缓存,这里不细说。列一下常见的部署方式: 

这种方式是不太好扩机器。有一种比较好的方式是:一致性哈希算法。 可以参考这篇文章:《一致性哈希算法》

Localcache


我对比了两种LocalCache。google的Guava库中的cache模块 和Ehcache。

Guava中本地缓存基本原理为:ConcurrentMap(利用分段锁降低锁粒度) + LRU算法。

对应map中的每一个键值对,可以设置过期时间,也可以通过如LRU(Least Recently Used 最近最少使用)做淘汰策略。

LRU算法实现原理本质上是一个hashmap+双向链表,每次访问操作都将节点放在链表头部,尾部自然就是最旧的节点了。 


Ehcache缓存是一个比较重的localcache。有一个比较有意思的点是:支持磁盘缓存,这样妈妈再也不用担心内存不够用了。

我的需求是:可以不用担心内存不足的问题,做一些配置或不需要强一致性数据缓存。甚至可以做伪热加载配置。

因此,我选择使用了Ehcache。如果只是做内存缓存,建议使用guava,有很多有意思的东西,比如缓存失效,自动从数据源加载数据等等。

Ehcache工具类

根据自身需要,封装了一个只走磁盘的无容量限制的localcache工具类,仅供参考,考虑到可以放在多个地方,因此没有走配置文件 


java package com.fenqile.creditcard.appgatewaysale.provider.util;

import com.alibaba.fastjson.JSONObject; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import net.sf.ehcache.config.CacheConfiguration; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory;

import java.security.MessageDigest;

/** 
* User: Rudy Tan 
* Date: 2018/3/28 
* * 本地缓存工具,基于ehcache,磁盘存储 
* * 可以运用于配置文件、接口数据等等, 
**/
 
public class LocalCacheUtil {

private static final CacheManager cacheManager = CacheManager.create();

private static Logger LOG = LoggerFactory.getLogger(LocalCacheUtil.class);

private static String md5(String str) {
   try {
       MessageDigest md = MessageDigest.getInstance("MD5");
       byte[] bytes = md.digest(str.getBytes("utf-8"));

       final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
       StringBuilder ret = new StringBuilder(bytes.length * 2);
       for (int i=0; i<bytes.length; i++) {
           ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
           ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
       }
       return ret.toString();
   }
   catch (Exception e) {
       throw new RuntimeException(e);
   }
}

private static Cache getCacheInstance(){
   String cacheKey = "local_cache_"+ md5(Thread.currentThread().getStackTrace()[1].getClassName());
   if (!cacheManager.cacheExists(cacheKey)){
       synchronized (cacheManager){
           if (!cacheManager.cacheExists(cacheKey)){
               CacheConfiguration cacheConfiguration =  new CacheConfiguration();
               cacheConfiguration.setTimeToIdleSeconds(60);
               cacheConfiguration.setTimeToLiveSeconds(60);
               cacheConfiguration.setName(cacheKey);
               cacheConfiguration.setMaxEntriesLocalHeap(1);
               cacheConfiguration.setMaxEntriesLocalDisk(100000);
               cacheConfiguration.setEternal(false);
               cacheConfiguration.setOverflowToDisk(true);
               cacheConfiguration.setMaxElementsInMemory(1);
               cacheConfiguration.setCopyOnRead(true);
               cacheConfiguration.setCopyOnWrite(true);

               Cache cache = new Cache(cacheConfiguration);
               cacheManager.addCache(cache);
           }
       }
   }
   return cacheManager.getCache(cacheKey);
}

private static Element serialization(String key, Object value, Integer expireTime){
   if (StringUtils.isEmpty(key)
           || null == expireTime
           || 0 == expireTime){
       return null;
   }

   String clazz = "";
   String content = "";
   if (null == value){
       clazz = "null";
       content = clazz + "_class&data_null";
   }else {
       clazz = value.getClass().getName();
       content = clazz + "_class&data_"+ JSONObject.toJSONString(value);
   }

   return new Element(key, content, expireTime, expireTime);
}

private static Object unSerialization(Element element){
   if (null == element){ return null; }

   String content = (String) element.getObjectValue();
   String[] data = content.split("_class&data_");
   Object response = null;
   try {
       if ("null".equalsIgnoreCase(data[0])){
           return null;
       }
       response = JSONObject.parseObject(data[1], Class.forName(data[0]));
   } catch (ClassNotFoundException e) {
       e.printStackTrace();
   }

   return response;
}


/**
* 设置本地缓存
*/

public static boolean setCache(String key, Object value, Integer expireTime){
   Cache cache = getCacheInstance();
   if (null == cache){
       LOG.info("setCache:cache is null, {}, {}, {}", key, value, expireTime);
       return false;
   }

   if (StringUtils.isEmpty(key)
           || null == expireTime
           || 0 == expireTime){
       LOG.info("setCache:params is not ok, {}, {}, {}", key, value, expireTime);
       return false;
   }

   synchronized (cache){
       cache.put(serialization(key, value, expireTime));
       cache.flush();
   }
   return true;
}

/**
* 获取本地缓存
*/

public static Object getCache(String key){
   Cache cache = getCacheInstance();
   if (null == cache
           || StringUtils.isEmpty(key)){
       LOG.info("getCache:params is not ok, {}", key);
       return null;
   }

   Element element = cache.get(key);
   return unSerialization(element);
}

/**
* 清理本地缓存
*/

public static boolean delCache(String key){
   Cache cache = getCacheInstance();
   if (null == cache){
       LOG.info("delCache:cache is null, {}", key);
       return true;
   }

   if (StringUtils.isEmpty(key)){
       LOG.info("delCache:params is not ok, {}", key);
       return true;
   }

   synchronized (cache){
       cache.put(serialization(key, null0));
       cache.flush();
   }
   return true;
}
}



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/329674.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员欢呼:微软Bing开始支持搜索源码、可直接运行

日常生活中&#xff0c;程序员们经常会遇见这样那样的问题&#xff0c;比如忘记了代码该怎么写&#xff0c;又或者需要实现一些特殊的算法和功能。这时候&#xff0c;你就可以去找微软 Bing 帮忙啦&#xff01; 微软最近联合 HackerRank 一起研发了一项新功能&#xff1a;源代码…

rabbitmq创建缓存连接工厂

转自&#xff1a; 【RabbitMQ-3】连接池的配置_小胖学编程的博客-CSDN博客文章目录1. rabbitmq的connection连接池1.1 问题提出1.1.1 Connection对象管理以及性能1.1.2 Channel对象管理以及性能1.2 Spring AMQP线程池配置1.2.1 ConnectionFactory连接工厂1.2.2 消费发送和接收…

python简短语法_写出优雅简洁的 python 语法(二)函数传参

Python中函数传递参数的形式包含多种&#xff0c;可根据情况而定使用哪种方式。基本所有语言都有简介明了的语法去替代繁琐的代码&#xff0c;优雅的写法不仅能提升代码美观&#xff0c;更能提高提高开发效率&#xff0c;让代码更加易读。位置传递&#xff0c;默认参数位置传参…

你真的了解Java中的三目运算符吗

转载自 你真的了解Java中的三目运算符吗三目运算符是我们经常在代码中使用的&#xff0c;a (bnull?0:1);这样一行代码可以代替一个if-else,可以使代码变得清爽易读。但是&#xff0c;三目运算符也是有一定的语言规范的。在运用不恰当的时候会导致意想不到的问题。本文就介绍一…

关于.NET下开源及商业图像处理(PSD)组件

1 前言 这篇博客的背景是&#xff1a;为了完成吉日嘎拉的“PSD文件损坏检测和图层检测”任务&#xff0c;查找了目前.NET各种开源的及商业的图像处理资料&#xff0c;在完成任务之后&#xff0c;进行总结。此次任务主要是用C#操作PSD(PhotoShop)文件&#xff0c;中文资料很少&a…

SpringBoot连接多RabbitMQ源

转自&#xff1a; SpringBoot连接多RabbitMQ源 - 掘金在实际开发中&#xff0c;很多场景需要异步处理&#xff0c;这时就需要用到RabbitMQ&#xff0c;而且随着场景的增多程序可能需要连接多个RabbitMQ。SpringBoot本身提供了默认的配置可以快速配置连接RabbitMQ&#xff0c;但…

满足其中一个条件则可_农村分户好处多,但并非人人都可分户!满足这4个条件才可以申请...

分户&#xff0c;一般指子女成年或者成家后从父母户口里面独立出去&#xff0c;自立一户&#xff1b;也可以是指夫妻离婚后一方将户口独立出去(离婚也可以不分户&#xff0c;变更婚姻状态就行)。简单的说分户是指原本在一个户口本上的人口&#xff0c;现在分出去自成一个户口本…

Synchronized的实现原理(一)

转载自 Synchronized的实现原理&#xff08;一&#xff09;synchronized&#xff0c;是Java中用于解决并发情况下数据同步访问的一个很重要的关键字。当我们想要保证一个共享资源在同一时间只会被一个线程访问到时&#xff0c;我们可以在代码中使用synchronized关键字对类或者对…

FOSS历史回顾:三代开源人的故事

现在是2016年&#xff0c;你环顾一下四周&#xff0c;开源早已无处不在了。开源无论是规范、形式、以及面貌都和最初的大相径庭&#xff0c;然而事实上&#xff0c;这也预示着新一代的开源程序员们的崛起。下面我们尝试解释下。 &#xff08;以下这一段落为作者自谦&#xff09…

Spring中@Autowired、@Qualifier、@Resource的区别

转自&#xff1a; Spring中Autowired、Qualifier、Resource的区别_老周聊架构的博客-CSDN博客_qualifier和resource区别1、AutowiredAutowired 可以单独使用。如果单独使用&#xff0c;它将按类型装配。因此&#xff0c;如果在容器中声明了多个相同类型的bean&#xff0c;则会…

map分组后取前10个_海关数据 | 图解前10个月外贸

*内容转载自微信公众号&#xff1a;海关发布RECOMMEND【 推荐阅读 】海关数据 | 图解前三季度我国外贸海关数据 | 图解8月外贸海关数据 | 一图看懂前7个月外贸海关数据 | 图解上半年度外贸增3.9%声明本微信订阅号不以商业营利为目的&#xff0c;不排除部分文字内容或图片转载自…

回顾build 2016:你好,这是微软迄今最好的Windows开发平台

按&#xff1a;本文作者陈计节&#xff0c;ThoughtWorks 高级咨询师。多年的跨平台 .NET 开发者&#xff0c;全栈工程师&#xff0c;技术布道师。擅长互联网应用程序的设计、开发和运维等工作。 在最近的开发者大会&#xff08;Build 2016&#xff09;上&#xff0c;微软面向开…

深入理解多线程(二)—— Java的对象模型

转载自 深入理解多线程&#xff08;二&#xff09;—— Java的对象模型上一篇文章中简单介绍过synchronized关键字的方式&#xff0c;其中&#xff0c;同步代码块使用monitorenter和monitorexit两个指令实现&#xff0c;同步方法使用ACC_SYNCHRONIZED标记符实现。后面几篇文章会…

8.1-CPU结构(学习笔记)

【README】 本文总结自bilibili《计算机组成原理&#xff08;哈工大刘宏伟&#xff09;》的视频讲解&#xff0c;非常棒&#xff0c;墙裂推荐&#xff1b; 【1】CPU结构 Cpu的首要功能就是解释指令&#xff1b;功能列表如下&#xff1a; 1) 取指令&#xff1a;从内存中读取…

生物信息 python 书籍_用python做生物信息数据分析(1-环境准备)

写在前面四五年前&#xff0c;接触生物信息的时候&#xff0c;阴差阳错&#xff0c;我选择用perl。事实上&#xff0c;直到嫌我&#xff0c;我还是认为我当初的选择&#xff0c;完全正确&#xff01;。在做一些小文本的快速处理上&#xff0c;perl在我看来&#xff0c;从来最优…

8.2-指令周期(学习笔记)

【README】 本文总结自bilibili《计算机组成原理&#xff08;哈工大刘宏伟&#xff09;》的视频讲解&#xff0c;非常棒&#xff0c;墙裂推荐&#xff1b; 【1】指令周期 【1.1】指令周期概述 1&#xff09;指令周期&#xff1a;取出并执行一条指令所需的全部时间&#xff1…

深入理解多线程(三)—— Java的对象头

转载自 深入理解多线程&#xff08;三&#xff09;—— Java的对象头上一篇文章中我们从HotSpot的源码入手&#xff0c;介绍了Java的对象模型。这一篇文章在上一篇文章的基础上再来介绍一下Java的对象头。主要介绍一下对象头的作用&#xff0c;结构以及他和锁的关系。 Java对象…

python tkinter 背景色改变不了_python - Tkinter背景颜色问题 - 堆栈内存溢出

我有一个脚本&#xff0c;其中包含Tkinter模块&#xff0c;我想每隔3分钟更改一次背景颜色&#xff0c;例如绿色3分钟&#xff0c;然后橙色&#xff0c;然后红色。 我有显示绿色的代码&#xff0c;但无法更改它。当我在代码中创建函数时&#xff0c;会遇到一些不同的错误&#…

回顾微软近年来对于Linux和开源的策略

2014年十月&#xff0c;在旧金山举办的一场活动中&#xff0c;微软的CEO Satya Nadella向公众表示&#xff0c;微软“爱Linux”。作为昔日的竞争对手&#xff0c;微软对Linux的态度逐渐从敌对转变为合作。自那次发言以来&#xff0c;微软在开源方面频频重拳出击&#xff0c;似乎…

深入理解多线程(四)—— Moniter的实现原理

转载自 深入理解多线程&#xff08;四&#xff09;—— Moniter的实现原理本文是《深入理解多线程系列文章》的第四篇。点击查看原文&#xff0c;阅读该系列所有文章。 在深入理解多线程&#xff08;一&#xff09;——Synchronized的实现原理中介绍过关于Synchronize的实现原理…