1.前言
一个正常的SQL执行流程为:
- Connection conn = DriverManager.getConnection();
- Statement stmt = conn.createStatement();
- ResultSet rs = stmt.executeQuery(sql);
- 操作rs读取数据;
- 关闭rs、stmt、conn。
示例代码如下:
public static void main(String[] args){
try{
Class.forName("com.mysql.cj.jdbc.Driver"); //调用Class.forName()方法加载驱动程序
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/RUNOOB?useSSL=false", "root","*****"); //创建连接
Statement stmt = conn.createStatement(); //创建Statement对象
String sql = "select * from stu"; //要执行的SQL
ResultSet rs = stmt.executeQuery(sql);//创建数据对象
System.out.println("编号"+"\t"+"姓名"+"\t"+"年龄");
while (rs.next()){
System.out.print(rs.getInt(1) + "\t");
System.out.print(rs.getString(2) + "\t");
System.out.print(rs.getInt(3) + "\t");
System.out.println();
}
rs.close();
stmt.close();
conn.close();
}catch(Exception e){
}
}
但如果每次请求都要DriverManager.getConnection()新建连接和关闭连接,操作较重,费时费力,也影响了业务请求。 其实Connection对象是可以重复利用的(只要保证Connection借出后归单一线程所有,其所创建的Statement和ResultSet在回收前都能关闭即可),这样Connection被重新获取后就可以跟新建的一样,从而避免底层Socket连接的频繁创建与关闭。数据库连接池便应运而生。
DataSource定义了一个getConnection()的接口,具体实现可以是直接新建,也可以是从连接池里获取。用户使用完Connection后,要手动close(),而这个close()也是个逻辑语义。对于MySQL JDBC的ConnectionImpl来说,close()是物理关闭;而对于Druid的DruidPooledConnection来说,close()就是归还。
2.Druid简介
当我们有了连接池,应用程序启动时就预先建立多个数据库连接对象,然后将连接对象保存到连接池中。当客户请求到来时,从池中取出一个连接对象为客户服务。当请求完成时,客户程序调用关闭方法,将连接对象放回池中[2]。跟现实生活中的共享单车是不是很像~
相比之下,连接池的优点显而易见:
- 资源复用:因为数据库连接可以复用,避免了频繁创建和释放连接引起的大量性能开销,同时也增加了系统运行环境的平稳性;
- 提高性能:当业务请求时,因为数据库连接在初始化时已经被创建,可以立即使用,而不需要等待连接的建立,减少了响应时间;
- 优化资源分配:对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源;
- 连接管理:数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
Druid连接池内部的数据结构如下(以minIdle=5,maxActive=10为例):
- 连接池采用LRU栈式置换策略(最近归还的会被最先借出);
- poolingCount:池中可用的空闲连接;
- activeCount:已经借出去的连接数。两者之和为所有连接数。此时池里有7个空闲连接,poolingCount=7;
- empty条件变量:连接池有空闲连接时会等待。获取连接时如果无可用空闲连接会触发signal;
- notEmpty条件变量:获取连接时如果为空会等待,归还或创建连接时会触发signal。
3.初始化流程init()
触发时机:首次getConnection()时或直接调用init()。
核心流程:
- 创建initialSize个连接;
- 启动LogStatsThread、CreateConnectionThread、DestroyConnectionThread三个线程。
3.1 LogStatsThread线程(Druid-ConnectionPool-Log-)
如果timeBetweenLogStatsMillis > 0,则每隔timeBetweenLogStatsMillis打印一次stat日志。
3.2 CreateConnectionThread线程(Druid-ConnectionPool-Create-)
图片
后台负责创建连接的线程。监听empty条件信号,收到信号后,如果满足条件则创建一个新连接;如果不满足,则忽略此次信号。
3.2.1 创建新连接的条件
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) { //如果不满足条件,则忽略此次信号并继续await()
empty.await();
continue; //再次收到empty条件信号后,重新回到for (;;)处
}
3.2.2 createPhysicalConnection()创建连接流程
//创建一条连接,并初始化
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
//此处省略一万行
try {
Connection conn = createPhysicalConnection(url, physicalConnectProperties); //创建一条物理连接
connectedNanos = System.nanoTime();
if (conn == null) {
throw new SQLException("connect error, url " + url + ", driverClass " + this.driverClass);
}
initPhysicalConnection(conn, variables, globalVariables); //初始化连接
initedNanos = System.nanoTime();
validateConnection(conn); //检测一下
validatedNanos = System.nanoTime();
setFailContinuous(false);
setCreateError(null);
} //此处省略一万行
return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
}
接下来再来看createPhysicalConnection(url, info)函数,它就是负责创建一条java.sql.Connection连接,如下:
public Connection createPhysicalConnection(String url, Properties info) throws SQLException {
Connection conn;
if (getProxyFilters().size() == 0) {
conn = getDriver().connect(url, info); //创建一条连接
} else {
conn = new FilterChainImpl(this).connection_connect(info);
}
return conn;
}
3.2.3 put(holder, createTaskId)将连接放入连接池
将连接放入连接池尾部,并发送notEmpty条件信号。
//将连接放入连接池尾部,并发送notEmpty条件信号
private boolean put(DruidConnectionHolder holder, long createTaskId) {
lock.lock();
try {
if (poolingCount >= maxActive) {
return false;
}
connections[poolingCount] = holder; //放入连接池尾部
incrementPoolingCount(); //可用连接数poolingCount+1
notEmpty.signal(); //发送notEmpty条件信号
notEmptySignalCount++;
} finally {
lock.unlock();
}
return true;
}
3.3 DestroyConnectionThread线程(Druid-ConnectionPool-Destroy-)
图片
定时扫描连接池进行探测和销毁。DestroyConnectionThread每隔timeBetweenEvictionRunsMillis扫描一次连接池中的空闲连接:
- 如果物理存活时间超过phyTimeoutMillis,则直接销毁;
- 如果( keepAlive && 空闲时间 >= keepAliveBetweenTimeMillis),则进行探测;
- 如果空闲时间 >= minEvictableIdleTimeMillis,则销毁(但要保证留下minIdle个);而如果空闲时间超过maxEvictableIdleTimeMillis则必须进行销毁;
- 如果( removeAbandoned == true && 连接借出时间 > removeAbandonedTimeoutMillis),则强制关闭其statement并归还。
public void run() {
shrink(true, keepAlive); //探测与销毁
if (isRemoveAbandoned()) { //检查连接泄漏
removeAbandoned();
}
}
3.3.1 shrink(checkTime, keepAlive)流程
public void shrink(boolean checkTime, boolean keepAlive) {
try {
final int checkCount = poolingCount - minIdle;
final long currentTimeMillis = System.currentTimeMillis();
for (int i = 0; i < poolingCount; ++i) {
DruidConnectionHolder connection = connections[i];
if (phyTimeoutMillis > 0) { //如果连接的物理存活时间超过限值,将被销毁。
long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
evictConnections[evictCount++] = connection;
continue;
}
}
long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis; //空闲时间
//如果空闲时间过短,直接跳过
if (idleMillis < minEvictableIdleTimeMillis
&& idleMillis < keepAliveBetweenTimeMillis
) {
break;
}
if (idleMillis >= minEvictableIdleTimeMillis) { //如果连接空闲时间超过minEvictableIdleTimeMillis
if (checkTime && i < checkCount) { //留尾部的minIdle个连接先不销毁
evictConnections[evictCount++] = connection;
continue;
} else if (idleMillis > maxEvictableIdleTimeMillis) { //尾部的minIdle个连接如果连接空闲时间>maxEvictableIdleTimeMillis,也会被销毁
evictConnections[evictCount++] = connection;
continue;
}
}
if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) { //如果连接空闲时间未超过minEvictableIdleTimeMillis,但超过了keepAliveBetweenTimeMillis就要进行探活
keepAliveConnections[keepAliveCount++] = connection;
}
}
} finally {
lock.unlock();
}
if (evictCount > 0) { //如果有需要销毁的,则进行关闭连接操作。
for (int i = 0; i < evictCount; ++i) {
DruidConnectionHolder item = evictConnections[i];
Connection connection = item.getConnection();
JdbcUtils.close(connection);
destroyCountUpdater.incrementAndGet(this);
}
Arrays.fill(evictConnections, null);
}
if (keepAliveCount > 0) { //如果有需要探测的,则进行探测。探活的,则放回可用池;探不活的,直接关闭,并通知创建。
// keep order
for (int i = keepAliveCount - 1; i >= 0; --i) {
//此处省略一万行
}
}
if (needFill) {
lock.lock();
try {
int fillCount = minIdle - (activeCount + poolingCount + createTaskCount); //需要补充创建的连接个数
for (int i = 0; i < fillCount; ++i) {
emptySignal(); //给CreateConnectionThread发送empty条件信号来创建连接
}
} finally {
lock.unlock();
}
}
}
3.3.2 removeAbandoned()连接泄漏检测
连接泄露检查,打开removeAbandoned功能,连接从连接池借出后,长时间不归还,将触发强制关闭其staement并归还。回收周期随timeBetweenEvictionRunsMillis进行,如果连接借出时间起超过removeAbandonedTimeout,则强制关闭其staement并归还。对性能会有一些影响,建议怀疑存在泄漏之后再打开,不建议在生产环境中使用。
//强制归还泄漏(借出后长时间未归还)的连接
public int removeAbandoned() {
long currrentNanos = System.nanoTime();
List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();
activeConnectionLock.lock();
try {
Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
for (; iter.hasNext();) {
DruidPooledConnection pooledConnection = iter.next();
if (pooledConnection.isRunning()) {
continue;
}
long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000); //连接借出时间
if (timeMillis >= removeAbandonedTimeoutMillis) {
iter.remove();
abandonedList.add(pooledConnection);
}
}
} finally {
activeConnectionLock.unlock();
}
if (abandonedList.size() > 0) {
for (DruidPooledConnection pooledConnection : abandonedList) {
JdbcUtils.close(pooledConnection); //强制归还
}
}
return removeCount;
}
4.获取连接流程getConnection()
图片
连接池最核心的功能就是连接的获取与回收。我们直接看getConnectionDirect(),它负责获取一个可用的连接。
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
for (;;) { //如果某次获取到的连接无效,一般会丢弃该连接并重新获取。
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis); //获取连接
} catch (GetConnectionTimeoutException ex) {
//......
}
if (testOnBorrow) { //如果testOnBorrow=true,则进行探测。
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
discardConnection(poolableConnection.holder); //探测失败,则丢弃此连接并重新获取。
continue;
}
} else {
if (testWhileIdle) { //如果testWhileIdle=true且空闲时间>timeBetweenEvictionRunsMillis,则进行探测。
final DruidConnectionHolder holder = poolableConnection.holder;
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
if (idleMillis >= timeBetweenEvictionRunsMillis
|| idleMillis < 0 // unexcepted branch
) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
discardConnection(poolableConnection.holder); //如果探测失败,则丢弃连接并重新获取。
continue;
}
}
}
}
//是否打开连接泄露检查。DestroyConnectionThread如果检测到连接借出时间超过removeAbandonedTimeout,则强制归还连接到连接池中。
//对性能会有一些影响,建议怀疑存在泄漏之后再打开,不建议在生产环境中使用。
if (removeAbandoned) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); //保存发起方的线程栈
poolableConnection.connectStackTrace = stackTrace;
poolableConnection.setConnectedTimeNano(); //重置借出时间
activeConnectionLock.lock();
try {
activeConnections.put(poolableConnection, PRESENT); //放进activeConnections
} finally {
activeConnectionLock.unlock();
}
}
return poolableConnection;
}
}
4.1 getConnectionInternal()获取一个连接
getConnectionInternal()负责从连接池获取一个连接(但该连接并不保证可用),并包装成DruidPooledConnection。
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
final int maxWaitThreadCount = this.maxWaitThreadCount;
DruidConnectionHolder holder;
for (boolean createDirect = false;;) {
try {
//......
if (maxWaitThreadCount > 0
&& notEmptyWaitThreadCount >= maxWaitThreadCount) { //如果等待获取连接的线程数超过maxWaitThreadCount,则抛出异常
throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
+ lock.getQueueLength());
}
//从可用连接池里获取连接,如果没有则阻塞等待。
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
//......
} //......
break;
}
//......
holder.incrementUseCount(); //连接的使用次数+1
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder); ////包装成一个DruidPooledConnection对象
return poolalbeConnection;
}
4.2 takeLast()阻塞等待尾部连接
//阻塞等待尾部连接
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
try {
while (poolingCount == 0) { //如果没有可用连接,就发送个empty条件信号给CreateConnectionThread,并等待notEmpty条件信号
emptySignal(); // send signal to CreateThread create connection
notEmptyWaitThreadCount++;
try {
notEmpty.await(); // signal by recycle or creator
} finally {
notEmptyWaitThreadCount--;
}
//......
}
} catch (InterruptedException ie) {
//......
}
//移除尾部连接
decrementPoolingCount();
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;
return last;
}
至此,向请求线程返回一个可用的连接DruidPooledConnection。
5.执行&异常处理
图片
如下为Mybatis执行SQL的核心函数(SqlSessionTemplate$SqlSessionInterceptor.invoke()):
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//......
try {
Object result = method.invoke(sqlSession, args); //下调DruidPooledPreparedStatement.execute()执行SQL
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
//......
throw (Throwable)unwrapped; //如果发生异常,继续上抛
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); //关闭连接,对应Druid是归还。
}
}
return unwrapped;
}
}
- method.invoke()会通过Druid获取连接,并调用DruidPooledPreparedStatement.execute();
- 执行结束后,close连接,此时会触发Druid的连接归还;
- 执行中如果发生异常,继续向上抛。
5.1 DruidPooledPreparedStatement.execute()
此时,进入Druid连接中statement的execute(),如果发生异常进入checkException()。
public boolean execute() throws SQLException {
conn.beforeExecute();
try {
return stmt.execute(); //执行SQL
} catch (Throwable t) {
errorCheck(t);
throw checkException(t); //如果发生异常,调用DruidDataSource.handleConnectionException()对连接进行处理,并继续上抛
} finally {
conn.afterExecute();
}
}
5.1.1 DruidDataSource.handleConnectionException()
public void handleConnectionException(DruidPooledConnection pooledConnection, Throwable t, String sql) throws SQLException {
//......
if (t instanceof SQLException) {
SQLException sqlEx = (SQLException) t;
// exceptionSorter.isExceptionFatal
if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) { //判断是否是致命异常
handleFatalError(pooledConnection, sqlEx, sql); //如果是致命异常,则销毁
}
throw sqlEx;
} else {
throw new SQLException("Error", t);
}
}
//对致命异常进行处理
protected final void handleFatalError(DruidPooledConnection conn, SQLException error, String sql ) throws SQLException {
final DruidConnectionHolder holder = conn.holder;
//......
if (requireDiscard) {
if (holder != null && holder.statementTrace != null) {
try {
for (Statement stmt : holder.statementTrace) { //关闭连接内的所有的statement
JdbcUtils.close(stmt);
}
} finally {
}
}
emptySignalCalled = this.discardConnection(holder); //销毁
}
LOG.error("{conn-" + (holder != null ? holder.getConnectionId() : "null") + "} discard", error);
//......
}
6.回收连接流程recycle()
图片
使用DruidPooledConnection连接进行SQL操作后,会调用DruidPooledConnection.recycle()进行回收操作。
public void recycle() throws SQLException {
DruidConnectionHolder holder = this.holder;
DruidAbstractDataSource dataSource = holder.getDataSource();
dataSource.recycle(this);
//......
}
6.1 DruidDataSource.recycle()
/**
* 回收连接
* 1)重置连接;2)归还到连接池。
*/
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
final DruidConnectionHolder holder = pooledConnection.holder;
final Connection physicalConnection = holder.conn;
try {
//归还前重置连接
holder.reset();
if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) { //限制连接的最大使用次数。超过此值,会被直接关闭。
discardConnection(holder);
return;
}
if (testOnReturn) { //如果testOnReturn=true,归还前也检测下
//......
}
final long currentTimeMillis = System.currentTimeMillis();
if (phyTimeoutMillis > 0) { //检测物理存活时间
long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
discardConnection(holder);
return;
}
}
lock.lock();
try {
result = putLast(holder, currentTimeMillis); //归还到连接池
recycleCount++;
} finally {
lock.unlock();
}
if (!result) {
JdbcUtils.close(holder.conn);
}
} catch (Throwable e) {
//......
}
}
6.2 将连接放入可用连接池尾部,并发送notEmpty条件信号
//将连接放入可用连接池尾部,并发送notEmpty条件信号
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
if (poolingCount >= maxActive || e.discard) {
return false;
}
e.lastActiveTimeMillis = lastActiveTimeMillis; //重置最近活跃时间
connections[poolingCount] = e; //归还到尾部
incrementPoolingCount();
notEmpty.signal();
notEmptySignalCount++;
return true;
}
至此,连接已成功回收。
7.总结
7.1 整个连接池的核心操作
- init()初始化:1)创建initialSize个连接;2)启动LogStatsThread、CreateConnectionThread、DestroyConnectionThread三个线程;
- getConnection()获取连接:获取后会从连接池移除,Connection只能归当前线程所用;
- recycle()回收连接:放回连接池后,其他线程就可以再次获取该连接重复利用了。
7.2 条件信号协作
- 获取连接时:如果连接池里没有连接,会发出empty条件信号,并等待notEmpty条件信号。CreateConnectionThread收到empty信号后,如果满足条件则创建一个新连接,也会发出notEmpty条件信号;如果不满足,则忽略此次empty信号。
- 回收连接时:连接放回连接池后,会发出notEmpty条件信号。如果有请求在阻塞等待获取连接,此时会被唤醒,从而获取连接。
7.3 几处检测和销毁逻辑
- 借出时:
如果testOnBorrow,则探测;
如果(testWhileIdle = true && 空闲时间 > timeBetweenEvictionRunsMillis)则进行探测;
- 执行时:
- 如果抛出异常且exceptionSorter判断是致命异常,就调用handleFatalError()进行销毁;
- 归还时:
- 如果连接使用次数超过phyMaxUseCount,则销毁;
- 如果testOnReturn=true,则探测;
- 如果连接建立时间走过phyTimeoutMillis,则销毁;
- DestroyConnectionThread每隔timeBetweenEvictionRunsMillis扫描一次连接池中的空闲连接:
- 如果物理存活时间超过phyTimeoutMillis,则销毁;
- 如果( keepAlive && 空闲时间 >= keepAliveBetweenTimeMillis),则进行探测;
- 如果空闲时间 >= minEvictableIdleTimeMillis,则销毁(但要保证留下minIdle个);而如果空闲时间超过maxEvictableIdleTimeMillis则必须销毁;
- 如果( removeAbandoned == true && 连接借出时间 > removeAbandonedTimeoutMillis),则被强制关闭其statement并归还。
8.常用&推荐配置
8.1 常用配置
官方完整介绍见:https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8
图片
图片
图片
8.2 推荐配置
<bean id="userdataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 -->
<property name="name" value="userdataSource" />
<property name="url" value="${userdataSource_url}" />
<property name="driverClassName" value="com.zhuanzhuan.arch.kms.jdbc.mysql.Driver" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="3" />
<property name="maxActive" value="20" />
<!-- 获取连接的等待超时时间,单位是毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
<property name="maxWait" value="60000" />
<!-- 探测的SQL -->
<property name="validationQuery" value="SELECT 1" />
<!-- 获取连接时,执行validationQuery检测连接是否有效 -->
<property name="testOnBorrow" value="false" />
<!-- 获取连接时,如果空闲时间超过timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 -->
<property name="testWhileIdle" value="true" />
<!-- 归还连接时,执行validationQuery检测连接是否有效 -->
<property name="testOnReturn" value="false" />
<!-- 定期检测的时间间隔,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 定期检测时,如果空闲时间超过此值则进行销毁(但要保证留下minIdle个连接),单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
</bean>
此配置说明:
- 线程池刚启动时会创建1个(initialSize)连接,随着程序的运行,池不忙时也会保持最少3个(minIdle)空闲连接,但总连接数(包括空闲和在用)不超过20个(maxActive);
- 获取连接时:
如果连接池没有空闲连接,最长等待60秒(maxWait);
【主动】如果获取到的连接空闲时间大于60秒(timeBetweenEvictionRunsMillis),则执行validationQuery检测连接是否还有效(有效则使用,无效则销毁);
- 执行SQL时:
- 【被动】如果发生致命异常(默认exceptionSorter=MySqlExceptionSorter,如CommunicationsException),则销毁该连接;
- DestroyConnectionThread每隔60秒(timeBetweenEvictionRunsMillis)扫描一次连接池中的空闲连接:
- 【主动】如果空闲时间超过300秒(minEvictableIdleTimeMillis),则销毁(但要保证留下minIdle=3个);而如果空闲时间超过7小时(maxEvictableIdleTimeMillis默认为7小时)则必须销毁。
9.监控
Druid通过SPI开放了很多扩展点,我们架构部基于此封装了监控组件,直接上报到Prometheus。效果如下:
图片
图片
关于作者
杜云杰,高级架构师,转转架构部负责人,转转技术委员会执行主席,腾讯云MVP。负责服务治理、MQ、云平台、APM、IM、分布式调用链路追踪、监控系统、配置中心、分布式任务调度平台、分布式ID生成器、分布式锁等基础组件。微信号:waterystone,欢迎建设性交流。