Dubbo Filter + SLF4J MDC 实现分布式日志追踪

  |  
阅读次数
  |  
字数 1,211
  |  
时长 ≈ 6 分钟

在使用dubbo进行分布式服务开发时,一个大系统会拆分成很多领域以及很多子服务,而且部署时会部署在不同服务器里面,这在我们使用日志排查问题时将会十分麻烦。

这里我们使用Dubbo的Filter以及SLF4J的MDC功能,来共同完成这个事情。

主要思路是:

  1. 消费端在调用调用服务端时,生成请求ID,然后将请求ID、服务器IP等参数在调用服务前进行注入,将参数带到服务提供方。
  2. 提供方在执行方法前,获取消费端设置的参数,包括请求ID、主机IP等需要用到的参数,然后设置到MDC里面,供打印日志使用,再将参数往下游服务进行传递。

代码实现:

由于dubbo在2.7.*的版本以及之前的版本包结构有所变更,
为了让大家明白这不同版本的配置差异,
这里服务端使用2.7.3的版本,消费端使用2.6.5的版本,来演示下不同版本下配置的异同。

1.定义上下游服务DubboFilter

服务消费端:ConsumerRpcTraceFilter

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
50
51
52
53
54
55
package com.faq.filter;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.common.utils.NetUtils;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import java.util.UUID;

/**
* 服务消费端TraceFilter
*
* @author tidy
* @date 2019/11/15 11:23
*/
@Activate(group = {Constants.CONSUMER})
@Slf4j
public class ConsumerRpcTraceFilter implements Filter {

/**
* @param invoker
* @param invocation
*
* @return
*
* @throws RpcException
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 如果连续调用多个服务,则会使用同个线程里之前设置的traceId
String traceId = MDC.get("traceId");
if (StringUtils.isBlank(traceId)) {
// 线程内,首次调用远端服务
traceId = UUID.randomUUID().toString();
}

// 设置日志traceId变量
MDC.put("traceId", traceId);

// 设置传递到提供端的参数
RpcContext.getContext().setAttachment("host", NetUtils.getLocalHost());
RpcContext.getContext().setAttachment("trace_id", traceId);

return invoker.invoke(invocation);
}

}

Read More

Grafana ESDOC一对多变量

  |  
阅读次数
  |  
字数 600
  |  
时长 ≈ 2 分钟

1.需求场景

需要添加一个Grafana变量,让一条ES的Document在Grafana展示时,可以出现在不同的变量筛选结果上。

2.需求效果

新建的变量名为“功能类”,需求值为(ALL、查询类、办理类、故障类、通用域)
我ES索引里面,一条Doc通过变量筛选后,可以对应多个功能类。

A -> 查询类、故障类
B -> 查询类
C -> 查询类、故障类、通用域

这里如果我们使用查询类的变量值进行筛选,应该出来A、B、C三条记录,如果使用故障类进行筛选,那出来的记录应该是A、C。

3.实现思路

  1. 在Doc上面加入新的Field,存储doc对应的功能类范围,例如上面的查询类、故障类 以及 查询类、故障类、通用域
  2. 在Grafana上新建Custom类型的变量,手动写上四个变量值。这里变量不适合使用Query类型,因为如果使用Query只能查出来查询类、故障类、通用域这种我们保存在文档里面的原始数据,而我们展示是需要每个种类单独展示的。
  3. 修改Grafana Panel,在查询语句上面使用模糊查询,将变量与Field值进行匹配。

    Read More

Dubbo 服务提供者部署在GBK服务器环境的乱码问题

  |  
阅读次数
  |  
字数 269
  |  
时长 ≈ 1 分钟

1.场景

将服务部署在一台机器上,过后发现消费端在注册中心中获取服务时,
如果获取到该服务器上的服务进行请求,处理结果为乱码,而请求另外一台服务器的服务时,结果却是正常。

2.排查

经排查这台服务器上的服务器编码为GBK,而其他正常的服务器则为UTF-8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ locale
LANG=zh_CN.GBK
LC_CTYPE="zh_CN.GBK"
LC_NUMERIC="zh_CN.GBK"
LC_TIME="zh_CN.GBK"
LC_COLLATE="zh_CN.GBK"
LC_MONETARY="zh_CN.GBK"
LC_MESSAGES="zh_CN.GBK"
LC_PAPER="zh_CN.GBK"
LC_NAME="zh_CN.GBK"
LC_ADDRESS="zh_CN.GBK"
LC_TELEPHONE="zh_CN.GBK"
LC_MEASUREMENT="zh_CN.GBK"
LC_IDENTIFICATION="zh_CN.GBK"
LC_ALL=zh_CN.GBK

3.解决

这时有两种解决方案:

  1. 修改系统的全局服务器编码为UTF-8(不知道为何,在启动脚本使用临时环境变量的方式提前执行了 export LANG=en_US.UTF-8 却无法生效。)
  2. 在启动脚本里,对jvm启动参数进行调整,设置编码格式,添加以下参数重启即可。
1
-Dfile.encoding=UTF-8

IDEA 利用Maven依赖图解决包冲突

  |  
阅读次数
  |  
字数 635
  |  
时长 ≈ 2 分钟

当我们试图向Maven项目里添加新依赖时,经常会遇到新依赖包与旧依赖包产生冲突的问题,如下图所示:

这里面,因为有两个包里面都包含完整路径为org/slf4j/impl/StaticLoggerBinder.class的类,所以导致系统不知道使用哪一个,从而启动失败,
所以,我们只需要将这两个包其中一个去除即可,现在让我们使用IDEA的一个小功能来完美解决这个包冲突问题吧。

Read More

J2EE Spring @Resource注解的扫描顺序

  |  
阅读次数
  |  
字数 836
  |  
时长 ≈ 3 分钟

让我们先来看看两段代码:

一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class FooService {}

@Service
public class BarService {}

@Controller
@RequestMapping(value = "/foo")
public class FooController {
@Resource
private FooService service;
}

@Controller
@RequestMapping(value = "/bar")
public class BarController {
@Resource
private BarService service;
}

Read More

Mac 解决16版MBP键盘B+N键重复点击的问题

  |  
阅读次数
  |  
字数 849
  |  
时长 ≈ 4 分钟

在16版MBP中的蝶式键盘有可能出现某个按钮重复点击的问题,就是按一次响应两次,重灾区是BN键,
偶然发现网上除了返修之外,还有一个折中方法,就是通过编写程序,在开机后自启动,在程序里面判断相应按钮的两次点击的间隔时间,
如果太短则忽略,从而达到避免重复点击的问题。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// alterkeys.c
// http://osxbook.com
//
// You need superuser privileges to create the event tap, unless accessibility
// is enabled. To do so, select the "Enable access for assistive devices"
// checkbox in the Universal Access system preference pane.

// modified by SF-Zhou
// To: Kill Double Typing on MacBook
// Complile: g++ -O2 -Wall -o kill_double_typing kill_double_typing.cpp -framework ApplicationServices
// Run: ./kill_double_typing

#include <ApplicationServices/ApplicationServices.h>
#include <iostream>
#include <chrono>
#include <unordered_map>
using namespace std;

typedef chrono::time_point<std::chrono::high_resolution_clock> Time;
typedef long long ll;

unordered_map<CGKeyCode, Time> last_time;

Time time_now() {
return chrono::high_resolution_clock::now();
}

// This callback will be invoked every time there is a keystroke.
CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
// Paranoid sanity check.
if ((type != kCGEventKeyDown) && (type != kCGEventKeyUp)) {
return event;
}

// The incoming keycode.
CGKeyCode keycode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);

if (keycode == 11 /* b */ || keycode == 45 /* n */) {
//if (keycode == 11 /* b */) {
if (type == kCGEventKeyUp) {
last_time[keycode] = time_now();
} else {
if (last_time.count(keycode)) {
ll microseconds = chrono::duration_cast<chrono::microseconds>(
time_now() - last_time[keycode]
).count();

// ignore if time less than 30ms
if (microseconds < 30000) {
return NULL;
}
}
}
}

// Set the modified keycode field in the event.
CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, (int64_t)keycode);

// We must return the event for it to be useful.
return event;
}

int main(void) {
CFMachPortRef eventTap;
CGEventMask eventMask;
CFRunLoopSourceRef runLoopSource;

// Create an event tap. We are interested in key presses.
eventMask = ((1 << kCGEventKeyDown) | (1 << kCGEventKeyUp));
eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 0, eventMask, myCGEventCallback, NULL);
if (!eventTap) {
fprintf(stderr, "failed to create event tap\n");
exit(1);
}

// Create a run loop source.
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);

// Add to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);

// Enable the event tap.
CGEventTapEnable(eventTap, true);

// Set it all running.
CFRunLoopRun();

// In a real program, one would have arranged for cleaning up.

exit(0);
}

Read More

Mac EasyConnect 未能正确打开SANGFOR SSL Virtual网卡

  |  
阅读次数
  |  
字数 144
  |  
时长 ≈ 1 分钟

1.场景:

今天打开EasyConnect提示:未能正确打开SANGFOR SSL Virtual网卡,暂时不能提供SSL CS服务,请联系管理员

2.排查:

经排查,最后发现是与其他虚拟网卡冲突了。
在昨天安装过网易mumu模拟器,没有安装成功,但是给我遗留了一个虚拟网卡。
将其卸载后重启就行了。

3.操作:

查看虚拟网卡:

1
kextstat

卸载指定虚拟网卡:

1
sudo  kextunload -b com.netease.nemu.kext.NemuDrv

删除虚拟网卡目录(Nemu):

1
sudo rm -rf /Library/Application\ Support/Nemu

Mac 使用Shuttle优雅切换http(s)代理

  |  
阅读次数
  |  
字数 972
  |  
时长 ≈ 4 分钟

MacOS中设置代理

MacOS中配置http(s)代理时,通常的做法是在系统偏好设置 - 网络中进行操作,选择一个网络,点击高级按钮,点击代理选项卡,分别勾选网页代理(HTTP)安全网页代理(HTTPS)然后填写上代理信息。

这种配置方式虽然可以实现需求,但缺点在于操作比较繁琐,特别是在需要频繁切换的情况下,效率极其低下。

基于该痛点,我们希望能避免重复操作,实现快速切换配置。

Read More

SpringBoot 配置测试用例不启用定时器

  |  
阅读次数
  |  
字数 267
  |  
时长 ≈ 1 分钟

我们先看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
@ActiveProfiles("local")
@Slf4j
public class FooServiceTest {
}

@SpringBootApplication
@EnableScheduling
@Slf4j
public class App {
}

使用以上代码,在测试用例中启动时,会执行App这个类进行初始化,在App中我们使用了@EnableScheduling表示启用定时器,这时候如果我们在运行测试用例时不需要启动定时器配置,我们应该使用@ConditionalOnProperty加一下条件限制,来达到这个功能,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
@ActiveProfiles("local")
@TestPropertySource(properties = "app.scheduling.enable=false")
@Slf4j
public class FooServiceTest {
}

@SpringBootApplication
@Slf4j
public class App {

@ConditionalOnProperty(
value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true
)
@Configuration
@EnableScheduling
public static class SchedulingConfiguration {
}

}

这里使用了一个静态内部类来达到此效果,在类上面加上配置启动限制@ConditionalOnProperty,默认为启用,然后我们在写测试用例时,不需要启动的时候则使用@TestPropertySource设置条件里面的属性app.scheduling.enablefalse即可。