发表于: 2017-12-04 16:24:02
1 883
今天完成的事
这是一篇很长的故事。
long long ago~
我从网上看到了redis还有一种注解形式的实现。
我想尝试一下。就开始了坑爹之旅。
所有的东西都写好了之后,有一个报错。
别小看这个bug。
看上去是一个不能进行转换类型。。
其实涉及很多东西。
首先你要知道SimpleKey是个啥。
知识点!
键生成器
缓存的本质就是键值对集合。 在默认情况下,缓存抽象使用方法签名以及参数作为key,并将该键与方法调用的结果作为Value,如果在Cache注解上没有指定Key,则Spring会使用KeyGenerator来生成一个key.
我带你看一下这老哥的源码。
第一个在DefaultKeyGenerator在spring4.0被干掉了。
我们来分析默认的下面那个SimpleKey
public class SimpleKeyGenerator implements KeyGenerator {
public SimpleKeyGenerator() {
}
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
public static Object generateKey(Object... params) {
if(params.length == 0) {
return SimpleKey.EMPTY;
} else {
if(params.length == 1) {
Object param = params[0];
if(param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
}
这个源码挺简单的,大概说了三件事,
如果方法没有入参,这是用SimpleKey,MPTY作为key
如果只有一个入参,这是用该入参作为Key
如果有多个入参,则返回包含所有入参的一个SimpleKey
看似扯的有点远。离我的bug没什么关系。
实在不然,我们这时候知道了注解会默认给我生成一个SimpleKey.
先记住这里。
再去看我们的bug。这个bug。百度是没有关联的。
开启谷歌搜索。
谷歌牛逼!点进去。
简单理解一下就是下面哪个继承redisConfig.
缺少KeyGenerator方法。
这个方法是什么呢。
其实就是个密钥。
我看一下我的redis,。配置类。确实没有。
改。
添加一个。(网上找的。
重新配置springXML文件。
<!-- jedis 配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" >
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean >
<!-- redis服务器中心 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<property name="poolConfig" ref="poolConfig" />
<property name="port" value="${redis.port}" />
<property name="hostName" value="${redis.host}" />
<property name="password" value="${redis.password}" />
<property name="timeout" value="${redis.timeout}" />
</bean >
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" >
<property name="connectionFactory" ref="connectionFactory" />
<property name="keySerializer" >
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer" >
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
</bean >
<bean id="methodCacheInterceptor" class="com.xiuzhen.utils.RedisCacheConfig" >
<constructor-arg ref="connectionFactory"/>
<constructor-arg ref="redisTemplate"/>
<constructor-arg ref="cacheManager"/>
</bean>
<cache:annotation-driven/>
<!-- spring自己的缓存管理器,这里定义了缓存位置名称 ,即注解中的value -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<property name="defaultExpiration" value="${redis.expiration}"/>
<constructor-arg name="redisOperations" ref="redisTemplate" />
</bean>
再次跑测试类。
又报错了。但是没得关系。
接着看报错信息,不可能是因为 KeyGenerator问题了。因为我已经配置好了。
点进去。
这就是simnpleKey,不能序列化的事了。
那我直接不让他序列化好了。
或者将hash值序列化。
<property name="HashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
<property name="HashValueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
注解掉。
再测试。
嘻嘻。
时隔三天。
搞定。
。
这件事成功了意味着什么呢。
意味着我的代码service,将变得无比整洁。
除了注解没有任何其余关于redis的东西。
给你看效果。
我整个页面所有需要缓存的东西只要在注解上面添加一个。
@Cacheable(cacheNames = "abc" )
但是这个事情其实还有一个小bug,但是解决了,解决方案见今日问题啊baby~
【接着做一点点任务7】
搭了点demo,没跑通,代码就不贴了,跟狗皮膏药似的。
而且我的网站用户属性还缺几个好像。。。不管了,明天再弄。
收获
任务六也算收尾了。
把最后几天做的事总结一下。
先说一下spring和redis注解怎么结合的。
【Spring缓存的基本原理】
和 spring 的事务管理类似,spring cache 的关键原理就是 spring AOP,通过 spring AOP,其实现了在方法调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。
和之前的统计数据库所做的时间监控AOP原理差不多,指定方法执行。
@Cacheable是最主要注解,它指定了被注解方法的返回值是可以被缓存的。 其工作原理是Spring首先会在缓存中查找数据,如果没有则执行方法并缓存结果,然后返回数据。
缓存名称是必须要提供的,可以使用 引号、Value或者acheNames属性来定义名称。 比如
此外,还可以我在声明中指定键值,@Cacheable注解提供了实现该功能的key属性,通过该属性,可以使用SpELl指定自定义键。 如下所示
@CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
@CacheEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空
@CacheConfig 类级别的全局缓存注解
Spring4.0之前并没有类级别的全局缓存注解。 前面的4个注解都是基于方法的,如果在同一个类中需要缓存的方法注解属性都类似,则需要一个个的重复增加,Spring4增加了@CacheConfig类级别的注解解决这个问题。
其实还有一些别的注解我理解的不多,列出来以后回来填坑。
【Spring cache+ redis与redis 的比较】
我费了三天时间到底为了什么呢?
这件事除了看上去代码整洁还有别的区别么?
1. 缓存级别不同
2. sprirng cache+redis的好处
3.集群环境下的springcache+redis
4. spring cache基本原理
和 spring 的事务管理类似,spring cache 的关键原理就是 spring AOP,通过 spring AOP,其实现了在方法调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。我们来看一下下面这个图:

上图显示,当客户端“Calling code”调用一个普通类 Plain Object 的 foo() 方法的时候,是直接作用在 pojo 类自身对象上的,客户端拥有的是被调用者的直接的引用。
而 Spring cache 利用了 Spring AOP 的动态代理技术,即当客户端尝试调用 pojo 的 foo()方法的时候,给他的不是 pojo 自身的引用,而是一个动态生成的代理类
图 3. 动态代理调用图

如上图所示,这个时候,实际客户端拥有的是一个代理的引用,那么在调用 foo() 方法的时候,会首先调用 proxy 的 foo() 方法,这个时候 proxy 可以整体控制实际的 pojo.foo() 方法的入参和返回值,比如缓存结果,比如直接略过执行实际的 foo() 方法等,都是可以轻松做到的。
【关于任务六的一点补充】
Redis集群方案应该怎么做?
这也算扩展一点,先说为什么做集群。
首先,无论我们是使用自己的物理主机,还是使用云服务主机,内存资源往往是有限制的,scale up不是一个好办法,我们需要scale out横向可伸缩扩展,这需要由多台主机协同提供服务,即分布式多个Redis实例协同运行。
其次,目前硬件资源成本降低,多核CPU,几十G内存的主机很普遍,对于主进程是单线程工作的Redis,只运行一个实例就显得有些浪费。同时,管理一个巨大内存不如管理相对较小的内存高效。因此,实际使用中,通常一台机器上同时跑多个Redis实例。
方案有下面几种
1.Redis官方集群方案 Redis Cluster
2.Redis Sharding集群
3.利用代理中间件实现大规模Redis集群
以我现在的水平是看不明白了。所以老规矩,先马,以后回来报仇。
缓存命中率
即从缓存中读取数据的次数与总读取次数的比率。 一般来讲,命中率越高也好。
命中率 = 从缓存中读取的次数 / (总读取次数[从缓存中读取的次数+从慢速设备上读取的次数])
Miss率 = 没从缓存中读取的次数/ (总读取次数[从缓存中读取的次数+从慢速设备上读取的次数])
这是一个非常重要的监控指标,如果要做缓存,就一定要监控这个指标,来看缓存是否工作良好。
目前来讲我的项目缓存率100%,为什么设置缓存了还要从数据库取数据。。。还是说缓存性能不够的时候数据库来凑?
过期策略
即如果缓存满了,从缓存中移除数据的策略,常见的有 LFU 、LRU、FIFO
FIFO (First in First Out) 先进先出策略,即先放入缓存的数据先被移除
LRU (Least Recently Used) 最久未使用策略, 即使用时间距离现在最久的那个数据被移除
LFU (Leaset Frequently Used) 最近最少使用策略,即一定时间内使用次数(频率)最少的那个数据被移除
TTL(Time To Live)存活期,即从缓存中创建时间点开始至到期的一个时间段(不管在这个时间段内有没被访问过都将过期)
TTI (Time To Idle)空闲期,即一个数据多久没有被访问就从缓存中移除的时间。
至此,我们基本了解了缓存的一些基本知识。 在Java中一般会对调用方法进行缓存控制,比如 findUserById(Sting id),先从缓存中查找有没有符合查询条件的数据,如果没有,则执行改方法从数据库中查找该用户,然后添加到缓存中,下次调用时将从缓存中获取。
从Spring3.1开始,提供了缓存注解,并且提供了Cache层的抽象。 此外,JSR-107也从Spring4.0开始得到全面支持。
Spring提供可一种可以在方法级别进行缓存的缓存抽象。 通过使用AOP对方法机型织入,如果已经为特定方法入参执行过该方法,那么不必执行实际方法就可以返回被缓存的结果。
为了启用AOP缓存功能,需要使用缓存注解对类中的相关方法进行标记,以便Spring为其生成具备缓存功能的代理类。 需要注意的是,Spring Cache仅提供了一种抽象而未提供具体的实现。 我们以便会自己使用AIP来做一定程度的封装实现。
表达式自定义键的生成
用到一个知识,spel,知识点!
当然不需要你掌握太多,应付一下任务六就行。
大概就这些。主要是第一个。。
遇到的问题
存入缓存的时候如果你不定义键值。
没发现问题对么。
这件事情很难受,主要是你不仔细看你看不出来问题。
但是这个是有问题的,这件事说明了如果我不主动定义键值。
他在我的同一个类下面所有的方法生成的键值都一样。
这样就不能存入多个缓存。
解决方法就是定义一个键值。别让他默认生成。
像这样。
这件事设计了spring-redis底层的一些知识点。。
我还没太懂。什么时候弄懂了下个博客啥的。(立个flag
明天的计划
开始任务7
禅道链接
http://task.ptteng.com/zentao/task-view-14550.html
评论