概述 之前有写过springboot缓存应用的说明(《springboot入门01 – 缓存的使用》)。不过实际的场景有时候会比较复杂一些,比如:需要同时使用redis和caffine来做多级缓存,或者需要在通用配置外应用一些个性化的配置。使用多个CacheManager来分别管理不同的缓存是应对这种问题的一个常规方案。 接下来介绍下如何实现多CacheManager应用。 多CacheManager应用 这里会通过一个具体的案例来进行演示。需求大致是这样的:在应用中的大部分场景都使通用的缓存配置,但是部分特殊场景需要做个性化的配置。在接下来演示中我们主要会用到Caffeine缓存。 配置 先修改下配置。这次如果继续使用SpringBoot的自启动CacheManager会有一些不太好控制的地方,因此不宜再使用默认的缓存配置,需要做些独立配置: 其中 caching.spec 表示通用的缓存配置, caching.special则表示一组需要做个性化配置的特例。 通用配置是一行字符串,读取的时候可以直接使用@Value注解,个性化配置则是一个Map结构,读取的时候需要用到@ConfigurationProperties注解,大致如下: (关于配置文件读取可以参考之前的一篇旧文:《springboot入门07 – 配置文件详解》) 创建CacheManager 接下来就是根据配置文件创建CacheManager了。 创建通用CacheManager直接使用CaffeineCacheManager就可以了: 注意@Primary注解,这个注解表示没有特意注明时,优先选择这个CacheManager。 管理个例的CacheManager略有些麻烦,这里使用了SimpleCacheManager,代码如下: 在使用这个CacheManager时还需要记得下在@CacheConfig或@Cacheable注解中注明对应的qualifier: 至此,多缓存配置已经是没有问题了。详细代码可以参考Git: zhyea / multi-cache 这里区分通用CacheManager与个例CacheManager主要依赖@Primary注解实现。接下来会介绍一些其他的方案。 继承CachingConfigurerSupport 继承抽象类CachingConfigurerSupport后,可以通过实现(重写)cacheManager()方法指明默认的CacheManager。嗯,也就是说,节省了一个@Bean和一个@Primary注解。代码如下: 管理个例的CacheManager还是需要@Bean注解并设置Qualifier的。 实现CacheResolver 这个方案,怎么说呢,应该是可操作性最强大的。如果场景再复杂些,完全可以考虑用这个方案来处理。但是就我们当前这个case来说,用CacheResolver来实现应该是最繁琐的了。太繁琐了,懒得写了。 姑且写一个简单的例子来演示下CacheResolver是怎么发挥作用的吧。 CacheResolver的实现类如下: 在代码中可以看到,是通过resolveCaches()方法决定了提供哪些缓存。 使用CacheResolver后就有机会可以考虑不注入CacheManager的实例到容器中了,因为CacheResolver会管理会用到的CacheManager的实例。 不过在应用缓存注解的情况下,要记得指定使用哪个CacheResolver,像这样: 就这样了。示例代码都放在了这里:zhyea / multi-cache 参考文档 Using Multiple Cache Managers in Spring
[阅读更多...]-
springboot入门13 – 多CacheManager应用
-
springboot入门12 – SpringBoot MyBatis读写分离
概述 随业务量增长,数据库读写分离是迟早要面临的问题。另外,公司在上规模后一般也会要求统一采用主从分布式数据库。 我习惯的处理方案是在应用层进行隔离:即将以写为主的业务放在一个应用上,以读为主的业务放在其他应用上。这应该算是最简单粗暴的解决方案了,却也能帮我应对90%需要读写分离的场景。不过总还有10%的特殊场景需要思考下怎样在应用内实现读写分离。 在应用内做读写分离大体上需要考虑三件事情: 多数据源实现 读写请求识别 读写请求分流 其中后两点是执行读写分离的关键。 接下来详细介绍下怎么在Springboot+MyBatis的应用中实现读写分离。这里会用到H2数据库和dbcp2数据库连接池。在测试中不会真的创建一个数据库集群,我们只需要能够验证写入和读取是访问的两个不同的数据库即可。 1. 多数据源实现 之前在《SpringBoot自定义数据源及多数据源配置》这篇文里我有介绍过怎样做多数据源实现。这次的做法也差不多。 下面是在配置文件中做的多数据源配置: 这里配置了两个H2的内存数据库,我们权且当它们是一个集群吧。 然后是在一个配置类DsConfig中读取配置: 和之前那篇文章《SpringBoot自定义数据源及多数据源配置》略有不同,这次是用DataSourceProperties来表示读取的配置信息。 注意不要忽略了@Primary注解,不然会报错。 可以这样使用DataSourceProperties创建DataSource的实例: 这里只是想试试这种方案。按照老路子创建DataSource实例也是OK的,并且还会更简洁: 至此多数据源配置已经完成。 2. 读写请求识别 也看过其他人的读写分离方案,其中读写请求识别这一层多是通过自定义注解+AOP来实现的。这种方案当然没问题,但是稍嫌有些繁琐,如果忘掉了添加注解就会导致意外。我更想要的是一种‘润物细无声’的实现。 之前做过一个为写入数据库的实例赋默认值的方案:《MyBatis写入时null问题统一处理方案》。这个方案的思路是通过自定义实现MyBatis拦截器来拦截写数据库请求并补上未赋值的数据。稍稍变通下,就可以改为拦截并识别查询语句: 查询请求主要是由Executor类的query方法实现的,所以只要针对其进行拦截并执行判断即可。 因为判断读写请求和执行读写分流是在两个环节执行,我们需要找个地方将判断结果存储起来,并且保证线程安全,很自然可以想到使用ThreadLocal执行存储。DsContextHolder就是基于ThreadLocal执行的存储。看下实现: 接下来就可以使用DsContextHolder中存储的信息来执行分流了。 3. 读写请求分流 读写请求分流这一层主要依赖了AbstractRoutingDataSource这个类。核心是下面这个方法: 看名字也能知道,determineTargetDataSource决定了为之后的请求提供哪个数据源。根据代码可以看出来,我们至少需要做两件事: 为resolvedDataSources赋值,即设置相关数据源 实现分流方法determineCurrentLookupKey() 具体如何继承AbstractRoutingDataSource类并实现路由方案可以参考如下代码: 在afterPropertiesSet()方法中完成了自定义数据源的创建和设置,并且还将dsWrite设置为了默认数据源。 方法determineCurrentLookupKey()基于DsContextHolder中存储的内容提供了分流的关键字。 大体上就是这样了。具体实现代码已经上传到了GitHub : zhyea/database-wr End!! 参考文档 mybatis读写分离
[阅读更多...]