发表于: 2017-12-04 16:24:02

1 882


今天完成的事

这是一篇很长的故事。

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. 缓存级别不同

    Spring cache是代码级的缓存,他一般是使用一个ConcurrentMap。也就是说实际上还是是使用JVM的内存来缓存对象的,
    那么肯定会造成大量的内存消耗。但是使用方便。
    Redis 作为一个缓存服务器,是内存级的缓存。它是使用单纯的内存来进行缓存。


2. sprirng cache+redis的好处

    那么Spring cache +redis的好处显而易见了。既可以很方便的缓存对象,同时用来缓存的内存的是使用redis的内存,不会消耗JVM的内存,提升了性能。当然这里Redis不是必须的,换成其他的缓存服务器一样可以,只要实现Spring的Cache类,并配置到XML里面就行了。

3.集群环境下的springcache+redis

    集群环境下,每台服务器的spring cache是不同步的,这样会出问题的,spring cache只适合单机环境
redis是设置单独的缓存服务器,所有集群服务器统一访问redis,不会出现缓存不同步的情况
spring cache是很早就有的东西,现在+redis是为了顺应时代,更好的兼容集群环境,加强保留spring cache功能,不如直接使用redis

4. spring cache基本原理

和 spring 的事务管理类似,spring cache 的关键原理就是 spring AOP,通过 spring AOP,其实现了在方法调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。我们来看一下下面这个图:


图 2. 原始方法调用图

上图显示,当客户端“Calling code”调用一个普通类 Plain Object 的 foo() 方法的时候,是直接作用在 pojo 类自身对象上的,客户端拥有的是被调用者的直接的引用。

而 Spring cache 利用了 Spring AOP 的动态代理技术,即当客户端尝试调用 pojo 的 foo()方法的时候,给他的不是 pojo 自身的引用,而是一个动态生成的代理类


图 3. 动态代理调用图
图 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


返回列表 返回列表
评论

    分享到