Elasticsearch不停机切换(上云)方案

news2024/9/23 3:41:02

如何给飞行中的飞机换引擎?

背景

  • 业务背景
  • 技术背景
    • 线下集群40个索引左右,总数据量不大,不到100G
    • 因为ES承担的业务鉴权业务,所以不能接受停机割接
      • 还有就是ES中数据来自各个业务方,推送的时机不定,也没有完备的重推机制,所以不能停机割接
    • 索引中基本都没有创建或者更新时间字段,即使部分有,也没有用起来
      • 也就无法使用logstash的增量同步功能。
    • 希望不进行业务改造,直接替换。
    • 虽然服务分为了读写服务,但通过读服务还是可以调用写入的API,通过写服务也可以调用读的API。

架构方案

  • 全量数据同步logstash
  • 脚步比对出来的差异数据,脚步补数

在这里插入图片描述

注意:

  • CLB及代理层的配置一定有冗余
  • 如果个CLB支撑不了,可以考虑
    • 方式一:直接申请多个CLB,并将这多个CLB的地址配置到应用中
    • 方式二:先申请一个EIP,在EIP的后面配置多个CLB,这样应用只配置一个EIP的地址就可以了
    • 方式三:CLB直接升配到NLB
  • CLB文档
  • NLB文档
  • 准备两套CLB及代理层的原因是:代理层是个Nginx集群,手动一台一台更新配置然后reload很慢,这时候数据写入的主ES是不确定的。

比对核心逻辑

  • 获取线下集群所有索引(跳过系统所以及不需要迁移的索引)
  • 遍历第一步获取到的索引集合
    • 获取线上、线下索引的文档总数,如果总数不一样,终止比对;
    • 如果总数一样,则通过search after(需要)分页分别从线上、线下获取数据比对。

注意:search_after的排序字段集合有几个要求

  1. 如果_id就是业务ID,则直接使用该字段;
  2. 如果_id是ES自动生成的ID,则需要使用业务ID字段来排序(需要保证该业务ID索引内部不重复;如果不能保证,则需要添加其他字段来保证唯一;保证唯一的目的就是比对的两个索引在相同位置的文档就应该是一样的,不一样就是有问题);
  3. 如果无法找到能构建复合主键的字段,则需要将索引数据完整的拉到内存中,然后根据mapping将所有字段拼接构建组合ID,然后去重,再依次比对。(索引条数不一样的,也可以通过类似的方式来查找异常的原因;采取这种简单粗暴方式的原因是:1、我们这种类型索引的数据量不大 2、这个比对程序其实就是个临时的工具,不会长期使用)

模板、mapping、index setting这些都需要比对。

比对核心代码

MapFlatUtil.java

import java.util.*;

/**
 * @Author jiankunking
 * @Date 2024/9/4 17:13
 * @Description:
 */
public class MapFlatUtil {

    static String PREFIX = ".";

    public static Map<String, Object> flat(Map<String, Object> map) {
        Map<String, Object> configMap = new LinkedHashMap<>();
        map.entrySet().forEach(entry -> {
            if (entry.getValue() instanceof Map) {
                Map<String, Object> subMap = flat(entry.getKey(), (Map<String, Object>) entry.getValue());
                if (!subMap.isEmpty()) {
                    configMap.putAll(subMap);
                }
            } else if (entry.getValue() instanceof List) {
                configMap.put(entry.getKey(), entry.getValue());
            } else {
                configMap.put(entry.getKey(), entry.getValue() == null ? "" : String.valueOf(entry.getValue()));
            }
        });
        return configMap;
    }

    private static Map<String, Object> flat(String parentNode, Map<String, Object> source) {
        Map<String, Object> flatMap = new LinkedHashMap<>();
        Set<Map.Entry<String, Object>> set = source.entrySet();
        set.forEach(entity -> {
            Object value = entity.getValue();
            String key = entity.getKey();
            String newKey = parentNode + PREFIX + key;
            if (value instanceof Map) {
                flatMap.putAll(flat(newKey, (Map<String, Object>) value));
            } else if (value instanceof List) {
                flatMap.put(newKey, value);
            } else {
                flatMap.put(newKey, value == null ? "" : String.valueOf(value));
            }
        });
        return flatMap;
    }
}

MapCompareUtil.java

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.jiankunking.branchcompare.es.SortUtil.mapComparator;

/**
 * @Author jiankunking
 * @Date 2024/9/14 9:48
 * @Description:
 */
@Slf4j
public class MapCompareUtil {

    public static boolean isMapEquals(Map<String, Object> offlineMap, Map<String, Object> onlineMap) throws JsonProcessingException {
        offlineMap = MapFlatUtil.flat(offlineMap);
        onlineMap = MapFlatUtil.flat(onlineMap);
        if (offlineMap.size() != onlineMap.size()) {
            return false;
        }

        for (Map.Entry<String, Object> offlineEntry : offlineMap.entrySet()) {
            String offlineEntryKey = offlineEntry.getKey();
            if (!onlineMap.containsKey(offlineEntryKey)) {
                return false;
            }
            Object offlineEntryValue = offlineEntry.getValue();
            Object onlineEntryValue = onlineMap.get(offlineEntryKey);

            Class offlineEntryValueClass = offlineEntryValue.getClass();
            Class onlineEntryValueClass = onlineEntryValue.getClass();

            if (offlineEntryValueClass != onlineEntryValueClass) {
                log.warn("value type not equals,offlineEntryValue:" + offlineEntryValueClass.getName() + ",onlineEntryValue:" + onlineEntryValueClass.getName());
                return false;
            }
            if (offlineEntryValue instanceof Map) {
                Map<String, Object> offlineMapValue = (Map<String, Object>) offlineEntryValue;
                Map<String, Object> onlineMapValue = (Map<String, Object>) onlineEntryValue;
                if (!isMapEquals(offlineMapValue, onlineMapValue)) {
                    return false;
                }
                continue;
            } else if (offlineEntryValue instanceof List) {
                List<Object> offlineList = (List<Object>) offlineEntryValue;
                List<Object> onlineList = (List<Object>) onlineEntryValue;
                if (offlineList.size() != onlineList.size()) {
                    log.warn("list size not equals,offlineList:" + offlineList.size() + ",onlineList:" + onlineList.size());
                    return false;
                }

                // List<Map>
                if (!offlineList.isEmpty() && offlineList.get(0) instanceof Map) {
                    List<Map<String, Object>> offlineEntryValueTmp = (List<Map<String, Object>>) offlineEntryValue;
                    List<Map<String, Object>> onlineEntryValueTmp = (List<Map<String, Object>>) onlineEntryValue;

                    List<SortUtil.Sort> sorts = new ArrayList<>();
                    // 按照map 的key 排序
                    for (Map.Entry<String, Object> entry : offlineEntryValueTmp.get(0).entrySet()) {
                        sorts.add(new SortUtil.Sort(entry.getKey(), SortUtil.Order.ASC));
                    }

                    List<Map<String, Object>> offlineEntryValueSorted = offlineEntryValueTmp.stream()
                                                                                            .sorted(mapComparator(sorts))
                                                                                            .collect(Collectors.toList());
                    List<Map<String, Object>> onlineEntryValueSorted = onlineEntryValueTmp.stream()
                                                                                          .sorted(mapComparator(sorts))
                                                                                          .collect(Collectors.toList());
                    for (int i = 0; i < offlineEntryValueSorted.size(); i++) {
                        Object offlineListItem = offlineEntryValueSorted.get(i);
                        Object onlineListItem = onlineEntryValueSorted.get(i);
                        if (!isMapEquals((Map<String, Object>) offlineListItem, (Map<String, Object>) onlineListItem)) {
                            return false;
                        }
                    }

                } else {
                    // List<简单类型>
                    offlineList.sort(Comparator.comparing(o -> o.toString()));
                    onlineList.sort(Comparator.comparing(o -> o.toString()));
                    for (int i = 0; i < offlineList.size(); i++) {
                        Object offlineListItem = offlineList.get(i);
                        Object onlineListItem = onlineList.get(i);
                        if (!simpleObjectEquals(offlineListItem, onlineListItem)) {
                            log.warn("list item not equals,offlineListItem:" + offlineListItem + ",onlineListItem:" + onlineListItem);
                            return false;
                        }
                    }
                }
                continue;
            }
            if (!simpleObjectEquals(offlineEntryValue, onlineEntryValue)) {
                log.warn("map value not equals,offlineEntryValue:" + offlineEntryValue + ",onlineEntryValue:" + onlineEntryValue);
                return false;
            }
        }
        return true;
    }

    // 只能处理简单对象 不能处理Map List等复杂类型
    private static boolean simpleObjectEquals(Object o1, Object o2) throws JsonProcessingException {
        String offlineJson = new ObjectMapper().writeValueAsString(o1);
        String onlineJson = new ObjectMapper().writeValueAsString(o2);
        if (offlineJson.equals(onlineJson)) {
            return true;
        }
        return false;
    }
}

SortUtil.java

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author jiankunking
 * @Date 2024/9/5 14:00
 * @Description: https://gist.github.com/IOsetting/25ca8d70c12c11390113d343f666cd6e
 */
public class SortUtil {
    public enum Order {ASC, DESC}

    /**
     * @param sorts keys and sort direction
     * @return sorted list
     */
    public static Comparator<Map<String, Object>> mapComparator(List<Sort> sorts) {
        return (o1, o2) -> {
            int ret = 0;
            for (Sort sort : sorts) {
                Object v1 = o1.get(sort.field);
                Object v2 = o2.get(sort.field);
                ret = singleCompare(v1, v2, sort.order == Order.ASC);
                if (ret != 0) {
                    break;
                }
            }
            return ret;
        };
    }

    public static class Sort {
        public String field;
        public Order order;

        public Sort(String field, Order order) {
            this.field = field;
            this.order = order;
        }
    }

    private static int singleCompare(Object ao, Object bo, boolean asc) {
        int ret;
        if (ao == null && bo == null) {
            ret = 0;
        } else if (ao == null) {
            ret = -1;
        } else if (bo == null) {
            ret = 1;
        } else if (ao instanceof BigDecimal) {
            ret = ((BigDecimal) ao).compareTo((BigDecimal) bo);
        } else if (ao instanceof Number) {
            if (((Number) ao).doubleValue() != ((Number) bo).doubleValue()) {
                ret = ((Number) ao).doubleValue() > ((Number) bo).doubleValue() ? 1 : -1;
            } else {
                ret = 0;
            }
        } else if (ao instanceof Date) {
            ret = ((Date) ao).compareTo((Date) bo);
        } else {
            ret = String.valueOf(ao).compareTo(String.valueOf(bo));
        }
        if (!asc) {
            return -ret;
        }
        return ret;
    }

    public static void main(String[] args) {
        List<Map<String, Object>> list = new ArrayList<>();
        List<Sort> sorts = new ArrayList<>();

        List<Map<String, Object>> sorted = list.stream()
                                               .sorted(mapComparator(sorts))
                                               .collect(Collectors.toList());
        for (Map<String, Object> map : sorted) {
            System.out.println(map.get("somekey"));
        }
    }
}

EsQueryUtil.java

public static SearchResponse searchAfterByMultiFields(RestHighLevelClient restHighLevelClient, String indexName, List<String> searchAfterSortFields, List<Object> searchAfterValues, int size) throws IOException {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.size(size);
        builder.trackTotalHits(true);

        builder.query(QueryBuilders.matchAllQuery());

        // USING SEARCH AFTER
        if (searchAfterValues != null && !searchAfterValues.isEmpty()) {
            builder.searchAfter(searchAfterValues.toArray());
        }
        for (String sortField : searchAfterSortFields) {
            builder.sort(sortField, SortOrder.ASC);
        }

        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(indexName);
        searchRequest.source(builder);
        // log.info(searchRequest.toString());
        log.info(searchRequest.source().toString());

        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        return response;
    }

    static List<Object> getSearchAfterValues(List<String> searchAfterSortFields, SearchHit hit) {
        List<Object> searchAfterValues = new ArrayList<>(searchAfterSortFields.size());
        Map<String, Object> map = hit.getSourceAsMap();
        for (String field : searchAfterSortFields) {
            if (field.equals("_id")) {
                searchAfterValues.add(hit.getId());
            } else {
                searchAfterValues.add(map.get(field));
            }
        }
        return searchAfterValues;

    }

反思

  • 要拉通全流程及相关人员,核对每个可能出现的问题及应对方案
  • 有些东西不能因为是临时的就放松警惕性
    • 比如本次代理层申请的机器是有两块的盘:1、一个50G的系统盘 2、一个500G的数据盘;但最终落地的时候云厂商同学还是把nginx的访问日志落到了系统盘,导致系统盘满了,系统受到的影响。
      • 这个500G的盘当时还讨论过,要用来存储访问日志,防止机器磁盘写满。
    • 任务列表也梳理了代理层遇到问题要发送告警,但没有一一核实,导致系统盘满的时候,没有第一时间收到告警。
    • 只要是在核心链路上的,不管是不是临时的,必须一一测试、验证。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2156459.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

漏洞复现_永恒之蓝

1.概述 永恒之蓝&#xff08;EternalBlue&#xff09;是一个影响Windows操作系统的远程代码执行漏洞&#xff0c;编号为CVE-2017-0144&#xff0c;最初由美国国家安全局&#xff08;NSA&#xff09;开发并利用&#xff0c;后来被黑客组织Shadow Brokers泄露。该漏洞存在于SMBv…

『功能项目』QFrameWork制作背包界面UGUI【72】

本章项目成果展示 我们打开上一篇71QFrameWork更新道具图片UGUI的项目&#xff0c; 本章要做的事情是制作背包UI界面&#xff0c;实现道具栏与背包道具的互通 首先将以下资源图片放进Art文件夹 将UICanvas的UISlot对象复制一份放在Image下 创建Scrollbar View 设置Scrollbar V…

数据处理与统计分析篇-day08-apply()自定义函数与分组操作

一. 自定义函数 概述 当Pandas自带的API不能满足需求, 例如: 我们需要遍历的对Series中的每一条数据/DataFrame中的一列或一行数据做相同的自定义处理, 就可以使用Apply自定义函数 apply函数可以接收一个自定义函数, 可以将Series对象的逐个值或DataFrame的行/列数据传递给自…

Object类代码结构

Object Object是所有类的父类。 方法结构如下 一些不知道的方法 private static native void registerNatives(); * JNI机制 * 这里定义了一个 native 方法 registerNatives()&#xff0c;它没有方法体。 * native 关键字表示这个方法的实现是由本地代码 * &#xff08;通常…

传输层 IV(TCP协议——流量控制、拥塞控制)【★★★★】

&#xff08;★★&#xff09;代表非常重要的知识点&#xff0c;&#xff08;★&#xff09;代表重要的知识点。 一、TCP 流量控制&#xff08;★★&#xff09; 1. 利用滑动窗口实现流量控制 一般说来&#xff0c;我们总是希望数据传输得更快一些。但如果发送方把数据发送得…

java基础知识20 Intern方法的作用

一 Intern方法作用 1.1 Intern方法 1.在jdk1.6中&#xff1a; intern()方法&#xff1a;在jdk1.6中&#xff0c;根据字符串对象&#xff0c;检查常量池中是否存在相同字符串对象 如果字符串常量池里面已经包含了等于字符串X的字符串&#xff0c;那么就返回常量池中这个字符…

基于高维多目标优化的无人机三维航迹规划,MATLAB代码

高维多目标优化问题是指目标数量大于3的优化问题&#xff0c;这类问题在实际应用中非常普遍&#xff0c;如工业生产、资源管理、工程设计等领域。随着目标数量的增加&#xff0c;问题的求解难度也随之增大&#xff0c;传统的多目标优化算法在处理高维多目标问题时面临着选择压力…

window系统DockerDesktop 部署windows容器

目录 参考文献1、安装Docker Desktop1.1 下载安装包1.2 安装教程1.3 异常解决 2、安装windows容器2.1 先启动DockerDesktop 软件界面2.2 检查docker版本2.3 拉取windows镜像 参考文献 windows容器docker中文官网 Docker: windows下跑windows镜像 1、安装Docker Desktop 1.1 …

Llama3.1的部署与使用

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 什么是Llama3.1&#xff1f; Llama3.1 是 Meta&#xff08;原 Facebook&#xff09;公…

java项目之线上辅导班系统的开发与设计

项目简介 基于springboot的线上辅导班系统的开发与设计的主要使用者分为&#xff1a; 管理员在后台主要管理字典管理、论坛管理、公开课管理、课程管理、课程报名管理、课程收藏管理、课程留言管理、师资力量管理、用户管理、管理员管理等。 &#x1f495;&#x1f495;作者&a…

二分查找算法(2) _在排序数组中查找元素的第一个和最后一个_模板

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 二分查找算法(2) _在排序数组中查找元素的第一个和最后一个_模板 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评…

算法-K个一组翻转链表

// 要实现没k个节点进行翻转的操作&#xff0c;可以按照一下步骤进行 // 1.计算链表长度 // 2.分组反转 // 3. 使用一个虚拟头节点来处理边界情况 // 4.每次处理k个节点进行反转 // 5.如果剩余节点不足k个 则保持原有顺序 // 6.依次反转每组中的节点 // 1.使用prevGroupEEnd追…

EvilScience靶机详解

主机发现 arp-scan -l 得到靶机ip 192.168.229.152 端口扫描 nmap -sV -A -T4 192.168.1.20 这段代码使用 nmap 命令来扫描目标主机 192.168.1.20&#xff0c;并执行以下操作&#xff1a;-sV&#xff1a;探测开放的端口&#xff0c;以确定服务/版本信息。-A&#xff1a;启…

[大语言模型] LINFUSION:1个GPU,1分钟,16K图像

1. 文章 2409.02097 (arxiv.org)https://arxiv.org/pdf/2409.02097 LINFUSION: 1 GPU, 1 MINUTE, 16K IMAGE 摘要 本文介绍了一种新型的扩散模型LINFUSION&#xff0c;它能够在保持高分辨率图像生成性能的同时显著降低时间和内存复杂度。该模型采用了基于Transformer的UNet进…

常用卫星学习

文章目录 Landsat-8 Landsat-8 由一台操作陆地成像仪 &#xff08;OLI&#xff09; 和一台热红外传感器 &#xff08;TIRS&#xff09;的卫星&#xff0c;OLI 提供 9 个波段&#xff0c;覆盖 0.43–2.29 μm 的波长&#xff0c;其中全色波段&#xff08;一般指0.5μm到0.75μm左…

Java的IO流(二)

目录 Java的IO流&#xff08;二&#xff09; 字节缓冲流 基本使用 使用缓冲流复制文件 字符缓冲流 缓冲流读取数据原理 字符编码 字符集 转换流 序列化流与反序列化流 基本使用 禁止成员被序列化 序列号不匹配异常 打印流 基本使用 系统打印流与改变流向 Prop…

【kaggle竞赛】毒蘑菇的二元预测题目相关信息和思路求解代码

毒蘑菇的二元预测 您提供了很多关于不同二元分类任务的资源和链接&#xff0c;看起来这些都是Kaggle竞赛中的参考资料和高分解决方案。为了帮助您更好地利用这些资源&#xff0c;这里是一些关键点的总结&#xff1a; Playground Season 4 Episode 8 主要关注的竞赛: 使用银行…

2024 硬盘格式恢复软件大揭秘

宝妈们硬盘存储图片、设计师用硬盘存储素材、学生们用硬盘存储作业和数据已经是一个普遍的社会现象了。但是有时候数据迁移之后想要一份全新的硬盘我们就会采取硬盘格式化的操作&#xff0c;如果格式化之后发现硬盘数据没有备份好硬盘格式化后能恢复数据吗&#xff1f;这次我就…

没错,我给androidx修了一个bug!

不容易啊&#xff0c;必须先截图留恋&#x1f601; 这个bug是发生在xml中给AppcompatTextView设置textFontWeight&#xff0c;但是却无法生效。修复bug的代码也很简单&#xff0c;总共就几行代码&#xff0c;但是在找引起这个bug的原因和后面给androidx提pr却花了很久。 //App…

git学习【完结】

git学习【完结】 文章目录 git学习【完结】一、Git基本操作1.创建本地仓库2.配置本地仓库1.局部配置2.全局配置 3.认识工作区、暂存区、版本库4.添加文件5.修改文件6.版本回退7.撤销修改8.删除文件 二、Git分支管理1.理解分支2.创建、切换、合并分支3.删除分支4.合并冲突5.合并…