如何基于etcd构建CDC链路

  1. 为什么要使用etcd?安全性,etcd的主从切换是秒级的,崩溃恢复非常快,etcd集群模式下也有主从,从节点长期没有收到Leader的心跳,会主动竞选为主节点,如果有从节点的任期比主节点更长,主节点会退位给该从节点。watcher机制,客户端和服务端之间会维护一个http的连接,etcd维护了一个全局的revision,只要有数据的增删改,就对revision进行更新,然后etcd内部的watchstore会看看是不是客户端监听的key变更了,如果是就主动推送。etcd可以实现长连接与主动推送

  2. 链路的设计:本地缓存-etcd-数据中台。本地缓存更新的方式有两种。其一是本地缓存执行定时任务,每隔3分钟/开机时去数据中台获取通道信息。其二是通过etcd监听数据变更,当通道配置有变化,本地的watcher会监听到变动,获取通道信息。

  3. 存在一些问题:

    1. 多次更新本地需要一直获取吗?不需要,如果本地在获取过程中监听到了配置变更,只会将重试标志位设置为true,等本次获取执行完毕再去判断该标志位,决定是否再次获取。
    2. etcd的性能瓶颈怎么办?可以不进行真实的数据写入吗,因为我们的目的还是获取到变更通知嘛,只要针对这个key做一些变更就行了

针对ak缓存的一些问题

链路设计:本地缓存-redis-mysql。本地缓存获取不到就去redis里边查,redis查不到就去mysql里边查。本地也会有定时任务(xx-job)进行更新。

  1. 实时性比较差:如果需要某个ak下线了,但是缓存里还有数据,这时候用这个ak来调用服务还是可行的,怎么办。
  2. 多个定时任务带来的流量高峰:xxl-job
  3. 缓存穿透、击穿、雪崩:
    1. 大量不明ak攻击数据库:缓存空数据、布隆过滤器
    2. 如果高频访问的ak缓存刚好过期:逻辑过期、互斥锁(每次只能一个请求进行数据库的获取)
    3. 缓存雪崩:过期时间、限流熔断

针对通道选取的一些问题

1. 选取的顺序是什么?长文本(先查看用户是否开启)->前缀推荐(通道组是否支持)->复用(通道组是否支持)->负载均衡(降级)
1. 选取逻辑:先获取endpoint,获取支持的所有通道,过滤掉被禁用的正在使用的,然后根据规则选取合适的通道

给定一个数n如23121;给定一组数字a如[2 4 9]求由a中元素组成的小于n的最大数

x指相同下标中目标字符串的数字

我们维护一个path,针对每一个path数位,考虑从大到小选数组中的数,如果选到与x相同的数,继续递归,如果选不到,就找一个不超过x的最大数,后面数位全部都取数组中的最大数,如果全都大于x,只能返回-1表示找不到该数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Main {
public static void main(String[] args) {
int[] nums = new int[]{2,4,9};
int n = 23121;
System.out.println(findMax(nums,n));
}

private static int findMax(int[] nums, int n) {
Arrays.sort(nums);
String s = String.valueOf(n);
int res = dfs(nums, s, 0, new StringBuilder());
if(res != -1) {
return res;
}
StringBuilder sb = new StringBuilder();
fillMax(sb, nums, s.length()-1);
return Integer.parseInt(sb.toString());
}

private static int dfs(int[] nums, String s, int index, StringBuilder path) {
if(index == s.length()) {
return -1;
}
int target = s.charAt(index) - '0';
for (int i = nums.length-1; i >= 0 ; i--) {
if(target == nums[i]) {
path.append(nums[i]);
int res = dfs(nums, s, index+1, path);
if(res != -1) {
return res;
}
path.deleteCharAt(index);
}
}
for (int i = nums.length-1; i >= 0 ; i--) {
if(target > nums[i]) {
fillMax(path,nums,s.length());
return Integer.parseInt(path.toString());
}
}
return -1;
}

private static void fillMax(StringBuilder path, int[] nums, int length) {
while (path.length() < length) {
path.append(nums[nums.length-1]);
}
}
}