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的排序字段集合有几个要求
- 如果_id就是业务ID,则直接使用该字段;
- 如果_id是ES自动生成的ID,则需要使用业务ID字段来排序(需要保证该业务ID索引内部不重复;如果不能保证,则需要添加其他字段来保证唯一;保证唯一的目的就是比对的两个索引在相同位置的文档就应该是一样的,不一样就是有问题);
- 如果无法找到能构建复合主键的字段,则需要将索引数据完整的拉到内存中,然后根据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的盘当时还讨论过,要用来存储访问日志,防止机器磁盘写满。
- 任务列表也梳理了代理层遇到问题要发送告警,但没有一一核实,导致系统盘满的时候,没有第一时间收到告警。
- 只要是在核心链路上的,不管是不是临时的,必须一一测试、验证。
- 比如本次代理层申请的机器是有两块的盘:1、一个50G的系统盘 2、一个500G的数据盘;但最终落地的时候云厂商同学还是把nginx的访问日志落到了系统盘,导致系统盘满了,系统受到的影响。
相关文章:

Elasticsearch不停机切换(上云)方案
如何给飞行中的飞机换引擎? 背景 业务背景 略 技术背景 线下集群40个索引左右,总数据量不大,不到100G因为ES承担的业务鉴权业务,所以不能接受停机割接 还有就是ES中数据来自各个业务方,推送的时机不定,也没有完备的重推机制&…...

归纳一下Invoke,beginInvoke,还有InvokeRequire
1.在WinForms中的Invoke和BeginInvoke WinForms是一个单线程的UI框架。在多线程的环境下操作UI控件时。需要使用Invoke和BeginInvoke跨线程调起UI线程 这两的区别如下Invoke:同步调用,当前代码不在UI线程上执行时,会卡住当前线程࿰…...

Prompt最佳实践|指定输出的长度
在OpenAI的官方文档中已经提供了[Prompt Enginerring]的最佳实践,目的就是帮助用户更好的使用ChatGPT 编写优秀的提示词我一共总结了9个分类,本文讲解第6个分类:指定输出长度 提供更多的细节要求模型扮演角色使用分隔符指定任务步骤提供样例…...

离散制造 vs 流程制造:锚定精准制造未来,从装配线到化学反应,实时数据集成在制造业案例中的多维应用
使用 TapData,化繁为简,摆脱手动搭建、维护数据管道的诸多烦扰,轻量替代 OGG, Kettle 等同步工具,以及基于 Kafka 的 ETL 解决方案,「CDC 流处理 数据集成」组合拳,加速仓内数据流转,帮助企业…...

教你一招:在微信小程序中为用户上传的图片添加时间水印
在微信小程序开发过程中,我们常常需要在图片上添加水印,以保护版权或增加个性化元素。本文将为大家介绍如何在微信小程序中为图片添加时间水印,让你的小程序更具特色。 实现步骤: 1. 创建页面结构 在pages目录下创建一个名为upl…...

MySQL --基本查询(上)
文章目录 1.Create1.1单行数据全列插入1.2多行数据指定列插入1.3插入否则更新1.4替换 2.Retrieve2.1 select列2.1.1全列查询2.1.2指定列查询2.1.3查询字段为表达式2.1.4 为查询结果指定别名2.1.5结果去重 2.2where 条件2.2.1英语不及格的同学及英语成绩 ( < 60 )2.2.2语文成…...

mysql学习教程,从入门到精通,SQL 删除数据(DELETE 语句)(19)
1、SQL 删除数据(DELETE 语句) 在SQL中,TRUNCATE TABLE 语句用于删除表中的所有行,但不删除表本身。这个操作通常比使用 DELETE 语句删除所有行要快,因为它不记录每一行的删除操作到事务日志中,而是直接重…...

RoguelikeGenerator Pro - Procedural Level Generator
这是怎么一回事? Roguelike Generator Pro:简单与力量的结合。使用GameObjects、Tilemaps或自定义解决方案轻松制作3D/2D/2.5D关卡。享受内置功能,如碰撞处理、高度变化、基本控制器和子随机化器,所有这些都由Drunkard Wlak程序生成算法提供支持。 我该如何使用它? 简单:…...

反病毒技术和反病毒软件(网络安全小知识)
一、反病毒技术的难点 病毒变异与多态性:病毒开发者不断利用新技术和漏洞,创造出新的病毒变种和多态病毒。这些病毒能够自我变异,从而避开传统的基于特征码的检测方法,使得反病毒软件难以识别和清除。 未知病毒检测:在…...

位图与布隆过滤器
引例 给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。 思路1:排序二分查找 思路2:哈希或红黑树 因为40亿个整数要占用16GB 102410241024Byte 约等于10亿Byte1GB 40亿*4Byte 16G…...

【题解】—— LeetCode一周小结38
🌟欢迎来到 我的博客 —— 探索技术的无限可能! 🌟博客的简介(文章目录) 【题解】—— 每日一道题目栏 上接:【题解】—— LeetCode一周小结37 16.公交站间的距离 题目链接:1184. 公交站间的距…...

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

算法练习题24——leetcode3296移山所需的最小秒数(二分模拟)
【题目描述】 【代码示例(java)】 class Solution {// 计算让工人们将山的高度降到0所需的最少时间public long minNumberOfSeconds(int mountainHeight, int[] workerTimes) {long left 0; // 最少时间初始为0long right 0; // 最大时间初始化为0// …...

excel 单元格一直显示年月日
excel 单元格一直显示年月日,在单元格上右键选择单元格格式,选择日期时单元格会显示成日期格式...

【线程】线程的控制
本文重点:理解线程控制的接口 前言 内核中是没有很明确线程的概念的,只有轻量级进程的概念,不会提供直接给我们线程的系统调用,而会给我们提供轻量级进程的系统调用。我们用户是需要线程的接口的,在应用层࿰…...

掌握 Spring:从新手到高手的常见问题汇总
一提起Spring,总感觉有太多知识,无法详尽,有些基础理解就先不说了,相信大家都已经用过Spring了 下面简单针对常见Spring面试题做些回答 核心特性 IOC容器spring事件资源管理国际化校验数据绑定类型转换spirng表达式面向切面编程……...

机器学习——Bagging
Bagging: 方法:集成n个base learner模型,每个模型都对原始数据集进行有放回的随机采样获得随机数据集,然后并行训练。 回归问题:n个base模型进行预测,将得到的预测值取平均得到最终结果。 分类问题…...

日志体系结构与框架:历史、实现与如何在 Spring Cloud 中使用日志体系
文章目录 1. 引言2. 日志体系结构3. 日志框架的发展历程日志框架特点对比 4. 日志记录器的使用与管理使用 SLF4J 和 Logback 的日志记录示例 5. Spring Cloud 中的日志使用5.1 日志框架集成5.2 分布式追踪:Spring Cloud Sleuth 和 Zipkin添加 Sleuth 和 Zipkin 依赖…...

图文深入理解SQL语句的执行过程
List item 本文将深入介绍SQL语句的执行过程。 一.在RDBMS(关系型DB)中,看似很简单的一条已写入DB内存的SQL语句执行过程却非常复杂,也就是说,你执行了一条诸如select count(*) where id 001 from table_name的非常简…...

ubuntu安装StarQuant
安装boost 下面展示一些 内联代码片。 sudo apt install libboost-all-dev -y安装libmongoc-1.0 链接: link // An highlighted block sudo apt install libmongoc-1.0-0 sudo apt install libbson-1.0 sudo apt install cmake libssl-dev libsasl2-dev编译源码 $ git clone…...

学习篇 | Jupyter 使用(notebook hub)
1. JupyterHub 1.1 快速尝试 jupyterhub -f/path/jupyter_config.py --no-ssl1.2 长期后台运行 bash -c "nohup jupyterhub -f/path/jupyter_config.py --no-ssl" > ~/jupyterhub.log 2>&1 &1.3 帮助 jupyterhub --help2. Jupyter Notebook 2.1 快…...

【裸机装机系列】8.kali(ubuntu)-虚拟内存swap交换分区扩展
推荐阅读: 1.kali(ubuntu)-为什么弃用ubuntu,而选择基于debian的kali操作系统 linux swap交换分区,相当于win系统虚拟内存的概念。当linux系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前…...

异步请求的方法以及原理
异步请求是指在发送请求后,不会阻塞程序的执行,而是继续执行后续的代码,等待请求返回后再执行相应的回调函数。常见的异步请求方法包括使用XMLHttpRequest对象(XHR)和fetch API。 异步请求的方法 1. XMLHttpRequest (X…...

SpringCloud入门(六)Nacos注册中心(下)
一、Nacos环境隔离 Nacos提供了namespace来实现环境隔离功能。 nacos中可以有多个namespace。namespace下可以有group、service等。不同namespace之间相互隔离,例如不同namespace的服务互相不可见。 使用Nacos Namespace 环境隔离 步骤: 1.在Nacos控制…...

【RDMA】mlxlink检查和调试连接状态及相关问题--驱动工具
简介 mlxlink工具用于检查和调试连接状态及相关问题。该工具可以用于不同的链路和电缆(包括被动、电动、收发器和背板)。 属于mft工具套件的一个工具,固件工具 Firmware Tools (MFT):https://blog.csdn.net/bandaoyu/article/details/14242…...

QT For Android开发-打开PPT文件
一、前言 需求: Qt开发Android程序过程中,点击按钮就打开一个PPT文件。 Qt在Windows上要打开PPT文件或者其他文件很容易。可以使用QDesktopServices打开文件,非常方便。QDesktopServices提供了静态接口调用系统级别的功能。 这里用的QDesk…...

SpringBoot教程(三十) | SpringBoot集成Shiro权限框架
SpringBoot教程(三十) | SpringBoot集成Shiro权限框架 一、 什么是Shiro二、Shiro 组件核心组件其他组件 三、流程说明shiro的运行流程 四、SpringBoot 集成 Shiro (shiro-spring-boot-web-starter方式)1. 添加 Shiro 相关 maven2…...

[ffmpeg] 视频格式转换
本文主要梳理 ffmpeg 中的视频格式转换。由于上屏的数据是 rgba,编码使用的是 yuv数据,所以经常会使用到视频格式的转换。 除了使用 ffmpeg进行转换,还可以通过 libyuv 和 directX 写 shader 进行转换。 之前看到文章说 libyuv 之前是 ffmpeg…...

git-repo系列教程(3) git-repo https证书认证问题
文章目录 问题描述解决步骤1.下载证书2.测试证书是否正常3.设置环境变量 总结 问题描述 在使用git repo 同步仓库时,发现不能同步,出现如下提示错误: % Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left …...

中序遍历二叉树全过程图解
文章目录 中序遍历图解总结拓展:回归与回溯 中序遍历图解 首先看下中序遍历的代码,其接受一个根结点root作为参数,判断根节点是否为nil,不为nil则先递归遍历左子树。 func traversal(root *TreeNode,res *[]int) {if root nil …...