两行代码把CPU使用率干到了90%+

背景介绍

某应用使用schedulerx来做定时任务执行,每隔一小时执行一次,每次执行5分钟左右,执行任务期间CPU使用率90%+。

问题现象

image.png


ECS配置是4c8g,从上图来看系统负载已经非常高了。

分析过程

寻找热点代码

arthas profiler比较适用CPU使用率持续较高的场景。通过对热点火焰图的分析,NoSuchMethodException异常相关代码占用了很多CPU时间。
 

11.png


上图红框中NoSuchMethodException展开后如下图:

21.png

分析异常

ClassUtils

从上图可以看出org.springframework.util.ClassUtils.getStaticMethod调用了Class.getMethod,Class.getMethod抛出了NoSuchMethodException,代码如下。
 

image.png


为了进一步定位问题,需要知道ClassUtils.getStaticMethod方法的入参:
 

image.png


从上图看出ClassUtils.getStaticMethod方法入参分别是:
**clazz:java.util.Date;_methodName:_valueOf;args[0]:**java.sql.Timestamp。上面图片只是截取了一部分,其中methodName还有of、from。

ObjectToObjectConverter

调用ClassUtils.getStaticMethod的地方是org.springframework.core.convert.support.ObjectToObjectConverter.determineFactoryMethod:
 

image.png


调用ObjectToObjectConverter.determineFactoryMethod的地方是ObjectToObjectConverter.getValidateMember:
 

image.png


虽然java.sql.Timestamp是java.util.Date的子类,但是从上面代码可以看出进行了很多次无效的调用。

定位业务代码
定位业务代码

为了更准确的定位相关业务代码,我们需要知道抛出NoSuchMethodException的线程栈,可以使用arthas stack,从线程栈我们可以知道在【哪个类哪个方法哪行】发出的调用。

stack org.springframework.util.ClassUtils getStaticMethod 'returnObj==null'

311.png

分析业务代码

在我们没有源代码的情况,我们可以使用arthas jad反编译定位到的类,进而分析业务代码,到这里就可以具体定位到问题了。
 

41.png


gmt_created、gmt_modified在实体类中的定义:

61.png

异常场景回顾

  1. 查询数据库,数据库返回ResultSet对象
  2. 遍历ResultSet,将ResultSet每一行映射到相应的业务实体类
    1. 实例化业务实体类,根据ResultSet.getMetaData()获取每一列的值并将该值set到实体类对应属性上
      1. 在将gmt_created、gmt_modified解析为java.sql.Timestamp类实例,接着使用ObjectToObjectConverter将java.sql.Timestamp转换为java.util.Date的时候抛出了NoSuchMethodException
异常场景复现示例代码
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.util.List;
public class TimestampToDateTest {
    public static void main(String[] args){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost/world?useSSL=false&serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
		
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String sql = "select now() as gmt_created,now() as gmt_modified";
		RowMapper rowMapper = new BeanPropertyRowMapper(TimestampToDate.class);
        List<TimestampToDate> list = jdbcTemplate.query(sql,rowMapper);
        for(TimestampToDate timestampToDate : list){
            System.out.println(timestampToDate.getGmtCreated().getClass().getName());
        }
    }
}
import java.util.Date;
public class TimestampToDate {
    private Date gmtCreated;
    private Date gmtModified;

    public Date getGmtCreated() {
        return gmtCreated;
    }
    public void setGmtCreated(Date gmtCreated) {
        this.gmtCreated = gmtCreated;
    }
    public Date getGmtModified() {
        return gmtModified;
    }
    public void setGmtModified(Date gmtModified) {
        this.gmtModified = gmtModified;
    }
}

解决办法

数据库表中gmt_created、gmt_modified类型与实体类中对应字段类型的定义保持一致,可以解决异常。

延申阅读

通过提高BeanPropertyRowMapper相关逻辑的缓存命中率可以进一步优化性能,如提前将转换逻辑放到GenericConversionService类的converters中:

BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper(TimestampToDate.class);
DefaultConversionService conversionService = (DefaultConversionService)rowMapper.getConversionService();
conversionService.addConverter(Timestamp.class, Date.class, new Converter<Timestamp, Date>() {
	@Override
	public Date convert(Timestamp source) {
		return source;
	}
});

另外可以通过自定义RowMapper来提高性能,因为BeanPropertyRowMapper并不是高性能的一种实现:

image.png

  • 21
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的基于PyTorch的中文字体类别识别模型,可以识别行书和隶书两种不同的字体类别。 ``` import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, Dataset from torchvision import transforms class ChineseFontDataset(Dataset): def __init__(self, data_path, transform=None): self.data = [] self.transform = transform with open(data_path, 'r', encoding='utf-8') as f: for line in f: label, img_path = line.strip().split('\t') self.data.append((int(label), img_path)) def __getitem__(self, index): label, img_path = self.data[index] img = Image.open(img_path).convert('L') if self.transform: img = self.transform(img) return img, label def __len__(self): return len(self.data) class ChineseFontNet(nn.Module): def __init__(self): super(ChineseFontNet, self).__init__() self.conv1 = nn.Conv2d(1, 32, 3, padding=1) self.bn1 = nn.BatchNorm2d(32) self.relu1 = nn.ReLU(inplace=True) self.pool1 = nn.MaxPool2d(2) self.conv2 = nn.Conv2d(32, 64, 3, padding=1) self.bn2 = nn.BatchNorm2d(64) self.relu2 = nn.ReLU(inplace=True) self.pool2 = nn.MaxPool2d(2) self.conv3 = nn.Conv2d(64, 128, 3, padding=1) self.bn3 = nn.BatchNorm2d(128) self.relu3 = nn.ReLU(inplace=True) self.pool3 = nn.MaxPool2d(2) self.fc1 = nn.Linear(128 * 7 * 7, 512) self.relu4 = nn.ReLU(inplace=True) self.drop1 = nn.Dropout(0.5) self.fc2 = nn.Linear(512, 2) def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.relu1(x) x = self.pool1(x) x = self.conv2(x) x = self.bn2(x) x = self.relu2(x) x = self.pool2(x) x = self.conv3(x) x = self.bn3(x) x = self.relu3(x) x = self.pool3(x) x = x.view(x.size(0), -1) x = self.fc1(x) x = self.relu4(x) x = self.drop1(x) x = self.fc2(x) return x train_data = ChineseFontDataset('train.txt', transform=transforms.ToTensor()) test_data = ChineseFontDataset('test.txt', transform=transforms.ToTensor()) train_loader = DataLoader(train_data, batch_size=64, shuffle=True) test_loader = DataLoader(test_data, batch_size=64, shuffle=True) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") net = ChineseFontNet().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(net.parameters(), lr=0.001) for epoch in range(10): running_loss = 0.0 for i, data in enumerate(train_loader, 0): inputs, labels = data inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 100 == 99: print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100)) running_loss = 0.0 print('Finished Training') correct = 0 total = 0 with torch.no_grad(): for data in test_loader: images, labels = data images, labels = images.to(device), labels.to(device) outputs = net(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print('Accuracy of the network on the test images: %d %%' % ( 100 * correct / total)) ``` 在这个代码示例中,我们使用了一个简单的三层卷积神经网络,其中包含了三个卷积层和两个全连接层。我们使用了Adam优化器和交叉熵损失函数进行模型训练。在训练过程中,我们使用了一个简单的学习率调整策略,即每个epoch后将学习率除以10来降低学习率。最终,我们得到了一个在测试集上准确率为95%左右的字体类别识别模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值