zoukankan      html  css  js  c++  java
  • 使用Spring Ehcache二级缓存优化查询性能

    最近在对系统进行优化的时候,发现有些查询查询效率比较慢,耗时比较长,

    通过压测发现,主要耗费的性能 消耗在 查询数据库,查询redis

    数据库:连接池有限,且单个查询不能消耗大量的连接池,占用大量IO,否则会引起整个应用的IO异常及连接池异常及数据库相关性能压力,导致无法访问

    redis:reidis是单线程的,如果大量的查询都会存 取 缓存,这样会导致IO异常及导致redis 慢查询,redis拥堵,redis崩溃等问题.

    注意:  下图的相关改造,适用于不太频繁改动的数据,但是会大量频繁访问的相关基础数据,

    如  图片,广告,基础数据,相关配置数据,数据字典等相关数据, 不适合动态要求较高的数据.且对数据一致性延时性较高的数据

     下图是优化之前的调用示意图

    优化之后的示意图

    redis是分布式缓存,根据单独的key可以查询到具体的内容数据,但是由于其分布式,需要访问网络需要消耗IO性能,在高并发电商环境的情况下,对带宽对IO对服务器都是考验

    使用EHCACHE作为内存缓存,优先访问内存缓存,不存在的情况下,优先将redis缓存数据放到每个应用独自的EHCACHE的缓存中,通过配置EHCACHE的生命周期,定期自动清理缓存,来达到提高性能的

    对业务进行了整改,整改过程中,使用了Ehcache,Redis进行优化,同时对redis的缓存数据结构进行了更改,  原先redis的数据结构使用了hset的形式,导致在压测的时候,KEY集中在同一个服务器,导致redis获取异常,故本次改成了set形式,使redis的key分散处理,不再集中在相同的key中,这样更能提高性能

    废话这么多,直接说重点,介绍接入EHCACHE

    本次改造,主要是对基础数据进行接入

    1:在resource下增加配置文件为myehcache.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache>
        <!-- 磁盘缓存位置 -->
        <diskStore path="java.io.tmpdir/myehcache"/>
        <!-- 默认缓存 -->
        <defaultCache
                maxEntriesLocalHeap="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                maxEntriesLocalDisk="10000000"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU"/>
        <cache name="testcache"
               maxElementsInMemory="50000"
               eternal="false"
               timeToIdleSeconds="30"
               timeToLiveSeconds="30"
               overflowToDisk="false"
               memoryStoreEvictionPolicy="LRU"/>
    </ehcache>

     ehcache的相关配置参数:

    diskStore    :指定数据存储位置,可指定磁盘中的文件夹位置
    defaultCache :默认的管理策略
    以下属性是必须的:
    name  :Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里).maxElementsInMemory:在内存中缓存的element的最大数目.
    maxElementsOnDisk:在磁盘上缓存的element的最大数目,默认值为0,表示不限制.
    eternal:设定缓存的elements是否永远不过期.如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断.
    overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上.
    以下属性是可选的:
    timeToIdleSeconds:对象空闲时间,指对象在多长时间没有被访问就会失效.只对eternal为false的有效.默认值0,表示一直可以访问.
    timeToLiveSeconds:对象存活时间,指对象从创建到失效所需要的时间.只对eternal为false的有效.默认值0,表示一直可以访问.
    diskPersistent:是否在磁盘上持久化.指重启jvm后,数据是否有效.默认为false.
    diskExpiryThreadIntervalSeconds:对象检测线程运行时间间隔.标识对象状态的线程多长时间运行一次.
    diskSpoolBufferSizeMB: DiskStore使用的磁盘大小,默认值30MB.每个cache使用各自的DiskStore.
    memoryStoreEvictionPolicy:如果内存中数据超过内存限制,向磁盘缓存时的策略.默认值LRU,可选FIFO、LFU.
    缓存的3种清空策略:
    FIFO ,first in first out(先进先出).
    LFU  , Less Frequently Used(最少使用),意思是一直以来最少被使用的.缓存的元素有一个HIT属性,HIT值最小的将会被清出缓存.
    LRU  ,Least Recently Used(最近最少使用)(EHCACHE 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,
    那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存.

    注意:
    timeToIdleSeconds:

    指对象在多长时间没有被访问就会失效.只对eternal为false的有效.默认值0,表示一直可以访问,如果配置了timeToIdleSeconds不为0,且timeToLiveSeconds也不为0,则在这个timeToIdleSeconds时间内没有被访问,也同时会过期释放
    timeToLiveSeconds:
    指对象从创建到失效所需要的时间.只对eternal为false的有效.默认值0,表示一直可以访问.
    如果仅仅配置timeToLiveSeconds,而timeToIdleSeconds配置为0,则表示缓存一直活跃,但是到了这个周期,则自动失效

    举例:
    timeToIdleSeconds 20 ,timeToLiveSeconds 30 说明:在20秒内,如果没有被访问,则自动过期,如果一直有被访问,在30秒内,依然过期.
    timeToIdleSeconds 0 ,timeToLiveSeconds 30 说明:无论有没有被访问,在30秒内,自动过期.

     2:在建一个spring-ehcache.xml,  并引用到applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:cache="http://www.springframework.org/schema/cache"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation=" http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/cache
           http://www.springframework.org/schema/cache/spring-cache-3.1.xsd">
    
        <!-- 自动扫描注解的bean -->
        <context:component-scan base-package="com.beijing.cache" />
        <!-- 注册EhCache注解驱动,使spring能识别EhCache的注解 -->
        <cache:annotation-driven cache-manager="cacheManager" />
        <!-- 注册EhCache缓存管理器 -->
        <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
            <property name="cacheManager" ref="ehcache"/>
        </bean>
        <!-- 构建EhCache的缓存管理工厂 -->
        <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
            <property name="configLocation" value="classpath:conf/myehcache.xml"/>
        </bean>
    </beans>

    3:配置注解,构建需要对缓存的方法(以下代码只是示例) 

        @Cacheable(value = "testcache") 
        public String queryUserInfoByParam(String type,String name){ 
            String name = null;
            if(StringUtils.isBlank(type) || StringUtils.isBlank(name)){
                return null;
            }
            String key = getCacheKey(type,name);//先查询redis缓存,如果redis缓存存在,则使用redis缓存
            name = redisClient.get(key);
            if(StringUtils.isBlank(name)){
                User user = getInfo(type,name);//查询数据库,查询配置
                if(null!=user && StringUtils.isNotBlank(user.getName())){
                    name = user.getName();
                    redisClient.set(key,name,60*30);//存储到redis中
                }
            }
            return name;
        }

    关键字 :

    @Cacheable(value = "testcache") ,testcache必须要与ehcache中的配置要一致,否则会抛出异常等情况

    Spring 使用@Cacheable添加缓存是基于面向切面的思想做的, 实际上就是使用Java动态代理,创建实例的时候注入的是代理对象,在代理对象里调用实际的对象,这样就可以在实际的方法执行前,处理一下缓存的逻辑:没有找到缓存就往下执行,执行完把结果加入到缓存中;找到缓存则直接返回缓存的结果,不调用执行实际的方法。

    关于Ehcache注解不生效的坑

    一个方法A调同一个类里的另一个有缓存注解的方法B,这样是不走缓存的。

    使用@Cacheable添加缓存实际上就是使用动态代理做的,在代理的方法前后做缓存的相应处理。单独的去调方法B是有缓存的,但是如果调方法A,A里面再去调B方法,哪怕B方法配置了缓存,也是不会生效的。

    解决方法:
     a、不使用注解的方式,直接取 Ehcache 的 CacheManger 对象,把需要缓存的数据放到里面,类似于使用 Map,缓存的逻辑自己控制
     b、把方法A和方法B放到两个不同的类里面,例如:如果两个方法都在service接口里,把方法B放到另一个service里面,这样A方法里调B方法,就可以使用B方法的缓存

     4:按照以上方式,那么基本就完成了EHCACHE的基本配置了,EHCACHE就能缓存,可以通过debug的方式进行测试或者加入日志的方式进行测试

  • 相关阅读:
    关于微信最新推出的应用号的看法
    HTML常见标签
    重读《从菜鸟到测试架构师》-- 模拟客户的访问行为(上)
    重读《从菜鸟到测试架构师》-- 大促带来的灾难
    重读《从菜鸟到测试架构师》-- 功能测试之百种变身
    重读《从菜鸟到测试架构师》-- 对黑盒子的全方位照明
    重读《从菜鸟到测试架构师》-- 如何把黑盒子分块
    重读《从菜鸟到测试架构师》-- 黑色的盒子里有什么(下)
    重读《从菜鸟到测试架构师》--黑色的盒子里有什么(中)
    重读《从菜鸟到测试架构师》-- 黑色的盒子里面有什么(上)
  • 原文地址:https://www.cnblogs.com/lewisat/p/11202714.html
Copyright © 2011-2022 走看看