Android中的全量更新、增量更新以及热更新

这篇具有很好参考价值的文章主要介绍了Android中的全量更新、增量更新以及热更新。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在客户端开发过程中,我们可能会遇到这样一种需求:点击某个按钮弹出一个弹窗,提示我们可以更新到apk的某个版本,或者我们可以通过服务端接口进行强制更新。在这种需求中,我们是不需要通过应用商店来更新我们的apk的,而是直接在apk内部进行版本更新。这次我们就来看看实现这种应用内更新的几种方式。当然,这种玩法只能在国内玩,海外的话会被Googleplay据审的。如果是海外的应用要更新apk,只能在GooglePlay上上传新版本的包。

全量更新

什么是全量更新呢?举个例子,假设现在用户手机上的apk是1.0版本,如果想要升级到2.0版本,全量更新的处理方式则是把2.0版本的apk全部下载下来进行覆盖安装。那么,我们该如果设计一个合理的全量更新方案呢?

  • 服务端
    需要提供一个接口,这个接口返回来的body中包含新版本的包的下载地址以及该包的md5值用于下载完成之后进行校验用
  • 客户端
    访问该服务端接口,下载新版本的包(其实就是字节流的读写),然后进行覆盖安装

做完上面这2点其实就可以实现一个较为完整的全量更新功能。

客户端核心代码如下:

package com.mvp.myapplication.update;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;

import androidx.core.content.FileProvider;

import com.mvp.myapplication.utils.MD5Util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class UpdateService extends Service {

    public static final String KEY_MD5 = "MD5";
    public static final String URL = "downloadUrl";


    private boolean startDownload;//开始下载
    public static final String TAG = "UpdateService";
    private DownloadApk downloadApkTask;
    private String downloadUrl;
    private String mMd5;
    private UpdateProgressListener updateProgressListener;
    private LocalBinder localBinder = new LocalBinder();

    public class LocalBinder extends Binder {
        public void setUpdateProgressListener(UpdateProgressListener listener) {
            UpdateService.this.setUpdateProgressListener(listener);
        }
    }

    private void setUpdateProgressListener(UpdateProgressListener listener) {
        this.updateProgressListener = listener;
    }

    /**
     * 获取FileProvider的auth
     */
    private static String getFileProviderAuthority(Context context) {
        try {
            for (ProviderInfo provider : context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS).providers) {
                if (FileProvider.class.getName().equals(provider.name) && provider.authority.endsWith(".update_app.file_provider")) {
                    return provider.authority;
                }
            }
        } catch (PackageManager.NameNotFoundException ignore) {
        }
        return null;
    }

    private static Intent installIntent(Context context, String path) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri fileUri = FileProvider.getUriForFile(context, getFileProviderAuthority(context), new File(path));
            intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setDataAndType(Uri.fromFile(new File(path)), "application/vnd.android.package-archive");
        }
        return intent;
    }

    public UpdateService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!startDownload && intent != null) {
            startDownload = true;
            mMd5 = intent.getStringExtra(KEY_MD5);
            downloadUrl = intent.getStringExtra(URL);

            downloadApkTask = new DownloadApk(this, mMd5);
            downloadApkTask.execute(downloadUrl);

        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return localBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return true;
    }

    @Override
    public void onDestroy() {
        if (downloadApkTask != null) {
            downloadApkTask.cancel(true);
        }
        if (updateProgressListener != null) {
            updateProgressListener = null;
        }
        super.onDestroy();
    }

    private static String getSaveFileName(String downloadUrl) {
        if (downloadUrl == null || TextUtils.isEmpty(downloadUrl)) {
            return System.currentTimeMillis() + ".apk";
        }
        return downloadUrl.substring(downloadUrl.lastIndexOf("/"));
    }

    private static File getDownloadDir(UpdateService service) {
        File downloadDir = null;
        if (Environment.getExternalStorageDirectory().equals(Environment.MEDIA_MOUNTED)) {
            downloadDir = new File(service.getExternalCacheDir(), "update");
        } else {
            downloadDir = new File(service.getCacheDir(), "update");
        }
        if (!downloadDir.exists()) {
            downloadDir.mkdirs();
        }
        return downloadDir;
    }

    private void start() {
        if (updateProgressListener != null) {
            updateProgressListener.start();
        }
    }

    private void update(int progress) {
        if (updateProgressListener != null) {
            updateProgressListener.update(progress);
        }
    }

    private void success(String path) {
        if (updateProgressListener != null) {
            updateProgressListener.success(path);
        }
        Intent i = installIntent(this, path);
        startActivity(i);//自动安装
        stopSelf();
    }

    private void error() {
        if (updateProgressListener != null) {
            updateProgressListener.error();
        }
        stopSelf();
    }

    private static class DownloadApk extends AsyncTask<String, Integer, String> {
        private final String md5;
        private UpdateService updateService;

        public DownloadApk(UpdateService service, String md5) {
            this.updateService = service;
            this.md5 = md5;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            if (updateService != null) {
                updateService.start();
            }
        }

        @Override
        protected String doInBackground(String... strings) {
            final String downloadUrl = strings[0];

            final File file = new File(UpdateService.getDownloadDir(updateService),
                    UpdateService.getSaveFileName(downloadUrl));

            Log.d(TAG, "download url is " + downloadUrl);
            Log.d(TAG, "download apk cache at " + file.getAbsolutePath());

            File dir = file.getParentFile();
            if (!dir.exists()) {
                dir.mkdirs();
            }

            HttpURLConnection httpConnection = null;
            InputStream is = null;
            FileOutputStream fos = null;
            long updateTotalSize = 0;
            URL url;
            try {
                url = new URL(downloadUrl);
                httpConnection = (HttpURLConnection) url.openConnection();
                httpConnection.setConnectTimeout(20000);
                httpConnection.setReadTimeout(20000);

                Log.d(TAG, "download status code: " + httpConnection.getResponseCode());


                if (httpConnection.getResponseCode() != 200) {
                    return null;
                }

                updateTotalSize = httpConnection.getContentLength();

                if (file.exists()) {
                    if (updateTotalSize == file.length()) {
                        // 下载完成
                        if (TextUtils.isEmpty(md5) || MD5Util.getMD5String(file).toUpperCase().equals(md5.toUpperCase())) {
                            return file.getAbsolutePath();
                        }
                    } else {
                        file.delete();
                    }
                }
                file.createNewFile();
                is = httpConnection.getInputStream();
                fos = new FileOutputStream(file, false);
                byte buffer[] = new byte[4096];

                int readSize = 0;
                long currentSize = 0;

                while ((readSize = is.read(buffer)) > 0) {
                    fos.write(buffer, 0, readSize);
                    currentSize += readSize;
                    publishProgress((int) (currentSize * 100 / updateTotalSize));
                }
                // download success
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            } finally {
                if (httpConnection != null) {
                    httpConnection.disconnect();
                }
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                if (TextUtils.isEmpty(md5) || MD5Util.getMD5String(file).toUpperCase().equals(md5.toUpperCase())) {
                    return file.getAbsolutePath();
                }
            } catch (IOException e) {
                e.printStackTrace();
                return file.getAbsolutePath();
            }
            Log.e(TAG, "md5 invalid");
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            if (updateService != null) {
                updateService.update(values[0]);
            }
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            if (updateService != null) {
                if (s != null) {
                    updateService.success(s);
                } else {
                    updateService.error();
                }
            }
        }
    }

    public static class Builder {
        private String downloadUrl;
        private String md5;
        private ServiceConnection serviceConnection;

        protected Builder(String downloadUrl) {
            this.downloadUrl = downloadUrl;
        }

        public static Builder create(String downloadUrl) {
            if (downloadUrl == null) {
                throw new NullPointerException("downloadUrl == null");
            }
            return new Builder(downloadUrl);
        }

        public String getMd5() {
            return md5;
        }

        public Builder setMd5(String md5) {
            this.md5 = md5;
            return this;
        }

        public Builder build(Context context, UpdateProgressListener listener) {
            if (context == null) {
                throw new NullPointerException("context == null");
            }
            Intent intent = new Intent();
            intent.setClass(context, UpdateService.class);
            intent.putExtra(URL, downloadUrl);
            intent.putExtra(KEY_MD5, md5);

            UpdateProgressListener delegateListener = new UpdateProgressListener() {
                @Override
                public void start() {
                    if (listener != null) {
                        listener.start();
                    }
                }

                @Override
                public void update(int var1) {
                    if (listener != null) {
                        listener.update(var1);
                    }
                }

                @Override
                public void success(String path) {
                    try {
                        context.unbindService(serviceConnection);
                    } catch (Throwable t) {
                        Log.e("UpdateService", "解绑失败" + t.getMessage());
                    }

                    if (listener != null) {
                        listener.success(path);
                    }
                }

                @Override
                public void error() {
                    try {
                        context.unbindService(serviceConnection);
                    } catch (Throwable t) {
                        Log.e("UpdateService", "解绑失败" + t.getMessage());
                    }
                    if (listener != null) {
                        listener.error();
                    }
                }
            };
            serviceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    LocalBinder binder = (LocalBinder) service;
                    binder.setUpdateProgressListener(delegateListener);
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                }
            };
            context.bindService(intent, serviceConnection, Context.BIND_IMPORTANT);
            context.startService(intent);
            return this;
        }
    }

    public interface UpdateProgressListener {
        void start();

        void update(int var);

        void success(String path);

        void error();
    }
}
package com.mvp.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.mvp.myapplication.update.UpdateService;

public class MainActivity extends AppCompatActivity {
    private Button btnAllUpdate, btnAddUpdate, btnHotUpdate;

    private String url,md5;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnAddUpdate = findViewById(R.id.btn_add_update);
        btnAllUpdate = findViewById(R.id.btn_all_update);
        btnHotUpdate = findViewById(R.id.btn_hot_update);

        Log.e("MainActivity","onCreate");

        btnAllUpdate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                UpdateService.Builder.create(url)
                        .setMd5(md5)
                        .build(MainActivity.this, new UpdateService.UpdateProgressListener() {
                            @Override
                            public void start() {
                                Log.e("MainActivity", "start");
                            }

                            @Override
                            public void update(int var) {
                                Log.e("MainActivity", "update ===> " + var);
                            }

                            @Override
                            public void success(String path) {
                                Log.e("MainActivity", "success ===> " + path);
                            }

                            @Override
                            public void error() {
                                Log.e("MainActivity", "error");
                            }
                        });
            }
        });
    }
}
  • AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        tools:targetApi="31">
        <service
            android:name=".update.UpdateService"
            android:enabled="true"
            android:exported="true"></service>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
        <!-- Android7以上需要 -->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.update_app.file_provider"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="android:authorities">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/update_app_path" />
        </provider>
    </application>

</manifest>
  • update_app_path
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <cache-path
        name="update_app_cache_files"
        path="/update" />
    <external-path
        name="update_app_external_files"
        path="/" />
    <external-cache-path
        name="update_app_external_cache_files"
        path="/update" />
</paths>

热更新

严格意义上来说,个人认为热更新并不是用来进行包体升级,更多的用来进行修复bug的。例如,由于某个程序员的失误,在某个类中抛出了一个空指针异常,导致程序执行到该类后一直崩溃。这种情况下,其实就可以使用热更新来处理。因为,我们并没有大改app中的功能,只是某个类报错了。但个人认为热更新其实也不能解决所有的奔溃问题的,这些黑科技或多或少都是有一些兼容性的问题的,像Tinker就必须要冷启动才能修复,而且受限于Android的版本。
具体是技术实现方式可以参考笔者之前写的一篇博客:Android热修复1以及Android热更新十:自己写一个Android热修复

增量更新

什么是增量更新呢?举了例子,假设我们需要将apk从v1.0升级到v2.0,这时我们可以通过全量更新的方式,下载2.0版本的apk然后进行覆盖安装。但是,一般情况下2.0版本的apk往往包含了1.0版本的功能,理论上我们只需要下载二者的差分包,然后将差分包与1.0版本的包进行合并即可生成一个2.0版本的包。这样做的好处自然就是节约了流量了。像几乎所有的应用商店都使用增量更新的方式来更新apk。那么,我们该我们使用增量更新呢?这个就要借助一个工具:bsdiff
注意:如果想要使用增量更新,那么必须要有一个旧版本的apk,如果用户安装完apk后直接把旧版本的apk删掉了,那么还是老老实实使用全量更新的方式吧。

  • 拆——拆分出差分包

bsdiff oldfile newfile1 patchfile

  • 合——将旧版本的包与差分包进行合并

bspatch oldfile newfile2 patchfile

使用上面两步便可以完成差分包的拆分与合并,新生成的newfile2 与newfile1的md5是一致的。但是上面这两步法我们是在pc端进行的,我们该如何在代码中实现上面的逻辑呢?首先,拆分的逻辑还是在pc端中进行,客户端只需要关注如何合并差分包。
首先,我们需要导入bspatch相关的类
全量更新,android,java,android studio
接着,我们新建一个类用于调用c相关的代码:

package com.mvp.myapplication.utils;

public class BSPatchUtil {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("bspatch");
    }

    /**
     * native方法 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath
     *
     * 返回:0,说明操作成功
     *
     * @param oldApkPath 示例:/sdcard/old.apk
     * @param outputApkPath 示例:/sdcard/output.apk
     * @param patchPath  示例:/sdcard/xx.patch
     * @return
     */
    public static native int bspatch(String oldApkPath, String outputApkPath,
                                   String patchPath);

}

package com.mvp.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.mvp.myapplication.update.UpdateService;
import com.mvp.myapplication.utils.BSPatchUtil;

import java.io.File;

public class MainActivity extends AppCompatActivity {
    private Button btnAllUpdate, btnAddUpdate, btnHotUpdate;

    private String url, md5;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnAddUpdate = findViewById(R.id.btn_add_update);
        btnAllUpdate = findViewById(R.id.btn_all_update);
        btnHotUpdate = findViewById(R.id.btn_hot_update);

        Log.e("MainActivity", "onCreate");

        btnAllUpdate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                UpdateService.Builder.create(url)
                        .setMd5(md5)
                        .build(MainActivity.this, new UpdateService.UpdateProgressListener() {
                            @Override
                            public void start() {
                                Log.e("MainActivity", "start");
                            }

                            @Override
                            public void update(int var) {
                                Log.e("MainActivity", "update ===> " + var);
                            }

                            @Override
                            public void success(String path) {
                                Log.e("MainActivity", "success ===> " + path);
                            }

                            @Override
                            public void error() {
                                Log.e("MainActivity", "error");
                            }
                        });
            }
        });

        btnAddUpdate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                genNewApk();
            }
        });
    }

    private void genNewApk() {
        String oldpath = getApplicationInfo().sourceDir;
        String newpath = (this.getCacheDir().getAbsolutePath()+ File.separator
                + "composed_hivebox_apk.apk");

        String patchpath = (this.getCacheDir().getAbsolutePath()+ File.separator
                + "bs_patch");
        Log.e("MainActivity", "oldpath is " + oldpath + "\n newpath is " + newpath + "\n patchpath is " + patchpath);
        BSPatchUtil.bspatch(oldpath, newpath, patchpath);

    }
}

注意:需要修改bspatch.c文件中的Java_com_mvp_myapplication_utils_BSPatchUtil_bspatch方法签名:改为BSPatchUtil 的包名,例如BSPatchUtil对应的路径为com.dxl.testbatch.util.BSPatchUtil,那么native方法签名就是Java_com_mvp_myapplication_utils_BSPatchUtil_bspatch
这样便可以通过jni调用到c层面的代码。

接着,修改build.gradle文件,添加下面圈中的闭包
全量更新,android,java,android studio
最后,我们执行Make Project命令,正常情况下便可以生成如下几个so库
全量更新,android,java,android studio
最后,我们在把so库放入jniLibs文件夹中,然后build下生成apk包
全量更新,android,java,android studio

然后,我们将差分包bs_patch放入手机的data/data目录下,点击按钮就会生成composed_hivebox_apk.apk这个apk包,将其与v2.0的包进行MD5对比,发现是一致的。如此,我们便实现了一个简单的增量更新逻辑。
Demo地址:https://gitee.com/hzulwy/add_-update/tree/master/MyApplication文章来源地址https://www.toymoban.com/news/detail-654381.html

到了这里,关于Android中的全量更新、增量更新以及热更新的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 离线数据仓库-关于增量和全量

    应用系统所产生的业务数据是数据仓库的重要数据来源,我们需要每日定时从业务数据库中抽取数据,传输到数据仓库中,之后再对数据进行分析统计。 为了方便上层指标的统计,数据的同步策略有 全量同步 和 增量同步 。 同步方式是针对对应的表而言的! 为什么要做数据

    2024年01月17日
    浏览(43)
  • 如何选择离线数据集成方案 - 全量&增量

    1 前言 我在上一篇中介绍了实时集成与离线集成该怎么选择,接着介绍一下离线集成中的增量与全量的选择问题。 要设计方案,我们先分析一下数据产生的方式。我们把音视频流这种非结构化的数据集成从这里排除出去,因为这种音视频流一般都是专业的厂商和系统来处理。

    2024年02月02日
    浏览(50)
  • 全量、增量数据在HBase迁移的多种技巧实践

    作者经历了多次基于HBase实现全量与增量数据的迁移测试,总结了在使用HBase进行数据迁移的多种实践,本文针对全量与增量数据迁移的场景不同,提供了1+2的技巧分享。 1.背景 在HBase使用过程中,使用的HBase集群经常会因为某些原因需要数据迁移。大多数情况下,可以用离线

    2024年02月06日
    浏览(47)
  • 【环境配置】Android-Studio-OpenCV-JNI以及常见错误 ( 持续更新 )

    最近一个项目要编译深度学习的库,需要用到 opencv 和 JNI,本文档用于记录环境配置中遇到的常见错误以及解决方案 解决办法: 删除文件 .idea/gradle.xml 和 .idea/workspace.xml , 重新编译; 解决办法:Invalid Gradle JDK configuration found 原因是NDK版本过高,跟当前的AndroidStudio版本不匹配

    2024年02月11日
    浏览(45)
  • Android中的SDK以及利用Android Studio生成aar

    广义上的SDK: 指的是为特定的软件包、软件框架、硬件平台、操作系统等建立应用程序时所使用的开发工具的集合。 比如你在编辑器里敲代码的时候它会自动补全代码,自动错误检查,你点一下Run,它会调用编译器来自动编译,编译完它会调用iPhone的模拟器来运行,这就是

    2024年02月12日
    浏览(47)
  • Redis主从架构、数据同步原理、全量同步、增量同步

    大家好,我是哪吒。 2023年再不会Redis,就要被淘汰了 图解Redis,谈谈Redis的持久化,RDB快照与AOF日志 Redis单线程还是多线程?IO多路复用原理 Redis集群的最大槽数为什么是16384个? Redis缓存穿透、击穿、雪崩到底是个啥?7张图告诉你 Redis分布式锁的实现方式 Redis分布式缓存、

    2024年02月07日
    浏览(66)
  • Oracle通过函数调用dblink同步表数据方案(全量/增量)

    创建对应的包,以方便触发调用 触发同步任务: SELECT yjb.pkg_scene_job.F_SYNC_DRUG_STOCK() AS a FROM dual WHERE 1=0; 没有结果行时是不会触发的,以下方式可触发: SELECT yjb.pkg_scene_job.F_SYNC_DRUG_STOCK() AS a FROM dual; PS:一定是使用(调用)到 触发函数yjb.pkg_scene_job.F_SYNC_DRUG_STOCK(),才可完成触

    2024年02月16日
    浏览(52)
  • 【大数据精讲】全量同步与CDC增量同步方案对比

    目录 背景 名词解释 问题与挑战 FlinkCDC DataX 工作原理 调度流程 五、DataX 3.0六大核心优势 性能优化 CDC        CDC又称变更数据捕获(Change Data Capture),开启cdc的源表在插入INSERT、更新UPDATE和删除DELETE活动时会插入数据到日志表中。CDC通过捕获进程将变更数据捕获到变更表中

    2024年01月24日
    浏览(43)
  • hive 全量表、增量表、快照表、切片表和拉链表

    全量表 :记录每天的所有的最新状态的数据, 增量表 :记录每天的新增数据,增量数据是上次导出之后的新数据。 快照表 :按日分区,记录截止数据日期的全量数据 切片表 :切片表根据基础表,往往只反映某一个维度的相应数据。其表结构与基础表结构相同,但数据往往

    2024年02月13日
    浏览(34)
  • 什么是全量数据、增量数据?如何统一一套系统?

    一、什么是全量数据、增量数据? 1.全量数据 2.增量数据 二、如何统一一套系统 1.为什么需要统一一套系统来处理全量数据和增量数据? 2.如何实践? 全量数据和增量数据是在数据库系统迁移时的概念。         当前需要迁移的数据库系统的全部数据。         在数据库系

    2024年02月05日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包