一、商品详情页面缓存
商品详情页面存入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里面取数据,减少了一段网络开销。