什么是Elastic AMP
Elastic APM 是一个应用程序性能监控系统。它可以请求的响应时间、数据库查询、对缓存的调用、外部 HTTP 请求等的详细性能信息,可以实时监控软件服务和应用程序。这可以帮助我们快速查明和修复性能问题。
Elastic APM 还会自动收集未处理的错误和异常。因此我们可以在出现新错误时识别它们并密切关注特定错误发生的次数。
服务器指标是另一个重要的信息来源。Elastic APM 代理会自动获取基本的主机级别指标和特定于代理的指标。
Elastic APM 目前支持 Node.js, Python, Ruby, PHP, Java, Go, RUM (JS), 和.NET.
工作原理
- Elastic AMP 通过Agent收集应用程序的指标信息
- Agent将收集的信息上传至AMP Server
- AMP Server对数据进行聚合后,存储至Elasticsearch
- 通过Kibana查看指标信息
环境安装
我们通过Docker搭建一个单机的环境来演示Elastic APM的功能。
1.安装ElasticSearch
代码语言:javascript复制docker network create elastic
docker pull docker.elastic.co/elasticsearch/elasticsearch:7.15.2
docker run -d --name es01-test --net elastic -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.15.2
代码语言:javascript复制2.安装Kibana
代码语言:javascript复制docker pull docker.elastic.co/kibana/kibana:7.15.2
docker run -d --name kib01-test --net elastic -p 5601:5601 -e "ELASTICSEARCH_HOSTS=http://es01-test:9200" docker.elastic.co/kibana/kibana:7.15.2
3.安装ElasticAPM
代码语言:javascript复制docker run -d -p 8200:8200 --name=apm-server --net elastic --user=apm-server docker.elastic.co/apm/apm-server:7.15.2 --strict.perms=false -e -E output.elasticsearch.hosts=["es01-test:9200"]
代码演示
Elastic AMP支持.NET Framwork4.6.1 和.NET Core2.1 ,支持的组件有:
GRPC,HttpClient,EF6,EFCore,ElasticSearch,Mysql,Mongo,Redis,Kafka,RabbitMQ等,具体详见https://www.elastic.co/guide/en/apm/agent/dotnet/master/supported-technologies.html。我们这次使用.NET Framwork新建一个Web项目来演示
1.新增Web项目
2.新增Nuget包
代码语言:javascript复制 <package id="Elastic.Apm" version="1.12.1" targetFramework="net472" />
<package id="Elastic.Apm.AspNetFullFramework" version="1.12.1" targetFramework="net472" />
<package id="Elastic.Apm.SqlClient" version="1.12.1" targetFramework="net472" />
<package id="Elastic.Apm.StackExchange.Redis" version="1.12.1" targetFramework="net472" />
<package id="StackExchange.Redis" version="2.2.88" targetFramework="net472" />
3.配置HttpModule
web.config中的system.webServer中新增以下节点
代码语言:javascript复制<modules>
<add name="ElasticApmModule" type="Elastic.Apm.AspNetFullFramework.ElasticApmModule, Elastic.Apm.AspNetFullFramework" />
</modules>
4.配置Agent
我们可以通过环境变量配置Agent的信息
代码语言:javascript复制protected void Application_Start()
{
Environment.SetEnvironmentVariable("ELASTIC_APM_SERVICE_NAME", "TestFromworkSite"); //服务名
Environment.SetEnvironmentVariable("ELASTIC_APM_ENVIRONMENT", "Dev"); //环境
Environment.SetEnvironmentVariable("ELASTIC_APM_SERVER_URL", "http://localhost:8200"); // APM server
Environment.SetEnvironmentVariable("ELASTIC_APM_FLUSH_INTERVAL", "5s"); //上传数据的周期
Environment.SetEnvironmentVariable("ELASTIC_APM_LOG_LEVEL", "Trace");
//..........
}
5.启动网站
直接启动网站即可在Kibana中看到对应的Service:TestFromworkSite
Elastic APM核心模块
1.Transaction:我们通过Transaction可以看其中Api的调用信息
2. Dependencies:通过Dependencies看到服务依赖关系
3. Error: 能通过Error看到程序中的错误信息
4. Matrics: 可以通过Matrics看到服务气的内存与CPU信息
Elastic监控MSSql与Redis等组件
新增MSSqlHelper
代码语言:javascript复制public class MSSqlHelper
{
public static DataSet SqlExecuteReader(string _sql, SqlParameter[] _parameters, CommandType _type = CommandType.Text, string _constring = @"Data Source=.SQLEXPRESS;Initial Catalog=dev;Integrated Security=True;")
{
DataSet ds = new DataSet();
try
{
using (SqlConnection conn = new SqlConnection(_constring))
{
SqlCommand cmd = new SqlCommand(_sql, conn);
if (_parameters != null)
{
foreach (SqlParameter p in _parameters)
{ cmd.Parameters.Add(p); }
}
cmd.CommandType = _type;
cmd.CommandTimeout = 10;//超时时间,单位S
conn.Open();
using (SqlDataAdapter sda = new SqlDataAdapter())
{
sda.SelectCommand = cmd;
sda.Fill(ds);//填充dataset
}
}
return ds;
}
catch (Exception ex)
{
throw;
}
}
}
新增RedisHelper
代码语言:javascript复制public class RedisHelper
{
private static IDatabase database;
public static void UseApmForRedis()
{
var connection = ConnectionMultiplexer.Connect("127.0.0.1:6379");
connection.UseElasticApm();
database = connection.GetDatabase();
}
public static void StringSet(string key,string value)
{
database.StringSet(key,value);
}
}
在Application_Start()中开启SqlServer与Redis的监控
代码语言:javascript复制Agent.Subscribe(new SqlClientDiagnosticSubscriber());
RedisHelper.UseApmForRedis();
HomeController.Index接口中新增sqlserver与Redis的调用
代码语言:javascript复制public ActionResult Index()
{
MSSqlHelper.SqlExecuteReader("select * from S_dev.UserObject", null);
RedisHelper.StringSet("a", "a");
}
启动程序即可看到MSSql与Redis的相关数据
Elastic APM Api的深入使用
1.StartTransaction与StartSpan开启自定义transaction与span
一些定时任务同样可以使用Elastic APM的Agent.Tracer.StartTransaction和来监控,并且我们可以通过StartSpan来新增一个自定义节点,这种方式需要我们自己处理异常信息。我们新增一个Job
代码语言:javascript复制public class TestJob
{
public void Run()
{
while (true)
{
var trans = Agent.Tracer.StartTransaction("TestJob", ApiConstants.TypeRequest);
try
{
MSSqlHelper.SqlExecuteReader("select * from S_dev.UserObject", null);
RedisHelper.StringSet("a", "a");
trans.SetLabel("name", "chester");
var span = trans.StartSpan("自定义Span", ApiConstants.TypeExternal, ApiConstants.SubtypeHttp, ApiConstants.ActionQuery);
try
{
//Http request
}
catch (Exception e)
{
span.CaptureException(e);
}
finally
{
span.End();
}
Thread.Sleep(1000);
}
catch (Exception ex)
{
trans.CaptureException(ex);
throw;
}
finally
{
trans.End();
}
}
}
}
Application_start中启动Testjob
代码语言:javascript复制Task.Run(() => new TestJob().Run());
启动程序即可看到对应的TestJob监控
2.CaptureTransaction与CaptureSpan开启自定义transaction与span
CaptureTransaction与CaptureSpan相对于StartTransaction与StartSpan可以帮助我们结束Transaction与Span,也可以自动捕获异常,新增一个TestJob2
代码语言:javascript复制public class TestJob2
{
public void Run()
{
while (true)
{
Agent.Tracer.CaptureTransaction("TestJob2", ApiConstants.TypeRequest, (trans) =>
{
MSSqlHelper.SqlExecuteReader("select * from S_dev.UserObject", null);
RedisHelper.StringSet("a", "a");
trans.SetLabel("name", "chester");
trans.CaptureSpan("自定义Span2", ApiConstants.TypeDb, (s) =>
{
//execute db query
}, ApiConstants.SubtypeMssql, ApiConstants.ActionQuery);
Thread.Sleep(1000);
});
}
}
}
Application_start中启动Testjob2
代码语言:javascript复制Task.Run(() => new TestJob2().Run());
启动程序即可看到对应的TestJob2监控
3.Agent全局拦截
我们可以通过过滤器拦截Transaction与Span,并为其添加例如label等附加内容
代码语言:javascript复制Agent.AddFilter((ITransaction t) =>
{
//t.SetLabel("foo", "bar");
return t;
});
Agent.AddFilter((ISpan span) =>
{
// ..
return span;
});