iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >ShardingJdbc批量操作引发fullGC解决
  • 731
分享到

ShardingJdbc批量操作引发fullGC解决

ShardingJdbc引发fullGCShardingJdbc批量操作 2022-11-13 19:11:34 731人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录正文内存分析为什么有这个 LocalCache 呢?解决方案正文 周五晚上告警群突然收到了一条告警消息,点开一看,应用 fullGC 了。 于是赶紧联系运维下载堆内存快照,进行

正文

周五晚上告警群突然收到了一条告警消息,点开一看,应用 fullGC 了。

于是赶紧联系运维下载堆内存快照,进行分析。

内存分析

使用 MemoryAnalyzer 打开堆文件

mat 下载地址:https://www.jb51.net/zt/matlab.html

下载下来后需要调大一下 MemoryAnalyzer.ini 配置文件里的-Xmx2048m

打开堆文件后如图:

发现有 809MB 的一个占用,应该问题就出在这块了。然后点击 Dominator Tree,看看有什么大的对象占用。

我们找大的对象,一级级往下点看看具体是谁在占用内存。点到下面发现是 sharding jdbc 里面的类,然后再继续往下发现了一个 localCache。

原来是一个本地缓存占了这么大的空间

为什么有这个 LocalCache 呢?

带着这个疑惑我们去代码里看看它是怎么使用的,根据堆内存分析上的提示,我直接打开了 sqlStatementParserEngine 类。

public final class SQLStatementParserEngine {
    private final SQLStatementParserExecutor sqlStatementParserExecutor;
    private final LoadingCache<String, SQLStatement> sqlStatementCache;
    public SQLStatementParserEngine(String databaseType, SQLParserRule sqlParserRule) {
        this.sqlStatementParserExecutor = new SQLStatementParserExecutor(databaseType, sqlParserRule);
        this.sqlStatementCache = SQLStatementCacheBuilder.build(sqlParserRule, databaseType);
    }
    public SQLStatement parse(String sql, boolean useCache) {
        return useCache ? (SQLStatement)this.sqlStatementCache.getUnchecked(sql) : this.sqlStatementParserExecutor.parse(sql);
    }
}

他这个里面有个 LoadingCache 类型的 sqlStatementCache 对象,这个就是我们要找的缓存对象。

从 parse 方法可以看出,它这里是想用本地缓存做一个优化,优化通过 sql 解析 SQLStatement 的速度。

在普通的场景使用应该是没问题的,但是如果是进行批量操作场景的话就会有问题。

就像下面这个语句:

@Mapper
public interface OrderMapper {
    Integer batchInsertOrder(List<Order> orders);
}
<insert id="batchInsertOrder" parameterType="com.mmc.sharding.bean.Order" >
        insert into t_order (id,code,amt,user_id,create_time)
        values
        <foreach collection="list" item="item" separator=",">
            (#{item.id},#{item.code},#{item.amt},#{item.userId},#{item.createTime})
        </foreach>
</insert>

1)我传入的 orders 的个数不一样,会拼出很多不同的 sql,生成不同的 SQLStatement,都会被放入到缓存中

2)因为批量操作的拼接,sql 本身长度也很大。如果我传入的 orders 的 size 是 1000,那么这个 sql 就很长,也比普通的 sql 更占用内存。

综上,就会导致大量的内存消耗,如果是请求速度很快的话,就就有可能导致频繁的 FullGC。

解决方案

因为是参数个数不同而导致的拼成 Sql 的不一致,所以我们解决参数个数就行了。

我们可以将传入的参数按我们指定的集合大小来拆分,即不管传入多大的集合,都拆为{300, 200, 100, 50, 25, 10, 5, 2, 1}这里面的个数的集合大小。如传入 220 大小的集合,就拆为[{200},{10},{10}],这样分三次去执行 sql,那么生成的 SQL 缓存数也就只有我们指定的固定数字的个数那么多了,基本不超过 10 个。

接下来我们实验一下,改造前和改造后的 gc 情况。

测试代码如下:

 @RequestMapping("/batchInsert")
    public String batchInsert(){
        for (int j = 0; j < 1000; j++) {
            List<Order> orderList = new ArrayList<>();
            int i1 = new Random().nextInt(1000) + 500;
            for (int i = 0; i < i1; i++) {
                Order order=new Order();
                order.setCode("abc"+i);
                order.setAmt(new BigDecimal(i));
                order.setUserId(i);
                order.setCreateTime(new Date());
                orderList.add(order);
            }
            orderMapper.batchInsertOrder(orderList);
            System.out.println(j);
        }
        return "success";
    }

GC 情况如图所示:

cache 里面存有元素:

修改代码后:

@RequestMapping("/batchInsert")
    public String batchInsert(){
        for (int j = 0; j < 1; j++) {
            List<Order> orderList = new ArrayList<>();
            int i1 = new Random().nextInt(1000) + 500;
            for (int i = 0; i < i1; i++) {
                Order order=new Order();
                order.setCode("abc"+i);
                order.setAmt(new BigDecimal(i));
                order.setUserId(i);
                order.setCreateTime(new Date());
                orderList.add(order);
            }
            List<List<Order>> shard = ShardingUtils.shard(orderList);
            shard.stream().forEach(
                    orders->{
                        orderMapper.batchInsertOrder(orders);
                    }
            );
            System.out.println(j);
        }
        return "success";
    }

GC 情况如下:

cache 里面存有元素:

可以看出 GC 次数有减少,本地缓存的条数由 600 多减到了 11 个,如果导出堆内存还能看出至少降低了几百 M 的本地内存占用。

另外,这个 cache 是有大小限制的,如果因为一个 sql 占了 600 多个位置,那么其他的 sql 的缓存就会被清理,导致其他 SQL 性能会受到影响,甚至如果机器本身内存不高,还会因为这个 cache 过大而导致频繁的 Full GC

大家以后在使用 Sharding JDBC 进行批量操作的时候就需要多注意了

另附上拆分为固定大小的数组工具方法如下:

public class ShardingUtils {
    private static Integer[] nums = new Integer[]{800,500,300, 200, 100, 50, 25, 10, 5, 2, 1};
    public static <T> List<List<T>> shard(final List<T> originData) {
        return shard(originData, new ArrayList<>());
    }
    private static <T> List<List<T>> shard(final List<T> originData, List<List<T>> result) {
        if (originData.isEmpty()) {
            return result;
        }
        for (int i = 0; i < nums.length; i++) {
            if (originData.size() >= nums[i]) {
                List<T> ts = originData.subList(0, nums[i]);
                result.add(ts);
                List<T> ts2 = originData.subList(nums[i], originData.size());
                if (ts2.isEmpty()) {
                    return result;
                } else {
                    return shard(ts2, result);
                }
            }
        }
        return result;
    }
}

以上就是Sharding Jdbc批量操作引发fullGC解决的详细内容,更多关于Sharding Jdbc引发fullGC的资料请关注编程网其它相关文章!

--结束END--

本文标题: ShardingJdbc批量操作引发fullGC解决

本文链接: https://www.lsjlt.com/news/171067.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作