SpringBoot,用200行代码完成一个一二级分布式缓存
缓存系统的用来代替直接访问数据库,用来提升系统性能,减小数据库复杂。早期缓存跟系统在一个虚拟机里,这样内存访问,速度最快。 后来应用系统水平扩展,缓存作为一个独立系统存在,如redis,但是每次从缓存获取数据,都还是要通过网络访问才能获取,效率相对于早先从内存里获取,还是差了点。如果一个应用,比如传统的企业应用,一次页面显示,要访问数次redis,那效果就不是特别好,因此,现在有人提出了一二级缓存。即一级缓存跟系统在一个虚拟机内,这样速度最快。二级缓存位于redis里,当一级缓存没有数据的时候,再从redis里获取,并同步到一级缓存里。
现在实现这种一二级缓存的也挺多的,比如 hazelcast,新版的Ehcache..不过,实际上,如果你用spring boot,手里又一个Redis,则不需要搞hazelcastEhcache,只需要200行代码,就能在spring boot基础上,提供一个一二级缓存,代码如下:
import java.io.UnsupportedEncodingException;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCachePrefix;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
@Conditional(StarterCacheCondition.class)
public class CacheConfig {
@Value("${springext.cache.redis.topic:cache}")
String topicName ;
@Bean
public MyRedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
MyRedisCacheManager cacheManager = new MyRedisCacheManager(redisTemplate);
cacheManager.setUsePrefix(true);
return cacheManager;
}
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic(topicName));
return container;
}
@Bean
MessageListenerAdapter listenerAdapter(MyRedisCacheManager cacheManager ) {
return new MessageListenerAdapter(new MessageListener(){
@Override
public void onMessage(Message message, byte[] pattern) {
byte[] bs = message.getChannel();
try {
String type = new String(bs,"UTF-8");
cacheManager.receiver(type);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
// 不可能出错
}
}
});
}
class MyRedisCacheManager extends RedisCacheManager{
public MyRedisCacheManager(RedisOperations redisOperations) {
super(redisOperations);
}
@SuppressWarnings("unchecked")
@Override
protected RedisCache createCache(String cacheName) {
long expiration = computeExpiration(cacheName);
return new MyRedisCache(this,cacheName, (this.isUsePrefix()? this.getCachePrefix().prefix(cacheName) : null), this.getRedisOperations(), expiration);
}
/**
* get a messsage for update cache
* @param cacheName
*/
public void receiver(String cacheName){
MyRedisCache cache = (MyRedisCache)this.getCache(cacheName);
if(cache==null){
return ;
}
cache.cacheUpdate();
}
//notify other redis clent to update cache( clear local cache in fact)
public void publishMessage(String cacheName){
this.getRedisOperations().convertAndSend(topicName, cacheName);
}
}
class MyRedisCache extends RedisCache{
//local cache for performace
ConcurrentHashMap<Object,ValueWrapper> local = new ConcurrentHashMap<>();
MyRedisCacheManager cacheManager;
public MyRedisCache(MyRedisCacheManager cacheManager,String name, byte[] prefix,
RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration) {
super(name, prefix, redisOperations, expiration);
this.cacheManager = cacheManager;
}
@Override
public ValueWrapper get(Object key) {
ValueWrapper wrapper = local.get(key);
if(wrapper!=null){
return wrapper;
}else{
wrapper = super.get(key);
if(wrapper!=null){
local.put(key, wrapper);
}
return wrapper;
}
}
@Override
public void put(final Object key, final Object value) {
super.put(key, value);
cacheManager.publishMessage(super.getName());
}
@Override
public void evict(Object key) {
super.evict(key);
cacheManager.publishMessage(super.getName());
}
@Override
public ValueWrapper putIfAbsent(Object key, final Object value){
ValueWrapper wrapper = super.putIfAbsent(key, value);
cacheManager.publishMessage(super.getName());
return wrapper;
}
public void cacheUpdate(){
//clear all cache for simplification
local.clear();
}
}
}
class StarterCacheCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), "springext.cache.");
String env = resolver.getProperty("type");
if(env==null){
return false;
}
return "local2redis".equalsIgnoreCase(env.toLowerCase());
}
}
代码的核心在于spring boot提供一个概念CacheManager&Cache用来表示缓存,并提供了多达8种实现,但由于缺少一二级缓存,因此,需要在Redis基础上扩展,因此实现了MyRedisCacheManger,以及MyRedisCache,增加一个本地缓存。
一二级缓存需要解决的的一个问题是缓存更新的时候,必须通知其他节点的springboot应用缓存更新。这里可以用Redis的 Pub/Sub 功能来实现,具体可以参考listenerAdapter方法实现。
使用的时候,需要配置如下,这样,就可以使用缓存了,性能杠杠的好
springext.cache.type=local2redis
# Redis服务器连接端口
spring.redis.host=172.16.86.56
spring.redis.port=6379
SpringBoot,用200行代码完成一个一二级分布式缓存的更多相关文章
- 不到 200 行代码,教你如何用 Keras 搭建生成对抗网络(GAN)【转】
本文转载自:https://www.leiphone.com/news/201703/Y5vnDSV9uIJIQzQm.html 生成对抗网络(Generative Adversarial Netwo ...
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质
原文:200行代码,7个对象--让你了解ASP.NET Core框架的本质 2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘&g ...
- 200行代码实现Mini ASP.NET Core
前言 在学习ASP.NET Core源码过程中,偶然看见蒋金楠老师的ASP.NET Core框架揭秘,不到200行代码实现了ASP.NET Core Mini框架,针对框架本质进行了讲解,受益匪浅,本 ...
- 200 行代码实现基于 Paxos 的 KV 存储
前言 写完[paxos 的直观解释]之后,网友都说疗效甚好,但是也会对这篇教程中一些环节提出疑问(有疑问说明真的看懂了 ),例如怎么把只能确定一个值的 paxos 应用到实际场景中. 既然 Talk ...
- 通过 Mesos、Docker 和 Go,使用 300 行代码创建一个分布式系统
[摘要]虽然 Docker 和 Mesos 已成为不折不扣的 Buzzwords ,但是对于大部分人来说它们仍然是陌生的,下面我们就一起领略 Mesos .Docker 和 Go 配合带来的强大破坏力 ...
- 200行代码实现简版react🔥
200行代码实现简版react
- 通过Mesos、Docker和Go,使用300行代码创建一个分布式系统
[摘要]虽然 Docker 和 Mesos 已成为不折不扣的 Buzzwords ,但是对于大部分人来说它们仍然是陌生的,下面我们就一起领略 Mesos .Docker 和 Go 配合带来的强大破坏力 ...
- 在Hibernate中使用Memcached作为一个二级分布式缓存
转自:http://www.blogjava.net/xmatthew/archive/2008/08/20/223293.html hibernate-memcached--在Hibernate ...
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质
2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...
随机推荐
- 使用 tablib 来自动化管理测试用例,其他的工具都不用学了
你在学习 python 自动化测试吗?听过 requests 库吗?tablib 是 requests 库作者常年维护的一个可以操作 Excel 等多种文件格式,将他们变成一种通用数据集的第三方库. ...
- HashMap、HashTable、ConcurrentHashMap、TreeMap、LinkedHashMap、WeakHashMap区别
1. HashMap 标准链地址法实现(下图).数组方式存储key/value,线程非安全,允许null作为key和value,key不可以重复,value允许重复,不保证元素迭代顺序是按照插入时 ...
- npm学习(十)之如何使用创建、发布、使用作用域包
前言 要求npm版本2或更高 作用域用于将相关包分组在一起,并为npm模块创建一个名称空间(类似于域).这里有更详细的解释. 如果一个包的名称以@开头,那么它就是一个有作用域的包.范围是@和斜杠之间的 ...
- MySQL数据库入门多实例配置
MySQL数据库入门——多实例配置 前面介绍了相关的基础命令操作,所有的操作都是基于单实例的,mysql多实例在实际生产环境也是非常实用的,因为必须要掌握 1.什么是多实例 多实例就是一台服务器上开启 ...
- 深入理解java虚拟机(1)走进jvm
1.JDK:java程序设计语言.java虚拟机.javaAPI 二.自动内存管理机制 ----------------------------------------------------- 1. ...
- 2019-11-29-Roslyn-打包自定义的文件到-NuGet-包
title author date CreateTime categories Roslyn 打包自定义的文件到 NuGet 包 lindexi 2019-11-29 08:23:21 +0800 2 ...
- MHA ssh检查,repl复制检查和在线切换日志分析
一.SSh 检查日志分析 执行过程及对应的日志: 1.读取MHA manger 节点上的配置文件 2.根据配置文件,得到各个主机的信息,逐一进行SSH检查 3.每个主机都通过SSH连接除了自己以外的其 ...
- phpstorm配置总结
phpstorm配合laravel框架作为项目开发,需要添加自动提示,减少查看文档的次数,本次使用的是idel-helper插件 在当前项目下 编辑composer.json文件文件,添加如下字符 & ...
- centos7部署nagios(二)
一.Nagios简介 分类: 监控 undefined Nagios是一款开源的电脑系统和网络监视工具,能有效监控Windows.Linux和Unix的主机状态,交换机路由器等网络设置,打印机等.在系 ...
- vue打包后element-ui部分样式(图标)异常问题
vue项目使用element-ui组件,打包后部分样式(上下左右箭头)异常,变成方框了. 页面报warn错误,有个字体找不到. 解决办法:在build文件夹下找到utils.js,加上一行public ...