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

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

背景

  • 业务背景
  • 技术背景
    • 线下集群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 AFTERif (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.xdnf.cn/news/1542534.html

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

相关文章

心理辅导系统的现代化:Spring Boot解决方案

1绪 论 1.1研究背景 随着计算机和网络技术的不断发展&#xff0c;计算机网络已经逐渐深入人们的生活&#xff0c;网络已经能够覆盖我们生活的每一个角落&#xff0c;给用户的网上交流和学习提供了巨大的方便。 当今社会处在一个高速发展的信息时代&#xff0c;计算机网络的发展…

MySQL --数据类型

文章目录 1.数据类型分类2.数值类型2.1 tinyint类型2.2 bit类型2.3小数类型2.31float2.32decimal 3.字符串类型3.1 char3.2varchar3.3 char和varchar比较 4.日期和时间类型5.enum和set 1.数据类型分类 2.数值类型 2.1 tinyint类型 数值越界测试&#xff1a; create table tt1…

Python画笔案例-056 绘制正方形金字塔

1、绘制正方形金字塔 通过 python 的turtle 库绘制 正方形金字塔,如下图: 2、实现代码 绘制正方形金字塔,以下为实现代码: """正方形金字塔.py """ import turtledef draw_square(length):for _ in

设计模式之组合模式例题

答案&#xff1a;C A 知识点&#xff1a;组合模式的意图&#xff1a;将对象组合成树型结构以表示“整体-部分”的层次结构&#xff0c;使得用户对单个对象和组合对象的使用具有一致性

数据挖掘实战-基于SARIMA时间序列模型预测阿里巴巴股票数据趋势

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

android kotlin Extension扩展函数

1、新建一个kt文件&#xff1a; 2、代码&#xff1a; class User(var name:String)/**扩展函数**/ fun User.Print(){print("用户名 $name") }// 扩展函数 swap,调换不同位置的值 fun MutableList<Int>.swap(index1: Int, index2: Int) {val tmp this[index1…

新手必看:一步步教你绑定常见邮箱到第三方应用(如何绑定QQ、163、Hotmail、Gmail等邮箱)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 邮箱绑定 📒📫 QQ邮箱📫 163邮箱📫 Hotmail邮箱📫 Gmail邮箱📫 Yahoo邮箱📫 iCloud邮箱📫 其他邮箱⚓️ 相关链接 ⚓️📖 介绍 📖 你是否曾经为绑定第三方邮箱而感到困惑?你不是一个人!许多人在尝试将QQ邮…

Linux进程概念1

前言 从这篇博客开始&#xff0c;后面我们主要讲xshell中Linux内容&#xff0c;C后续会继续补充一点 Linux基础内容我就不讲了&#xff0c;直接从进程开始讲 1. 进程是什么 像这种程序正在运行&#xff0c;还没有结束的过程就是一个进程&#xff0c;进程在电脑底部就是.exe文…

vscode 顶部 Command Center,minimap

目录 vscode 顶部 Command Center 设置显示步骤: minimap设置 方法一:使用设置界面 方法二:使用命令面板 方法三:编辑 settings.json 文件 左侧目录树和编辑器字体不一致: vscode 顶部 Command Center Visual Studio Code (VSCode) 中的 Command Center 是一个集中…

11年408考研真题解析-计算机网络

第一题&#xff1a; 解析&#xff1a;网络层虚电路服务和数据报服务 传输服务只有&#xff1a;有连接可靠和无连接不可靠两种&#xff0c;直接排除BC。 网络层指的是IP协议&#xff0c;由图二可知&#xff1a;运输层&#xff0c;网际层&#xff0c;网络接口层唯一有连接可靠的协…

vue3 本地windows下的字体的引用

1、先上了张效果&#xff1a; 2、windows 字体的路径&#xff1a;c:/windows/fonts/ 我们用华文行楷来测试下&#xff0c;先将华文行楷拷贝到/src/assets/fonts目录下。 3、然后我们来定义css&#xff1a; font-face {font-family: fyxk;font-style: normal;src: local(Opensan…

图结构感知的Transformer:一种新的图表示学习方法

人工智能咨询培训老师叶梓 转载标明出处 尽管图神经网络&#xff08;GNNs&#xff09;在处理图数据方面取得了显著成就&#xff0c;但它们在表达能力和捕获长距离依赖方面存在局限性。为了突破这些局限&#xff0c;研究者们开始探索将Transformer架构应用于图表示学习。在此基…

人工智能的前景与未来就业市场:机遇、挑战与社会影响

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经逐渐渗透到我们生活的方方面面&#xff0c;它不仅引领着技术革新的浪潮&#xff0c;更在无声中重塑着我们的就业市场和社会结构。站在这个时代的交汇点上&#xff0c;我们不禁要问&#xff1a;人工智能将…

Web Components之继承

我们在使用Web Components自定义组件的时候&#xff0c;我们需要继承HTMLElement这个浏览器内置对象&#xff0c;但是如果我要一些高级封装&#xff0c;给组件内置一些方法的话。我们就需要使用继承的方式&#xff0c;在父类中实现基本功能的封装。 1 父类的封装 以下是我的继…

Java多线程(1)—线程基础

一、关于线程 1.1 简介 计算机线程&#xff08;Thread&#xff09;是操作系统能够进行运算调度的最小单位。线程的优势在于提高了程序的效率和响应能力&#xff0c;尤其在处理 I/O 操作或多任务时。多线程编程能够充分利用多核处理器的计算能力&#xff0c;达到更高的性能。 …

Spring 源码分析

Spring 源码版本 4.2.8.RELEASE Bean 生命周期 动态代理 代理模式 优点: 在不修改目标对象的功能前提下&#xff0c;能通过代理对象对目标功能扩展缺点: 代理对象需要与目标对象实现一样的接口&#xff0c;所以会有很多代理类&#xff0c;一旦接口增加方法&#xff0c;目标对…

Apifox 「定时任务」操作指南,解锁自动化测试的新利器

定时任务是按照预设时间自动执行的任务&#xff0c;它可以有效解决一些常见问题&#xff0c;比如频繁执行的回归测试和大规模的接口测试&#xff0c;这些任务需要在固定时间点或间隔周期内自动运行&#xff0c;以确保软件的持续集成和持续交付过程中的稳定性和可靠性。通过使用…

Windows下 批量重命名文件【bat实现】-两个小问题

Windows下 批量重命名文件【bat实现】_bat批量重命名文件ren-CSDN博客 上面是原来的教程&#xff0c;我遇到了两个小问题&#xff0c;问题及解决如下&#xff1a; ①dir/b>rename.csv : 无法将“dir/b>rename.csv”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。…

【每天学个新注解】Day 2 Lombok注解简解(一)—@Data、@Build、@Value

Data 相当于同时使用了 Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode 1、如何使用 需要同时使用Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode注解一个Bean的时候。 2、代码示例 例&#xff1a; Data public cla…

CCC SPAKE2+流程解析

1、SPAKE2流程及SCP03通道介绍 SPAKE2流程发生在CCC车主配对过程中的Phase2。 SPAKE2流程为车辆和手机之间的数据交换建立了一个安全通道SCP03。 那这个SCP03通道是干啥的&#xff1f; 我们可以先简单的理解为&#xff1a;建立安全通道前&#xff0c;车辆和手机之间交互的AP…