java并发编程学习20--基于springboot的秒杀系统实现2--redis缓存

【为何使用redis

  • 性能极高,redis能读的速度是110000次/s,写的速度是81000次/s
  • 丰富的数据类型,redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操做
  • redis命令友好易用
  • springboot 已经自动集成了redis

【redis配置

1.首先在build.gradle中引入redis的依赖:
compile('org.springframework.boot:spring-boot-starter-data-redis')
其实作完这一步咱们已经能够直接使用springboot提供的RedisTemplate,可是咱们须要进一步优化,而且使用注解配置缓存

2.添加缓存配置类:
 - KeyGenerator代表咱们本身定义key生成的策略
 - RedisCustomSerializer代表咱们本身定义序列化的方式,这里使用了protostuff来序列化,protostuff是目前最高效,节省空间的序列化方式     

3.在springboot启动类上代表启用缓存:@EnableCaching

4.定义缓存的名称集合,统一管理缓存名称

5.在须要使用缓存的查询服务上使用:@Cacheable(keyGenerator = "keyGenerator")

6.在须要清理缓存的业务服务上使用:@CacheEvict(keyGenerator = "keyGenerator")

图片描述

【缓存配置类

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.lang.Nullable;

import java.time.Duration;

/**
 * redis缓存配置类
 * @author ibm
 * @since 0
 * @date 2018-4-12
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Override
    @Nullable
    @Bean
    public KeyGenerator keyGenerator() {
        return new RedisCustomKeyGenerator();
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        RedisCustomSerializer customSerializer = new RedisCustomSerializer();
        template.setValueSerializer(customSerializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     *  设置 redis 数据默认过时时间
     *  设置@cacheable 序列化方式
     * @return
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(){
        RedisCustomSerializer customSerializer = new RedisCustomSerializer();
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer
        (customSerializer)).entryTtl(Duration.ofHours(1));
        return configuration;
    }
}

【自定义序列化

import com.example.seckill.dao.entity.KillProduct;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.runtime.RuntimeSchema;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.lang.Nullable;

/**
 * 自定义的redis序列化
 * @author ibm
 * @since 0
 * @date 2018-4-22
 */
public class RedisCustomSerializer implements RedisSerializer {

    private final RuntimeSchema<KillProduct> schema = RuntimeSchema.createFrom(KillProduct.class);

    @Nullable
    @Override
    public byte[] serialize(@Nullable Object o) throws SerializationException {
        KillProduct killProduct = (KillProduct)o;
        byte[] bytes = ProtostuffIOUtil.toByteArray(killProduct,schema,
                LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
        return bytes;
    }

    @Nullable
    @Override
    public Object deserialize(@Nullable byte[] bytes) throws SerializationException {
        if(bytes != null){
            KillProduct killProduct = schema.newMessage();
            //反序列化
            ProtostuffIOUtil.mergeFrom(bytes,killProduct,schema);
            return killProduct;
        }else {
            return null;
        }
    }
}

【自定义key生成策略

这里有一个很差的地方是我直接使用第一个参数做为key的标示,是的程序中必须将id放在第一位,但这里只是一个事例,代表咱们的key能够在这里进行自定义。
import org.springframework.cache.interceptor.KeyGenerator;

import java.lang.reflect.Method;

/**
 * 自定义的redis缓存key生成策略
 * @author ibm
 * @since 0
 * @date 201804013
 */
public class RedisCustomKeyGenerator implements KeyGenerator {

    /**
     * 简单的指定生成killProduct的缓存id,这里能够根据业务类型自定义全部的key生成策略
     * @param target   被调用方法的类实例
     * @param method  方法的名称
     * @param params 方法的参数
     * @return 缓存key
     */
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return params[0];
    }

    /**
     * 提供redisTemplate使用的key查询方法
     * @param cacheName 缓存名称
     * @return 缓存的key前缀
     */
    public static final String getKey4CacheName(String cacheName){
        //spring在生成key的时候会用cacheName::的前缀
        return cacheName + "::";
    }
}

【使用spring注解操做缓存

  • 在使用的类(读与写的类都须要)上咱们使用以下注解代表这个服务使用缓存的名称是什么,也能够直接在方法上指明cacheName可是要写屡次。
    @CacheConfig(cacheNames = RedisCacheName.KILL_PRODUCT)
  • 在查询的服务方法上添加以下注解代表该方法的返回值须要缓存。
    @Cacheable(keyGenerator = "keyGenerator")
  • 当被缓存的数据发生改变,缓存须要被清理或者修改,这里使用以下注解清除指定key的缓存。
    @CacheEvict(keyGenerator = "keyGenerator")

图片描述

图片描述

【redis客户端查看缓存

使用redis-cli命令进入redis(docker exec -it containerId  redis-cli)
输入keys * 查看全部的缓存
咱们能够看见缓存是按照cacheName + "::" + id 的方式生成的,而咱们的key生成策略也是针对于生成id的那一部分。

图片描述

【值得注意的一点

咱们在使用缓存的时候应该注意缓存的对象应该处于哪一层,试想若是个人缓存在dao这一层,可是事务在service层,一个service方法包含了多个dao方法,若是在执行service方法的时候,拥有缓存的dao方法成功,可是接下来的到方法失败,那么咱们的缓存就生效了,可是数据并无落库,这就产生了数据不一致的问题。因此咱们的缓存应该在事务的更上层。事务是一个原子操做,全部的缓存,消息,这种非强一致性要求的操做,都应该在事务成功提交后执行。java

相关文章
相关标签/搜索