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.rhkb.cn/news/429523.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT 在国内使用的方法

AI如今很强大&#xff0c;聊聊天、写论文、搞翻译、写代码、写文案、审合同等等&#xff0c;ChatGPT 真是无所不能~ 作为一款出色的大语言模型&#xff0c;ChatGPT 实现了人类般的对话交流&#xff0c;最主要是能根据上下文进行互动。 接下来&#xff0c;我将介绍 ChatGPT 在国…

重生之我们在ES顶端相遇第15 章 - ES 的心脏-倒排索引

文章目录 前言为什么叫倒排索引数据结构如何生成如何查询TF、IDF参考文档 前言 上一章&#xff0c;简单介绍了 ES 的节点类型。 本章&#xff0c;我们要介绍 ES 中非常重要的一个概念&#xff1a;倒排索引。 ES 的全文索引就是基于倒排索引实现的。 本章内容建议重点学习&…

Win10 安装Node.js 以及 Vue项目的创建

一、Node.js和Vue介绍 1. Node.js Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。它允许你在服务器端运行 JavaScript&#xff0c;使得你能够使用 JavaScript 来编写后端代码。以下是 Node.js 的一些关键特点&#xff1a; 事件驱动和非阻塞 I/O&#xff1a;Node…

Parallels Desktop 20(Mac虚拟机) v20.0.0 for Mac 最新破解版(支持M系列)

Parallels Desktop 20 for Mac 正式发布&#xff0c;完全支持 macOS Sequoia 和 Windows 11 24H2&#xff0c;并且在企业版中引入了全新的管理门户。 据介绍&#xff0c;新版本针对 Windows、macOS 和 Linux 虚拟机进行了大量更新&#xff0c;最大的亮点是全新推出的 Parallels…

数据库提权【笔记总结】

文章目录 UDF提权以有webshell只有数据库权限条件复现msf工具sql语句提权 MOF提权前言条件复现msf工具php脚本提权 sqlserver提权前言条件xp_cmdshell提权复现 沙盒提权介绍复现 Oracle提权靶场搭建执行任意命令复现 通过注入存储过程提权&#xff08;低权限提升至DBA&#xff…

安全帽佩戴识别摄像机:守护安全的智能之眼

在现代工业和建筑等诸多领域中&#xff0c;安全始终是重中之重。每一处施工现场、每一个生产车间都潜藏着可能对人员造成伤害的风险因素。而安全帽&#xff0c;作为保护工作人员头部免受伤害的关键装备&#xff0c;其是否被正确佩戴就显得尤为关键。此时&#xff0c;安全帽佩戴…

智创未来,景联文科技提供全方位数据采集服务

随着科技的日新月异&#xff0c;人工智能技术正在改变我们的生活与工作方式&#xff0c;数据成为推动人工智能&#xff08;AI&#xff09;技术发展的基石。 高质量的数据对于训练机器学习模型、提升算法性能以及实现智能应用的广泛部署至关重要。 景联文科技凭借卓越的技术实力…

Kafka 为什么这么快?

Kafka 是一款性能非常优秀的消息队列&#xff0c;每秒处理的消息体量可以达到千万级别。今天来聊一聊 Kafka 高性能背后的技术原理。 1 批量发送 Kafka 收发消息都是批量进行处理的。我们看一下 Kafka 生产者发送消息的代码&#xff1a; private Future<RecordMetadata>…

PowerMill 2025简体中文版百度云资源分享下载

如大家所了解的&#xff0c;PowerMill是一款专业的CAM&#xff08;计算机辅助制造&#xff09;软件。主要用于加工行业&#xff0c;可以帮助用户进行高效、精准的加工工艺设计和数控编程&#xff0c;以达到生产部件的高精度和高质量。 对于初次接触的小伙伴来说&#xff0c;目…

GAMES101(15节)

Irradiance辐射度量学 辐射度量学在渲染领域&#xff0c;可以帮助理解基于物理的光照模型 radiant energy辐射能量Q&#xff0c;累计总能量&#xff08;单位J joule焦耳&#xff09;&#xff0c;就像太阳能板&#xff0c;光照时间越长接收能量越多&#xff0c;收到的能量总和…

前后端分离,使用MOCK进行数据模拟开发,让前端攻城师独立于后端进行开发

mock是什么 Mock生成随机数据,拦截Ajax 请求&#xff0c;前后端分离&#xff0c;让前端攻城师独立于后端进行开发。 增加单元测试的真实性 通过随机数据,模拟各种场景。 在实际开发过程中&#xff0c;前端是通过axios来请求数据的&#xff0c;很多时候前端开发者就是通过写固定…

浅谈Spring Cloud:OpenFeign

RestTemplate 方式调用存在的问题&#xff1a; String url "http://userservice/user/" order.getUserId(); User user restTemplate.getForObject(url, User.class); 这是通过URL地址来访问的。但是&#xff1a; 代码可读性差&#xff0c;编程体验不统一参数复…

Lucene详解介绍以及底层原理说明

文章目录 什么是Lucene?示意图Lucene 的使用场景&#xff1a;Lucene 的生态系统&#xff1a; 相关概念1. **Document&#xff08;文档&#xff09;**2. **Field&#xff08;字段&#xff09;**3. **Analyzer&#xff08;分析器&#xff09;**4. **Tokenizer&#xff08;分词器…

前端基础知识+算法(一)

文章目录 算法二分查找条件注意方式基本原理左闭右闭正向写法 左闭右开正向写法 前端基础知识定时器及清除盒子垂直水平居中的方式垂直水平1.flex布局2.grid布局3.定位对于块级元素 解决高度塌陷的方式1.给父元素一个固定的高度2.给父元素添加属性 overflow: hidden;3.在子元素…

深度学习-从零基础快速入门到项目实践,这本书上市了!!!

此书地址&#xff1a; 《【2024新书】深度学习 从零基础快速入门到项目实践 文青山 跟我一起学人工智能 机器学习算法原理代码实现教程 深度学习项目分析 深度学习 从零基础快速入门到项目实践》【摘要 书评 试读】- 京东图书 除深度学习外我还写了一本软件测试书。我大概是国…

[Excel VBA办公]如何使用VBA批量删除空行

在处理Excel数据时&#xff0c;空行可能会干扰数据分析和展示。以下是一个VBA代码示例&#xff0c;帮助你批量删除工作表中的空行。 1. 代码说明 此代码将遍历指定工作表&#xff0c;删除所有空行&#xff0c;确保数据整洁。 2. VBA代码 删除sheet1的空行 Sub DeleteEmptyRow…

VMware虚拟机因磁盘空间不足黑屏无法登录

在虚拟机里存储了一些文件之后&#xff0c;再打开发现进不去了&#xff0c;只有光标一直在左上角&#xff0c;登录的框都是黑的&#xff0c;具体如下&#xff1a; 明明知道登录框的存在却怎么也触碰不到它T_T &#xff0c;先说解决方法&#xff1a; 产生这个问题的原因是因为磁…

大数据Flink(一百二十一):Flink CDC基本介绍

文章目录 Flink CDC基本介绍 一、什么是CDC 二、CDC的实现机制 三、​​​​​​​​​​​​​​传统 CDC ETL 分析 四、​​​​​​​​​​​​​​基于 Flink CDC 的 ETL 分析 五、​​​​​​​​​​​​​​什么是 Flink CDC 六、​​​​​​​​​​​​​​…

OpenCV特征检测(10)检测图像中直线的函数HoughLinesP()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在二值图像中使用概率霍夫变换查找线段。 该函数实现了用于直线检测的概率霍夫变换算法&#xff0c;该算法在文献 181中有所描述。 HoughLines…

go webapi上传文件

一、导入依赖 import "net/http" 我这里用到了Guid所以安装依赖 go get github.com/google/uuid 二、main.go package mainimport ("fmt""github.com/jmoiron/sqlx""github.com/tealeg/xlsx""log""path/filepath&q…