首页 > 编程知识 正文

高并发下的数据库连接(mysql高并发数据重复)

时间:2023-05-06 20:56:01 阅读:91124 作者:924

作者:番茄炒蛋

资料来源:https://juejin.im/post/6877815196509798408

一、数据库连接池

1. 分析数据库连接的过程

前三个分组中,第一个分组是客户端发送到服务端的“SYN”分组,第二个分组是服务端返回到客户端的“ACK”分组和“SYN”分组,第三个分组是客户端(三次握手) ) )。

第二部分是MySQL服务器端验证客户端密码的过程。 第一个分组是服务端要求客户端认证的消息,第二个和第三个分组是客户端向服务端发送加密密码的分组,最后两个分组是服务端向客户端返回认证OK的消息。 从图中可以看到,整个连接过程大约消耗了4毫秒

可以看到,建立数据库连接的过程还需要一段时间。 每次运行sql并频繁地创建连接是很花时间的。

2. 数据库连接池

数据库连接池有两种最重要的配置,一种是控制从连接池中获取连接数的过程的最小连接数,另一种是最大连接数。

如果当前连接数小于最小连接数,则创建新连接以处理数据库请求;如果连接池有空闲连接,则复用空闲连接; 如果空闲池没有连接,并且当前连接数小于最大连接数,则创建新的连接处理请求。 如果当前连接数已经超过了最大连接数,则根据配置中设置的时间(C3P0的连接池配置为checkoutTimeout ),等待旧连接变为可用。 如果超过此设置时间,将向用户抛出错误。 在一般的线上,建议最小连接数控制在10左右,最大连接数控制在20~30左右。 (hmdxs )

3. 维护数据连接池

(1 (一) C3P0型号

启动线程以定期检测连接池中的连接是否可用。 例如,使用连接向数据库发送“select 1”命令,检查是否抛出异常,如果抛出异常,则从连接池中删除连接并尝试关闭。 目前,C3P0连接池可以通过这种方式检测连接是否可用,也是我推荐的方法。

)2) DBCP

获取连接后,检查连接是否可用,并仅在连接可用时执行SQL语句。 例如,DBCP连接池的testOnBorrow配置项控制是否打开此验证。 这种方式在获取连接时会引入额外的开销,但在线系统中尽量不打开,可以在测试服务中使用。

二、线程池

1、线程池的执行流程

JDK 1.5中引入的线程池执行程序是线程池的实现,有两个重要参数:核心线程计数和最大线程计数。 如果线程池中的线程数小于核心线程计数,则在处理新任务时将创建新线程。 如果线程数大于核心线程计数,则使任务排队,并运行当前空闲的线程; 队列中的任务满后,继续创建线程,直到到达最大线程计数; 如果线程数达到最大头计数时提交了新任务,我们必须放弃它们。

【提示】当然网络上有更全面的线程池执行过程,请参考!

2、CPU密集型、I/O密集型的线程池执行流程改进

请尝试针对不同类型的系统改进线程池的处理过程。 首先,由JDK实现的这个线程池优先将任务临时保存在队列中,而不是创建更多的线程。 这适用于执行CPU密集型任务,即CPU运算量较大的任务。 为什么会这样呢? 因为在执行CPU密集型任务时CPU很忙,所以创建与CPU内核数相当的线程就可以了,但是反而变多了

会造成线程上下文切换,降低任务执行效率。所以当前线程数超过核心线程数时,线程池不会增加线程,而是放在队列里等待核心线程空闲下来。

但是,我们平时开发的 Web 系统通常都有大量的 IO 操作,比方说查询数据库、查询缓存等等。任务在执行 IO 操作的时候 CPU 就空闲了下来,这时如果增加执行任务的线程数而不是把任务暂存在队列中,就可以在单位时间内执行更多的任务,大大提高了任务执行的吞吐量。所以你看 Tomcat 使用的线程池就不是 JDK 原生的线程池,而是做了一些改造,当线程数超过 coreThreadCount 之后会优先创建线程,直到线程数到达 maxThreadCount,这样就比较适合于 Web 系统大量 IO 操作的场景了,你在实际使用过程中也可以参考借鉴。

3、建议

(1)线程池中队列中任务的堆积数量需要监控。

(2)建议不要设置线程池的任务队列为无界队列,大量的任务堆积会占用大量的内存空间,一旦内存空间被占满就会频繁地触发 Full GC,造成服务不可用。

(3)使用线程池时就需要预先初始化所有的核心线程。如果灵巧的豌豆未经过预热可能会导致系统重启后产生比较多的慢请求。

(4)池化技术核心是一种空间换时间优化方法的实践,所以要关注空间占用情况,避免出现空间过度使用出现内存泄露或者频繁垃圾回收等问题

三、数据库优化方案

单机部署的数据库,依据一些云厂商的 Benchmark 的结果,在 4 核 8G 的机器上运行 MySQL 5.7 时,大概可以支撑 500 的 TPS 和 10000 的 QPS。当查询请求增加时,应该如何做主从读写分离来解决问题。

1. 主从复制

首先从库在连接到主节点时会创建一个 IO 线程,用以请求主库更新的 binlog,并且把接收到的 binlog 信息写入一个叫做 relay log 的日志文件中,而主库也会创建一个 log dump 线程来发送 binlog 给从库;同时,从库还会创建一个 SQL 线程读取 relay log 中的内容,并且在从库中做回放,最终实现主从的一致性。这是一种比较常见的主从复制方式。

无限制地增加从库的数量就可以抵抗大量的并发呢?实际上并不是的。因为随着从库数量增加,从库连接上来的 IO 线程比较多,主库也需要创建同样多的 log dump 线程来处理复制的请求,对于主库资源消耗比较高,同时受限于主库的网络带宽,所以在实际使用中,一般一个主库最多挂 3~5 个从库。

【注意】主从除了带来部署的复杂度之外,还会存在主从同步延时的情况。一般我们会把从库落后的时间作为一个重点的数据库指标做监控和报警,正常的时间是在毫秒级别,一旦落后的时间达到了秒级别就需要告警了。(监控字段的key:slave_behind_master)

2.读写分离

数据源拦截器拦截sql请求判断本次sql请求是读请求还是写请求,然后转发给不同的库。

3.访问数据库

我们已经使用主从复制的技术将数据复制到了多个节点,也实现了数据库读写的分离,这时,对于数据库的使用方式发生了变化。以前只需要使用一个数据库地址就好了,现在需要使用一个主库地址和多个从库地址,并且需要区分写入操作和查询操作,如果结合分库分表,复杂度会提升更多。为了降低实现的复杂度,业界涌现了很多数据库中间件来解决数据库的访问问题,这些中间件可以分为两类。

第一类以淘宝的 TDDL( Taobao Distributed Data Layer)为代表,以代码形式内嵌运行在应用程序内部。你可以把它看成是一种数据源的代理,它的配置管理着多个数据源,每个数据源对应一个数据库,可能是主库,可能是从库。当有一个数据库请求时,中间件将 SQL 语句发给某一个指定的数据源来处理,然后将处理结果返回。(仅支持Java语言)

另一类是单独部署的代理层方案,这一类方案代表比较多,如早期阿里巴巴开源的 Cobar,基于 Cobar 开发出来的 Mycat,360 开源的 Atlas,美团开源的基于 Atlas 开发的 DBProxy 等等。

它一般使用标准的 MySQL 通信协议,所以可以很好地支持多语言。由于它是独立部署的,所以也比较方便进行维护升级,比较适合有一定运维能力的大中型团队使用。它的缺陷是所有的 SQL 语句都需要跨两次网络:从应用到代理层和从代理层到数据源,所以在性能上会有一些损耗。并且需要单独维护,比较麻烦。

4.分库分表

数据库分库分表的方式有两种:一种是垂直拆分,另一种是水平拆分。垂直拆分,顾名思义就是对数据库竖着拆分,也就是将数据库的表拆分到多个不同的数据库中,专库专用。

对数据库进行垂直拆分是一种偏常规的方式,这种方式其实你会比较常用,不过拆分之后,虽然可以暂时缓解存储容量的瓶颈,但并不是万事大吉,因为数据库垂直拆分后依然不能解决某一个业务模块的数据大量膨胀的问题,一旦你的系统遭遇某一个业务库的数据量暴增,在这个情况下,你还需要继续寻找可以弥补的方式。这时候可以考虑水平拆分。

和垂直拆分的关注点不同,垂直拆分的关注点在于业务相关性,而水平拆分指的是将单一数据表按照某一种规则拆分到多个数据库和多个数据表中,关注点在数据的特点。

拆分规则:

(1)按照某一个字段的哈希值做拆分

(2)按照某一个字段的区间来拆分

分库分表引入的问题

(1)分区键

从分库分表规则中你可以看到,无论是哈希拆分还是区间段的拆分,我们首先都需要选取一个数据库字段,这带来一个问题是:我们之后所有的查询都需要带上这个字段,才能找到数据所在的库和表。

(2)join 代码层面控制

(3)count() 单独的数据表或是redis做计数控制

当然,虽然分库分表会对我们使用数据库带来一些不便,但是相比它所带来的扩展性和性能方面的提升,我们还是需要做的,因为,经历过分库分表后的系统,才能够突破单机的容量和请求量的瓶颈

作者:西红柿炒蛋

出处:https://juejin.im/post/6877815196509798408

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。