资讯专栏INFORMATION COLUMN

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

bovenson / 2210人阅读

摘要:在查询的服务方法上添加如下注解表明该方法的返回值需要缓存。当被缓存的数据发生改变,缓存需要被清理或者修改,这里使用如下注解清除指定的缓存。事务是一个原子操作,所有的缓存,消息,这种非强一致性要求的操作,都应该在事务成功提交后执行。

【为什么使用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 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 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方法成功,但是接下来的到方法失败,那么我们的缓存就生效了,但是数据并没有落库,这就产生了数据不一致的问题。所以我们的缓存应该在事务的更上层。事务是一个原子操作,所有的缓存,消息,这种非强一致性要求的操作,都应该在事务成功提交后执行。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69084.html

相关文章

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

    摘要:在查询的服务方法上添加如下注解表明该方法的返回值需要缓存。当被缓存的数据发生改变,缓存需要被清理或者修改,这里使用如下注解清除指定的缓存。事务是一个原子操作,所有的缓存,消息,这种非强一致性要求的操作,都应该在事务成功提交后执行。 【为什么使用redis 性能极高,redis能读的速度是110000次/s,写的速度是81000次/s 丰富的数据类型,redis支持二进制案例的 Str...

    W4n9Hu1 评论0 收藏0
  • java并发编程学习21--基于springboot秒杀系统实现3--存储过程

    摘要:但是经过测试自身的是次秒,是一个相当不错的数据,所以我们这里将事务直接交给,使用存储过程来降低行级锁的持有时间。存储过程代码使用存储过程之前必须保证数据库已经创建了存储过程。表示使用在存储过程中替代最后需要还原回来。 【什么是存储过程 所谓的存储过程是指:是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执...

    keithyau 评论0 收藏0
  • java并发编程学习21--基于springboot秒杀系统实现3--存储过程

    摘要:但是经过测试自身的是次秒,是一个相当不错的数据,所以我们这里将事务直接交给,使用存储过程来降低行级锁的持有时间。存储过程代码使用存储过程之前必须保证数据库已经创建了存储过程。表示使用在存储过程中替代最后需要还原回来。 【什么是存储过程 所谓的存储过程是指:是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执...

    dreamans 评论0 收藏0
  • java并发编程学习19--基于springboot秒杀系统实现1--项目介绍

    摘要:当秒杀日期尚未达到会提示用户秒杀尚未开始当用户多次秒杀同一商品会提示用户重复秒杀当秒杀日期过期或者秒杀商品的库存为零会提示用户秒杀结束。 【秒杀系统业务分析 在秒杀系统当中有两个核心的表:秒杀商品(kill_product)与秒杀明细(kill_item),具体的逻辑是一个用户秒杀商品的库存减一,秒杀明细的记录增加一条。这两步作是处于同一事务之中。 当秒杀日期尚未达到会提示用户秒杀尚...

    CollinPeng 评论0 收藏0
  • 重磅发布- Java商城秒杀系统的设计与实战视频教程(SpringBoot版)

    摘要:技术列表缓存中间件服务协调调度中间件消息中间件综合性质的中间件分布式锁分布式唯一生成服务雪花算法邮件服务权限认证授权矿建的登录认证服务以及等等。 概要介绍:历经一个多月的时间,debug亲自录制的Java商城秒杀系统的设计与实战视频教程(SpringBoot版)终于完成了!在本课程中,debug真正的将之前所讲解的相关技术融入到了本课程中,即本课程所介绍的秒杀系统是一个真正意义上的项目...

    崔晓明 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<