SpringBoot,用200行代码完成一个一二级分布式缓存

     缓存系统的用来代替直接访问数据库,用来提升系统性能,减小数据库复杂。早期缓存跟系统在一个虚拟机里,这样内存访问,速度最快。 后来应用系统水平扩展,缓存作为一个独立系统存在,如redis,但是每次从缓存获取数据,都还是要通过网络访问才能获取,效率相对于早先从内存里获取,还是差了点。如果一个应用,比如传统的企业应用,一次页面显示,要访问数次redis,那效果就不是特别好,因此,现在有人提出了一二级缓存。即一级缓存跟系统在一个虚拟机内,这样速度最快。二级缓存位于redis里,当一级缓存没有数据的时候,再从redis里获取,并同步到一级缓存里。

现在实现这种一二级缓存的也挺多的,比如 hazelcast,新版的Ehcache..不过,实际上,如果你用spring boot,手里又一个Redis,则不需要搞hazelcastEhcache,只需要200行代码,就能在spring boot基础上,提供一个一二级缓存,代码如下:

1import java.io.UnsupportedEncodingException; 2import java.util.concurrent.ConcurrentHashMap; 3 4import org.springframework.beans.factory.annotation.Value; 5import org.springframework.boot.autoconfigure.AutoConfigureBefore; 6import org.springframework.boot.bind.RelaxedPropertyResolver; 7import org.springframework.context.annotation.Bean; 8import org.springframework.context.annotation.Condition; 9import org.springframework.context.annotation.ConditionContext; 10import org.springframework.context.annotation.Conditional; 11import org.springframework.context.annotation.Configuration; 12import org.springframework.core.type.AnnotatedTypeMetadata; 13import org.springframework.data.redis.cache.RedisCache; 14import org.springframework.data.redis.cache.RedisCacheManager; 15import org.springframework.data.redis.cache.RedisCachePrefix; 16import org.springframework.data.redis.connection.Message; 17import org.springframework.data.redis.connection.MessageListener; 18import org.springframework.data.redis.connection.RedisConnectionFactory; 19import org.springframework.data.redis.core.RedisOperations; 20import org.springframework.data.redis.core.RedisTemplate; 21import org.springframework.data.redis.listener.PatternTopic; 22import org.springframework.data.redis.listener.RedisMessageListenerContainer; 23import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; 24 25 26 27@Configuration 28@Conditional(StarterCacheCondition.class) 29public class CacheConfig { 30 31 @Value("${springext.cache.redis.topic:cache}") 32 String topicName ; 33 34 35 36 @Bean 37 public MyRedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { 38 MyRedisCacheManager cacheManager = new MyRedisCacheManager(redisTemplate); 39 cacheManager.setUsePrefix(true); 40 return cacheManager; 41 } 42 43@Bean 44 RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, 45 MessageListenerAdapter listenerAdapter) { 46 47 RedisMessageListenerContainer container = new RedisMessageListenerContainer(); 48 container.setConnectionFactory(connectionFactory); 49 container.addMessageListener(listenerAdapter, new PatternTopic(topicName)); 50 51 return container; 52 } 53 54 @Bean 55 MessageListenerAdapter listenerAdapter(MyRedisCacheManager cacheManager ) { 56 return new MessageListenerAdapter(new MessageListener(){ 57 58 @Override 59 public void onMessage(Message message, byte[] pattern) { 60 byte[] bs = message.getChannel(); 61 try { 62 String type = new String(bs,"UTF-8"); 63 cacheManager.receiver(type); 64 } catch (UnsupportedEncodingException e) { 65 e.printStackTrace(); 66 // 不可能出错 67 } 68 69 70 71 } 72 73 }); 74 } 75 76 77 78 class MyRedisCacheManager extends RedisCacheManager{ 79 80 81 public MyRedisCacheManager(RedisOperations redisOperations) { 82 super(redisOperations); 83 84 } 85 86 87 @SuppressWarnings("unchecked") 88 @Override 89 protected RedisCache createCache(String cacheName) { 90 long expiration = computeExpiration(cacheName); 91 return new MyRedisCache(this,cacheName, (this.isUsePrefix()? this.getCachePrefix().prefix(cacheName) : null), this.getRedisOperations(), expiration); 92 } 93 94 /** 95 * get a messsage for update cache 96 * @param cacheName 97 */ 98 public void receiver(String cacheName){ 99 MyRedisCache cache = (MyRedisCache)this.getCache(cacheName); 100 if(cache==null){ 101 return ; 102 } 103 cache.cacheUpdate(); 104 105 } 106 107 //notify other redis clent to update cache( clear local cache in fact) 108 public void publishMessage(String cacheName){ 109 this.getRedisOperations().convertAndSend(topicName, cacheName); 110 } 111 112 } 113 114 class MyRedisCache extends RedisCache{ 115 //local cache for performace 116 ConcurrentHashMap<Object,ValueWrapper> local = new ConcurrentHashMap<>(); 117 MyRedisCacheManager cacheManager; 118 public MyRedisCache(MyRedisCacheManager cacheManager,String name, byte[] prefix, 119 RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration) { 120 super(name, prefix, redisOperations, expiration); 121 this.cacheManager = cacheManager; 122 } 123 @Override 124 public ValueWrapper get(Object key) { 125 ValueWrapper wrapper = local.get(key); 126 if(wrapper!=null){ 127 return wrapper; 128 }else{ 129 wrapper = super.get(key); 130 if(wrapper!=null){ 131 local.put(key, wrapper); 132 } 133 134 return wrapper; 135 } 136 137 } 138 139 @Override 140 public void put(final Object key, final Object value) { 141 142 super.put(key, value); 143 cacheManager.publishMessage(super.getName()); 144 } 145 146 @Override 147 public void evict(Object key) { 148 super.evict(key); 149 cacheManager.publishMessage(super.getName()); 150 } 151 152 153 @Override 154 public ValueWrapper putIfAbsent(Object key, final Object value){ 155 ValueWrapper wrapper = super.putIfAbsent(key, value); 156 cacheManager.publishMessage(super.getName()); 157 return wrapper; 158 } 159 160 public void cacheUpdate(){ 161 //clear all cache for simplification 162 local.clear(); 163 } 164 165 } 166 167 168} 169 170class StarterCacheCondition implements Condition { 171 172 173 @Override 174 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 175 RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( 176 context.getEnvironment(), "springext.cache."); 177 178 String env = resolver.getProperty("type"); 179 if(env==null){ 180 return false; 181 } 182 return "local2redis".equalsIgnoreCase(env.toLowerCase()); 183 184 } 185 186}

代码的核心在于spring boot提供一个概念CacheManager&Cache用来表示缓存,并提供了多达8种实现,但由于缺少一二级缓存,因此,需要在Redis基础上扩展,因此实现了MyRedisCacheManger,以及MyRedisCache,增加一个本地缓存。

一二级缓存需要解决的的一个问题是缓存更新的时候,必须通知其他节点的springboot应用缓存更新。这里可以用Redis的 Pub/Sub 功能来实现,具体可以参考listenerAdapter方法实现。

使用的时候,需要配置如下,这样,就可以使用缓存了,性能杠杠的好

1springext.cache.type=local2redis 2 3# Redis服务器连接端口 4spring.redis.host=172.16.86.56 5spring.redis.port=6379
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )