1. php 中如何使用缓存,使用哪种缓存机制最好;
php的缓存三种.有文件缓存,数据库缓存,memcache缓存;
memcache缓存要求对服务器支持,而且它的缓存是由期限的,一般是30天。这种缓存的效率是最高的。读存取的速度最快。
数据库缓存
和
文件缓存比较简单。适用小的项目。和php新手
2. 数据库缓存机制是什么缓存是如何作用数据库
缓存的介质一般是内存,所以读写速度很快。但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。
3. 谈谈RecyclerView中的缓存
Android深入理解RecyclerView的缓存机制
RecyclerView在项目中的使用已经很普遍了,可以说是项目中最高频使用的一个控件了。除了布局灵活性、丰富的动画,RecyclerView还有优秀的缓存机制,本文尝试通过源码深入了解一下RecyclerView中的缓存机制。
RecyclerView做性能优化要说复杂也复杂,比如说布局优化,缓存,预加载等等。其优化的点很多,在这些看似独立的点之间,其实存在一个枢纽:Adapter。因为所有的ViewHolder的创建和内容的绑定都需要经过Adaper的两个函数onCreateViewHolder和onBindViewHolder。
因此我们性能优化的本质就是要减少这两个函数的调用时间和调用的次数。如果我们想对RecyclerView做性能优化,必须清楚的了解到我们的每一步操作背后,onCreateViewHolder和onBindViewHolder调用了多少次。因此,了解RecyclerView的缓存机制是RecyclerView性能优化的基础。
为了理解缓存的应用场景,本文首先会简单介绍一下RecyclerView的绘制原理,然后再分析其缓存实现原理。
RecyclerView滑动时会触发onTouchEvent#onMove,回收及复用ViewHolder在这里就会开始。我们知道设置RecyclerView时需要设置LayoutManager,LayoutManager负责RecyclerView的布局,包含对ItemView的获取与复用。以LinearLayoutManager为例,当RecyclerView重新布局时会依次执行下面几个方法:
上述的整个调用链:onLayoutChildren()->fill()->layoutChunk()->next()->getViewForPosition(),getViewForPosition()即是是从RecyclerView的回收机制实现类Recycler中获取合适的View,下面主要就来从看这个Recycler#getViewForPosition()的实现。
上述逻辑用流程图表示:
RecyclerView在Recyler里面实现ViewHolder的缓存,Recycler里面的实现缓存的主要包含以下5个对象:
public final class Recycler {
final ArrayList mAttachedScrap = new ArrayList<>();
ArrayList mChangedScrap = null;
RecyclerView在设计的时候讲上述5个缓存对象分为了3级。每次创建ViewHolder的时候,会按照优先级依次查询缓存创建ViewHolder。每次讲ViewHolder缓存到Recycler缓存的时候,也会按照优先级依次缓存进去。三级缓存分别是:
使用自定义ViewCacheExtension后,view离屏后再回来不会走onBindViewHolder()方法。
holder.setIsRecyclable(false),这样的话每次都会走onCreateViewHolder()和onBindViewHolder()方法
1.提前初始化viewHolder,放到缓存池中
viewPool.putRecycledView(adapter.onCreateViewHolder(recyclerView, 1))
2.提前初始化view,在onCreateViewHolder的时候去取view
3.自定义ViewCacheExtension
4.适当的增加cacheSize
4.公用缓存池,比如多个viewPager+fragment场景使用,或者全局单利缓存池,感觉用户不大。
有2中做法有值
第一种
第二种
不会,因为prefetch(GapWorker中的一个方法)之后mViewCacheMax会变成mRequestedCacheMax + extraCache
有2种方式可以让缓存失效
第一种
recyclerView.setItemViewCacheSize(-1)
第二种
recyclerView.setItemViewCacheSize(0)
layoutManager.isItemPrefetchEnabled = false
设置不缓存后,来回滑动让view进入屏幕离开屏幕,viewHolder的item时会多次走onBindViewHolder()方法。
4. 求生之路系列(一)浏览器缓存机制
http可以说是现在前端领域(甚至整个互联网)发展过程中使用最多的一个应用层协议。其传输层一般都是使用tcp协议来保证可靠传输的,由于tcp3次握手以及4次挥手的链接建立与断开机制,导致每一次进行http请求所消耗的网络资源相对较大。。所以减少请求次数,合理的数据缓存成为互联网开发的重中之重!!
尤其在前端领域,http缓存在加快网页性能和为用户节约网络资源。作为一名小前端,又恰好面试的时候又被问蒙蔽了。。就在这里对http缓存机制进行一个小小的总结吧~~
总的来说,浏览器要从服务器上面真正的拿到数据还要通过下面几关:
Cache-control是http响应头的一个字段。就是用来与客户端约定响应的数据的缓存的有效时间。
在上图中可以发现Cache-Control有一个max-age=691200的参数。这个就是表示该资源的最大生存时间为691200秒。 在这个时间过后才会再次向服务器发出请求申请新的资源 ,否则直接使用本地资源。不过,就算生存时间过了,服务器不会这么温顺地给你发新的资源(你说发就发岂不是很丢脸???),还会进一步的进行判断,是否应该发送新的资源(看到后面就知道啦~)
除了max-age之外,Cache-Control还有别的参数可以选择:
另外,浏览器的不同行为例如enter f5也会有不同的表现哦。(这个后面再总结吧~)
更多的介绍可以看看下面网络和几位老哥的总结:
眼尖的盆友,可能会发现 上面的图中还有一个Expires字段,有些还有Pragma,其实这两个都是个http1.0的旧产物,跟Cache-Control设置max-age是一个意思。由于Cache-Control是http1.1提出的,而且在http1.1甚至http2.0大行其道的今日,这两个东西已经没有作用了。当Cache-Control和上面两个东西同时在http头中存在时,优先使用Cache-Control。
注意:浏览器要发出请求必须要先在max-age时间过后(可以用ctrl+F5,来跳过这个检查)。如果在一个时间内在发出请求,chrome的network会返回一个假200(其实是读缓存的假请求2333333)。
一句话总结:Cache-Control是一个用于控制(告诉)客户端,该响应的资源应该存哪里,存多久。
当浏览器发现max-age(或其他)时间过后 ,浏览器就开始向服务器请求获取新的资源。但是服务器并不会轻易的把新的资源返回给客户端。
当浏览器第一次访问某个网站,请求服务器时,服务器在返回的http响应头中加入Last-Modified字段,故名思意表示该资源最后一次修改是在某一个时间,如下图
浏览器在收到了Last-Modified后,在以后的每一次请求中(请求头)都会带上一个字段If-Modified-Since,这个字段的值就是上一次收到的Last-Modified的值。
服务器端则根据查看该资源是否在这个时间点后被修改过!!~如果没有被修改过,则服务器会返回304,表示资源未被修改过,使用缓存就可以了。否则如果有Etag则进行下一步判断(后面说),没有则200返回新资源。
Last-Modified 表面上非常靠谱,但是也存在一种情况,如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。
ETag是http1.1中为了解决上面问题的一个http字段(一般是在响应头里面的)。这个ETag的值的什么呢?一般是由服务器根据资源的内容通过md5(或者其他)计算出的一个唯一标志。ok,浏览器得到这个东西之后,每次请求该资源的时候就会带上这个值,这个值是放在请求头的if-None-Match中,表示如果不匹配就给我新的吧,匹配就返回304~~
还有这个if-Match,这个我就不是很理解到底是什么意思了。。。知道的朋友可以告诉我一下。。。
什么是412错误,先决条件失败是什么意思。。
最后盗个图:
另外,浏览器有多个刷新页面的方法,下面来看看对缓存来说都有什么区别吧(用chrome来测试,据说不同浏览器不同哦~):
从上面图中可以看出,这种形式的刷新页面,是会判断过期的(max-age)。。就是说是按整个缓存流程走下去。。
哇 好多304。。说明F5刷新页面跳过了过期判断(包括了max-age、expire等等),直接从ETage开始。。
全部200!!说明 这个是真·强刷·无双!!从请求头的图中,可以看到浏览器是同配置Cache-Control: no-cache来叫服务器重发请求的!~
第一次写博客,各位老哥,给个面子,有错就提出来哈~~
by the offer, of the offer, for the offer!!~~~
相关参考:
5. 缓存机制是一种常用的平滑切换方法
是的。缓存机制是属于一种常用的平滑切换方法的。缓存机制是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。
6. 来了解 Github 当前最火开源项目 RecyclerView 的缓存机制
最近在网上看到许多关于 RecyclerView 缓存相关的技术文章,也是在其中看到了许多的知识点,我将之收集了起来进行汇总和整理,利用自己的见解来分析 RecyclerView 的缓存机制的相关问题
首先 RecyclerView 是由 Google 推出来对 GridView 和 ListView 进行取代的列表方案,RecyclerView 本身它是不关心视图相关的问题的,由于 ListView的紧耦合的问题, google 的改进就是 RecyclerView 自身不用参与任何视图有关的问题,它不用在意应该将子 View 放在合适的位置,也不在意如何进行分割这些子 View,更不在意每个子View所显示的外观,本质上来说就是 RecyclerView 它只负责回收和重用的工作
● 能够替代 Listview 和 GridView ,不仅可以加载列表同时也能够加载表格
● 能够支持瀑布流这种高级的显示方式
● 内置了强劲的垃圾回收机制
● 规范了其 Viewholder 的使用
在 RecyclerView 中,是没有 onItemClickListener 方法的,所以只能在适配器中处理事件,如果要从适配器上添加或移除条目,就必须要明确通知适配器。这跟先前的 notifyDataSetChanged 方法有略微不同
整体总结了几点如下:
● Adapter:包装数据集合且为每个条目创建视图
● ViewHolder:对每个用于显示数据条目的子View进行保存
● LayoutManager:在适当的位置放置于每个条目的视图
● ItemDecoration:绘制一些装饰视图在每个条目的视图的周围或上面
● ItemAnimator:在条目被添加、移除或者重排序时对其添加动画效果
RecyclerView 的缓存可以分为四级,也有的人将之分成三级,但大致的理解是一样的
● mAttachedScrap 和 mChangedScrap ,用来缓存其还在屏幕内的 ViewHolder
● mAttachedScrap 对当前还在屏幕中的 ViewHolder进行存储;从 id 和 position 来对 ViewHolder进行查找
● mChangedScrap 表达数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时必须对 ViewHolder进行改变
● mCachedViews ,是用来缓存移除屏幕之外的 ViewHolder,通常其缓存容量是 2,但可以通过 setViewCacheSize 方法来改变缓存的容量大小,假如mCachedViews 的容量已满,那么则会根据 FIFO 其中的规则来对旧 ViewHolder 进行移除处理
● ViewCacheExtension ,是开发给用户的自定义扩展缓存,是需要用户自己管理 View 的创建和缓存
● RecycledViewPool ,ViewHolder 缓存池,如果在有限的 mCachedViews 中存不下新的 ViewHolder 时,那么就会把 ViewHolder 存入RecyclerViewPool 中
● 根据 Type 来对进行 ViewHolder 查找
● 每个 Type 基本上默认最多缓存 5 个
● 具有可以多个 RecyclerView 共享 RecycledViewPool
onCreateViewHolder 用于对 item.xml 进行机芯实例化,并会以 ViewHolder 的形式呈现
onBindViewHolder 致用在初始和滑动 RecyclerView 时,给予item里面的子控件赋值
在我的理解中, onCreateViewHolde r和 onBindViewHolder 加起来就类似于 ListView adapter 里面的
虽然getView()里面既有parent可以用来实例化 item.xml ,又有 position 可以找到item位置来赋值,但是本质上还是如同 onCreateViewHolder 和 onBindViewHolder 的功能!
差异区别就只是在item的表示形式从View变成了 ViewHolder
以上就是关于 RecyclerView 缓存的所有内容
关于RecyclerView的缓存,总的来说,Scrap是屏幕内的缓存一般我们不怎么需要特别注意;Cache可直接拿来复用的缓存,性能高效
ViewCacheExtension 需要开发者自定义的缓存,API设计比较奇怪,慎用
RecycledViewPool 四级缓存,可以避免用户调用onCreateViewHolder 方法,提高性能,在 ViewPager+RecyclerView 的应用场景下可以大有作为
如果喜欢文章中的内容欢迎大家点赞和评论,你们的鼓励将是我前进的动力
有需要文章中的源码,或者想要了解更多关于Android开发相关的进阶资料
欢迎大家在评论区下发留言,或者私信我
7. 什么是缓存机制
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
缓存的介质一般是内存,所以读写速度很快。但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。
Hibernate的缓存包括Session的缓存和SessionFactory的缓存,其中SessionFactory的缓存又可以分为两类:内置缓存和外置缓存。Session的缓存是内置的,不能被卸载,也被称为Hibernate的第一级缓存。SessionFactory的内置缓存和Session的缓存在实现方式上比较相似,前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据。SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的拷贝,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来,SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的拷贝,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的第二级缓存。
8. APP中缓存、加载与刷新机制设计【转载】
1、为什么要加缓存?
场景一:【等待】,在向服务器请求新的数据时。我们让用户看到什么?第一种是漂亮的等待加载页面;第二种是缓存的内容。对于第二种,用户可以对页面进行操作,等待新数据时可以查看旧数据,更具有“可操作性”与“可用性”,从而减轻了从服务器获取数据这一动作的大小和时间长短,增强了用户体验。另一方面,如果内容更新的间隔较长或者用户刷新的间隔较短,在没有缓存的情况下,很多数据我们会多次重复的向服务器获取,增加了成本。
场景二:【结果】没有联网,或者在地铁上网络太差无法加载数据时,如果留给用户一个空白页面,实在是感觉有点不负责任啊。并且很多功能在没有联网的情况下也有使用的可能性,比如:APP中的通讯录,查看一些聊天记录,通知信息,文章列表等。因为用户打开APP不一定是要看新信息,说不定是回顾老信息(或许老信息里也有用户之前没看的),所以恰当的缓存可以满足更多的用户场景。
场景三:【金钱】有一天,一个用户发现自己装了某个APP后流量用的特别快,Ta可能永远将这个APP打入冷宫了,而增加缓存正是节省流量的一个方法。虽然节省的不多或者用户也察觉不到,但是作为一个有态度的产品经理,应该多做一些思考。
2、什么是缓存?
缓存可分为如下几类:
(1)app缓存。
(2)固定缓存。
(3)可手动清理的缓存。
(4)不可手动清理的缓存。
(5)临时缓存。
其中,临时缓存常用于一个功能页面内,保存各栏目的缓存。同一个功能里会把子功能分为多个栏目进行划分,每个标签栏目下的内容在本次使用中都可保存为临时缓存,在该功能里切换栏目,不需要重新加载数据,使用缓存显示。
对于用户来说,使用时达到了无缝切换浏览,对于服务器来说,在短时间内数据很少会有更新,所以在一般情况下能满足用户的正常需求,并达到体验优秀。
临时缓存的清理机制是:退出该功能模块就清除之前的缓存。也就是说下次进入该功能模块,需要重新获取一次数据。
很多时候我们都会用到临时缓存,因为那些信息真的不是那么重要,而且不需要经常反复查看,那对于那些我们经常使用而且经常需要反复查看的信息,马海祥建议采取固定缓存,保存在本地,方便下次翻阅时不需要再一次向服务器请求数据了。
对于固定缓存又会细分为可手动清理的缓存和不可手动清理的缓存。
第一种是我们最常见的缓存,几乎所有产品都采用这种缓存方式。平时用户浏览文章、图集加载的数据就以这种形式缓存在本地,下次看回这篇文章、图集时就不需要加载了。用户也可以手动把这些缓存清理了,释放空间。
而对于某些特殊场景,例如一些相对固定的数据,我们不愿意一开始就打包进App里,这样会占太大容量,造成产品包很大,也不愿意每次进入页面都向服务器加载这些信息,那怎么办?建议的解决方法就是我们可以只加载一次就永远存在本地了,这样安装包也不会大,以后也不用加载了。
3、如何清理缓存?
一般App都会在“设置”里提供一个清理缓存的功能,一键把空间释放。除此之外,App最好要设计自动清理机制,可以通过两个维度来设计这个机制。
(1)、时间
通过设定一个固定的时间,或者根据用户使用周期灵活设定时间来清理缓存。每个产品的场景不一,用户使用频率不一,设定这个机制的时候就需要结合实际情况考虑了。
(2)、容量
一般是设定一个容量上限,采用堆栈的设计原理进行缓存清理,溢出堆栈的旧数据将自动清除。
1、页面加载
方案1:单页面整体加载
这种加载比较简单,一般运用在页面内容比较单一的情况下,所以直接一次性加载完所有数据后再显示内容。其单页面加载失败的状态相对来说也比较好处理。
方案2:单页面分块加载
这种方案的特点是,能让用户逐步看到内容,在这个渐进的过程中降低用户的焦虑心理。
其中又可以分为,模块间有关联性的,先加载父内容,再加载子内容。如优酷,先把栏目加载出来,再加载各栏目的内容。
模块间没有绝对关联性的,可独自加载各自模块内容,根据请求的速度不同分别显示。这样处理有一定几率让用户在没完全刷出数据的情况下就能找到自己需要的功能,如大众点评、淘宝客户端等。
框架固定,内容更新的,可先把框架显示出来,再把各模块的数据各自加载显示,如各种iOS自带应用。
这种分模块加载的需要特别注意加载失败的状态,毕竟每个模块都提示加载失败,点击重试是很挫的一件事,可以根据信息的优先级来决定哪些数据失败了采用默认状态,哪些数据采用失败提示。
方案3:跨页面加载
父页面&子页面 or 同一app内,页面间字段可以复用的,在加载子页面时不需要重新加载新数据。
方案4:预加载
这种加载方式的特点是,在加载一个页面内容的同时,预测用户的下一步行为,并为他下一步需要使用的页面加载内容,使得他在下一步的操作中能立刻获取信息而不需要加载等待。
预加载提供给用户无缝的产品使用体验,使得用户在使用产品的过程中更直接流畅,没有被打断的感觉。
具体的例子有:
在浏览图集的时候,当看到第一张的图片时,就自动后台加载第二第三第四张图片,用户浏览完第一张图片切换到第二张时就不会有加载等待的过程。
在浏览新闻列表时,就把每篇新闻的内容在后台进行预加载,用户选择看某篇新闻时,能立刻阅读到内容。
但是这种方案也需要面临很多的问题,马海祥觉得最直接的就是流量问题,因为会自动跑掉很多用户可能根本用不上的数据流量,所以,一般情况下马海祥建议可以设定在wifi环境才采用这种加载模式。又或者设定加载规则,只把主要内容预加载,而部分次要内容可以在用户真的用到的时候才加载,例如预加载新闻正文的情况,可以只加载文本信息,图片信息等到用户进入内页才加载。这种预加载与分块加载结合的方式也普遍运用在各个场景。
另外,预加载也需要时间的,他只是不在客户端显示给用户,默默在后台运作而已,需要特殊考虑未加载完用户就使用到那些信息的情况,所以在做预加载设计时需要同时考虑另一种适合该情况的普通加载方式。
预加载需要根据具体的场景来进行设计,设定好信息优先级,综合考虑各种类型信息的具体大小流量,整体考虑预加载的方式,这些都是需要经过精心分析思考的。
随着网络环境的发展,预加载将成为以后产品普遍的加载方式,他提供给用户的无缝使用体验**地提升了产品的可用性。
2、操作加载
除了页面的信息需要加载,页面内的操作也是需要通过给服务器发送请求记录的。
方案1:加载层
进行一个操作后,弹出模态的提示层,告知用户正在加载。采用模态的提示主要是防止用户在该过程中进行其他操作,导致当前加载出错。由于采用模态的提示,并且有可能因为网络原因导致长时间处于加载状态,建议提供一个“关闭”的操作,中止本次加载,恢复App可用状态。加载失败时可在当前浮层变换为失败提示。模态提示层是最稳妥的方式,但他会使用户在使用过程中有打断的感觉。
方案2:控件自身加载状态
这种方式是把操作加载的状态与控件的样式结合起来了,对某个控件进行操作后,控件变换为加载状态,此时控件不能重复操作。由于这种加载方式是控件的自身状态,不影响其他操作,所以用户也可以对页面进行其他操作,可能会导致同时有多个请求的情况,增加了加载失败的风险,这也算是这种方式的弊端,不过这种极端情况很少出现。请求失败后,可配合Toast提示告知用户失败的原因。
方案3:后台加载
用户在操作后,客户端立刻反馈操作成功,然后把请求放到后台与服务器交互,这一过程用户不需要了解,不需要等待,在正常情况**验是非常棒的。
但是在极端情况下会出现一些莫名其妙的状况,由于是后台记录请求并与服务器交互,所以实际请求是否成功客户端是不说明的,全部以操作成功来显示,这就会导致用户误以为操作成功了,但实际上下次来看发现没有成功。
所以,这种加载方式是需要根据具体使用场景来权衡使用的,对于一些重要的操作,建议还是使用模态的方式加载,对于一些小操作,如点赞、订阅、关注,可采用后台加载的方式。
3、下一页加载还是当前也加载
用户进入首页,正式迈出体验的第一步,接下来迎接的就是基于用户目标的界面间跳转。完成界面的跳转,会有各种加载策略,但无论形式如何,我们都可以将其归为两大类:“下一页加载”、“当前页加载”。
(1)“下一页加载”满足了用户提前窥视的需求
我们把页面看成“点”,页面流是连接这些点的“线”,我们以“用户想买一条牛仔裤”这一场景作为案例做了简单的眼动研究,从应用启动到商品浏览再到商品确定最后进入下单页,用户所呈现的瞳孔梯次增大,即E>D>C>B>A,为了解释这一现象,通过与被试交流,我们发现相比于各种浏览,用户更期待看到他们想看到的东西。因此此时的”下一页加载“正好,满足了用户提前窥视的需求。
(2) Wait!I Need Think Think
我们以同样的方式又对“使用支付宝对手机充值”这一场景做了研究,从开始支付到二次确认支付,用户所呈现的瞳孔都比较大,即A与B近似相等,通过访谈,我们发现与“递增体验流”不同的是,当用户遇到判断逻辑的界面时,用户并非急于想看下一页面到底包含怎样的内容,而是非0即1的验证心态,即我的操作效成功了吗?因此在判断逻辑界面中,用户的内容窥视需求并不强,当然也没什么内容,要么仅是一个小小的Toast,再大一点就是一个简单的信息反馈界面(意味着“下一页加载”在这里就是个鸡肋),用户反而对非0即1的验证需求较为强烈,其中还伴随着等待结果过程中的紧张感、激动感,因此界面通过 当前页加载 表明系统正在努力地处理用户交代的指令**了用户的紧张感、激动感,直到结果显现——“处理成功”,完成了非0即1验证的满足感。
4、先加载还是先展示
当需加载的是功能时,可以先展示再加载,当需加载的是内容时,则反过来。
淘宝
打开APP的第一个页面是功能,所以先展示再加载的:
随便点击一个模块(不要点菜单),下面要展示的将要是内容(商品),所以是先加载再展示的,没有加载完都不展示:
京东
同样的,功能模块先展示后加载:
内容先加载,没加载完不展示:
两种方式各有利弊:
先展示,后加载:
优点:给用户0等待的错觉
缺点:当前数据有可能是错的,而且得等用户操作到最后一步才会发现
先加载,后展示:
优点:保证数据的质量和准确
缺点:网络不好时,造成等待
显然,功能模块对于一个产品来说是既有固定的,在短时间内几乎不会更新,所以这种数据出现错误或与当前状态不同的几率小得多,因此,可以使用先展示后加载的方式。
另一方面,内容(特别是商品数据)是最容易产生变动的,为了保证每一个消费者看到的数据都是最真实,最准确的,所以务必要先加载再展示。
1、空白页面刷新失败有提示
现在的应用都标榜以内容为中心,所以都会极力避免空白页面的出现。对于大部分的应用,最好的方法就是使用缓存,进入页面之后,先显示之前的缓存,然后再进行内容的刷新。其次,消灭空白页面的第二种方法就是提供系统推荐项进行替代。但是对于一些页面,页面内容跟用户的使用状态关系密切,无法避免会出现空白页面,这时候会使用一些引导类的提示,使得页面变得更加丰富,同时可以促进用户产生内容。
但是一些资讯类应用,比如读读日报,打开默认是空白页面,然后再加载内容(我不是很明白这种设定)。其他一些应用,比如:豆瓣一刻和MONO,每天第一次进入应用的时候也会出现空白页面。我猜想第二类应用的展示方式的原因是这样的。他们的内容**都是严格以天为单位的,每天固定时段**精选内容。他们会希望你每天只看并且看完当天的东西,所以一旦到了第二天,昨天的内容就是累赘了。所以每天第一次进入应用的时候会出现空白页面,象征着每天都是从新开始。此时就会对应一个“空白刷新”逻辑。
空白刷新对应的场景是这样子的:用户想要刷新出内容,并且用户知道这里可以刷出新内容,但是没有刷新成功,这时候需要给用户一个交待。所以需要提示用户。同时,提示完用户之后需要给用户一个解决方法,这就是“点击后重试”。
2、缓存页面刷新失败无提示
常见的应用比如知乎、网易新闻、好奇心日报、微信朋友圈等,这些应用都会采用缓存的形式,打开之后显示的是缓存内容,然后系统会给服务器发送请求,如果有内容更新的话就会自动更新一次内容,更新之后的内容直接覆盖当前的内容。更新失败之后是没有提示的。但是有一些应用,比如有道词典、企鹅FM、网易云音乐等,他们更新失败之后是有提示的。
我觉得这两种应用的区分点在于
应用的使用频率;
内容的时间连续性;
界面之间的关系紧密度。
比如说网易新闻,作为一个打发时间的工具,每天使用频率就会比较高,所以用户进来之后是想看看有没有更新。其次,网易新闻的内容是连续不断更新的,所以用户会知道当前显示的内容是我看看过并且处理过的。最后,新闻列表页面显示的是摘要,用户可以通过摘要快速进行判断是否要进入详情页,摘要有助于帮助用户回忆上一次的使用场景。
所以这就对应着一个这样的场景:用户只是想看看有没有更新,所以他们已经做好了“没有新内容”的心理预期,所以即使是更新不了内容,用户也不会想太多。反倒是,如果进行了错误提示,用户可能会有一种挫败感。因为他知道现在有内容,只是因为网络的原因而没有更新,他要进行的任务受到了外界因素的阻碍,由此产生一种细微的挫败感。
3、缓存页面刷新失败有提示
另一类应用,使用频率没那么高,或者内容不具备时间连续性的,又或者说当前界面无法唤起用户上一次的使用场景。那么就有必要进行率先你失败提示了。
比如说企鹅FM,音频类的应用注定使用不会那么频繁,因为通过视觉接收的信息会比通过听觉接收的信息更快更多,同时音频类对环境的要求较高(比如用耳机时要求环境不那么嘈杂,外放时要求在私人场所)。其次,此类应用都是实时推荐的,不存在时间连续性的问题,用户无法通过时间来判断内容是否被阅读过。再者,标题也无法帮你快速做出判断,你还是要进去听过才知道内容是什么。最后如果不提醒,用户进入到详情页再收到提醒,就会觉得应用浪费了用户的时间。所以,对于此类内容,刷新失败是有必要进行提醒的。
9. 华为技术架构师分享:高并发场景下缓存处理的一些思路
在实际的开发当中,我们经常需要进行磁盘数据的读取和搜索,因此经常会有出现从数据库读取数据的场景出现。但是当数据访问量次数增大的时候,过多的磁盘读取可能会最终成为整个系统的性能瓶颈,甚至是压垮整个数据库,导致系统卡死等严重问题。
常规的应用系统中,我们通常会在需要的时候对数据库进行查找,因此系统的大致结构如下所示:
1.缓存和数据库之间数据一致性问题
常用于缓存处理的机制我总结为了以下几种:
首先来简单说说Cache aside的这种方式:
Cache Aside模式
这种模式处理缓存通常都是先从数据库缓存查询,如果缓存没有命中则从数据库中进行查找。
这里面会发生的三种情况如下:
缓存命中:
当查询的时候发现缓存存在,那么直接从缓存中提取。
缓存失效:
当缓存没有数据的时候,则从database里面读取源数据,再加入到cache里面去。
缓存更新:
当有新的写操作去修改database里面的数据时,需要在写操作完成之后,让cache里面对应的数据失效。
关于这种模式下依然会存在缺陷。比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。
Facebook的大牛们也曾经就缓存处理这个问题发表过相关的论文,链接如下:
分布式环境中要想完全的保证数据一致性是一件极为困难的事情,我们只能够尽可能的减低这种数据不一致性问题产生的情况。
Read Through模式
Read Through模式是指应用程序始终从缓存中请求数据。 如果缓存没有数据,则它负责使用底层提供程序插件从数据库中检索数据。 检索数据后,缓存会自行更新并将数据返回给调用应用程序。使用Read Through 有一个好处。
我们总是使用key从缓存中检索数据, 调用的应用程序不知道数据库, 由存储方来负责自己的缓存处理,这使代码更具可读性, 代码更清晰。但是这也有相应的缺陷,开发人员需要给编写相关的程序插件,增加了开发的难度性。
Write Through模式
Write Through模式和Read Through模式类似,当数据发生更新的时候,先去Cache里面进行更新,如果命中了,则先更新缓存再由Cache方来更新database。如果没有命中的话,就直接更新Cache里面的数据。
2.缓存穿透问题
在高并发的场景中,缓存穿透是一个经常都会遇到的问题。
什么是缓存穿透?
大量的请求在缓存中没有查询到指定的数据,因此需要从数据库中进行查询,造成缓存穿透。
会造成什么后果?
大量的请求短时间内涌入到database中进行查询会增加database的压力,最终导致database无法承载客户单请求的压力,出现宕机卡死等现象。
常用的解决方案通常有以下几类:
1.空值缓存
在某些特定的业务场景中,对于数据的查询可能会是空的,没有实际的存在,并且这类数据信息在短时间进行多次的反复查询也不会有变化,那么整个过程中,多次的请求数据库操作会显得有些多余。
不妨可以将这些空值(没有查询结果的数据)对应的key存储在缓存中,那么第二次查找的时候就不需要再次请求到database那么麻烦,只需要通过内存查询即可。这样的做法能够大大减少对于database的访问压力。
2.布隆过滤器
通常对于database里面的数据的key值可以预先存储在布隆过滤器里面去,然后先在布隆过滤器里面进行过滤,如果发现布隆过滤器中没有的话,就再去redis里面进行查询,如果redis中也没有数据的话,再去database查询。这样可以避免不存在的数据信息也去往存储库中进行查询情况。
什么是缓存雪崩?
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
如何避免缓存雪崩问题?
1.使用加锁队列来应付这种问题。当有多个请求涌入的时候,当缓存失效的时候加入一把分布式锁,只允许抢锁成功的请求去库里面读取数据然后将其存入缓存中,再释放锁,让后续的读请求从缓存中取数据。但是这种做法有一定的弊端,过多的读请求线程堵塞,将机器内存占满,依然没有能够从根本上解决问题。
2.在并发场景发生前,先手动触发请求,将缓存都存储起来,以减少后期请求对database的第一次查询的压力。数据过期时间设置尽量分散开来,不要让数据出现同一时间段出现缓存过期的情况。
3.从缓存可用性的角度来思考,避免缓存出现单点故障的问题,可以结合使用 主从+哨兵的模式来搭建缓存架构,但是这种模式搭建的缓存架构有个弊端,就是无法进行缓存分片,存储缓存的数据量有限制,因此可以升级为Redis Cluster架构来进行优化处理。(需要结合企业实际的经济实力,毕竟Redis Cluster的搭建需要更多的机器)
4.Ehcache本地缓存 + Hystrix限流&降级,避免MySQL被打死。
使用 Ehcache本地缓存的目的也是考虑在 Redis Cluster 完全不可用的时候,Ehcache本地缓存还能够支撑一阵。
使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑。
然后去调用我们自己开发的降级组件(降级),比如设置的一些默认值呀之类的。以此来保护最后的 MySQL 不会被大量的请求给打死。
10. Glide 图片库原理(三)缓存机制
查找缓存使用
用完移除
源码中查看EngineKey-----相当于key,算法序列abcdsxxfaskldfjklf...
源码中查看Source-----相当于value(Bitmap),调用系统转换成Bitmap
情景: 相册类的App经常需要同时展示大量的图片,这种情况下图片的质量可以低一点,因为加载速度优先于图片的质量。
解决办法: 我们可以设置译码的格式,在RequestOptions中加入.encodeFormat(Bitmap.CompressFormat.WEBP).encodeQuality(10))的选项,①encodeFormat的参数有Bitmap.CompressFormat.PNG,Bitmap.CompressFormat.JPEG,Bitmap.CompressFormat.WEBP(质量从高到低);②encodeQuality设置的是0-100的int类型,一个质量百分比参数,越小质量越低。
情景: 大体的意思应该是同一个URL在不同的时间可能会指向不同的资源,所以同样需要实时更新。
解决办法相同
情景: 开发一款有头像的APP,我们修改了头像并且更新到了服务端,可是当我们点击查看大图时加载出来的还是原来的头像。
解决办法: 这是Glide强大的缓存带来的副作用,我们可以在RequestOptions中加入.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)的选项。那么缓存的功能就会全部关闭,从而使得每次都是从服务端加载,所以头像会是最新。
情景: 省流量模式的应用情景就是减少不必要图片的加载。
解决办法: 我们可以在RequestOptions中加入onlyRetrieveFromCache(true)的选项。那么图片就只会从缓存中读取,如果没有缓存则不加载图片,从而达到减少流量消耗的目的。