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自身的运行原理和设计 ...
随机推荐
- [.net core]6.launchSettings.json,调试配置
展开properties, 双击查看内容, { "iisSettings": { "windowsAuthentication": false, "a ...
- 新版 Scrapy 中 sys.conf.settings 的替代方法
新版 Scrapy 中 sys.conf.settings 的替代方法 在 scrapy 项目目录下,有个 settings.py 文件,此文件是用来存放爬虫项目的各种配置,比如说 MongoDB 的 ...
- RabbitMQ入门教程(十六):RabbitMQ与Spring集成
原文:RabbitMQ入门教程(十六):RabbitMQ与Spring集成 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https: ...
- 深入理解java虚拟机(3)垃圾收集器与内存分配策略
一.根搜索算法: (1)定义:通过一系列名为"GC Roots"的对象作为起点,从这些起点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时 ...
- Action获取请求参数的3中方式
方式一:Action本身作为Model对象,通过属性注入(Setter)方法讲参数数据封装到Action中 具体为:在Action中,提供和参数名相同的几个属性,并为其提供set方法,那么,该参数会被 ...
- 修改MIGO或者ML81N产生的会计凭证项目文本增强
在程序:MM07MFF9_F_BELEG_ERGAENZEN下做隐式增强
- 阅读脚本控制pwm代码
在现有的项目上通过SoC的EHRPWM3B管脚产生PWM脉冲做为摄像头的framsync信号. datasheet描述: PWMSS:PWM Subsystem Resources eHRPWM: E ...
- zabbix-agent安装遇到的坑
问题: libc.so.6(GLIBC_2.14)(64bit) is needed by zabbix-agent-3.4.11-1.el7.x86_64 原因: 系统环境是 centos6 ,下载 ...
- Java读取二进制文件的方式
public static void readFile(String fileName){ File file = new File(fileName); if(file.exists()){ try ...
- Linux下网络设置
1.临时IP配置 # ifconfig eth0 192.168.110.118 netmask 255.255.255.0 gateway 192.168.110.2 up # ...