欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

程序员文章站 2024-03-21 11:05:04
...

4.查询性能优化:多机缓存

一、学习目标:

上一节通过服务器的水平扩展,将应用部署在多台机器上来处理请求极大地提升了TPS,但这多台数据库仍然使用同一台机器的数据库。
接下来将使用一些查询优化的技术,来完成商品详情页的查询优化解决方案。

  • 掌握多级缓存的定义
  • 掌握 redis缓存,本地缓存
  • 掌握热点nginx lux缓存

二、缓存设计原则概览

  • 缓存要使用快速存取设备,内存
  • 将缓存推到离用户最近的地方,减少网络延时
  • 脏缓存清理,数据库发生变更后,缓存中的旧数据就是 脏缓存 ,如何清理,清理策略

** 多级缓存**

  • redis缓存
  • 热点数据,内存本地缓存
  • nginx proxy cache缓存
  • nginx lua缓存

三、Redis集中式缓存介绍

安装、原理、session管理、token管理见前文。
key-value格式的数据库。
memorycatch 是完全的内存数据库。
而redis可以将数据刷新到磁盘,容许一定数量的丢失。一般将其当做易失性的存储。

Why集中式缓存?

应用服务器水平拓展后都连接这同一台redis服务器。
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

1.单机版

故障瓶颈,容量上限
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

2.sentinal哨兵模式

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

redis2做redis1的从属备份redis,redis上的改动都被同步至redis2上备份。
但党redis1挂掉的时候,理应应用服务器理应自动向redis2请求服务,但是由于并发环境太复杂,应用服务器很难感知到redis2挂掉了。因此产生了sentinel哨兵模式。

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

在第一次使用时,应用服务器首先ask sentinel,该使用哪台redis,sentinel回复告知,应用服务器再去向redis请求。
sentinel工具与redis建立心跳机制。任何导致心跳断开的情况,sentinel都认为是redis1的故障。则发送命令,令redis2变更为master,redis1变更为slave,并通知应用服务器change(如下图)。

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

3.集群cluster模式

哨兵模式的缺点也很明显,同一时间只有一台redis对外提供服务。
在cluster之前,使用分片与读写分离:
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

但在新增redis5的时候,需要各种繁杂的数据迁移。可扩容性还是差。
因此出现了cluster集群模式:
redis2个读,2个写。并自动竞选出master和slave。并且每一个redis都知道所有的关系。并将这关系路由表发给应用服务器,应用服务器将这些分片信息维护在自己的内存当中。
当redis3出现故障时,自动通过帕克机制重新分配地位,哈希分片都会自动重新调整。
但应用服务器内还是错误的分片路由列表。当应用服务器按照错误列表找到了redis2请求某数据时,redis2发现服务器请求的key不属于自己调整之后的管理范畴,则向服务器返回reask请求,让服务器重新拉取最新的分片路由表。
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存
以上三种模式均得到了 jedis 的实现。

四、Redis集中式缓存商品详情页

在controller层,将详情信息缓存起来,不去走下游service的调用了。减少对数据库的依赖。
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

但是被redis自己序列化后的乱码
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

更改了默认的键值的序列化方式,使其易读。
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

五、本地热点缓存

即JVM服务器的本地

  • 热点数据
  • 脏读非常不敏感,数据库一旦改动,就会出现很多脏缓存数据被读取而不知

可以通过MQ进行更新,但得不偿失。

  • 内存可控,且珍贵

仅仅作为瞬时访问,因此缓存有效时间很短。

得支持并发读写。单纯的hashmap无法满足。还得以LRU等策略进行淘汰,按时间KEY自动失效等等。

实现:

Guava cache

  • 可控只得大小和超时时间
  • 可配置的lru策略
  • 线程安全

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

@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(60, 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);
    }
}

提升1000TPS。

六、Nginx proxy catch

  • nginx反向代理前置启用时,proxy catch才能用
  • 依靠文件系统索引级的文件来完成缓存操作
  • 依靠内存来 缓存文件地址

但由于其是将缓存存储在本地磁盘系统了,并不是保存在内存当中,因此QPS没有明显提升,甚至比jvm本地缓存的性能下降了。平均时延更长了。

七、Nginx Lua原理

  • lua协程机制

线程空间站内的,依托于线程,根据用户模拟出来的

  • nginx协程机制

无需考虑异步编程模式

  • nginx lua插载点
  • OpenResty

1.协程机制

  • 依附于线程的内存模型,切换开销小
  • 遇到阻塞及时归还执行权,代码同步
  • 无需加锁

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

2.Nginx协程

  • nginx的每一个worker进程都是在epoll或kqueue这种事件模型之上,封装成协程。

  • 每一个请求都有一个协程进行处理
  • 即使ngx_lua运行lua,相对c有一定的开销,但依旧能保证高并发能力。

3.Nginx协程机制

  • nginx每个工作进程创建一个lua虚拟机。用来跑lua文件
  • 工作进程内的所有协程共享同一个vm
  • 每一个外部请求由一个lua协程处理,之间数据隔离
  • lua代码调用io等异步接口时,协程被挂起,上下文数据保持不变
  • 自动保存,不阻塞工作进程
  • io异步操作完成后还原协程上下文,代码继续执行。即代码是同步型的编程,比较简单。

即在接收到HTTP请求后,分配一个lua协程去处理,但碰到比如反向代理需要等待后端服务器返回数据时,把socket句柄放置的epoll监听队列中,把自己挂起。继续执行其他协程,等到后端服务器返回时,epoll会监听到,然后协程唤醒自己再处理后面的response操作。
也正因此,各个协程之间的数据是隔离的。
nginx是一个worker就是一个线程,底下是很多的协程。完全基于协程的方式串行处理。
而java servlet在处理http请求时,是一个请求,分配一个线程去处理的。

4.Nginx处理阶段

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

5.Nginx lua挂载点

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

6.OpenResty

  • 借助Nginx的事件驱动模型和非阻塞IO,可以实现高性能的web应用程序。
  • 提供了大量组件如Mysql、redis、memcache等,使在nginx上开发web应用更方便简单。

(1) hello world

Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存
即在访问helloworld时,返回访问/item/get?id=6的结果。

(2) shared dic

共享内存字典,所有worker进程可见

类似于guaua catch一样的内存字典,且支持lru淘汰

但具有内存限制。
所以

(3) openresty redis支持

nginx从redis中读取肯定比从shared dic中要慢一点,
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

更新机制就在nginx处省掉了,让下游应用服务器去更新,nginx只读不写。
nginx可以实时感知redis中的数据,可以避免脏读。
Java性能优化打造亿级流量秒杀系统:4.查询性能优化:多机缓存

因此,nginx可以只将热点数据存在自己的内存缓存中,非热点数据就读redis slave。

八、总结

越靠近上游,缓存占用的系统资源越昂贵,对应的更新机制越难,性能越高。
因此没有统一通用的缓存方案,需要根据实际业务场景,对脏读的容忍程度,热点数据的程度,来决定动态请求的缓存方案。