阿里云个人服务器搭建项目笔记六:商品详情信息缓存策略

一、商品详情页面缓存

商品详情页面存入redis缓存中
获取商品详情controller接口方法更改

//根据商品详情页的id到redis内获取,实现这个功能需要序列化itembo以及itembo内部聚合的killbo
            itemBO = (ItemBO) redisTemplate.opsForValue().get("item_" + id);

            //若redis内不存在对应的itembo,则访问下游的service,去数据库取
            if(itemBO == null){
            //只进一次service获取mysql数据库数据
                itemBO = itemService.getItemById(id);
                //存到redis,并且存到redis内,设置上过期时间10分钟,第二次刷新之后的十分钟都从redis获取
                redisTemplate.opsForValue().set("item_"+id, itemBO);
                redisTemplate.expire("item_"+id, 10, TimeUnit.MINUTES);
            }

修改key - value的序列化方式

@Component
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //首先解决key的序列化方式,最简单的string即可
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);

        //解决value的序列化方式
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(DateTime.class, new JodaDateTimeJsonSerializer());
        simpleModule.addDeserializer(DateTime.class, new JodaDateTimeJsonDeserializer());

	//使缓存包含类的信息        
		objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        objectMapper.registerModule(simpleModule);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }
}

编写datetime格式数据的序列化和反序列化方法

public class JodaDateTimeJsonSerializer  extends JsonSerializer<DateTime> {

    @Override
    public void serialize(DateTime dateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(dateTime.toString("yyyy-MM-dd HH:mm:ss"));
    }
}
public class JodaDateTimeJsonDeserializer extends JsonDeserializer<DateTime> {
    @Override
    public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        String dateString = jsonParser.readValueAs(String.class);
        DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
        return DateTime.parse(dateString, formatter);
    }
}

部署,压测:
cpu-H
redis占用2%左右
平均响应时间300ms左右
tps最高2000+

二、使用guava设计多级缓存策略

其实设计这个步骤的初衷是将商品模型直接存储在jvm中在一段时间内持久保存,首先考虑的是将商品模型做成一个HashMap,并让这个HashMap进行商品详情的缓存。但是这样做首先会有多线程的问题,不支持并发读写的问题。若改用ConcurentHashMap是基于段的方式加锁,在写锁加上去之后,会对读的性能有很大影响,并且还要考虑失效时间和淘汰机制这些复杂的内容,所以单纯的ConcurentHashMap非常复杂。
所以这里选用谷歌的guava cache来实现热点数据的缓存。

1.导入guava cache依赖

    <!--guava将热点数据存入jvm内存中-->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>18.0</version>
    </dependency>

2.写CommonCache接口和实现类

//封装本地缓存操作类
public interface CacheService {
    //存方法
    void setCommonCache(String key, Object value);

    //取方法
    Object getCommonCache(String key);
}
@Service
public class CacheServiceImpl implements CacheService {

    private Cache<String, Object> commonCache = null;
    //被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
    @PostConstruct
    public void init(){
        //commonCache设置的方法是一串设置方法的实现,最后.build()来创建commonCache
        commonCache = CacheBuilder.newBuilder()
                //设置缓存容器的初始容量为10
                .initialCapacity(10)
                //设置缓存中最大存储100个key,超过100个之后会按照lru的策略移除缓存项
                .maximumSize(100)
                //设置写缓存之后多少秒过期
                .expireAfterWrite(60, TimeUnit.SECONDS).build();
    }
    @Override
    public void setCommonCache(String key, Object value) {
        commonCache.put(key, value);
    }

    @Override
    public Object getCommonCache(String key) {
        return commonCache.getIfPresent(key);
    }
}

最后在获取商品详情的controller接口再次更改获取model的方式
1.先从guava cache取,没有去redis取,再没有去service层(mysql)取。
2.若redis有,存入guava cache;若service有,存入redis,存入guava cache。

 //商品详情页的浏览,(浏览功能一般用get请求)
    @GetMapping(value = "/get")
    @ResponseBody
    public CommonReturnType getItem(@RequestParam(name = "id") Integer id){
        ItemBO itemBO = null;
        //先取本地缓存
        itemBO = (ItemBO) cacheService.getCommonCache("item_"+id);
        //本地缓存不存在,去redis找
        if(itemBO == null){
            //根据商品详情页的id到redis内获取,实现这个功能需要序列化itembo以及itembo内部聚合的killbo
            itemBO = (ItemBO) redisTemplate.opsForValue().get("item_" + id);

            //若redis内不存在对应的itembo,则访问下游的service,去数据库取
            if(itemBO == null){
                itemBO = itemService.getItemById(id);
                //存到redis,并且存到redis内,加上过期时间
                redisTemplate.opsForValue().set("item_"+id, itemBO);
                redisTemplate.expire("item_"+id, 10, TimeUnit.MINUTES);
            }
            //从redis获取完之后,将数据保存到本地缓存之中,填充本地缓存
            cacheService.setCommonCache("item_"+id, itemBO);
        }

总结:
guava cache可以方便的控制cache的大小和存活时间,而且可以配置淘汰策略,并且线程安全。

压测:
这次效果出现了飞跃式的增长:
cpu-H 数据库服务器的cpu基本无压力
响应时间:150ms左右
tps:3200左右

不用再经过反向代理服务器–>tomcat服务器–>redis/mysql这个繁杂的过程,直接去tomcat服务器一层的jvm里面取数据,减少了一段网络开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值