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自身的运行原理和设计 ...
随机推荐
- E - 卿学姐与城堡的墙(树状数组求逆序数)
卿学姐与城堡的墙 Time Limit: 2000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others) Submit ...
- php文件上传php.ini配置参数
php文件上传服务器端配置参数 file_uploads = On,支持HTTP上传uoload_tmp_dir = ,临时文件保存目录upload_max_filesize = 2M,允许上传文件的 ...
- [Vue] vue的一些面试题2
1.Vue.observable 你有了解过吗?说说看 vue2.6 发布一个新的 API,让一个对象可响应.Vue 内部会用它来处理 data 函数返回的对象.返回的对象可以直接用于渲染函数和计算属 ...
- 缓存---LRU算法实现
2.LRU 以下是基于双向链表+HashMap的LRU算法实现,对算法的解释如下: 设置一个map存放对应的键和值,同时设置一个双向链表,来保存最近最久未使用的关系,如果访问一个键,键存在于m ...
- RabbitMQ入门教程(十三):虚拟主机vhost与权限管理
原文:RabbitMQ入门教程(十三):虚拟主机vhost与权限管理 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://b ...
- TypeError: esri.layers.WMSLayer is not a constructor
最近加载wms地图后,总是报这个错误,因为错误,导致后续的代码无法加载,导致无法功能使用. 原因是,由于方法公用,有的新功能在使用时,引用依赖包时,未引用完整,导致加载此处加载wms图层的时候, 报此 ...
- the server responsed width a status of 404 (Not Found)
最近使用VS code写代码,使用Beautify插件格式化代码后,报以下错误: 反复查看代码,初始感觉没什么问题,有点懵.. 随着时间的推移,后来果然发现问题所在: 在加载模块的地方,多出了个空格( ...
- php框架之laravel
常见问题: 1. 访问网站500错误 这是因为laravel的缓存路径没有找到 laravel缓存文件路径是在 config/cache.php中设置,默认存在storage文件夹中 解决:需要保证s ...
- vue.js(3)--v-bind与v-on
vue中的v-bind与v-on的使用 (1)实例 <!DOCTYPE html> <html lang="en"> <head> <me ...
- 使用Lombok来优雅的编码
介绍在项目中使用Lombok可以减少很多重复代码的书写.比如说getter/setter/toString等方法的编写. IDEA中的安装打开IDEA的Setting –> 选择Plugins选 ...