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

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


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

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

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

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

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

SpringBoot,用200行代码完成一个一二级分布式缓存的更多相关文章

  1. 不到 200 行代码,教你如何用 Keras 搭建生成对抗网络(GAN)【转】

    本文转载自:https://www.leiphone.com/news/201703/Y5vnDSV9uIJIQzQm.html 生成对抗网络(Generative Adversarial Netwo ...

  2. 200行代码,7个对象——让你了解ASP.NET Core框架的本质

    原文:200行代码,7个对象--让你了解ASP.NET Core框架的本质 2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘&g ...

  3. 200行代码实现Mini ASP.NET Core

    前言 在学习ASP.NET Core源码过程中,偶然看见蒋金楠老师的ASP.NET Core框架揭秘,不到200行代码实现了ASP.NET Core Mini框架,针对框架本质进行了讲解,受益匪浅,本 ...

  4. 200 行代码实现基于 Paxos 的 KV 存储

    前言 写完[paxos 的直观解释]之后,网友都说疗效甚好,但是也会对这篇教程中一些环节提出疑问(有疑问说明真的看懂了 ),例如怎么把只能确定一个值的 paxos 应用到实际场景中. 既然 Talk ...

  5. 通过 Mesos、Docker 和 Go,使用 300 行代码创建一个分布式系统

    [摘要]虽然 Docker 和 Mesos 已成为不折不扣的 Buzzwords ,但是对于大部分人来说它们仍然是陌生的,下面我们就一起领略 Mesos .Docker 和 Go 配合带来的强大破坏力 ...

  6. 200行代码实现简版react🔥

    200行代码实现简版react

  7. 通过Mesos、Docker和Go,使用300行代码创建一个分布式系统

    [摘要]虽然 Docker 和 Mesos 已成为不折不扣的 Buzzwords ,但是对于大部分人来说它们仍然是陌生的,下面我们就一起领略 Mesos .Docker 和 Go 配合带来的强大破坏力 ...

  8. 在Hibernate中使用Memcached作为一个二级分布式缓存

    转自:http://www.blogjava.net/xmatthew/archive/2008/08/20/223293.html   hibernate-memcached--在Hibernate ...

  9. 200行代码,7个对象——让你了解ASP.NET Core框架的本质

    2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...

随机推荐

  1. E - 卿学姐与城堡的墙(树状数组求逆序数)

    卿学姐与城堡的墙 Time Limit: 2000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit  ...

  2. php文件上传php.ini配置参数

    php文件上传服务器端配置参数 file_uploads = On,支持HTTP上传uoload_tmp_dir = ,临时文件保存目录upload_max_filesize = 2M,允许上传文件的 ...

  3. [Vue] vue的一些面试题2

    1.Vue.observable 你有了解过吗?说说看 vue2.6 发布一个新的 API,让一个对象可响应.Vue 内部会用它来处理 data 函数返回的对象.返回的对象可以直接用于渲染函数和计算属 ...

  4. 缓存---LRU算法实现

    2.LRU   以下是基于双向链表+HashMap的LRU算法实现,对算法的解释如下:   设置一个map存放对应的键和值,同时设置一个双向链表,来保存最近最久未使用的关系,如果访问一个键,键存在于m ...

  5. RabbitMQ入门教程(十三):虚拟主机vhost与权限管理

    原文:RabbitMQ入门教程(十三):虚拟主机vhost与权限管理 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://b ...

  6. TypeError: esri.layers.WMSLayer is not a constructor

    最近加载wms地图后,总是报这个错误,因为错误,导致后续的代码无法加载,导致无法功能使用. 原因是,由于方法公用,有的新功能在使用时,引用依赖包时,未引用完整,导致加载此处加载wms图层的时候, 报此 ...

  7. the server responsed width a status of 404 (Not Found)

    最近使用VS code写代码,使用Beautify插件格式化代码后,报以下错误: 反复查看代码,初始感觉没什么问题,有点懵.. 随着时间的推移,后来果然发现问题所在: 在加载模块的地方,多出了个空格( ...

  8. php框架之laravel

    常见问题: 1. 访问网站500错误 这是因为laravel的缓存路径没有找到 laravel缓存文件路径是在 config/cache.php中设置,默认存在storage文件夹中 解决:需要保证s ...

  9. vue.js(3)--v-bind与v-on

    vue中的v-bind与v-on的使用 (1)实例 <!DOCTYPE html> <html lang="en"> <head> <me ...

  10. 使用Lombok来优雅的编码

    介绍在项目中使用Lombok可以减少很多重复代码的书写.比如说getter/setter/toString等方法的编写. IDEA中的安装打开IDEA的Setting –> 选择Plugins选 ...