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自身的运行原理和设计 ...
随机推荐
- datetime的timedelta对象
datetime.timedelta对象代表两个时间之间的时间差,两个date或datetime对象相减就可以返回一个timedelta对象. 如果有人问你昨天是几号,这个很容易就回答出来了.但是如果 ...
- PostGIS 爆管分析之找出上游阀门(优化版)
说明 前面描述过利用postgis查找上游阀门的原理,以及代码,其实当初写完就发现又很大的优化空间,但一直没有时间去做. 最近遇到一个情况,处理60w+条管网数据时,效率太慢了,于是腾时间优化了一版. ...
- C# System.Reflection (反射)
在使用.NET创建的程序或组件时,元数据(metadata)和代码(code)都存储于“自成一体”的单元中,这个单元称为装配件.我们可以在程序运行期间访问这些信息. 在System.Reflectio ...
- Spring+Spring MVC+Hibernate框架搭建实例
前言:这里只是说明整个搭建流程,并不进行原理性的讲解 一 下面所需要用到的数据库配置: 数据库方面,使用mysql创建一个users表,具体代码如下: 1 2 3 4 5 6 7 8 9 10 11 ...
- Assets.xcassets的详细使用方法
开始之前,首先回顾一下iOS7初体验(1)——第一个应用程序HelloWorld中的一张图,如下所示: 本文分享一下Images.xcassets的体验~_~ 1. 打开此前使用过的HelloWorl ...
- linux误卸载openssl后的恢复
一.原因 由于在编译mysql时,报ssl错误,于是想卸载openssl再重新安装 rpm -qa|grep openssl rpm -e openssl-.el7_6..x86_64 rpm -e ...
- Vasya And The Matrix CodeForces - 1016D (思维+构造)
Now Vasya is taking an exam in mathematics. In order to get a good mark, Vasya needs to guess the ma ...
- php随机获取数组里面的值
srand() 函数播下随机数发生器种子,array_rand() 函数从数组中随机选出一个或多个元素,并返回.第二个参数用来确定要选出几个元素.如果选出的元素不止一个,则返回包含随机键名的数组,否则 ...
- C++线程同步 -- windows
简介: 在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程.但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入, 由于CPU时间调度上的问题,写入 ...
- Linux查看某进程的线程
首先得知道你要查的进程的PID: 比如我要查看看我unimrcpserver的线程 第二种方法:top top命令可以实时显示各线程情况.就相当于windows的资源管理器.