I will not list the specific design ideas and implementation principles of CAT here. This article mainly records some problems encountered in the process of using CAT, such as distributed logview, Cache, DB buried point monitoring, etc. There are not many problems. But rather typical.
(The CAT version involved in this article is 1.3.6)
1. Log tree series implementation of distributed logview
At present, two types are used, one is the rpc call based on the dubbo application , and the other is the rest service call based on the http request . First, let's talk about the implementation of the message tree . When tracking messages across services, messages are concatenated through the three attributes of the root message id , parent message id , and child message id to form a message tree. The key point is the acquisition and transmission of the three ids of the tree .
There are two points here, the first is the principle of CAT message tree generation :
We need to implement Cat 's Context context, and then generate a context object containing node data through Cat.logRemoteCallClient(context) (in the method, the message id of each node is obtained by creating a message tree object , and the context is filled), when the remote server receives the In this context , use the Cat.logRemoteCallServer(context) method to read the message id of each node and form a message tree.
The second is how the message should be delivered :
The rpc calling method of the dubbo application : the rpc context to be passed by the calling process , which includes the calling information, parameters and status information, etc., you can put the message id information into the RpcContext , and then pass the message to the service by calling the invoke method of the Invocation object. end. Finally, through dubbo's spi extension mechanism, com.alibaba.dubbo.rpc.Filter is implemented to obtain the content of rpcContext .
Rest -style http request method: When calling, the service requester puts the message id information in the Http-Header , and on the service provider, it is intercepted by filter , and the message id in the http-header is obtained . Start a message tree.
Without further ado, let's get on the code.
1). Partial implementation of the dubbo calling method (first of all, it is necessary to understand the spi related configuration of dubbo, the configuration of CAT monitoring, etc.)
public class DubboCatFilter implements Filter {
private static final ThreadLocal<Cat.Context> CAT_CONTEXT = new ThreadLocal<Cat.Context>();
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
URL url = invoker.getUrl();
String sideKey = url.getParameter(Constants.SIDE_KEY);
String loggerName = invoker.getInterface().getSimpleName() + "." + invocation.getMethodName();
String type = "PigeonCall";
if (Constants.PROVIDER_SIDE.equals(sideKey)) {
type = "PigeonService";
}
Transaction t = Cat.newTransaction(type, loggerName);
Result result = null;
try {
Cat.Context context = getContext();
if (Constants.CONSUMER_SIDE.equals(sideKey)) {
createConsumerCross(url, t);
Cat.logRemoteCallClient(context);
} else {
createProviderCross(url, t);
Cat.logRemoteCallServer(context);
}
setAttachment(context);
result = invoker.invoke(invocation);
if (result.hasException()) {
//给调用接口出现异常进行打点
Throwable throwable = result.getException();
Event event = null;
if (RpcException.class == throwable.getClass()) {
Throwable caseBy = throwable.getCause();
if (caseBy != null && caseBy.getClass() == TimeoutException.class) {
event = Cat.newEvent("DUBBO_TIMEOUT_ERROR", loggerName);
} else {
event = Cat.newEvent("DUBBO_REMOTING_ERROR", loggerName);
}
} else if (RemotingException.class.isAssignableFrom(throwable.getClass())) {
event = Cat.newEvent("DUBBO_REMOTING_ERROR", loggerName);
}else{
event = Cat.newEvent("DUBBO_BIZ_ERROR", loggerName);
}
event.setStatus(result.getException());
completeEvent(event);
t.addChild(event);
t.setStatus(result.getException().getClass().getSimpleName());
} else {
t.setStatus(Message.SUCCESS);
}
return result;
} catch (RuntimeException e) {
Event event = null;
if (RpcException.class == e.getClass()) {
Throwable caseBy = e.getCause();
if (caseBy !=null && caseBy.getClass() == TimeoutException.class) {
event = Cat.newEvent("DUBBO_TIMEOUT_ERROR", loggerName);
} else {
event = Cat.newEvent("DUBBO_REMOTING_ERROR", loggerName);
}
} else {
event = Cat.newEvent("DUBBO_BIZ_ERROR", loggerName);
}
event.setStatus(e);
completeEvent(event);
t.addChild(event);
t.setStatus(e.getClass().getSimpleName());
if (result == null) {
throw e;
} else {
return result;
}
} finally {
t.complete();
CAT_CONTEXT.remove();
}
}
static class DubboCatContext implements Cat.Context {
private Map<String,String> properties = new HashMap<String, String>();
@Override
public void addProperty(String key, String value) {
properties.put(key,value);
}
@Override
public String getProperty(String key) {
return properties.get(key);
}
}
private void setAttachment(Cat.Context context) {
RpcContext.getContext().setAttachment(Cat.Context.ROOT,context.getProperty(Cat.Context.ROOT));
RpcContext.getContext().setAttachment(Cat.Context.CHILD,context.getProperty(Cat.Context.CHILD));
RpcContext.getContext().setAttachment(Cat.Context.PARENT,context.getProperty(Cat.Context.PARENT));
}
private Cat.Context getContext(){
Cat.Context context = CAT_CONTEXT.get();
if (context==null) {
context = initContext();
CAT_CONTEXT.set(context);
}
return context;
}
private Cat.Context initContext() {
Cat.Context context = new DubboCatContext();
Map<String,String> attachments = RpcContext.getContext().getAttachments();
if (attachments!=null&&attachments.size()>0) {
for (Map.Entry<String,String> entry:attachments.entrySet()) {
if (Cat.Context.CHILD.equals(entry.getKey()) || Cat.Context.ROOT.equals(entry.getKey()) || Cat.Context.PARENT.equals(entry.getKey())) {
context.addProperty(entry.getKey(),entry.getValue());
}
}
}
return context;
}
private void createConsumerCross(URL url, Transaction t) {
Event crossAppEvent = Cat.newEvent("PigeonCall.app", getProviderAppName(url));
Event crossServerEvent = Cat.newEvent("PigeonCall.server", url.getHost());
Event crossPortEvent = Cat.newEvent("PigeonCall.port", url.getPort() + "");
crossAppEvent.setStatus(Event.SUCCESS);
crossServerEvent.setStatus(Event.SUCCESS);
crossPortEvent.setStatus(Event.SUCCESS);
completeEvent(crossAppEvent);
completeEvent(crossPortEvent);
completeEvent(crossServerEvent);
t.addChild(crossAppEvent);
t.addChild(crossPortEvent);
t.addChild(crossServerEvent);
}
private void createProviderCross(URL url, Transaction t) {
String consumerAppName = RpcContext.getContext().getAttachment(Constants.APPLICATION_KEY);
if (StringUtils.isEmpty(consumerAppName)) {
consumerAppName = RpcContext.getContext().getRemoteHost() + ":" + RpcContext.getContext().getRemotePort();
}
Event crossAppEvent = Cat.newEvent("PigeonService.app", consumerAppName);
Event crossServerEvent = Cat.newEvent("PigeonService.client", url.getHost());
crossAppEvent.setStatus(Event.SUCCESS);
crossServerEvent.setStatus(Event.SUCCESS);
completeEvent(crossAppEvent);
completeEvent(crossServerEvent);
t.addChild(crossAppEvent);
t.addChild(crossServerEvent);
}
private void completeEvent(Event event) {
AbstractMessage message = (AbstractMessage) event;
message.setCompleted(true);
}
}
2). Partial implementation of http-restful calling method
CatHttpClientProxy.java
public void requestByGet(String url) {
Transaction t = Cat.newTransaction("PigeonCall", "method000");
//创建默认的httpClient实例
CloseableHttpClient httpClient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000).setConnectionRequestTimeout(1000)
.setSocketTimeout(5000).build();
try {
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(requestConfig);
//串联埋点
Cat.Context context = new CatHttpContext();
this.createConsumerCross(url, t);
Cat.logRemoteCallClient(context);
httpGet.setHeader(Cat.Context.ROOT, context.getProperty(Cat.Context.ROOT));
httpGet.setHeader(Cat.Context.PARENT, context.getProperty(Cat.Context.PARENT));
httpGet.setHeader(Cat.Context.CHILD, context.getProperty(Cat.Context.CHILD));
System.out.println("执行get请求:...." + httpGet.getURI());
CloseableHttpResponse httpResponse = null;
//发送get请求
httpResponse = httpClient.execute(httpGet);//请求返回的Resp,含http的header和执行结果实体Entity
try {
//response实体
HttpEntity entity = httpResponse.getEntity();//不包含header
if (null != entity) {
System.out.println("响应状态码:"+ httpResponse.getStatusLine());
System.out.println("-------------------------------------------------");
System.out.println("响应内容:" + EntityUtils.toString(entity));
}
} finally {
httpResponse.close();
}
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
e.printStackTrace();
t.setStatus(e.getClass().getSimpleName());
} finally {
t.complete();
try {
closeHttpClient(httpClient);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void createConsumerCross(String url, Transaction t){
Event crossAppEvent = Cat.newEvent("PigeonCall.app", "serverName");
Event crossServerEvent = Cat.newEvent("PigeonCall.server", "serverIp");
Event crossPortEvent = Cat.newEvent("PigeonCall.port", "serverPort");
crossAppEvent.setStatus(Event.SUCCESS);
crossServerEvent.setStatus(Event.SUCCESS);
crossPortEvent.setStatus(Event.SUCCESS);
completeEvent(crossAppEvent);
completeEvent(crossPortEvent);
completeEvent(crossServerEvent);
t.addChild(crossAppEvent);
t.addChild(crossPortEvent);
t.addChild(crossServerEvent);
}
private void completeEvent(Event event){
AbstractMessage message = (AbstractMessage) event;
message.setCompleted(true);
}
private void closeHttpClient(CloseableHttpClient client) throws IOException{
if (client != null) {
client.close();
}
}
2. CAT monitors the redis cache in detail
There is a convention for the identification of caches in the CAT source code , which is based on matching the "Cache." string, and supports memcached monitoring by judging the string "Cache.memcached" , but there is no display support for redis , and the source code needs to be modified. Add the judgment string "Cache.redis" ;
1). Modify the class: cat-home - com.dianping.cat.report.page.statistics.task.utilization.TransactionReportVisitor.java
Add judgment support for redis :
private static final String REDIS = "Cache.redis";
public TransactionReportVisitor() {
m_types.add("URL");
m_types.add("Service");
m_types.add("PigeonService");
m_types.add("Call");
m_types.add("PigeonCall");
m_types.add("SQL");
m_types.add(MEMCACHED);
m_types.add(REDIS);
}
@Override
public void visitType(TransactionType type) {
String typeName = type.getId();
Domain domain = m_report.findOrCreateDomain(m_domain);
if ("Service".equals(typeName)) {
typeName = "PigeonService";
} else if ("Call".equals(typeName)) {
typeName = "PigeonCall";
} else if (typeName.startsWith(MEMCACHED)) {
typeName = MEMCACHED;
} else if (typeName.startsWith(REDIS)){
typeName = REDIS;
}
......
}
2). Modify the class: cat-core - com.dianping.cat.config.server.ServerConfigManager.java
Add judgment support for redis :
public boolean isCacheTransaction(String type) {
return StringUtils.isNotEmpty(type) && (type.startsWith("Cache.memcached") || type.startsWith("Cache.redis"));
}
3). Modify the class: cat-consumer - com.dianping.cat.consumer.storage.StorageAnalyzer.java
Add judgment support for redis:
private void processCacheTransaction(MessageTree tree, Transaction t) {
String cachePrefix = "Cache.";
String ip = "Default";
String domain = tree.getDomain();
String cacheType = t.getType().substring(cachePrefix.length());
String name = t.getName();
String method = name.substring(name.lastIndexOf(":") + 1);
List<Message> messages = t.getChildren();
for (Message message : messages) {
if (message instanceof Event) {
String type = message.getType();
if (type.equals("Cache.memcached.server") || type.equals("Cache.redis.server")) {
ip = message.getName();
int index = ip.indexOf(":");
if (index > -1) {
ip = ip.substring(0, index);
}
}
}
}
......
}
3. CAT monitors the DB database in detail
If your orm framework uses mybatis , you can consider implementing the interceptor Interceptor to monitor the DB at the bottom. CAT also has a convention for the database buried point , and there is hard code in the code here . The specific points are as follows:
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
//得到类名,方法
String[] strArr = mappedStatement.getId().split("\\.");
String methodName = strArr[strArr.length - 2] + "." + strArr[strArr.length - 1];
Transaction t = Cat.newTransaction("SQL", "methodName");
//获取SQL类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
Cat.logEvent("SQL.Method", sqlCommandType.name().toLowerCase());
String JDBC_CONNECTION = "jdbc:mysql://unknown:3306/%s?useUnicode=true";
Cat.logEvent("SQL.Database", String.format(JDBC_CONNECTION, serverIp, dbName));
The spring configuration is as follows:
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
<!-- 插件配置 -->
<property name="plugins">
<array>
<bean class="com.kubbo.java.common.cat.CatMybatisPlugin"></bean>
</array>
</property>
</bean>
The above only lists one implementation plan for each problem, just a reference idea for students who are studying CAT. Personal research on CAT is just the beginning, and there are inevitably some flaws in what I said. Corrections and exchanges are welcome.
http://my.oschina.net/u/129971/blog/688371