SpringBoot电商秒杀项目优化知识点总结(第五章)

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值