这里写目录标题
- 5-2 缓存设计原则概览
- 5-3 Redis集中式缓存介绍(缺点是需要网络I/O开销)
- 5-4 Redis集中式缓存商品详情页接入(上)
- 5-5 Redis集中式缓存商品详情页接入(下)
- 5-7 本地数据热点缓存(上)
- 5-8 本地数据热点缓存(下)
- 5-10 nginx proxy cache缓存实现及压测结果验证
- 5-11 nginx lua原理(上)
- 5-12 nginx lua原理(中)
- 5-13 nginx lua原理(下)
- 5-14 Nginx Lua实战:使
- 5-15 又见OpenResty
- 5-16 OpenResty实战---Hello World :代理转发
- 5-17 OpenResty实战---Shared dic:共享内存字典,所有worker进程可见,Iru淘汰
- 5-18 OpenResty实战---Redis支持 (shared dic的数据更新机制不好 容量大小也是问题)
5-2 缓存设计原则概览
分布式解决了部分链路上耗时的问题 但是数据库存取的瓶颈没有的优化
用快速存取设备,用内存(缺点是断电即丢失)
将缓存推到离用户最近的地方
脏缓存清理(关键数据必须要存储在数据库上 缓存同步问题 还你村民清理策略)
多级缓存:
redis缓存(集中式管理 缺点是是有网络开销 和集中式负载均衡的性能瓶颈)
热点内存本地缓存(JVM内存中)
nginx proxy cache缓存(在文件系统磁盘上 慢)
nginx lua缓存(内存缓存 快)
5-3 Redis集中式缓存介绍(缺点是需要网络I/O开销)
redis缓存(jedis的依赖已经继承了三种状态的管理 单机版和集群的差别仅仅是水平扩展后的容量问题):
单机版(单点故障瓶颈 容量限制)
sentinal哨兵模式(引入sentinal哨兵节点用来统一管理主从redis数据库的切换 应用程序只需要感知sentinal即可 缺点:同一时间只有一台redis对外产生服务 即便是读写分离 也会产生容量瓶颈限制 不易于扩展 扩展可在后端根据哈希值做分片机制 缺点是 后端分片机制麻烦 且redis数据库迁移麻烦 脏数据无法容忍)
集群cluster模式(每一台redis都和其他redis有网状连接 每台redis都能互相感知其他节点的状态 当集群中有超过一半的redis存活即可正常工作 后端服务器只需要连接其中任何一台redis即可得到集群中全部信息 后端服务器不需要管理redis集群)
5-4 Redis集中式缓存商品详情页接入(上)
在SpringMVC的Controller层引入redis 并缓存从下游service层活的的数据
@Autowired
private RedisTemplate redisTemplate;
//商品详情页浏览
@RequestMapping(value = "/get", method = {RequestMethod.GET})
@ResponseBody
public CommonReturnType getItem(@RequestParam(name = "id") Integer id) {
//根据商品的id到redis内获取
ItemModel 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_",10, TimeUnit.MINUTES);
}
ItemVO itemVO = convertVOFromModel(itemModel);
return CommonReturnType.create(itemVO);
}
5-5 Redis集中式缓存商品详情页接入(下)
优化redis中的乱码数据
RedisConfig.java
@Component
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class RedisConfig {
@Bean
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());
//在jackson序列化方法中 若没有包含类的信息 就必须在redis转化方法中强制指定 以使得可以获得对应的放射参数
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.registerModule(simpleModule);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
JodaDateTimeJsonDeserializer.java
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);
}
}
JodaDateTimeJsonSerializer.java
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"));
}
}
5-7 本地数据热点缓存(上)
只存放热点数据
脏读非常不敏感(分布式架构下每台服务器都有自己本地的缓存 清除缓存可以使用mq即异步消息队列做广播消息的发送 让每台后端服务器监听 来清除本地热点缓存 一般热点数据的定义即变化少 且商品详情页的价格信息不正确影响不会特别大 且生命周期特别短导致脏读的数据非常少)
内存可控(本地缓存就是JVM缓存)
java的hashmap不支持并发写
concurrenthashmap基于段的的处理方式加锁 特别是put操作中 在写锁加上后对读锁性能产生影响
且需要考虑淘汰机制 采用Guava cache:
1.可控制的大小和超时时间
2.可配置的Iru策略:最近最少访问的key优先被淘汰
3.线程安全
5-8 本地数据热点缓存(下)
引入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
CacheService.java
public interface CacheService {
//存方法
void setCommonCache(String key, Object value);
//取方法
Object getFromCommonCache(String key);
}
CacheServiceImpl.java
@Service
public class CacheServiceImpl implements CacheService {
private Cache<String,Object> commonCache = null;
@PostConstruct
public void init() {
commonCache = CacheBuilder.newBuilder()
//设置缓存容器的初始容量为10
.initialCapacity(10)
//设置缓存中最大可以存储100个KEY 超过100个会按照LRU策略一处缓存项
.maximumSize(100)
//设置写入缓存后多少秒过期
.expireAfterWrite(10, TimeUnit.SECONDS).build();
}
@Override
public void setCommonCache(String key, Object value) {
commonCache.put(key,value);
}
@Override
public Object getFromCommonCache(String key) {
return commonCache.getIfPresent(key);
}
}
ItemController.java
@Autowired
private CacheService cacheService;
//商品详情页浏览
@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) {
//根据商品的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_",10, TimeUnit.MINUTES);
}
//填充本地缓存
cacheService.setCommonCache("item_"+id,itemModel);
}
ItemVO itemVO = convertVOFromModel(itemModel);
return CommonReturnType.create(itemVO);
}
5-10 nginx proxy cache缓存实现及压测结果验证
nginx反向代理前置
依靠文件系统存索引级的文件
依靠内存缓存文件地址(key存在内存 值以文件的形式存在磁盘 所以实际性能更低)
在nginx.conf中添加
#levels表示文件的级别 可以直接在tmp_cache目录下直接以getitem的id=1的url作为文件名存储 也可以做二级目录 先将url做hash 取最后一位做文件目录的索引 再取一位做二级目录的索引 这样文件内容就会被分散到多个目录中 减少寻址的消耗
#keys_zone表示在内存中开了100m大小的空间用来存储所有的key
#inactive表示有效期为7天
#max_size表示存储的文件总大小为10个G
#申明一个cache缓存节点的内容
proxy cache path /usr/local/openrestv/nginx/tmp_cache levels=1:2 keys_zone=tmp_cache: 100m inactive=7d max_size=10g;
在在nginx.conf的location /中添加
proxy_cache tmp_cache;
proxy_cache_key $uri;
#表示状态码为这些的时候才写入缓存
proxy_cache_valid 200 206 304 302 7d ;
在nginx目录下启动nginx 默认后台启动
sbin/nginx -c conf/nginx.conf
5-11 nginx lua原理(上)
lua协程机制
nginx协程机制:编写代码是无需考虑异步方式
nginx lua插载点
OpenResty
5-12 nginx lua原理(中)
nginx的每一个Worker进程都是在epoll或kqueue这种事件模型之上,封装成协程
每一个请求都有一个协程进行处理
即使ngx_lua须要运行Lua,相对C有一定的开销,但依旧能保证高并发能力
nginx每个工作进程创建一个lua虚拟机工作进程内的所有协程共享同一个vm
每个外部请求由一个lua协程处理,之间数据隔离
lua代码调用io等异步接口时,协程被挂起,上下文数据自动保存,不阻塞工作进程
io异步操作完成后还原协程上下文,代码继续执行
nginx处理阶段
NGX_HTTP_POST_READ_PHASE = 0,//读取请求头
NGX_HTTP_SERVER_REWRITE_PHASE,//执行rewrite→rewrite_handler
NGX_HTTP_FIND_CONFIG_PHASE,//根据uri替换location
NGX_HTTP_REWRITE_PHASE,//根据替换结果继续执行rewrite→rewrite_handler
NGX_HTTP_POST_REWRITE_PHASE,//执行rewrite后处理
NGX_HTTP_PREACCESS_PHASE,//认证预处理请求限制,连接限制→limit_conn_handerlimit_req_handler
NGX_HTTP_ACCESS_PHASE,//认证处理→auth_basic_handler,access_handler
NGX_HTTP_POST_ACCESS_PHASE,//认证后处理,认证不通过,丢包
NGX_HTTP_TRY_FILES_PHASE,//尝试try标签
NGX_HTTP_CONTENT_PHASE,//内容处理→static_handler
NGX_HTTP_LOG_PHASE//日志处理→log_handler
5-13 nginx lua原理(下)
nginx lua插载点
init_by_lua:系统启动时调用
init_worker_by_lua: worker进程启动时调用
set_by_lua: nginx变量用复杂
lua returnrewrite_by_lua:重写url规则
access_by_lua:权限验证阶段
content_by_lua:内容输出节点
5-14 Nginx Lua实战:使
我们可以使用对应的挂载点 对某个location的固定的url进行编程
用ngx.say()完成html文本的输出
在nginx.conf中加入
location /staticitem/get{
default_type "text/ html";
content_by_lua_file ../lua/staticitem.lua;
}
在openresty/lua/staticitem.lua中写入
ngx.say(“hello static item lua”);
5-15 又见OpenResty
OpenResty由Nginx核心加很多第三方模块组成,默认集成了Lua开发环境,使得Nginx可以作为一个Web Server使用。
借助于Nginx的事件驱动模型和非阻塞IO,可以实现高性能的Web应用程序。
OpenResty提供了大量组件如Mysql、Redis、Memcached等等,使在Nginx上开发Web应用更方便更简单。
5-16 OpenResty实战—Hello World :代理转发
在nginx.conf中加入
location /helloworld{
content_by_lua_file ../lua/helloworld.lua;
}
在openresty/lua/helloword.lua中写入
ngx.exec(“/item/get?id=6”);
5-17 OpenResty实战—Shared dic:共享内存字典,所有worker进程可见,Iru淘汰
在nginx.conf中加入
lua_shared_dict my_cache 128m;
location /luaitem/get {
default_type "application/json";
content_by_lua_file ../lua/itemsharedic.lua;
}
在nginx/lua/itemsharedic.lua中加入
function get_from_cache(key)
local cache_ngx = ngx.shared.my_cache
local value = cache_ngx:get(key)
return value
end
#exptime表示是否强制执行
function set_to_cache(key,value,exptime)
if not exptime then
exptime = 0
end
local cache_ngx = ngx.shared.my_cache
local succ,err,forcible = cache_ngx:set(key,value,exptime)
return succ
end
local args = ngx.req.get_uri_args()
local id = args["id"]
local item_model = get_from_cache("item_"..id)
#nil表示真正获取不到 null表示一个字符串
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)
5-18 OpenResty实战—Redis支持 (shared dic的数据更新机制不好 容量大小也是问题)
当热点数据不是特别热 并且需要更新时可以采用redis支持
nginx优先读redis 若无数据再访问后端服务器
后端服务器优先读redis 若无数据则访问mysql 并且写入redis
nginx只需要管redis是否有数据 更新的操作交给下游服务器做 解决了数据更新机制的问题
修改nginx.conf
location /luaitem/get {
default_type "application/json";
content_by_lua_file ../lua/itemredis.lua;
}
在nginx/lua/itemredis.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("172.26.68.13",6379)
local item_model = cache:get("item_"..id)
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)