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

相關文章
相關標籤/搜索