轻松构建微服务之高效缓存

微信公众号:内核小王子 关注可了解更多关于数据库,JVM内核相关的知识; 若是你有任何疑问也能够加我pigpdong[^1]前端

前言

在分布式系统中最好耗性能的地方就是最后端的数据库,通常状况下数据库上的insert操做很快,而update和delete操做若是带有索引也不会慢,前提要控制好单表的数据量,而且不要建太多索引, 而最容易出现性能问题的每每是select语句,咱们抛开join和group不说,大多数应用都是读多写少并且,并且带有排序和limit等耗时操做,有些查询还须要根据非索引字段进行过滤,以及like操做会加重慢查询, 在微服务中这些查询接口每每以rpc的形式对外提供服务,由于网络开销致使总体响应时间增长,因此在某些性能要求较高的业务中引入缓存是很是必要的,下面咱们将引入缓存的具体位置进行分类介绍.mysql

image

客户端缓存

移动客户端能够将一些静态资源缓存在设备上,避免重复从应用层获取,在网络不通畅的状况下也能够避免没有数据前端UI错乱, 而PC端浏览器通常能够经过nginx设置cache-control,expires,if-modified-since来控制缓存,避免重复请求服务器,也能够经过 cookie将一部分数据存在用户浏览器中,下次请求能够将cookie发送给服务端,通常用cookie存储用户登陆信息nginx

CDN缓存

一些静态资源,尤为是图片,咱们能够在高并发的状况下,让用户优先访问离用户最近而且同一个网络供应商的CDN节点,避免跨运营商垮地域访问, 相比于集中式的机房内的服务器,CDN厂商的覆盖范围更广,在每一个运营商,每一个地区都有本身的POP点,因此总能够找到更加靠近用户网络的CDN节点就近获取静态资源,CDN节点通常用来存储 不频繁变动的静态图片,页面等资源,通常发布新版本,或者上新一个新活动均可以提早将这些静态资源提早推送到CDN节点进行预热,使用CDN通常经过CNAME的方式将域名解析交给CDN厂商的DNS服务器和全局负载均衡器ajax

image

反向代理层缓存

反向代理层通常须要作动静分离,将静态资源存在在ngnix本地,静态资源通常数据库大请求频繁,作动静分离可使应用层能够有更多资源处理动态请求,而静态资源不用直接请求应用层,能够极大提升系统吞吐量 在作了动静分离后,浏览器能够直接经过ajax请求服务端获取动态数据,浏览器将数据进行整合后显示给用户.redis

分布式缓存

image
image

目前分布式缓存通常单独部署在应用层进行读写控制,读取的时候先去查询缓存服务器,没有命中在去查询数据库并写入缓存,更新的时候先更新数据库,而后在将缓存失效, 使用分布式缓存来替代应用层在JVM内缓存,能够避免各个JVM内缓存不一致的状况,也让缓存能够集群化部署更容易水平扩展,算法

目前分布式缓存主要由memecache和redis,memecache主要提供key-value存储,内存使用率较高,对大数据性能较好,可是集群支持不友好. 而redis提供多种数据结构,string,set,list,zset,hash等,还提供了RDB全量持久化,和AOF增量持久化,将内存中得数据化存储在硬盘上,重启能够再次加载使用,不过开启持久化后会影响redis的内存使用率,尤为是开启AOF同步后还会影响redis的写性能,redis还提供了集群化master-slave数据备份以及多master进行分片来提升吞吐量. 不过memecache采用多线程模型,分为主线程和worker线程,而redis是单线程IO复用模型,对于IO操做能够将性能发挥的最大化,可是redis也提供了排序,聚合等操做,这些操做在单线程下会影响吞吐量. memechache集群只能经过客户端在读写的时候根据统一的分片算法选择对应的机器,不支持master-slave数据备份sql

image

redis提供分片功能,将整个集群的16384个slot根据服务器的性能和读写频率分道不一样的master节点上,每一个master能够下挂若干个slave节点,slave从master异步同步数据,当master挂了以后,slave能够经过选举生成新德master, master能够提供读写服务,而slave只提供读服务,而redis集群对外提供服务也能够单独加一层proxy也能够直接链接客户端,两种方式各有利弊,可根据实际场景进行选择数据库

像redis和memecache这种提供内存服务的应用,内存管理的效率直接影响系统的性能,在C语言中咱们使用malloc和free分配和释放内存,对于开发人员若是malloc和free不匹配容易形成内存泄露,频繁使用也会形成大量的内存碎片,并且频繁进行这种系统调用也会影响性能.后端

memecache会预先申请一块内存,而后将这块内存切分为若干个chunk,chunk的大小能够根据一个增加因子控制,好比增加因子为1.25,第一组chunk的大小为88字节,则第二组的大小为88*1.25=114字节,让后将相同大小的chunk归类为一个slab,当客户端有一个写请求后,会根据写入数据的大小选择对应的chunk,若是这个值占用空间小于chunk大小就会形成必定空间的浪费,删除缓存的时候会标记这个chunk未使用.浏览器

而redis是现场申请内存的方式进行存储数据,也不多对内存进行优化,因此redis必定程度上会产生内存碎片,而且当redis发生swap的时候也不会触发内存整理.

固然redis并非全部数据都存储在内存中,当物理内存用完时或者达到某一个阈值,redis能够将一些好久没有用到的value存储到磁盘,只将key存在内存中,也就是所谓的swap操做,须要计算哪些key须要交换到磁盘,不过这种状况下当客户端发起一个读请求,value不在内存中得时候须要从硬盘读取,默认状况下redis会阻塞.

目前通过benchmark测试,redis性能要优于memecache,缘由多是memecache用了libevent库,而该库为了迎合通用作了大量的代码冗余,而redis使用libevent里面的两个文件修改实现了epoll event loop,另外一方面redis是单线程的,不用考虑精装修改资源的状况,而memecache采用CAS的方式,CAS的实现须要为每个cache key设置一个隐藏的token, 这个token会做为版本号,在set的时候会递增,带来CPU和内存的双重开销,尽管开销很小,在QPS很高的状况下会带来性能上的细微差异.

JVM本地缓存

本地缓存,这类缓存通常存储在JVM堆空间内,因为容量受限制,也会影响到FullGC,固然也能够考虑使用堆外内存或者用jemalloc管理内存, 因此咱们只是经过本地缓存来缓存一些并发访问量特别高而且查询数据库很耗时的数据,并且这类数据可能不必定和数据库彻底保持一致,因此业务不会使用改变量作一些金额和状态相关的核心操做. 这类缓存的典型表明为guava和ehcache,也有一些缓存放在ORM框架中,去缓存数据中的查询操做.

数据库缓存

数据库自己也会有缓存功能,目前建议只针对一些读多写少特别频繁的业务表开启,大多数状况都建议关闭,由于mysql的缓存中当有任何一条记录的update操做就会将缓存失效,若是频繁update就会致使数据库频繁缓存和清除

使用说明

  • 容量评估

在使用缓存前,最好作下容量评估,缓存系统主要消耗的是服务器内存,所以使用缓存时候必须对应用须要缓存的数据大小进行评估,包括缓存的数据结构,过时时间,缓存大小,缓存数量,而后根据将来必定时间内的业务增加状况进行预估.

  • 业务分离

建议将使用的缓存进行分离,核心业务和非核心业务可使用不一样的缓存实例,最好能作到业务之间相互隔离,避免不一样业务线共用一套缓存致使冲突.

  • 监控

全部的缓存实例都须要有监控,内存使用状况,慢查询,大对象,任何缓存key都设置过时时间,过时时间最好在原有设置上加减一个随机值,避免一块儿失效致使雪崩。

  • 先更新数据库,后失效缓存

如下为先更新数据库后清缓存的两种状况,一种最后缓存清空后下一次读请求就会恢复,另一种发生的几率很小

image

如下为先清缓存后更新数据库,会致使缓存中得数据一直是脏数据

image

其次,咱们要考虑下若是数据库是主从部署,从库支持读取,那么数据写入主库后,而应用读取从库还未更新的数据并写入缓存致使缓存里的数据一直是脏数据,这种状况咱们能够提供一种供参考的方案:经过canel订阅mysql的binlog的方式去修改缓存能够避免该问题。

  • 应用不要过渡依赖缓存

咱们通常不会要求缓存服务器的更新和数据库的更新在同一个事物内,因此确定有几率缓存和数据库不一致的状况,因此 数据的最终一致性最好不要依赖缓存,能够在应用层和或者数据库CAS的方式增长校验,另外应用也不该该严重依赖缓存,当缓存服务器挂掉以后至少要保证服务可以在没有高并发状况下继续正常对外提供服务, 固然也不要过渡依赖缓存服务器的持久化功能,毕竟并不能完整复原历史数据.

相关文章
相关标签/搜索