最近我们app的服务器吃不消了,所以我在为服务器增加缓存层之后,又想到在app端进行二级缓存以减少app对服务器的访问。我想很多app应该在项目的初期架构的时候就考虑到了这个问题,但是我当时开发这个app的时候完全不懂架构和设计模式,所以对性能根本没有考虑,导致了现在服务器经常崩溃。关于服务器的优化之后有时间再说,今天我们先来看看如何对一个app的数据的请求和缓存进行架构。
一.数据请求的分类、数据的可缓存性分析和数据同步##
如何对请求进行归类?这是个问题,在Http请求里设定了八种请求的方式,但是显然并不适合我们这里的情况。我对我们app中的数据请求进行归类之后进行了一下两种分类
-
1.根据需要对请求做那些操作进行分类:
-
1.
GET
:需要去内存、硬盘和服务器中取数据的请求,请求时可以提供参数,但是参数并不会上传到内存、硬盘和服务器中。
-
2.
INSERT
:需要将数据上传至内存、硬盘和服务器中,返回的数据根据具体情况判断是否需要缓存到内存或者硬盘中。
-
3.
NONE
:向服务器进行验证操作,必须和服务器连接,上传的参数不会被存储在内存、硬盘和服务器中,返回的数据也不会被缓存在内存或者硬盘中
-
2.根据请求需要进行到哪个层次进行分类:
-
1.
to_memory
:仅仅向内存进行数据请求。
-
2.
to_local
:在向内存请求数据无果之后,去硬盘中进行数据请求
-
3.
to_network
:在向内存和硬盘请求数据无果之后,去服务器中进行数据请求。
什么样的数据更适合在本地进行缓存呢?
-
1.我们首先可以考虑服务器数据库中不常更新的数据表,例如:国家、地区、机关的信息表等等,这种数据需要客户端开发人员与后台人员进行沟通后确认。我认为像更改频率在
1天/次以上
并且
用户不可操作
的数据表就可以称为不常更新数据表。
-
2.仅仅关于个人的信息,例如:用户个人信息、用户联系人表、用户行为表等等。这里需要注意的一点是
仅仅关于个人
,因为像手Q发的动态和百度贴吧的帖子也是个人发的信息,那么这类数据适不适合进行本地缓存呢?我的结论是
不适合
,因为像这种个人数据,掺杂着其他用户的信息,一旦其他用户和这种个人数据交互的时候,本人app中的数据就需要花很大的力气进行
数据同步
。(
这里的例子不是很准确,因为像手Q和贴吧都可以以推送的形式进行数据同步并且送达率很高。而我们一般开发者使用的都是第三方的推送,一旦推送量比较大送达率很难接近50%,所以这种数据同步的方式不可取。
)
前面我们说了两类可以在本地进行缓存的数据表,那么这两类数据该如何与服务器同步?
-
1.对于不常更新的数据表,我们可以选定两三个除GET以外的用户使用频率不高的请求(例如:登录接口等等),这些请求在使用的时候必然会连上服务器。我们可以在这些接口中附加<时间戳:数据表>的键值对列表字段,然后让服务器对时间戳进行判断,判断本地缓存的数据表中的数据是否被更新过,最后在接口中返回需要更新的数据表标识,以便客户端在下次在本地请求该过期表的时候跳过内存和硬盘缓存直接去服务器取回相应的更新数据并更新本地表。
-
2.对于个人数据,只要保证网络请求成功的时候也在本地缓存中插入缓存,即可同步数据。
-
3.其实还有一种方式就是在服务器更改数据之后,将数据推送给客户端,但是我在比较了第三方推送的推送率之后选择了放弃这个办法,因为客户端和服务器之间的数据完整性是比较重要的。
二、数据引擎的设计模式##
分析了上面的三个问题之后,我们得想想要使用什么样的方法来设计我们的数据引擎,熟悉java设计模式的同学可能会想到很多东西。我最近看了
Android源码设计模式解析与实战
之后也了解到了许多设计模式,现在的问题是怎么用这些设计模式呢?还好我最近又翻译了
Fresco和OkHttp
的源码,所以可以从这两个牛逼的开源项目中吸取一点经验。
-
1.
单例
:这个我想绝大部分同学应该没问题,所以简单讲讲:我们可以将 内存缓存、硬盘缓存、Http请求服务、数据引擎 这些需要花费大量资源创建的实例设置为单例。
-
2.
Builder模式
:对于一些构造参数比较多的类,我们可以使用这个模式来使代码变得清晰;
-
3.
外观模式
:我们的数据引擎,只需要暴露出一个 数据引擎的实例就可以了,其他像 内存缓存、硬盘缓存、Http请求服务等,都可以作为子系统集成在数据引擎实例中。
-
4.
静态工厂模式
:对于有多个构造函数的类,我们可以使用这个模式,使客户端在使用的时候不会传错参数。
-
5.设计模式六大原则中的五点:
-
1.
单一职责原则
:数据引擎中每一个子系统都只能关注自己的功能实现,不参与其他功能的任何逻辑
-
2.
开闭原则
:对于数据引擎,我们不能在其内部持有各个子系统的具体实现,在开发过程中我们可能会随时遇见更好的子系统的实现,为了方便更改实现,我们需要在数据引擎中持有各个子功能抽象出来的接口,当我们遇见更好的子系统的实现的时候只需要让其实现该接口,可以随时更换实现。
-
3.
里氏替换原则
:在2的基础上,任何子系统的实现都能直接放入数据引擎中,而不需要任何其他的配置,如if等条件。
-
4.
依赖倒置原则
:各个子功能的实现类不需要有任何交流,交流都是通过子功能接口实现的。用职责链模式实现这个原则,后面会讲到。
-
5.
接口隔离原则
:每个子功能的接口需要最精简。
-
6.
职责链模式
:这里是借鉴OkHttp的拦截器。将 MemoryCache-》DiskCache-》NetWork 这样的运行流程以
拦截器链
的形式串起来。拦截器链中子功能的交流都是以接口的形式,这也就实现了 依赖倒置原则。
-
7.
缓存生命周期的log展示
:这里是借鉴Fresco,内存缓存和硬盘缓存 的缓存条目 从存在到删除可以看成一个完整的生命周期。为了方便debug,我们可以将(插入、更新、删除、清空等操作)看成一个个事件,然后建立一个监听器监听整个生命周期。
-
8.
享元模式
:由于我们请求和缓存可能会比较频繁,所以我们可以以对象池的形式复用对象,以减少大量创建和销毁对象所需要的时间。
三、具体代码实现##
务虚了这么久,也应该务实了。先上项目例子
数据引擎架构项目源码
,建议大家下载过来,然后结合博客一起观看下面我来分析一下
数据引擎架构
的实现代码。
1.请求和返回类设计
我们可以将所有的数据请求,当成类似于网络请求的格式。这样一来我们可以创建两个类一个Request,一个Response供客户端使用
public class Request {
private static final String TAG="Request";
private static final Object RECYCLER_LOCK = new Object();
private static int MAX_RECYCLED = 5;
private static Request sFirstRecycledRequest;
private static int sRecycledCount;
private Request mNextRecycledRequest;
@NonNull
private RequestFlag mRequestFlag;
@NonNull
private CacheKey mCacheKey;
@Nullable
private Object mParam;
private boolean isCacheToMemory=true;
private boolean isSaveToLocal=true;
private boolean[] interceptorIsEnable=new boolean[]{true,true,true,true,true,true,true,true,true};
public static Request setFlag(@NonNull RequestFlag requestFlag){
return obtain(requestFlag,null,null,null,-1);
}
public static Request setFlagParam(@NonNull RequestFlag requestFlag, @NonNull Object param){
return obtain(requestFlag,param,null,null,-1);
}
public static Request setFlagParamKey(@NonNull RequestFlag requestFlag, @NonNull Object param, @NonNull String key){
return obtain(requestFlag,param,key,null,-1);
}
public static Request setFlagParamInterceptorIsEnable(@NonNull RequestFlag requestFlag, @NonNull Object param, @NonNull boolean[] interceptorIsEnable){
return obtain(requestFlag,param,null,interceptorIsEnable,-1);
}
public static Request setFlagParamWhichServiceUnable(@NonNull RequestFlag requestFlag, @NonNull Object param, int whichUnable){
return obtain(requestFlag,param,null,null,whichUnable);
}
public void recycle() {
synchronized (RECYCLER_LOCK) {
if (sRecycledCount < MAX_RECYCLED) {
reset();
sRecycledCount++;
if (sFirstRecycledRequest != null) {
mNextRecycledRequest= sFirstRecycledRequest;
}
FLog.v(TAG,"回收Request sRecycledCount:"+sRecycledCount);
sFirstRecycledRequest= this;
}
}
}
private static Request obtain(@NonNull RequestFlag requestFlag, Object param, String key, boolean[] interceptorIsEnable, int whichUnable) {
synchronized (RECYCLER_LOCK) {
if (sFirstRecycledRequest!= null) {
Request requestToReuse = sFirstRecycledRequest;
sFirstRecycledRequest= requestToReuse.mNextRecycledRequest;
requestToReuse.mNextRecycledRequest= null;
requestToReuse.mRequestFlag=requestFlag;
if (param==null){
requestToReuse.mCacheKey=new SimpleCacheKey(requestFlag.toString());
}else {
requestToReuse.mParam=param;
if (key!=null){
requestToReuse.mCacheKey = new SimpleCacheKey(key);
}else {
requestToReuse.mCacheKey=new SimpleCacheKey(JsonUtil.objectToJson(param));
if (interceptorIsEnable!=null) {
requestToReuse.interceptorIsEnable = interceptorIsEnable;
}else {
if (whichUnable!=-1)requestToReuse.interceptorIsEnable[whichUnable]=false;
}
}
}
sRecycledCount--;
FLog.v(TAG,"从对象池中获取Request sRecycledCount:"+sRecycledCount);
return requestToReuse;
}
}
FLog.v(TAG,"对象池已空,创建一个Request sRecycledCount:"+sRecycledCount);
if (param==null){
return new Request(requestFlag);
}else {
if (key!=null){
return new Request(requestFlag,param,key);
}else {
if (interceptorIsEnable!=null) {
return new Request(requestFlag,param,interceptorIsEnable);
}else {
if (whichUnable==-1){
return new Request(requestFlag,param);
}else {
return new Request(requestFlag,param,whichUnable);
}
}
}
}
}
private void reset() {
mRequestFlag=null;
mCacheKey=null;
mParam=null;
isCacheToMemory=true;
isSaveToLocal=true;
interceptorIsEnable=new boolean[]{true,true,true,true,true,true,true,true,true};
}
private Request() {
}
private Request(@NonNull RequestFlag requestFlag) {
mRequestFlag = requestFlag;
mCacheKey=new SimpleCacheKey(mRequestFlag.toString());
}
private Request(@NonNull RequestFlag requestFlag, @Nullable Object param) {
mRequestFlag = requestFlag;
mParam = param;
mCacheKey=new SimpleCacheKey(JsonUtil.objectToJson(param));
}
private Request(@NonNull RequestFlag requestFlag, @Nullable Object param, boolean[] interceptorIsEnable) {
mRequestFlag = requestFlag;
mParam = param;
this.interceptorIsEnable = interceptorIsEnable;
mCacheKey=new SimpleCacheKey(JsonUtil.objectToJson(param));
}
private Request(@NonNull RequestFlag requestFlag, @Nullable Object param, int whichUnable) {
mRequestFlag = requestFlag;
mParam = param;
interceptorIsEnable[whichUnable]=false;
mCacheKey=new SimpleCacheKey(JsonUtil.objectToJson(param));
}
private Request(@NonNull RequestFlag requestFlag, @Nullable Object param, String key) {
mCacheKey = new SimpleCacheKey(key);
mRequestFlag = requestFlag;
mParam = param;
}
public Request setParam(@Nullable Object param) {
this.mParam = param;
return this;
}
public Request setRequestFlag(@NonNull RequestFlag requestFlag) {
mRequestFlag = requestFlag;
return this;
}
public Request setServiceIsEnable(int serviceNum, boolean isEnable) {
if (serviceNum<interceptorIsEnable.length)interceptorIsEnable[serviceNum]=isEnable;
return this;
}
public Request setInterceptorIsEnable(boolean[] interceptorIsEnable) {
this.interceptorIsEnable = interceptorIsEnable;
return this;
}
public boolean getWhichServiceIsEnable(int serviceNum) {
return serviceNum < interceptorIsEnable.length && interceptorIsEnable[serviceNum];
}
public Request setCacheKey(@NonNull CacheKey cacheKey) {
mCacheKey = cacheKey;
return this;
}
public Request setCacheToMemory(boolean cacheToMemory) {
isCacheToMemory = cacheToMemory;
return this;
}
public Request setSaveToLocal(boolean saveToLocal) {
isSaveToLocal = saveToLocal;
return this;
}
public boolean[] getInterceptorIsEnable() {
return interceptorIsEnable;
}
@Nullable
public Object getParam() {
return mParam;
}
@NonNull
public RequestFlag getRequestFlag() {
return mRequestFlag;
}
@NonNull
public CacheKey getCacheKey() {
return mCacheKey;
}
public boolean isCacheToMemory() {
return isCacheToMemory;
}
public boolean isSaveToLocal() {
return isSaveToLocal;
}
@Override
public String toString() {
return "Request:{" +
"mCacheKey=" + mCacheKey +
", mRequestFlag=" + mRequestFlag +
", mParam=" + mParam +
", isCacheToMemory=" + isCacheToMemory +
", isSaveToLocal=" + isSaveToLocal +
", interceptorIsEnable=" + Arrays.toString(interceptorIsEnable) +
'}';
}
}
-
1.Request:我们在考虑请求的时候我总结了以下几个在请求链之中需要用到的东西
-
1.Object mParam:请求的参数,由于各种数据请求需要的参数是不同的,所以我们可以将请求参数设置为Object,在具体请求中进行更改
-
2.CacheKey mCacheKey:CacheKey是一个接口,这个接口的实现类会作为内存和硬盘缓存时候的key来使用
-
3.RequestFlag mRequestFlag:每一个不同的请求我们都需要用一个enum类来标识。RequestFlag是一个接口,我们根据前面第一个请求的分类(
NONE、GET、INSERT
),实现了三个不同的enum,然后让这三个enum实现RequestFlag。这样一来就能用一个RequestFlag,标识出一个请求的种类,一个RequestFlag中除了有请求的分类,里面还有请求的层次(
to_memory、to_local、to_network
)。
-
4.boolean isCacheToMemory:在请求从硬盘或者网络返回的时候,我们需要判断该请求返回的数据是否需要被内存缓存,默认是需要的
-
5.boolean isSaveToLocal:在请求从网络返回的时候,我们需要判断该请求返回的数据是否需要被存储在本地,默认是需要的
-
6.boolean[] interceptorIsEnable:在请求链之中,我们可能需要屏蔽某些拦截器,此时可以在这里设置哪些拦截器奏效,注意这里的拦截是指请求上传时候的拦截。
-
7.
除上面几个参数,其他的参数都是为了创建对象池构建的,这里的对象池使用了链表的方式,在使用obtain方法获取的时候,将链首的对象取出。在使用recycle方法回收本对象的时候,将本对象设为链首。链表设置了最大值,当超过最大值的时候,就直接使用创建的对象
-
8.
在获取Reuqest对象的时候,使用了由于要传入的参数比较多,所以使用了静态工厂模式和类似Builder的链式创建模式
public class Response<Response1,Response2 ,Response3> {
private boolean isSuccess;
private Exception mException;
private Response1 mResponse1;
private Response2 mResponse2;
private Response3 mResponse3;
public static <T1,T2,T3> Response<T1,T2,T3> getFailedResponse(Exception exception){
return new Response<T1,T2,T3>(false,exception);
}
public static Response getCommonResponseOne(Object response1){
return new Response<Object,Object,Object>(response1);
}
public static Response getCommonResponseTwo(Object response1,Object response2){
return new Response<Object,Object,Object>(response1,response2);
}
public static Response getCommonResponseThree(Object response1,Object response2,Object response3){
return new Response<Object,Object,Object>(response1,response2,response3);
}
public static <T> Response getResponseOne(T response1){
return new Response<T,Object,Object>(response1);
}
public static <T1,T2> Response getResponseTwo(T1 response1,T2 response2){
return new Response<T1,T2,Object>(response1);
}
public static <T1,T2,T3> Response getResponseOne(T1 response1,T2 response2,T3 response3){
return new Response<T1,T2,T3>(response1,response2,response3);
}
private Response(boolean isSuccess, Exception exception) {
this.isSuccess = isSuccess;
mException = exception;
}
private Response(Response1 response1) {
mResponse1 = response1;
isSuccess=true;
}
private Response(Response1 response1, Response2 response2) {
mResponse1 = response1;
mResponse2 = response2;
isSuccess=true;
}
private Response(Response1 response1, Response2 response2, Response3 response3) {
mResponse1 = response1;
mResponse2 = response2;
mResponse3 = response3;
isSuccess=true;
}
public Response1 getResponse1() {
return mResponse1;
}
public void setResponse1(Response1 response1) {
mResponse1 = response1;
}
public Response2 getResponse2() {
return mResponse2;
}
public void setResponse2(Response2 response2) {
mResponse2 = response2;
}
public Response3 getResponse3() {
return mResponse3;
}
public void setResponse3(Response3 response3) {
mResponse3 = response3;
}
public boolean isSuccess() {
return isSuccess;
}
public Exception getException() {
return mException;
}
}
-
2.Response:这个类比较简单,因为我们在客户端使用的时候,可能会将一个数据结果转化为多个实体供界面使用,所以有三个泛型参数可以设置。还有就是请求出错的时候返回的Exception,注意这里的出错仅仅指的是
异常
,其他类似于数据不符合的情况不在考虑之中。
2.拦截链的骨架
我们在前面的设计模式中提到了,在设计一个架构的时候需要面向接口编程,这样才会符合前面说的设计模式六大原则。