第五章 查询性能优化之多极缓存
1.redis集中式缓存
//商品详情页浏览 @RequestMapping(value = "/get",method = {RequestMethod.GET}) @ResponseBody public CommonReturnType getItem(@RequestParam(name = "id")Integer id){ ItemModel itemModel = null; // 先从本地缓存取 itemModel = (ItemModel) cacheService.getFromCommonCache("item_"+id); if(itemModel == null) { // 再从redis缓存中取 itemModel = (ItemModel) redisTemplate.opsForValue().get("item_"+id); if(itemModel == null) { // 最后从数据库取 itemModel = itemService.getItemById(id); redisTemplate.opsForValue().set("item_"+id, itemModel); redisTemplate.expire("item_"+id, 10, TimeUnit.MINUTES); } cacheService.setCommonCache("item_"+id, itemModel); } ItemVO itemVO = convertVOFromModel(itemModel); return CommonReturnType.create(itemVO); }
springboot redis存储数据会编码,去除编码直接string序列化
@Component @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600) public class RedisConfig { @Bean public RedisTemplate<Object, Object> redisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); // 解决key序列化方式 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); template.setKeySerializer(stringRedisSerializer); Jackson2JsonRedisSerializer jsonRedisSerializer = 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.registerModule(simpleModule); // 序列化时增加类型,方便解析成对应类型 objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jsonRedisSerializer.setObjectMapper(objectMapper); template.setValueSerializer(jsonRedisSerializer); return template; } }
/** 时间序列化 **/
public class JodaDateTimeJsonSerializer extends JsonSerializer<DateTime>{
@Override
public void serialize(DateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.toString("yyyy-MM-dd HH:mm:ss"));
}
}
/**时间反序列化**/
public class JodaDateTimeJsonDeserializer extends JsonDeserializer<DateTime> {
@Override
public DateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String dateTime = p.readValueAs(String.class);
DateTimeFormatter format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
return format.parseDateTime(dateTime);
}
}
缺点:redis的存或者取都需要经过网络IO达到redis Server上,并根据redis协议更新对应状态,所以相对操作本地缓存会慢些
2.本地热点缓存,使用谷歌的guava cache
@Service public class CacheServiceImpl implements CacheService { private Cache<String, Object> commonCache = null; @PostConstruct public void init() { commonCache = CacheBuilder.newBuilder() // 初始容量为10 .initialCapacity(10) // 最大容量100 .maximumSize(100) // 失效时间60秒 .expireAfterWrite(60, TimeUnit.MICROSECONDS) .build(); } @Override public void setCommonCache(String key, Object value) { commonCache.put(key, value); } @Override public Object getFromCommonCache(String key) { // TODO Auto-generated method stub return commonCache.getIfPresent(key); } }
CacheService的使用在上面getItem方法的代码中。
3.nginx缓存,shared dic共享内存字典
也是key和value类型的缓存。优势:基于nginx内存的直接缓存,并且是离用户最近的节点,缺点:但是更新机制不太好,占用nginx的内存
使用lua脚本,/usr/local/openresty/lua/itemsharedic.lua
#从缓存中获取
function get_from_cache(key) local cache_ngx = ngx.shared.my_cache local value = cache_ngx:get(key) return value end #设置缓存 function set_to_cache(key,value,expire) if not expire then expire = 0 end local cache_ngx = ngx.shared.my_cache local succ,err,forcible = cache_ngx:set(key,value,expire) return succ end #获取请求的参数/item/get?id=6 local args = ngx.req.get_uri_args() local id = args["id"] local item_model = get_from_cache("item_"..id) #从缓存中获取 if item_model == nil then local resp = ngx.location.capture("/item/get?id="..id) #调用后台 item_model = resp.body set_to_cache("item_"..id,item_model,1*60) end ngx.say(item_model)
修改nginx.conf, 引入lua脚本
http { init_by_lua_file ../lua/init.lua; upstream backend_server{ server 192.168.31.146:8090 weight=1; server 192.168.205.12:8090 weight=1; } lua_shared_dict my_cache 128m; #定义nginx共享字典缓存 server { listen 80; server_name localhost;
location /luaitem/get {
default_type "application/json";
content_by_lua_file ../lua/itemsharedic.lua;
}
4.nginx直接获取redis缓存数据
优点:避免访问后台应用的网络时间,减少消耗。虽然增加redis的负担,但是redis集群有多台主备redis分担,影响不大
新建itemnginx.lua文件
local args = ngx.req.get_uri_args(); local id = args["id"] local redis = require "resty.redis" local cache = redis:new() local ok,err = cache:connect("47.99.51.246",6379) if not ok then ngx.log(ngx.ERR,"connect error") return end local item_model = cache:get("item_"..id) ngx.log(ngx.ERR, item_model) if item_model == ngx.null or item_model == nil then local resp = ngx.location.capture("/item/get?id="..id) item_model = resp.body end ngx.say(item_model)
修改nginx.conf,引入lua文件
location /luaitem/get { default_type "application/json"; #content_by_lua_file ../lua/itemsharedic.lua; content_by_lua_file ../lua/itemnginx.lua; }