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…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...
