• 数据库缓存mybatis,redis


    • 简介

      处理并发问题的重点不在于你的设计是怎样的,而在于你要评估你的并发,并在并发范围内处理。
      你预估你的并发是多少,然后测试r+m是否支持。缓存的目的是为了应对普通对象数据库的读写限制,依托与nosql的优势进行高速读写。

      redis本身也有并发瓶颈。所以你要把读写和并发区分开来处理。只读业务是不是可以用mysql分布做只读库和只读表,进行读写分离+库分布,
      拆库拆表不能搞定再考虑上多级缓存
      任何设计,你外面套一层,就多一倍的维护成本,缓存不是万金油。

      这里多级缓存主要指的是二级缓存技术,也就是依托nosql的高速读取优势。

    • mybatis

      如果底层ORM框架用的是mybatis框架,就可以用mybatis自带的缓存机制,mybatis自带一级缓存和二级缓存。
      一级缓存指的是sqlsession缓存,是session级别缓存
      二级缓存指的是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的,这里的namespace指的是xml里的命名空间
      mybatis默认开启一级缓存,二级缓存默认不开启,需要手动开启。

      一级缓存
      mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存数据,
      此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。
      具体流程:

            1.第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来

            2.第二次执行select会从缓存中查数据,如果select相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率

        注意事项:

            1.如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,
      这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读

            2.当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置

            3.mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSession的HashMap存储缓存数据时,
      是使用[namespace:sql:参数]作为key,查询返回的语句作为value保存的。
      例如:-1242243203:1146242777:winclpt.bean.userMapper.getUser:0:2147483647:select * from user where id=?:19

      二级缓存
      二级缓存是mapper级别的缓存,也就是同一个namespace的mappe.xml,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域

      二级缓存默认是没有开启的,需要在xml配置setting全局参数中配置开启二级缓存

      <settings>
              <setting name="cacheEnabled" value="true"/><!--开启mybatis缓存 默认是false:关闭二级缓存--><settings>

      然后再具体的xml里配置

      <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>当前mapper下所有语句开启二级缓存

      这里配置了一个LRU缓存,并每隔60秒刷新,最大存储512个对象,而却返回的对象是只读的

      若想禁用当前select语句的二级缓存,添加useCache="false"修改如下:

      <select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">

      具体流程:

            1.当一个sqlseesion执行了一次select后,在关闭此session的时候,会将查询结果缓存到二级缓存

            2.当另一个sqlsession执行select时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,
      找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能 

      注意事项:

            1.如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前mapper缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读

            2.mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSession的HashMap存储缓存数据时,是使用[namespace:sql:参数]作为key,查询返回的语句作为value保存的。例如:-1242243203:1146242777:winclpt.bean.userMapper.getUser:0:2147483647:select * from user where id=?:19

    • redis

      这里我实现了spring集成Jedis,并通过解析key实现自定义缓存失效时间。官网可以下载linux下的redis,没有windows下的,但micro开发小组在github上维护了windows环境下的redis,
      https://github.com/MSOpenTech/redis/releases

      pom.xml引入依赖
        <!--redis start-->
              <dependency>
                  <groupId>org.springframework.data</groupId>
                  <artifactId>spring-data-redis</artifactId>
                  <version>1.6.0.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>redis.clients</groupId>
                  <artifactId>jedis</artifactId>
                  <version>2.7.3</version>
              </dependency>
       <!--redis end-->

      在xml配置文件里配置各种bean

       <!--JedisPool线程池-->
          <!--redis是单线程的,为了满足java多线程需求,jedis引入线程池的概念-->
          <bean id="jedisPool" class="redis.clients.jedis.JedisPoolConfig">
              <property name="maxIdle" value="100"/>
              <property name="maxTotal" value="700"/>
              <property name="maxWaitMillis" value="1000"/>
              <property name="testOnBorrow" value="false"/>
          </bean>
      
          <!--创建redis连接工厂-->
          <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
              <property name="hostName" value="${redis.hostname}"/>
              <property name="port" value="${redis.port}"/>
              <property name="password" value="${redis.password}"/>
              <property name="database" value="${redis.database}"/>
              <property name="poolConfig" ref="jedisPool"/>
          </bean>
      
          <!--配置redistempLate -->
          <bean id="redistempLate" class="org.springframework.data.redis.core.RedisTemplate">
              <property name="connectionFactory" ref="jedisConnectionFactory"/>
              <!--<property name="defaultSerializer">-->
                  <!--<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>-->
              <!--</property>-->
          </bean>
          <!--配置缓存配置信息-->
          <!--这是用的spring的cacheManager,不支持缓存有效期动态配置-->
          <!--<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">-->
              <!--&lt;!&ndash;配置redis模板&ndash;&gt;-->
              <!--<constructor-arg name="redisOperations" ref="redistempLate"/>-->
              <!--&lt;!&ndash;默认缓存失效时间&ndash;&gt;-->
              <!--<property name="defaultExpiration" value="120"/>-->
              <!--&lt;!&ndash;是否使用前缀&ndash;&gt;-->
              <!--<property name="usePrefix" value="false"/>-->
      
          <!--</bean>-->
      
          <!--手动实现的redisCacheManager,支持缓存有效期动态配置,可在@Cacheable中的value属性添加有效时间-->
          <bean id="myRedisCacheManager" class="com.djkj.demo.common.MyRedisCacheManager">
              <!--配置redis模板-->
              <constructor-arg name="redisOperations" ref="redistempLate"/>
              <!--默认缓存失效时间-->
              <property name="defaultExpiration" value="120"/>
              <!--是否使用前缀-->
              <property name="usePrefix" value="false"/>
              <!--缓存名字和有效期的分隔符-->
              <property name="separator" value="#"/>
      
              <!-- 多个缓存有效期,一般的单个工程可以省略此项 -->
              <!--<property name="expires">-->
                  <!--<map>-->
                      <!--<entry key="caiya_a" value="1800"/>-->
                  <!--</map>-->
              <!--</property>-->
      
          </bean>
      
          <!--配置RedisCacheConfig,redis缓存启动类-->
          <bean id="redisCacheConfig" class="com.djkj.demo.common.RedisCacheConfig">
              <constructor-arg ref="jedisConnectionFactory"/>
              <constructor-arg ref="redistempLate"/>
              <constructor-arg ref="myRedisCacheManager"/>
          </bean>

      这里jedisPool的数据源地址根据实际情况设置,reis默认port为6379,如果你的redis设置了校验密码则这里需要否则不用,database设置的0,redis默认生成15个数据库,0表示采用的是第一个。

      最主要的是redisCacheConfig缓存启动类和myRedisCacheManager缓存管理类,启动类里设置了缓存的key的生成策略,管理类主要实现了自定义有效期
      redisCacheConfig
      package com.djkj.demo.common;
      
      import org.springframework.cache.annotation.CachingConfigurerSupport;
      import org.springframework.cache.annotation.EnableCaching;
      import org.springframework.cache.interceptor.KeyGenerator;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.data.redis.cache.RedisCacheManager;
      import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
      import org.springframework.data.redis.core.RedisTemplate;
      
      import java.lang.reflect.Method;
      
      @Configuration
      @EnableCaching
      public class RedisCacheConfig extends CachingConfigurerSupport {
      
          private volatile JedisConnectionFactory jedisConnectionFactory;
          private volatile RedisTemplate<String,String> redisTemplate;
          private volatile RedisCacheManager redisCacheManager;
      
      
          public RedisCacheConfig(){
              super();
          }
      
          public RedisCacheConfig(JedisConnectionFactory jedisConnectionFactory
                  ,RedisTemplate<String,String> redisTemplate, RedisCacheManager redisCacheManager){
              this.jedisConnectionFactory = jedisConnectionFactory;
              this.redisTemplate = redisTemplate;
              this.redisCacheManager = redisCacheManager;
          }
      
          public JedisConnectionFactory getJedisConnectionFactory() {
              return jedisConnectionFactory;
          }
      
          public RedisTemplate<String, String> getRedisTemplate() {
              return redisTemplate;
          }
      
          public RedisCacheManager getRedisCacheManager() {
              return redisCacheManager;
          }
        //主键生成策略 @Bean
      public KeyGenerator customKeyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object o, Method method, Object... objects) { StringBuilder stringBuilder=new StringBuilder(); stringBuilder.append(o.getClass().getName()); stringBuilder.append(method.getName()); for (Object obj : objects) { stringBuilder.append(obj.toString().hashCode()); } System.out.println(stringBuilder.toString()); return stringBuilder.toString(); } }; } }

      myRedisCacheManager

      package com.djkj.demo.common;
      
      import org.apache.commons.lang3.StringUtils;
      import org.apache.log4j.Logger;
      import org.springframework.cache.Cache;
      import org.springframework.data.redis.cache.RedisCache;
      import org.springframework.data.redis.cache.RedisCacheManager;
      import org.springframework.data.redis.cache.RedisCachePrefix;
      import org.springframework.data.redis.core.RedisOperations;
      
      import javax.script.ScriptEngine;
      import javax.script.ScriptEngineManager;
      import javax.script.ScriptException;
      import java.util.Collection;
      import java.util.Collections;
      import java.util.regex.Pattern;
      
      public class MyRedisCacheManager extends RedisCacheManager {
      
          private static final Logger logger = Logger.getLogger(MyRedisCacheManager.class);
      
          private static final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript");
      
          private static final Pattern pattern = Pattern.compile("[+\-*/%]");
      
          private String defaultCacheName;
      
          private String separator = "#";
      
          public MyRedisCacheManager(RedisOperations redisOperations) {
              this(redisOperations, Collections.<String>emptyList());
          }
      
          public MyRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
              super(redisOperations, cacheNames);
          }
      
          @Override
          public Cache getCache(String name) {
              String cacheName="";
              String expirationStr="";
              Long expiration=0L;
              String[] params = name.split(getSeparator());
              if(params.length>=1){
                  cacheName = params[0];
              }
              if(params.length>=2){
                  expirationStr = params[1];
              }
      
              if(StringUtils.isBlank(cacheName)){
                  cacheName = defaultCacheName;
              }
              Cache cache = (RedisCache) super.getCache(cacheName);
              if (cache == null) {
                  return null;
              }
      
              if(StringUtils.isNotEmpty(expirationStr)){
                  try {
                      expiration = Double.valueOf(expirationStr).longValue();
                  }catch (Exception e){
                      logger.error("expiration exchange failed!");
                  }
              }
      
              if (expiration==null || expiration == 0L) {
                  logger.warn("Default expiration time will be used for cache '{}' because cannot parse '{}', cacheName : " + cacheName + ", name : " + name);
                  return cache;
              }
      
              return new RedisCache(cacheName, (isUsePrefix() ? getCachePrefix().prefix(cacheName) : null), getRedisOperations(), expiration);
          }
      
      
          public String getSeparator() {
              return separator;
          }
      
          public void setSeparator(String separator) {
              this.separator = separator;
          }
      
          private Long getExpiration(final String name, final int separatorIndex) {
              Long expiration = null;
              String expirationAsString = name.substring(separatorIndex + 1);
              try {
                  // calculate expiration, support arithmetic expressions.
                  if(pattern.matcher(expirationAsString).find()){
                      expiration = (long) Double.parseDouble(scriptEngine.eval(expirationAsString).toString());
                  }else{
                      expiration = Long.parseLong(expirationAsString);
                  }
              } catch (NumberFormatException ex) {
                  logger.error(String.format("Cannnot separate expiration time from cache: '%s'", name), ex);
              } catch (ScriptException e) {
                  logger.error(String.format("Cannnot separate expiration time from cache: '%s'", name), e);
              }
      
              return expiration;
          }
      
          @Override
          public void setUsePrefix(boolean usePrefix) {
              super.setUsePrefix(usePrefix);
          }
      
          @Override
          public void setCachePrefix(RedisCachePrefix cachePrefix) {
              super.setCachePrefix(cachePrefix);
          }
      
          public void setDefaultCacheName(String defaultCacheName) {
              this.defaultCacheName = defaultCacheName;
          }
      }

      根据符号  #  将缓存名切割,前面的作为缓存名,后面的作为有效期


    • 具体应用方法

      在service层通过@Cacheable注解使用缓存
      //设置缓存有效时间和刷新时间
      @Cacheable(value="testPojoCache" ,keyGenerator = "customKeyGenerator")
      @Override
      public List<TestPojo> query(TestPojo bean) {
      List<TestPojo> testList = testPojoMapper.query(bean);
      if(testList.size()>0){
      for(TestPojo pojo:testList){
      pojo.setTime(sdf.format(new Date()));
      pojo.setAttr1(bean.getAttr1());
      }
      }
      return testList;
      }

      testPojoCache表示缓存name,30表示有效期,keyGenerator表示key的生成策略,用的是在配置类里通过@Bean配置的bean。

    • 进阶说明

      spring通过集成jedis的方式有别于直接通过获取Jedis对象的set方法,通过jedis.set(key,value)只会在redis服务器产生一条数据,而用注解会产生两条数据,以上面的例子为例,首先会生成一条key为testPojoCache的缓存数据,value内容是一个集合,放的是通过生存策略生成的值,再以这生成的值为key生成一条缓存数据,value为这个service方法返回的对象数据。当你根据不同的条件参数调用service的方法,都会在testPojoCache里存放键值,然后再创建缓存。那怎么调用缓存呢?在调用service方法前,redis会根据生成策略生成的值到testPojoCache里去找,看有没有。若果有,根据键值获取缓存;如果没有就查询数据库。


  • 相关阅读:
    一个兼容各浏览器的阻止冒泡的StopPropagation解决方案
    百度面试题:从输入URL到显示网页,后台发生了什么?
    三角形面积公式S=(1/2)absinC的证明
    正弦定理证明(方法二)
    高中数学总结(高一上半部分内容)
    解析几何部分
    正弦定理的证明(方法一)
    平面向量的坐标表示
    将三角函数值转换为角度
    余弦定理方法证明
  • 原文地址:https://www.cnblogs.com/zhouyun-yx/p/10413444.html
Copyright © 2020-2023  润新知