缓存设计的时候考虑的问题
用快速存取设备 用内存
将缓存推到离用户最近的地方
脏缓存清理
多级缓存
redis缓存
热点内存本地缓存
nginx proxy cache缓存
nginx lua缓存
redis缓存
当做集中式缓存的中间件 并将其作为内存级别的存储 易失性的
redis是和mysql相同的集中式存储概念 所有的set和get都会到这一台redis服务器上进行 区别仅是它是key-value数据库 且易失性设备
redis具有三种模式 单机版和Sentinal哨兵模式 集群cluster模式
redis支持主从机制 所以redis服务器可以有多台slave 我们希望如果主服务器挂掉之后可以自动切换到slave服务器 所以必要要有sentinel哨兵
心跳破坏掉之后
如果我们想要使用多台redis服务器 进行分流 按照上面的架构图 应用服务器需要进行计算 得到此次操作应该去哪台服务器 数据之间的一致性受到了挑战 并且产生了一些开销
而在集群cluster模式下 所有的redis服务器的状态都是相通的(Paxos算法) 虽然仍然会有服务器之间的开销 但是应用不需要管理redis服务器集群的状态了
实现redis集中式缓存商品详情页
//与之前的usermodel相同 这里也需要让itemmodel实现序列化接口
public CommonReturnType getItem(@RequestParam(name = "id")Integer id){
ItemModel itemModel = null;
//先取本地缓存
itemModel = (ItemModel) cacheService.getFromCommonCache("item_"+id);
if(itemModel == null){
//根据商品的id到redis内获取
itemModel = (ItemModel) redisTemplate.opsForValue().get("item_"+id);
//若redis内不存在对应的itemModel,则访问下游service
if(itemModel == null){
itemModel = itemService.getItemById(id);
//设置itemModel到redis内
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);
}
实现redisconfig类 更改序列化方式
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//首先解决key的序列化方式
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;
}
本地数据热点缓存
需要考虑
热点数据
脏读不敏感
内存可控 jvm堆栈的内存大小宝贵
本地数据的缓存其实就是有更新策略的hashmap 那么使用的其实就是封装好的Cache类 guava cache
由谷歌提供 可控制大小和超时时间 可配置lru策略 线程安全
nginx proxy cache
需要考虑
nginx反向代理前置 就是说这个服务器必须要进行反向代理才能做缓存
这种缓存是依靠文件系统存索引级的文件 将数据存入文件系统汇总
依靠服务器的内存缓存文件地址
在nginx的配置文件中加上
proxy_cache_path /usr/local/openresty/nginx/tmp_cache levels=1:2 keys_zone=tmp_cache:100m inactive=7d max_size=10g;
在server结点里面添加
proxy_cache tmp_cache;
proxy_cache_key $uri;
proxy_cache_valid 200 206 304 302 7d;
压测可知性能变差 因为访问文件而不是访问内存 速度较慢
引入了缓存的nginx的脚本语言nginx lua
它和nginx一样拥有协程机制 无需考虑异步的机制
nginx的每个worker进程都是在epoll或kqueue这种时间模型之上封装成协程
每一个请求都有一个协程进行处理 机试ngx_lua运行lua相对C有一定的开销 但是仍然可以保证高并发能力
nginx协程机制
nginx每个工作进程创建一个lua虚拟机
工作进程内的所有协程共享同一个vm
每个外部请求由一个lua携程处理 之间数据隔离
lua代码调用io等异步接口时 协程被挂起 上下文数据保存
自动保存 不阻塞工作进程
io异步操作完成后还原协程上下文 代码继续执行
lua的挂载点
OpenResty
由Nginx核心加很多第三方模块组成 默认继承了lua开发环境 使得nginx可以作为一个WebServer使用
借助于Nginx的事件驱动模型和非阻塞IO 可以实现高性能的Web应用程序
OpenResty听过了大量组件 如MySQL Redis Memcached等等 使得在Nginx上开发web应用更加简单方便
实践内容
shared dic 共享内存字典,所有worker进程可见,lru淘汰
openresty redis支持
热点数据缓存在nginx本地内存中 非热点、高流量数据读redis slave 在与master主从同步的过程中实现对脏数据的更新