缓存,就是让数据更接近于使用者,目的是让访问速度更快。
应用级缓存
即在程序员层级进行缓存利用。
缓存回收策略
缓存主要有以下回收策略:
- 基于空间。
- 基于容量
- 基于空间
- 基于Java引用对象:软引用:当JVM堆内存不足时,垃圾回收器对其进行回收。弱引用:当垃圾回收器回收内存时,发现弱引用,则立即回收。
- 回收算法:
- FIFO(First In First Out):先进先出算法
- LRU(Least Recently Used):最近最少使用
- LFU(Least Frequently Used):最不常用算法
关于Java引用可以看博主前 https://blog.youkuaiyun.com/anLA_/article/details/78277090
Java缓存类型
- 堆内存:最快的缓存,没有序列化和反序列化。但是容易被GC回收
- 堆外缓存:Gauva Cache、Ehcache3.x 、MapDB 3.x、
- 磁盘缓存:可以使用Ehcache 3.x、MapDB、
- 分布式缓存:使用ehcache-clustered + Terracotta实现Java进程间分布式缓存,当然也可以使用如Redis实现分布式缓存
浏览器缓存
浏览器缓存是指当我们使用浏览器访问一些网站页或者HTTP时候,根据服务端返回的缓存设置头将响应内容缓存到浏览器,下次可以直接使用缓存内容或者仅需要去服务端验证内容是否过期即可。这样的好处是可以减少浏览器和服务端之间来回的传输数据量,节省带宽以提升性能。
HTTP缓存
- Last-Modified:发送时会有意个If-Modified-Since请求头,其值是请求响应中的Last-Modified,即浏览器会用这个时间去服务端验证内容是否发生了变更。如果返回304,则表示服务端通知浏览器”你缓存内容没有变化,直接使用缓存展示把“
可以按Ctrl+F5强制刷新,因为此时浏览器在请求时,就不会带If-Modified-Since,但会带上Cache-Control:no-cache和Pragma:no-cache. - from cache:前面F5或者Ctrl+F5,都会去与服务端进行交互,但是也有完全不会与服务端进行交互的例子,当有URL从A页面,跳转回A页面时,几步会去服务端验证。
HttpClient客户端缓存
HttpClient4.3开始提供HTTP/1.1兼容的客户端缓存,可以把该层看成浏览器缓存。HttpClient通过职责链模式来支持可插拔的组件结构。
Nginx HTTP缓存设置
Nginx提供了expires、etag、if-modified-since指令来实现浏览器缓存控制。
- 浏览器发起请求,首先到Nginx,Nginx根据URL在Nginx本地查找是否有代理层本地缓存
- Nginx没有找到本地缓存,则访问后端获取最新文档,并放入Nginx本地缓存,返回200状态码和最新的文档给浏览器
- Nginx找到本地缓存,首先验证文档是否过期。如果验证没过期,则进行测试
if-modified-since
。
如果确实是304,则内容不需要访问后端,即不需要后端计算/渲染,直接Nginx代理层就把内容返回,速度更块。
用户首先访问全国各地的CDN节点(使用入ATS、Squid实现),如果SDN没命中,则会回源Nginx集群,该集群做二级缓存,如果没有命中缓存,则最后回源到后端应用集群。
Nginx代理层缓存
使用Nginx配置代理层缓存。
有时候缓存内容是错误的,需要手工清理。
缓存数据应该注意
- 按照不同需求,考虑不过期缓存,与带过期时间缓存
- 更新缓存方式,全量重写会导致系统响应慢,可以考虑将缓存维度化并进行增量跟新
- 使用Redis时,最好将大Value拆分为小Value,并进行查询。因为Redis是单线程。
- 热点缓存,对于请求次数多,负载高的数据,建议缓存起来,下次直接用。
缓存崩溃与快速回复
- 取模,对于取模机制,如果其中一个机器坏了,将会导致大量缓冲不命中,超大流量可能导致后端服务出现问题。此时一般可以由主/从顶上来,当然,当大量缓存不命中时,一般是建立另一个集群,然后把数据迁移到新集群。
- 一致性哈希:当然,当实例失效,后果将会如上所示。
快速回复:
- 主从机制,做好冗余,即其中一部分不可用,将对等的补上去
- 如果因为缓存导致可用性下降,可以考虑部分用户降级,然后慢慢减少降级量,后台通过Worker预热缓存数据。
连接池
池化可以用Apache commons-pool 2实现,比如DBCP、Jedis连接池都是使用commons-pool 2实现的。
数据库
数据库连接池配置,可以从以下几个步骤入手:
- 数据库连接配置
- 池配置,配置连接池的一些参数
- 验证数据库连接有效性
- 关闭孤儿连接,即如果获取连接后一直没有释放回池中,即该连接泄漏了,如果不管比,则会造成连接数被用完
在Java中使用数据库连接时,要记得设置超时事件,在JVM关闭/重启时,一定要销毁连接池(bean 配置destroy-method=“close”)。如果没有加destroy-method,而且启动次数太频繁,将造成重启后旧的数据库连接池连接不释放,这样可能造数据库连接耗尽。
数据库连接使用建议:
一定要注意网络阻塞/不稳定时的级联效应,连接池内部应该根据当前网络的状态(比如超时太多),对于一定时间内的请求全部timeout,根本不进行等待(await),即有熔断和快速失败。
以及当前等待的人数,比如现在等待1000个,那么接下来的等待是没有意义的,这样还会造成滚雪球效应。
异步并发
- 异步Future
- 异步Callback:通过回调机制实现,即首先发出网络请求,当网络返回时回调相关方法。采用线程池分发事件通知,从而有效支持大量并发连接。这种机制并不能提升性能,而是为了支撑大量并发连接或者提升吞吐量。