怎样给飞行中的飞机换引擎?
配景
- 业务配景
- 技术配景
- 线下集群40个索引左右,总数据量不大,不到100G
- 因为ES承担的业务鉴权业务,以是不能接受停机割接
- 尚有就是ES中数据来自各个业务方,推送的时机不定,也没有完备的重推机制,以是不能停机割接
- 索引中根本都没有创建或者更新时间字段,纵然部分有,也没有用起来
- 盼望不进行业务改造,直接替换。
- 虽然服务分为了读写服务,但通过读服务还是可以调用写入的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 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的盘其时还讨论过,要用来存储访问日记,防止机器磁盘写满。
- 任务列表也梳理了署理层遇到问题要发送告警,但没有一一核实,导致系统盘满的时候,没有第一时间收到告警。
- 只要是在核心链路上的,不管是不是临时的,必须一一测试、验证。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |