你还不知道怎么做数据库读写分离么-用这个中间件让你性能提10倍 (你还不知道怎么打开这个开关)

教程大全 2025-07-21 05:30:42 浏览

前 言

订单缓存方案上线之后,我们以为又开启了岁月安好的日子,但是,在一周后的某一天,DBA直接跑来了,DBA直接说:“leader让我直接找你,是这样的,上次加了缓存优化后,效果确实不错,但是我发现订单查询sql在今天的12:00至12:05之间有大量的慢sql,查询时间超过了2.5s。”

这个时候,我们立马开启了排查问题模式,首先,check了一下上次加的缓存,发现缓存正常,然后接着根据DBA提供的信息搜索日志,此时,发现在这个时间段订单请求量突增,大概是平常订单请求量的2到3倍,然后经过了解,发现在这个时间段内,营销系统那边做了一些活动,导致订单请求量突增。

说白了就是做了促销活动后,大量下单的用户会不断刷新订单来查询订单的信息,比如看一下订单是否开始配送,此时大量的请求会打到了MySQL上去,此时单库又抗不了这么读请求,就导致了数据库负载很高,从而严重降低了MySQL的查询效率。

现在我们缓存也加过了,但是数据库负载还是很高,此时该怎么办呢?

其实也很简单,既然单个库扛不住,那就搞2个库一起来抗呗,因为对于外卖订单来说是典型的读多写少的场景,所以,在这个场景下,我们可以搞个一主两从的架构来进行优化,就像这样:

也就是写数据走主库,而读数据走从库,可以看到,此时由于我们搞了2个从库,这2个从库可以一起来抗大量的读请求。

非常关键的一点就是,从库会通过主从复制,从主库中不断的同步数据,以此来保证从库的数据和主库是一模一样的,所以想要实现读写分离,那么,就先要了解主从复制是怎么玩儿的。

主从复制的原理是什么?

我们以mysql一主两从架构为例,也就是一个Master节点下有两个slave节点,在这套架构下,写请求统一交给master节点处理,而读请求交给slave节点处理。

为了保证slave节点和master节点的数据一致性,master节点在写入数据之后,同时会把数据复制一份到自己的各个slave节点上。

在复制的过程中一共会使用到三个线程,一个是binlog dump线程,位于master节点上,另外两个线程分别是I/O线程和SQL线程,它们都分别位于slave节点上,如下图:

结合图片,我们一起来看下主从复制的核心流程:

(1)当master节点接收到一个写请求时,这个写请求可能是增删改操作,此时会把写请求的操作都记录到binlog日志中。

(2)master节点会把数据复制给slave节点,如图中的slave01节点和slave02节点,这个过程,首先得要每个slave节点连接到master节点上,当slave节点连接到master节点上时,master节点会为每一个slave节点分别创建一个binlog dump线程,用于向各个slave节点发送binlog日志。

(3)binlog dump线程会读取master节点上的binlog日志,然后将binlog日志发送给slave节点上的I/O线程。

(4)slave节点上的I/O线程接收到binlog日志后,会将binlog日志先写入到本地的relaylog中,relaylog中就保存了binlog日志。

(5)slave节点上的SQL线程,会来读取relaylog中的binlog日志,将其解析成具体的增删改操作,把这些在master节点上进行过的操作,重新在slave节点上也重做一遍,达到数据还原的效果,这样就可以保证master节点和slave节点的数据一致性了。

主从复制的有几种模式?

mysql的主从复制,分为全同步复制、异步复制、半同步复制和增强半同步复制这四种。

全同步复制

首先,全同步复制,就是当主库执行完一个事务之后,要求所有的从库也都必须执行完该事务,才可以返回处理结果给客户端;因此,虽然全同步复制数据一致性得到保证了,但是主库完成一个事物需要等待所有从库也完成,性能就比较低了。

异步复制

而异步复制,当主库提交事物后,会通知binlog dump线程发送binlog日志给从库,一旦binlog dump线程将binlog日志发送给从库之后,不需要等到从库也同步完成事务,主库就会将处理结果返回给客户端。

因为主库只管自己执行完事务,就可以将处理结果返回给客户端,而不用关心从库是否执行完事务,这就可能导致短暂的主从数据不一致的问题了,比如刚在主库插入的新数据,如果马上在从库查询,就可能查询不到。

而且,当主库提交事物后,如果宕机挂掉了,此时可能binlog还没来得及同步给从库,这时候如果为了恢复故障切换主从节点的话,就会出现数据丢失的问题,所以异步复制虽然性能高,但数据一致性上是较弱的。

mysql主从复制,默认采用的就是异步复制这种复制策略。

半同步复制

半同步复制,顾名思义就是在同步和异步中做了折中选择,我们可以结合着MySQL官网来看下是半同步主从复制的过程,来看下这样图:

当主库提交事务后,至少还需要一个从库返回接受到binlog日志,并成功写入到relaylog的消息,这个时候,主库才会将处理结果返回给客户端。

相比前2种复制方式,半同步复制较好地兼顾了数据一致性以及性能损耗的问题。

你还不知道怎么打开这个开关

同时,半同步复制也存在以下几个问题:

当主库成功提交事物并处于等待从库确认的过程中,这个时候,从库都还没来得及返回处理结果给客户端,但因为主库存储引擎内部已经提交事务了,所以,其他客户端是可以到从主库中读到数据的。

但是,如果下一秒主库突然挂了,就像这样图一样:

此时,下一次请求过来,因为主库挂了,就只能把请求切换到从库中,因为从库还没从主库同步完数据,所以,从库中当然就读不到这条数据了,和上一秒读取数据的结果对比,就造成了幻读的现象了。

增强半同步复制

最后,增强半同步复制,是mysql 5.7.2后的版本对半同步复制做的一个改进,原理上几乎是一样的,主要是解决幻读的问题。

主库配置了参数rpl_semi_sync_master_wait_point = AFTER_SYNC后,主库在存储引擎提交事物前,必须先收到从库数据同步完成的确认信息后,才能提交事务,以此来解决幻读问题。

可以参考下MySQL官网是怎么描述增强半同步主从复制过程的:

主从延迟问题和常规解决方案

主库写入的速度是很快的,因为主库是多线程并发写入的,但是,从库是单线程从主库拉取数据的,所以从库从主库复制数据的速度,就比较慢了,从而产生了主从延迟的问题。

mysql 从 5.6版本开始,就支持多线程复制,但是5.6版本是基于库级别去操作,也就是说会给每个数据库开启一个线程,不同库处理时在同一时间内是互不影响的;但是,当业务的压力集中到一个库时,又会回到和单线程复制一样的状况了。

直到mysql 5.7版本,开始引入了基于组提交(group_commit)的概念,这个时候才 真正 支持多路复制功能,官方称为enhanced multi-threaded slave(简称MTS),所以,推荐大家尽可能选择MySQL 5.7之后的版本。

而主库挂载的从库数量过多,也会导致主从复制延迟的问题,一般我们是建议一个主库挂载从库的数量,在3~5个比较合适。

另外,我们执行的SQL语句中,如果慢SQL语句过多,也会导致主从复制延迟,比如,我们工作中会遇到批量插入的场景,如果一批插入的数据量过大,就容易造成执行时间过长。

假如,从执行完一份 批量插入数据的SQL语句开始,到在从库上能查到这些数据的这个过程中,如果耗费了10秒,就导致主从库之间就延迟10秒了;所以,SQL优化会是一个常态化的工作,可以通过慢SQL日志或监控平台监控慢SQL,如果单个数据写入时间过长的话,可以将一批数据分片分批次写入。

最后,如果出现网络延迟或者机器的性能比较差,也会导致主从复制延迟的问题,这种情况没什么可说的,及时优化网络提升机器性能就行了。

读写分离实战

读写分离配置核心组件流程图:

读写分离配置步骤

(1)配置文件中配置主从库连接信息

(2)注入数据源

(3)数据源切换上下文,其中使用了ThreadLocal保存当前线程的数据源

(4)继承AbstRACtRoutingDataSource类重写determineCurrentLookupKey方法实现数据源动态切换

(5)创建读库的自定义注解

(6)切面类

(7)需要走读库的业务方法上添加@ReadOnly注解,那么执行这些业务方法时就会被切面拦截修改数据源从而走读库进行查询。

(8)写主库、读从库的效果

1)生成订单

2)查询订单


IT方面的中间件技术指的是什么?有没有比较好理解的定义?谢谢!

单纯的中间件的概念很好理解,底层是操作系统,顶层是具体的应用软件,老的软件开发是直接在操作系统上编写代码形成软件,这样的方式比较基础,开发的难度比较大,同时对人员和时间的消耗也比较大,中间件是介于二者之间的一类软件,把一些基层的具有共性的东西集成好了,相当于半成品,或者某种工具和机器,再在工具上做软件,方便快捷,易于维护,对开发人员的要求也降低了,省时省力省成本。 方正飞鸿智能信息平台,一款企业级的基础架构中间件,用于开发B\S架构软件。

Java中Set、List、Map集合类(接口)的特点及区别。分别有哪些常用实现类。

list与Set、Map区别及适用场景1、List,Set都是继承自Collection接口,Map则不是2、List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。 ) 和List对比: Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。 List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。 适合储存键值对的数据5.线程安全集合类与非线程安全集合类 LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;HashMap是非线程安全的,HashTable是线程安全的;StringBuilder是非线程安全的,StringBuffer是线程安全的。 下面是具体的使用介绍:ArrayList与LinkedList的区别和适用场景Arraylist:优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。 缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。 LinkedList:优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。 LinkedList 适用于要头尾操作或插入指定位置的场景缺点:因为LinkedList要移动指针,所以查询操作性能比较低。 适用场景分析:当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。 ArrayList与Vector的区别和适用场景ArrayList有三个构造方法:Java代码public ArrayList(int initialCapacity)//构造一个具有指定初始容量的空列表。 public ArrayList()//构造一个初始容量为10的空列表。 public ArrayList(Collection c)//构造一个包含指定 collection 的元素的列表 Vector有四个构造方法:Java代码public Vector()//使用指定的初始容量和等于零的容量增量构造一个空向量。 public Vector(int initialCapacity)//构造一个空向量,使其内部数据数组的大小,其标准容量增量为零。 public Vector(Collection c)//构造一个包含指定 collection 中的元素的向量public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量构造一个空的向量ArrayList和Vector都是用数组实现的,主要有这么三个区别是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定的结果。 而ArrayList不是,这个可以从源码中看出,Vector类中的方法很多有synchronized进行修饰,这样就导致了Vector在效率上无法与ArrayList相比;2.两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是不同。 可以设置增长因子,而ArrayList不可以。 是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。 适用场景分析是线程同步的,所以它也是线程安全的,而ArrayList是线程异步的,是不安全的。 如果不考虑到线程的安全因素,一般用ArrayList效率比较高。 2.如果集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有一定的优势。 HashSet与Treeset的适用场景 是二差树(红黑树的树据结构)实现的,Treeset中的数据是自动排好序的,不允许放入null值 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束 要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的String对象,hashcode是一样,所以放入的内容不能重复。 但是同一个类的对象可以放入不同的实例适用场景分析:HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。 为快速查找而设计的Set,我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。 HashMap与TreeMap、HashTable的区别及适用场景HashMap 非线程安全HashMap:基于哈希表实现。 使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。 TreeMap:非线程安全基于红黑树实现。 TreeMap没有调优选项,因为该树总处于平衡状态。 适用场景分析:HashMap和HashTable:HashMap去掉了HashTable的contains方法,但是加上了containsValue()和containsKey()方法。 HashTable同步的,而HashMap是非同步的,效率上比HashTable要高。 HashMap允许空键值,而HashTable不允许。 HashMap:适用于Map中插入、删除和定位元素。 Treemap:适用于按自然顺序或自定义顺序遍历键(key)。

executeQuery();和addBatch();executeBatch();除了执行单个和批量处理的不同外,还有什么不同?

executeQuery()是专门用来执行DQL(就是数据查询语句SELECT)语句的;而addBatch()和executeUpdate()是用来执行数据插入的,不同就是一个批量,一个单个插入。 这两个不要一起使用,那样addBatch()就和没写一样。 addBatch()是用来缓存数据的,将多条sql语句缓存起来,再通过executeBatch()方法一次性发给数据库,大大提高执行效率。 executeUpdate()注重的及时性,每写一条sql语句就发送给数据库保存起来,没有缓存,这样频繁操作数据库效率非常低。 还有一点需要注意的是:使用addBatch()缓存数据时要在循环中设置条件,当循环达到指定次数后执行executeBatch(),将缓存中的sql全部发给数据库,然后执行clearBatch()清楚缓存,否则数据过大是会出现OutOfMemory(内存不足)。

本文版权声明本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请联系本站客服,一经查实,本站将立刻删除。

发表评论

热门推荐