详解App保活实现原理

概述

早期的 Android 系统不完善,导致 App 侧有很多空子可以钻,因此它们有着有着各种各样的姿势进行保活。譬如说在 Android 5.0 以前,App 内部通过 native 方式 fork 出来的进程是不受系统管控的,系统在杀 App 进程的时候,只会去杀 App 启动的 Java 进程;因此诞生了一大批“毒瘤”,他们通过 fork native 进程,在 App 的 Java 进程被杀死的时候通过am命令拉起自己从而实现永生。那时候的 Android 可谓是魑魅横行,群魔乱舞;系统根本管不住应用,因此长期以来被人诟病耗电、卡顿。同时,系统的软弱导致了 Xposed 框架、阻止运行、绿色守护、黑域、冰箱等一系列管制系统后台进程的框架和 App 出现。

不过,随着 Android 系统的发展,这一切都在往好的方向演变。

Android 5.0 以上,系统杀进程以uid为标识,通过杀死整个进程组来杀进程,因此 native 进程也躲不过系统的法眼。

Android 6.0 引入了待机模式(doze),一旦用户拔下设备的电源插头,并在屏幕关闭后的一段时间内使其保持不活动状态,设备会进入低电耗模式,在该模式下设备会尝试让系统保持休眠状态。

Android 7.0 加强了之前鸡肋的待机模式(不再要求设备静止状态),同时对开启了 Project Svelte,Project Svelte 是专门用来优化 Android 系统后台的项目,在 Android 7.0 上直接移除了一些隐式广播,App 无法再通过监听这些广播拉起自己。

Android 8.0 进一步加强了应用后台执行限制:一旦应用进入已缓存状态时,如果没有活动的组件,系统将解除应用具有的所有唤醒锁。另外,系统会限制未在前台运行的应用的某些行为,比如说应用的后台服务的访问受到限制,也无法使用 Mainifest 注册大部分隐式广播。

Android 9.0 进一步改进了省电模式的功能并加入了应用待机分组,长时间不用的 App 会被打入冷宫;另外,系统监测到应用消耗过多资源时,系统会通知并询问用户是否需要限制该应用的后台活动。

然而,道高一尺,魔高一丈。系统在不断演进,保活方法也在不断发展。大约在 4 年前出现过一个MarsDaemon,这个库通过双进程守护的方式实现保活,一时间风头无两。不过好景不长,进入 Android 8.0 时代之后,这个库就逐渐消亡。

一般来说,Android 进程保活分为两个方面:

  • 保持进程不被系统杀死。
  • 进程被系统杀死之后,可以重新复活。

随着 Android 系统变得越来越完善,单单通过自己拉活自己逐渐变得不可能了;因此后面的所谓「保活」基本上是两条路:1. 提升自己进程的优先级,让系统不要轻易弄死自己;2. App 之间互相结盟,一个兄弟死了其他兄弟把它拉起来。

当然,还有一种终极方法,那就是跟各大系统厂商建立 PY 关系,把自己加入系统内存清理的白名单;比如说国民应用微信。当然这条路一般人是没有资格走的。

大约一年以前,大神 gityuan 在其博客上公布了 TIM 使用的一种可以称之为「终极永生术」的保活方法;这种方法在当前 Android 内核的实现上可以大大提升进程的存活率。笔者研究了这种保活思路的实现原理,并且提供了一个参考实现Leoric。接下来就给大家分享一下这个终极保活黑科技的实现原理。

保活的底层技术原理

知己知彼,百战不殆。既然我们想要保活,那么首先得知道我们是怎么死的。一般来说,系统杀进程有两种方法,这两个方法都通过 ActivityManagerService 提供:

1.killBackgroundProcesses

2.forceStopPackage

在原生系统上,很多时候杀进程是通过第一种方式,除非用户主动在 App 的设置界面点击「强制停止」。不过国内各厂商以及一加三星等 ROM 现在一般使用第二种方法。第一种方法太过温柔,根本治不住想要搞事情的应用。第二种方法就比较强力了,一般来说被 force-stop 之后,App 就只能乖乖等死了。

因此,要实现保活,我们就得知道 force-stop 到底是如何运作的。既然如此,我们就跟踪一下系统的forceStopPackage这个方法的执行流程:

首先是ActivityManagerService里面的forceStopPackage这方法:

public void forceStopPackage(final String packageName, int userId) {

    // .. 权限检查,省略

    long callingId = Binder.clearCallingIdentity();
    try {
        IPackageManager pm = AppGlobals.getPackageManager();
        synchronized(this) {
            int[] users = userId == UserHandle.USER_ALL
                    ? mUserController.getUsers() : new int[] { userId };
            for (int user : users) {

                // 状态判断,省略..

                int pkgUid = -1;
                try {
                    pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING,
                            user);
                } catch (RemoteException e) {
                }
                if (pkgUid == -1) {
                    Slog.w(TAG, "Invalid packageName: " + packageName);
                    continue;
                }
                try {
                    pm.setPackageStoppedState(packageName, true, user);
                } catch (RemoteException e) {
                } catch (IllegalArgumentException e) {
                    Slog.w(TAG, "Failed trying to unstop package "
                            + packageName + ": " + e);
                }
                if (mUserController.isUserRunning(user, 0)) {
                    // 根据 UID 和包名杀进程
                    forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
                    finishForceStopPackageLocked(packageName, pkgUid);
                }
            }
        }
    } finally {
        Binder.restoreCallingIdentity(callingId);
    }
}

在这里我们可以知道,系统是通过uid为单位 force-stop 进程的,因此不论你是 native 进程还是 Java 进程,force-stop 都会将你统统杀死。我们继续跟踪forceStopPackageLocked这个方法:

final boolean forceStopPackageLocked(String packageName, int appId,
        boolean callerWillRestart, boolean purgeCache, boolean doit,
        boolean evenPersistent, boolean uninstalling, int userId, String reason) {
    int i;

    // .. 状态判断,省略

    boolean didSomething = mProcessList.killPackageProcessesLocked(packageName, appId, userId,
            ProcessList.INVALID_ADJ, callerWillRestart, true /* allowRestart */, doit,
            evenPersistent, true /* setRemoved */,
            packageName == null ? ("stop user " + userId) : ("stop " + packageName));

    didSomething |=
            mAtmInternal.onForceStopPackage(packageName, doit, evenPersistent, userId);

    // 清理 service
    // 清理 broadcastreceiver
    // 清理 providers
    // 清理其他

    return didSomething;
}

这个方法实现很清晰:先杀死这个 App 内部的所有进程,然后清理残留在 system_server 内的四大组件信息;我们关心进程是如何被杀死的,因此继续跟踪killPackageProcessesLocked,这个方法最终会调用到ProcessList内部的removeProcessLocked方法,removeProcessLocked会调用ProcessRecord的kill方法,我们看看这个kill:

void kill(String reason, boolean noisy) {
    if (!killedByAm) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
        if (mService != null && (noisy || info.uid == mService.mCurOomAdjUid)) {
            mService.reportUidInfoMessageLocked(TAG,
                    "Killing " + toShortString() + " (adj " + setAdj + "): " + reason,
                    info.uid);
        }
        if (pid > 0) {
            EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
            Process.killProcessQuiet(pid);
            ProcessList.killProcessGroup(uid, pid);
        } else {
            pendingStart = false;
        }
        if (!mPersistent) {
            killed = true;
            killedByAm = true;
        }
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }
}

这里我们可以看到,首先杀掉了目标进程,然后会以uid为单位杀掉目标进程组。如果只杀掉目标进程,那么我们可以通过双进程守护的方式实现保活;关键就在于这个killProcessGroup,继续跟踪之后发现这是一个 native 方法,它的最终实现在libprocessgroup中,代码如下:

int killProcessGroup(uid_t uid, int initialPid, int signal) {
    return KillProcessGroup(uid, initialPid, signal, 40 /*retries*/);
}

注意这里有个奇怪的数字:40。我们继续跟踪:

static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) {

    // 省略

    int retry = retries;
    int processes;
    while ((processes = DoKillProcessGroupOnce(cgroup, uid, initialPid, signal)) > 0) {
        LOG(VERBOSE) << "Killed " << processes << " processes for processgroup " << initialPid;
        if (retry > 0) {
            std::this_thread::sleep_for(5ms);
            --retry;
        } else {
            break;
        }
    }

    // 省略
}

瞧瞧我们的系统做了什么骚操作?循环 40 遍不停滴杀进程,每次杀完之后等 5ms,循环完毕之后就算过去了。

看到这段代码,我想任何人都会蹦出一个疑问:假设经历连续 40 次的杀进程之后,如果 App 还有进程存在,那不就侥幸逃脱了吗?

实现方法

那么,如何实现这个目的呢?我们看这个关键的5ms。假设,App 进程在被杀掉之后,能够以足够快的速度(5ms 内)启动一堆新的进程,那么系统在一次循环杀掉老的所有进程之后,sleep 5ms 之后又会遇到一堆新的进程;如此循环 40 次,只要我们每次都能够拉起新的进程,那我们的 App 就能逃过系统的追杀,实现永生。是的,炼狱般的 200ms,只要我们熬过 200ms 就能渡劫成功,得道飞升。不知道大家有没有玩过打地鼠这个游戏,整个过程非常类似,按下去一个又冒出一个,只要每次都能足够快地冒出来,我们就赢了。

现在问题的关键就在于:如何在 5ms 内启动一堆新的进程?

再回过头来看原来的保活方式,它们拉起进程最开始通过am命令,这个命令实际上是一个 java 程序,它会经历启动一个进程然后启动一个 ART 虚拟机,接着获取 ams 的 binder 代理,然后与 ams 进行 binder 同步通信。这个过程实在是太慢了,在这与死神赛跑的 5ms 里,它的速度的确是不敢恭维。

后来,MarsDaemon 提出了一种新的方式,它用 binder 引用直接给 ams 发送 Parcel,这个过程相比am命令快了很多,从而大大提高了成功率。其实这里还有改进的空间,毕竟这里还是在 Java 层调用,Java 语言在这种实时性要求极高的场合有一个非常令人诟病的特性:垃圾回收(GC);虽然我们在这 5ms 内直接碰上 gc 引发停顿的可能性非常小,但是由于 GC 的存在,ART 中的 Java 代码存在非常多的 checkpoint;想象一下你现在是一个信使有重要军情要报告,但是在路上却碰到很多关隘,而且很可能被勒令暂时停止一下,这种情况是不可接受的。因此,最好的方法是通过 native code 给 ams 发送 binder 调用;当然,如果再底层一点,我们甚至可以通过ioctl直接给 binder 驱动发送数据进而完成调用,但是这种方法的兼容性比较差,没有用 native 方式省心。

通过在 native 层给 ams 发送 binder 消息拉起进程,我们算是解决了「快速拉起进程」这个问题。但是这个还是不够。还是回到打地鼠这个游戏,假设你摁下一个地鼠,会冒起一个新的地鼠,那么你每次都能摁下去最后获取胜利的概率还是比较高的;但如果你每次摁下一个地鼠,其他所有地鼠都能冒出来呢?这个难度系数可是要高多了。如果我们的进程能够在任意一个进程死亡之后,都能让把其他所有进程全部拉起,这样系统就很难杀死我们了。

新的黑科技保活中通过 2 个机制来保证进程之间的互相拉起:

1.2 个进程通过互相监听文件锁的方式,来感知彼此的死亡。

2.通过 fork 产生子进程,fork 的进程同属一个进程组,一个被杀之后会触发另外一个进程被杀,从而被文件锁感知。

具体来说,创建 2 个进程 p1, p2,这两个进程通过文件锁互相关联,一个被杀之后拉起另外一个;同时 p1 经过 2 次 fork 产生孤儿进程 c1,p2 经过 2 次 fork 产生孤儿进程 c2,c1 和 c2 之间建立文件锁关联。这样假设 p1 被杀,那么 p2 会立马感知到,然后 p1 和 c1 同属一个进程组,p1 被杀会触发 c1 被杀,c1 死后 c2 立马感受到从而拉起 p1,因此这四个进程三三之间形成了铁三角,从而保证了存活率。

分析到这里,这种方案的大致原理我们已经清晰了。基于以上原理,我写了一个简单的 PoC,代码在这里:https://github.com/tiann/Leoric有兴趣的可以看一下。

改进空间

本方案的原理还是比较简单直观的,但是要实现稳定的保活,还需要很多细节要补充;特别是那与死神赛跑的 5ms,需要不计一切代价去优化才能提升成功率。具体来说,就是当前的实现是在 Java 层用 binder 调用的,我们应该在 native 层完成。笔者曾经实现过这个方案,但是这个库本质上是有损用户利益的,因此并不打算公开代码,这里简单提一下实现思路供大家学习:

如何在 native 层进行 binder 通信

libbinder 是 NDK 公开库,拿到对应头文件,动态链接即可。

难点:依赖繁多,剥离头文件是个体力活。

如何组织 binder 通信的数据?

通信的数据其实就是二进制流;具体表现就是 (C++/Java) Parcel 对象。native 层没有对应的 Intent Parcel,兼容性差。

方案:

1.Java 层创建 Parcel (含 Intent),拿到 Parcel 对象的 mNativePtr(native peer),传到 Native 层。

2.native 层直接把 mNativePtr 强转为结构体指针。

3.fork 子进程,建立管道,准备传输 parcel 数据。

4.子进程读管道,拿到二进制流,重组为 parcel。

如何应对

今天我把这个实现原理公开,并且提供 PoC 代码,并不是鼓励大家使用这种方式保活,而是希望各大系统厂商能感知到这种黑科技的存在,推动自己的系统彻底解决这个问题。

两年前我就知道了这个方案的存在,不过当时鲜为人知。最近一个月我发现很多 App 都使用了这种方案,把我的 Android 手机折腾的惨不忍睹;毕竟本人手机上安装了将近 800 个 App,假设每个 App 都用这个方案保活,那这系统就没法用了。

系统如何应对

如果我们把系统杀进程比喻为斩首,那么这个保活方案的精髓在于能快速长出一个新的头;因此应对之法也很简单,只要我们在斩杀一个进程的时候,让别的进程老老实实呆着别搞事情就 OK 了。具体的实现方法多种多样,不赘述。

用户如何应对

在厂商没有推出解决方案之前,用户可以有一些方案来缓解使用这个方案进行保活的流氓 App。这里推荐两个应用给大家:

  • 冰箱
  • Island

通过冰箱的冻结和 Island 的深度休眠可以彻底阻止 App 的这种保活行为。当然,如果你喜欢别的这种“冻结”类型的应用,比如小黑屋或者太极的阴阳之门也是可以的。

其他不是通过“冻结”这种机制来压制后台的应用理论上对这种保活方案的作用非常有限。

总结

1.对技术来说,黑科技没有什么黑的,不过是对系统底层原理的深入了解从而反过来对抗系统的一种手段。很多人会说,了解系统底层有什么用,本文应该可以给出一个答案:可以实现别人永远也无法实现的功能,通过技术推动产品,从而产生巨大的商业价值。

2.黑科技虽强,但是它不该存在于这世上。没有规矩,不成方圆。黑科技黑的了一时,黑不了一世。要提升产品的存活率,终归要落到产品本身上面来,尊重用户,提升体验方是正途。

以上就是详解App保活实现原理的详细内容,更多关于App保活实现原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • TCP 四种定时器(重传定时器,坚持计时器,保活定时器,时间等待计时器)

    TCP 四种定时器 重传定时器 主要为了防止报文丢失或者阻塞.当A向B发送报文时,就会启动重传定时器,若在定时器到达之后,仍没有收到B的确认报文,则A会重新发送上次发送的报文.同时,令重传定时器复位.继续计时. 坚持计时器 此计时器针对下面场景: 当B向A发送了0窗口报文,B此时已经没有空间接受A发送的数据了,通知A停止发送.A在收到后即停止发送,等待一段时间后,B有了一些空间,可以继续接收了.此时再向A发送非0窗口报文.如果此非0窗口报文在网络中阻塞或者丢失了,那么A将永远以为B没有空间接收数

  • C# 定时器保活机制引起的内存泄露问题解决

    C# 中有三种定时器,System.Windows.Forms 中的定时器和 System.Timers.Timer 的工作方式是完全一样的,所以,这里我们仅讨论 System.Timers.Timer 和 System.Threading.Timer 1.定时器保活 先来看一个例子: class Program { static void Main(string[] args) { Start(); GC.Collect(); Read(); } static void Start() { F

  • node后端服务保活的实现

    引言 目前的项目中使用了node,作为一个简单的后端服务,随着承担着越来越多的线上业务的服务,就要求了服务端的稳定性,而其中最重要的一点就是服务保活.有进程终止后自动重启的能力. forever forever是一个简单的命令行工具,他能确保一个给定的脚本持续运行.forever完全基于命令行操作,在forever进程之下,创建node的子进程,通过monitor监控node子进程的运行情况,一旦文件更新,或者进程挂掉,forever会自动重启node服务器,确保应用正常运行. 所以就看一下fo

  • Android 后台运行白名单实现保活

    保活现状 我们知道,Android 系统会存在杀后台进程的情况,并且随着系统版本的更新,杀进程的力度还有越来越大的趋势.系统这种做法本身出发点是好的,因为可以节省内存,降低功耗,也避免了一些流氓行为. 但有一部分应用,应用本身的使用场景就需要在后台运行,用户也是愿意让它在后台运行的,比如跑步类应用.一方面流氓软件用各种流氓手段进行保活,另一方面系统加大杀后台的力度,导致我们一些真正需要在后台运行的应用被误杀,苦不堪言. 优雅保活? 为了做到保活,出现了不少「黑科技」,比如 1 个像素的 Acti

  • 详解Android进程保活的方法

    关于 Android 平台的进程保活这一块,想必是所有 Android 开发者瞩目的内容之一.你到网上搜 Android 进程保活,可以搜出各种各样神乎其技的做法,绝大多数都是极其不靠谱.前段时间,Github还出现了一个很火的"黑科技"进程保活库,声称可以做到进程永生不死. 怀着学习和膜拜的心情进去Github围观,结果发现很多人提了 Issue 说各种各样的机子无法成功保活. 看到这里,我瞬间就放心了.坦白的讲,我是真心不希望有这种黑科技存在的,它只会滋生更多的流氓应用,拖垮我大

  • Android应用保活实践详解

    最近在做的项目中需要app在后台常驻,用于实时上传一些健康信息数据,便于后台实时查看用户的健康状况.自从Android7.0以上后台常驻实现越来越难,尤其是8.0及以上.关于保活的文章比比皆是,但是效果并不理想,关于保活的方法也就常说的哪几种,重点在于怎么组合运用.最终实现效果为:用户不主动强制杀死的话,能够一直存活(小米,华为,vivo,oppo,三星).其中三星s8,华为nova2s用户强制杀死也能存活. 项目结构 常见的保活方案 关于Android应用保活的文章很多,这里不再阐述,可自行百

  • 详解Android 8.0以上系统应用如何保活

    最近在做一个埋点的sdk,由于埋点是分批上传的,不是每次都上传,所以会有个进程保活的机制,这也是自研推送的实现技术之一:如何保证Android进程的存活. 对于Android来说,保活主要有以下一些方法: 开启前台Service(效果好,推荐) Service中循环播放一段无声音频(效果较好,但耗电量高,谨慎使用) 双进程守护(Android 5.0前有效) JobScheduler(Android 5.0后引入,8.0后失效) 1 像素activity保活方案(不推荐) 广播锁屏.自定义锁屏(

  • Android进程保活之提升进程优先级

    一.1 像素 Activity 提高进程优先级 使用 Activity 可以提升进程的 oom_adj 值 ; APP 进入后台后 , 使用 BroadcastReceiver 广播接收者 , 监听 Android 系统的锁屏广播事件 ; 屏幕锁定 : 启动只有 1 1 1 像素的透明 Activity 界面 ; 屏幕解锁 : 退出上述 1 1 1 像素的透明 Activity 界面 ; 1.主界面 MainActivity 主界面 , 主要负责注册广播接收者 ; package kim.hsl

  • 详解App保活实现原理

    概述 早期的 Android 系统不完善,导致 App 侧有很多空子可以钻,因此它们有着有着各种各样的姿势进行保活.譬如说在 Android 5.0 以前,App 内部通过 native 方式 fork 出来的进程是不受系统管控的,系统在杀 App 进程的时候,只会去杀 App 启动的 Java 进程:因此诞生了一大批"毒瘤",他们通过 fork native 进程,在 App 的 Java 进程被杀死的时候通过am命令拉起自己从而实现永生.那时候的 Android 可谓是魑魅横行,群

  • 详解App保活技术实现

    前言 通过ioctl跟binder驱动交互,实现以最快的方式唤醒新的保活服务,最大程度防止保活失败.同时,我也将跟您分享,我是怎么做到在不甚了解binder的情况下,快速实现ioctl binder这种高级操作. 声明:现在这个保活方式在MIUI等定制Android系统中已经不能保活,大部分时候只能活在模拟器中了.但对与我们的轻量定制的Android系统,一些系统级应用的保活,这个方案还是有用的. 这是一个Android设计的不合理的地方,路子可以堵,但还是有必要留一个统一的保活接口的.这个接口

  • 一文详解Vue3响应式原理

    目录 回顾 vue2.x 的响应式 vue3的响应式 Reflect 回顾 vue2.x 的响应式 实现原理: 对象类型:通过object.defineProperty()对属性的读取.修改进行拦截(数据劫持) 数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹) Object.defineProperty(data,'count ",{ get(){}, set(){} }) 存在问题: 新增属性.删除属性,界面不会更新 直接通过下标修改数组,界面不会自动更新 但是

  • 详解jquery选择器的原理

    详解jquery选择器的原理 html部分 <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <script src="js/minijquery.js"></script> </head> <body>

  • 详解C#扩展方法原理及其使用

    1.写在前面 今天群里一个小伙伴问了这样一个问题,扩展方法与实例方法的执行顺序是什么样子的,谁先谁后(这个问题会在文章结尾回答).所以写了这边文章,力图从原理角度解释扩展方法及其使用. 以下为主要内容: 什么是扩展方法 扩展方法原理及自定义扩展方法 扩展方法的使用及其注意事项 2.什么是扩展方法 一般而言,扩展方法为现有类型添加新的方法(从面向对象的角度来说,是为现有对象添加新的行为)而无需修改原有类型,这是一种无侵入而且非常安全的方式.扩展方法是静态的,它的使用和其他实例方法几乎没有什么区别.

  • 详解 Java HashMap 实现原理

    HashMap 是 Java 中最常见数据结构之一,它能够在 O(1) 时间复杂度存储键值对和根据键值读取值操作.本文将分析其内部实现原理(基于 jdk1.8.0_231). 数据结构 HashMap 是基于哈希值的一种映射,所谓映射,即可以根据 key 获取到相应的 value.例如:数组是一种的映射,根据下标能够取到值.不过相对于数组,HashMap 占用的存储空间更小,复杂度却同样为 O(1). HashMap 内部定义了一排"桶",用一个叫 table 的 Node 数组表示:

  • 详解PHP的执行原理和流程

    简介 先看看下面这个过程: • 我们从未手动开启过PHP的相关进程,它是随着Apache的启动而运行的: • PHP通过mod_php5.so模块和Apache相连(具体说来是SAPI,即服务器应用程序编程接口): • PHP总共有三个模块:内核.Zend引擎.以及扩展层: • PHP内核用来处理请求.文件流.错误处理等相关操作: • Zend引擎(ZE)用以将源文件转换成机器语言,然后在虚拟机上运行它: • 扩展层是一组函数.类库和流,PHP使用它们来执行一些特定的操作.比如,我们需要mysq

  • 详解Bagging算法的原理及Python实现

    目录 一.什么是集成学习 二.Bagging算法 三.Bagging用于分类 四.Bagging用于回归 一.什么是集成学习 集成学习是一种技术框架,它本身不是一个单独的机器学习算法,而是通过构建并结合多个机器学习器来完成学习任务,一般结构是:先产生一组"个体学习器",再用某种策略将它们结合起来,目前,有三种常见的集成学习框架(策略):bagging,boosting和stacking 也就是说,集成学习有两个主要的问题需要解决,第一是如何得到若干个个体学习器,第二是如何选择一种结合策

  • 详解Feign的实现原理

    目录 一.什么是Feign 二.为什么用Feign 三.实例 3.1.原生使用方式 3.2.结合 Spring Cloud 使用方式 四.探索Feign 五.总结 一.什么是Feign Feign 是⼀个 HTTP 请求的轻量级客户端框架.通过 接口 + 注解的方式发起 HTTP 请求调用,面向接口编程,而不是像 Java 中通过封装 HTTP 请求报文的方式直接调用.服务消费方拿到服务提供方的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,实际发出的是远程的请求.让我们更加便捷和优雅的去调⽤基于 HT

  • 详解高性能缓存Caffeine原理及实战

    目录 一.简介 二.Caffeine 原理 2.1.淘汰算法 2.1.1.常见算法 2.1.2.W-TinyLFU 算法 2.2.高性能读写 2.2.1.读缓冲 2.2.2.写缓冲 三.Caffeine 实战 3.1.配置参数 3.2.项目实战 四.总结 一.简介 下面是Caffeine 官方测试报告. 由上面三幅图可见:不管在并发读.并发写还是并发读写的场景下,Caffeine 的性能都大幅领先于其他本地开源缓存组件. 本文先介绍 Caffeine 实现原理,再讲解如何在项目中使用 Caffe

随机推荐