面试701
2023-07-21 10:52:51 1 举报
AI智能生成
面试必备
作者其他创作
大纲/内容
mysql
使用explain时,关注什么字段
1.Type13;最为常见的扫描方式有:13;system:系统表,少量数据,往往不需要进行磁盘IO;13;const:常量连接;13;eq_ref:主键索引(primary key)或者非空唯一索引(unique not null)等值扫描;13;ref:非主键非唯一索引等值扫描;13;range:范围扫描;13;index:索引树扫描;13;ALL:全表扫描(full table scan);13;上面各类扫描方式由快到慢:13;system > const > eq_ref > ref > range > index > ALL
2.Extra13;Extra的值有13;NULL13;Using index SQL所需要返回的所有列数据均在一棵索引树上,而无需访问实际的行记录。13;Using where SQL使用了where条件过滤数据13;Using index condition 说明,确实命中了索引,但不是所有的列数据都在索引树上,还需要访问实际的行记录。13;Using filesort 得到所需结果集,需要对所有记录进行文件排序。13;典型的,在一个没有建立索引的列上进行了order by,13;就会触发filesort,常见的优化方案是,在order by的列上添加索引,避免每次查询都全量排序。13;Using temporary 需要建立临时表(temporary table)来暂存中间结果。13;这类SQL语句性能较低,往往也需要进行优化。13;典型的,group by和order by同时存在,且作用于不同的字段时,13;就会建立临时表,以便计算出最终的结果集。
Mysql慢查询优化
1.首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。13;13;2.分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。13;13;如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
?mysql的事务
事务的隔离级别有哪些
Read Uncommitted(未提交读,读取未提交内容)13; 事务中的修改即使没有提交,其他事务也能看见,事务可以读到未提交的数据称为脏读13; 也存在不可重复读、幻读问题13; 13; Read Committed(提交读,读取提交内容)13; 一个事务开始后只能看见已经提交的事务所做的修改,在事务中执行两次同样的查询可能得到不一样的结果,也叫做不可重复读(前后多次读取,不能读到相同的数据内容),也存幻读问题13; 13; Repeatable Read(可重复读,mysql默认的事务隔离级别)13; 解决脏读、不可重复读的问题,存在幻读的问题,使用 MMVC机制 实现可重复读13; 13; 幻读问题:MySQL的InnoDB引擎通过MVCC自动帮我们解决,即多版本并发控制13; 13; Serializable(可串行化)13; 解决脏读、不可重复读、幻读,可保证事务安全,但强制所有事务串行执行,所以并发效率低
数据库的隔离级别会导致哪些问题
脏读: 事务中的修改即使没有提交,其他事务也能看见,事务可以读到未提交的数据称为脏读13;13;不可重复读: 同个事务前后多次读取,不能读到相同的数据内容,中间另一个事务也操作了该同一数据13;13;幻读:当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,发现两次不一样,产生幻读13;13;幻读和不可重复读的区别是:前者是一个范围,后者是本身,从总的结果来看, 两者都表现为两次读取的结果不一致
事务的底层是如何实现的?又是怎么样回滚的?
MMVC机制是什么
事务的4种属性分别代表什么
原子性Atomicity13;一致性Consistency13;隔离性Isolation13;持久性Durability
B+树的特性 与B树的区别
B-TREE 每个节点都是一个二元数组: [key, data],所有节点都可以存储数据。key为索引key,data为除key之外的数据13;13;B-TREE的缺点:插入删除新的数据记录会破坏B-Tree的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持B-Tree性质。造成IO操作频繁。区间查找可能需要返回上层节点重复遍历,IO操作繁琐
B+Tree的改进:非叶子节点不存储data,只存储索引key;只有叶子节点才存储data13;13;3层的b+树可以表示上百万的数据,如果上百万的数据查找需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高13;在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree13;B+TREE 只在叶子节点存储数据 & 所有叶子结点包含一个链指针 & 其他内层非叶子节点只存储索引数据。只利用索引快速定位数据索引范围,先定位索引再通过索引高效快速定位数据。
存储引擎InnoDB、MyISAM异同点和选择
Innodb 支持事务,行锁,适合高并发,默认的引擎,读写均衡,写大于读场景,需要事务,不支持全文索引,可以通过插件实现, 更多使用ElasticSearch 13;13;Myisam 不支持事务,表锁,不适合高并发,非默认,读多写少场景,不需要事务,支持全文索引
InnoDB 主键索引,又是聚簇索引 (就是数据 和索引放在一起,我们称之为聚簇索引,性能最高) 表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引13;第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域13;13;MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址,MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复
?索引
常用的索引
普通索引:最基本的索引,仅加速查询 13;唯一索引:加速查询,列值唯一,允许为空;组合索引则列值的组合必须唯一13;主键索引:加速查询,列值唯一,一个表只有1个,不允许有空值13;组合索引:加速查询,多条件组合查询13;覆盖索引:索引包含所需要的值,不需要“回表”查询,比如查询 两个字段,刚好是 组合索引 的两个字段13;全文索引:对内容进行分词搜索,仅可用于Myisam, 更多用ElasticSearch做搜索
索引优缺点
索引好处:13; 快速定位到表的位置,减少服务器扫描的数据13; 有些索引存储了实际的值,特定情况下只要使用索引就能完成查询
索引缺点:13; 索引会浪费磁盘空间,不要创建非必要的索引13; 插入、更新、删除需要维护索引,带来额外的开销13; 索引过多,修改表的时候重构索引性能差
索引的优化实践
前缀索引,特别是TEXT和BLOG类型的字段,只检索前面几个字符,提高检索速度13; 尽量使用数据量少的索引,索引值过长查询速度会受到影响13; 选择合适的索引列顺序13; 内容变动少,且查询频繁,可以建立多几个索引13; 内容变动频繁,谨慎创建索引13; 根据业务创建适合的索引类型,比如某个字段常用来做查询条件,则为这个字段建立索引提高查询速度13; 组合索引选择业务查询最相关的字段
mysql为什么会选错索引
在多个索引的情况下,优化器一般会通过比较扫描行数、是否需要临时表以及是否需要排序等,来作为选择索引的判断依据13;选择索引 b,则需要在 b 索引上扫描 5w 条记录,然后同样回到主键索引上过滤掉不满足 a 条件的记录,因为索引有序,所以使用 b 索引不需要额外排序13;13;解决方法:13;使用force index a让mysql直接选择a索引来处理此处查询13;13;select * from testwnn321 where (a between 1000 and 2000) and (b between 50000 and 100000) order by b limit 1 走索引B13;13;select * from testwnn321 where (a between 1000 and 2000) and (b between 50000 and 100000) 走索引a
数据表有频繁的删除或更新操作导致的数据空洞造成的13;造成原因:分析器 explain 的结果预估的 rows 值跟实际情况差距比较大,分析器分析扫描行数用的是抽样调查13;解决方案:统计信息不对,那就修正。analyze table test 命令,可以用来重新统计索引信息
索引的失效问题,一般怎么设计索引
唯一索引和普通索引的区别
唯一索引:13;定义:UNIQUE索引可以强制执行值唯一的一列或多列。一个表可以有多个UNIQUE索引。13;场景:联系人的电子邮件唯一、联系人的身份证唯一 123@qq.com 数据库层面的严格唯一
查询上的区别:13;13;对唯一索引,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止检索13;对普通索引,查找到满足条件的第一个记录’ab‘后,需查找下个记录,直到碰到第一个不满足k=’ab‘条件的记录13;结论:mysql采用page页(一页16K)为数据单位从磁盘load出数据,除非刚好值为’ab‘的记录在一页的最后一条数据,否则执行性能区别微乎其微
修改上的区别:13;13;对于唯一索引,所有更新操作要先判断该操作是否违反唯一性约束,唯一索引不会用change buffer13;若所修改的数据在内存中13; 找到索引对应该存储的位置,判断、到没有冲突,插入值,语句执行结束13; 找到索引对应该存储的位置,插入值,语句执行结束。13;13;若所修改的数据不在内存中13; 需要将数据页读入内存,判断到没有冲突,插入值,语句执行结束13; 将更新记录在change buffer,语句执行结束
MySQL中的varchar和char有什么区别,应该怎么选择
char(16) 长度固定,存储字符,插入的长度小于定义长度时,则用空格填充,存取速度比varchar快得多,适合存储很短的,固定长度的字符串,如手机号,MD5值等13;13;varchar(16) 长度可变,存储字符,小于定义长度时,按实际插入长度存储,存取速度比char慢得多,适合用在长度不固定场景,如收货地址,邮箱地址等
大数据量sql分页优化思路
现象:千万级别数据很正常,比如数据流水、日志记录等,数据库正常的深度分页会很慢13;慢的原因:select * from product limit N,M13;MySQL执行此类SQL时需要先扫描到N行,然后再去取M行,N越大,MySQL扫描的记录数越多,SQL的性能就会越差13;13;1、后端、前端缓存13;13;2、使用ElasticSearch分页搜索13;13;3、合理使用 mysql 查询缓存,覆盖索引进行查询分页13; select title,cateory from product limit 1000000,10013; 13;4、如果id是自增且不存在中间删除数据,使用子查询优化,定位偏移位置的 id13;select * from oper_log where type='BUY' limit 1000000,100; //5.秒13; 13;select id from oper_log where type='BUY' limit 1000000,1; // 0.4秒 13;13;select * from oper_log where type='BUY' and id>=(select id from oper_log where type='BUY' limit 1000000,1) limit 100; //0.8秒 13;select * from (select id from oper_log where type='BUY' limit 1000000,1 ) as a 13;使用子查询,覆盖索引 先将Id查出来 然后再查其它的字段
生产环境的数据库,你会做哪些操作保证安全
数据安全13; 1、短期增量备份,比如一周一次。 定期全量备份,比如一月一次13;13; 2、检查是否有非授权用户,是否存在弱口令,网络防火墙检查13;13; 3、导出数据是否进行脱敏,防止数据泄露或者黑产利用13;13; 4、数据库 全量操作日志审计,防止数据泄露13; 13; 5、数据库账号密码 业务独立,权限独立控制,防止多库共用同个账号密码13; 13; 6、高可用 主从架构,多机房部署
业务性能13; 1、应用上线前会审查业务新增的sql,和分析sql执行计划13; 比如是否存在 select * ,索引建立是否合理13; 2、开启慢查询日志,定期分析慢查询日志13;13; 3、监控CPU/内存利用率,读写、网关IO、流量带宽 随着时间的变化统计图13; 13; 4、吞吐量QPS/TPS,一天内读写随着时间的变化统计图
mysql常见日志种类和作用
redo 重做日志13; 作用:确保事务的持久性,防止在发生故障,脏页未写入磁盘。重启数据库会进行redo log执行重做,到达事务一致性13; 13;undo 回滚日志13; 作用:保证数据的原子性,记录事务发生之前的数据的一个版本,用于回滚。13; innodb事务的可重复读和读取已提交 隔离级别就是通过mvcc+undo实现13; 13;errorlog 错误日志13; 作用:Mysql本身启动、停止、运行期间发生的错误信息13; 13;slow query log 慢查询日志13; 作用:记录执行时间过长的sql,时间阈值可以配置,只记录执行成功13;13;binlog 二进制日志13; 作用:用于主从复制,实现主从同步13; 13;relay log 中继日志13; 作用:用于数据库主从同步,将主库发送来的binlog先保存在本地,然后从库进行回放13; 13;general log 普通日志13; 作用:记录数据库操作明细,默认关闭,开启会降低数据库性能
?数据库主从的用途、原理流程
1.存在几个线程:主库一个线程 从库两个线程13;2.主库生成一个log dump线程,和从库IO线程交互13;3.IO线程请求主库binlog,写入到中级日志relay log13;4.SQL线程读取中继日志,解析然后写入到从库
数据库主从遇到的问题和解决办法
数据库主从复制的目的:13; 容灾使用,用于故障切换13; 业务需要,进行读写分离减少主库压力
为什么会有同步延迟问题,怎么解决?
保证性能第一情况下,不能百分百解决主从同步延迟问题,只能增加缓解措施。13;13;现象:主从同步,大数据量场景下,会发现写入主库的数据,在从库没找到。13;13;原因:13; 1、主从复制是单线程操作,当主库TPS高,产生的超过从库sql线程执行能力13;13; 2、从库执行了大的sql操作,阻塞等待13; 13; 3、服务器硬件问题,如磁盘,CPU,还有网络延迟等13; 13;解决办法:13; 1、业务需要有一定的容忍度,程序和数据库直接增加缓存,降低读压力13;13; 2、业务适合的话,写入主库后,再写缓存,读的时候可以读缓存,没命中再读从库13;13; 3、读写分离,一主多从,分散主库和从库压力13; 13; 4、提高硬件配置,比如使用SSD固态硬盘、更好的CPU和网络13; 13; 5、进行分库分表,减少单机压力
mysql主从同步日志的几种方式以及区别
Mysql主从复制数据一致性校验方案
什么场景下会出现主从数据不一致13;1、本身复制延迟导致13;2、主库宕机或者从库宕机都会导致复制中断13;3、把一个从库提升为主库,可能导致从库和主库的数据不一致性
如果不一致你会怎么修复
Mysql主从复制是基于binlog复制,难免出现复制数据不一致的风险,引起用户数据访问前后不一致的风险13;所以要定期开展主从复制数据一致性的校验并修复,避免这些问题13;13;解决方案之一,使用Percona公司下的工具13;13;pt-table-checksum工具进行一致性校验13; 原理:13; 主库利用表中的索引,将表的数据切割成一个个chunk(块),然后进行计算得到checksum值。13; 从库也执相应的操作,并在从库上计算相同数据块的checksum,然后对比主从中各个表的checksum是否一致并存储到数据库,最后通过存储校验结果的表就可以判断出哪些表的数据不一致
pt-table-sync(在从库执行)工具进行修复不一致数据,可以修复主从结构数据的不一致,也可以修复非主从结构数据表的数据不一致13;13; 原理:在主库上执行数据的更改,再同步到从库上,不会直接更改成从的数据。在主库上执行更改是基于主库现在的数据,也不会更改主库上的数据,可以同步某些表或整个库的数据,但它不同步表结构、索引,只同步不一致的数据13;注意:13; 默认主库要检查的表在从库都存在,并且同主库表有相同的表结构13; 如果表中没有索引,pt-table-checksum将没法处理,一般要求最基本都要有主键索引13; pt-table-sync工具会修改数据,使用前最好备份下数据,防止误操作
pt-table-checksum怎么保证某个chunk的时候checksum数据一致性?13;当pt工具在计算主库上某chunk的checksum时,主库可能在更新且从库可能复制延迟,那该怎么保证主库与从库计算的是”同一份”数据,答案把要checksum的行加上for update锁并计算,这保证了主库的某个chunk内部数据的一致性
开发过程中应该如何注意避免死锁
在程序中,操作多张表时,尽量以相同的顺序来访问(避免形成等待环路)13;13;批量操作单张表数据的时候,先对数据进行排序(避免形成等待环路) A线程 id:1 ,10 ,20按顺序加锁 B线程id:20,10,1 这种的话就容易锁。13;13;如果可以,大事务化成小事务,甚至不开启事务 select for update==>insert==>update = insert into update on duplicate key13;13;尽量使用索引访问数据,避免没有 where 条件的操作,避免锁表 有走索引是记录行锁,没走索引是表锁13;13;使用等值查询而不是范围查询查询数据,命中记录,避免间隙锁对并发的影响 1,10,20 等值where id in (1,10,20) 范围查询 id>1 and id<2013;13;避免在同一时间点运行多个对同一表进行读写的脚本,特别注意加锁且操作数据量比较大的语句;我们经常会有一些定时脚本,避免它们在同一时间点运行
mysql和redis的一致性
延时双删流程:13;先删除缓存13;再更新数据库13;休眠一会(比如1秒),再次删除缓存13;13;删除缓存重试流程:13;写请求更新数据库13;缓存因为某些原因,删除失败13;把删除失败的key放到消息队列13;消费消息队列的消息,获取要删除的key13;重试删除缓存操作
基本思路13;在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。13;13;伪代码如下:13;public void write( String key, Object data ){ redis.delKey( key ); db.updateData( data ); Thread.sleep( 500 ); redis.delKey( key );}
为什么要双删呢:13;因为第一次删除的是还没更新前的数据,第二次删除则是因为读取的并发性导致的缓存重新写入数据出现的垃圾数据。
explain对应类型分析,索引原理和索引失效情况
sql优化经验,思路是什么
mysql写入时候的buffer以及redo日志相关
kafka
使用kafka的原因
1.接入第三方数据时,为了防止上游突发流量,kafka在中间可以起一个缓冲的作用。13;2.异步处理 ,有些业务比如说建立YJ任务等的,客户也不需要等着立即返回消息,这样可以提高接口响应速度13;3.解耦13;13;高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition, consumer group 对partition进行consume操作。13;•可扩展性:kafka集群支持热扩展13;•持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失13;•容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)13;•高并发:支持数千个客户端同时读写
kafka缺点
由于是批量发送,数据并非真正的实时;13;对于mqtt协议不支持;13;不支持物联网传感数据直接接入;13;仅支持统一分区内消息有序,无法实现全局消息有序;13;监控不完善,需要安装插件;13;依赖zookeeper进行元数据管理;
ISR OSR AR分别代表什么
ISR:In-Sync Replicas 副本同步队列13;AR:Assigned Replicas 所有副本13;OSR:从ISR中踢出去的消息都在OSR中13;13;ISR的伸缩性指的是Leader维护ISR过程中,Follower从Leader同步数据时,有延迟时间或者延迟条数达到任意一个阈值,都会把该follower剔除ISR,存到OSR中 ,新加入的也会再OSR中 13;ISR+OSR=AR
kafka中的 zookeeper 起到什么作用
早期版本的kafka用zk做meta信息存储,consumer的消费状态,group的管理以及 offset的值
kafka2.x中,leader副本所在的节点宕机后,zk提供的监控功能能够实时感知到,并立即开启新一轮的leader选举,从ISR副本集合中Follower副本中选一个作为新的Leader,当旧的Leader副本重启回来后,只能作为Follower副本加入13; 到集群中。
新的consumer使用了kafka内部的group coordination协议,也减少了对zookeeper的依赖,但是broker依然依赖于ZK,zookeeper 在kafka中还用来选举controller 和 检测broker是否存活等等。
负责 Kafka集群元数据管理,集群协调工作,在每个 Kafka 服务器启动的时候去连接并将自己注册到 Zookeeper,类似注册中心13;Kafka 使用 Zookeeper 存放「集群元数据」、「集群成员管理」、 「Controller 选举」、「其他管理类任务」等13;13;集群元数据:Topic 对应 Partition 的所有数据都存放在 Zookeeper 中,且以 Zookeeper 保存的数据为准。13;Controller 选举:即选举 Broker 集群的控制器 Controller。其实它除了具有一般 Broker 的功能之外,还具有选举主题分区 Leader 节点的功能。13;在启动 Kafka系统时,其中一个 Broker 会被选举为控制器,负责管理主题分区和副本状态,还会执行分区重新分配的管理任务。13;如果在 Kafka 系统运行过程中,当前的控制器出现故障导致不可用,那么 Kafka 系统会从其他正常运行的 Broker 中重新选举出新的控制器。
生产者有哪些发消息的模式
发后即忘模式「fire-and-forget」,它只管发送消息,并不需要关心消息是否发送成功。其本质上也是一种异步发送的方式,消息先存储在缓冲区中,达到设定条件后再批量进行发送。这是 kafka 吞吐量最高的方式,但同时也是消息最不可靠的方式,因为对于发送失败的消息并没有做任何处理,某些异常情况下会导致消息丢失。13;只是关心消息的吞吐量,且允许少量消息发送失败,也不关注消息的发送顺序的话。可以使用这种。配合参数 acks = 0
同步发送模式 「sync」,调用 send() 方法会返回一个 Future 对象,再通过调用 Future 对象的 get() 方法,等待结果返回,根据返回的结果可以判断消息是否发送成功, 由于是同步发送会阻塞,只有当消息通过 get() 返回数据时,才会继续下一条消息的发送。13;要求消息必须是按顺序发送的话,且数据只能落在一个 Partition 上,那么可以使用同步发送「sync」的方式,13;并结合参数来设置 retries 的值让消息发送失败时可以进行多次重试「retries > 0」,13;再结合参数设置「acks=all & max_in_flight_requests_per_connection=1」(设置为1,即使发生了重试也会保证分区写入有序)
异步发送模式「async」,在调用 send() 方法的时候指定一个 callback 函数,当 Broker 接收到返回的时候,该 callback 函数会被触发执行,通过回调函数能够对异常情况进行处理,当调用了回调函数时,只有回调函数执行完毕生产者才会结束,否则一直会阻塞。13;如果业务需要知道消息是否发送成功,但对消息的顺序并不关心的话,那么可以用「异步async + 回调 callback 函数」的方式来发送消息,并配合参数 retries=0,待发送失败时将失败的消息记录到日志文件中进行后续处理。
kafka如何解决JVM GC问题
kafka的缓冲区13; 当一个Batch数据被发送到了kafka服务端,这个Batch的内存空间不再使用了。此时这个Batch底层的内存空间先不交给JVM去垃圾回收,而是把这块内存空间给放入一个缓冲池。13; 如果Batch数据被发送到kafka服务端了,此时Batch底层的内存块就直接还回缓冲池 就可以了。这样就解决了频繁的大量内存的GC问题。13; 初始化分配固定的内存 32MB,然后把32MB划分为N多个内存块,一个内存块默认的是16KB。这样缓冲池里面就有很多的内存块,需要创建一个新Batch的时候就从缓冲池里面取一个16KB的内存块就可以了
kafka快的原因
顺序写 由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快
存储模型,topic多分区,每个分区多segment段
页缓存Page cache,没利用JVM内存,因为容易GC影响性能13;首先 Kafka 为了保证磁盘写入性能,通过 mmap 内存映射的方式利用操作系统的 Page Cache 异步写入 。也可以称为 os cache,意思就是操作系统自己管理的缓存。那么在写磁盘文件的时候,就可以先直接写入 os cache 中,接下来由操作系统自己决定什么时候把 os cache 里的数据真正刷入到磁盘中, 这样大大提高写入效率和性能13;13;生产者,发送到broker 然后再到os cache ,最后再sync到磁盘上
index索引文件查找,利用分段和稀疏索引
Zero-copy 零拷技术减少拷贝次数13;让操作系统的 os cache 中的数据直接发送到网卡后传出给下游的消费者,中间跳过了两次拷贝数据的步骤,从而减少拷贝的 CPU 开销, 减少用户态内核态的上下文切换次数, 从而优化数据传输的性能, 而Socket缓存中仅仅会拷贝一个描述符过去,不会拷贝数据到Socket缓存
Pull 拉模式 使用拉模式进行消息的获取消费,与消费端处理能力相符。
Kafka高效文件存储设计特点
Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。13;通过索引信息可以快速定位message13;producer生产数据,要写入到log文件中,写的过程中一直追加到文件末尾,为顺序写,官网数据表明。同样的磁盘,顺序写能到600M/S,而随机写只有100K/S
?Kafka 网络模型
Kafka 采用多路复用方案,Reactor 设计模式,并引用 Java NIO 的方式更好的解决网络超高并发请求问题
kafka producer如何优化打入速度
提高 batch.size
增加 partition 数
设置 acks=-1 时,如果延迟增大:可以增大 num.replica.fetchers(follower 同步数据的线程数)来调解;
kafka producer 打数据,ack 为 0, 1, -1 的时候代表啥, 设置 -1 的时候,什么情况下,leader 会认为一条消息 commit了
1(默认) 数据发送到Kafka后,经过leader成功接收消息的的确认,就算是发送成功了。在这种情况下,如果leader宕机了,则会丢失数据。13;0 生产者将数据发送出去就不管了,不去等待任何返回。这种情况下数据传输效率最高,但是数据可靠性确是最低的。13;-1 producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成,可靠性最高。当ISR中所有Replica都向Leader发送ACK时,leader才commit,这时候producer才能认为一个请求中的消息都commit了。
kafka为什么没有做读写分离
场景不适用:读写分离适合那种读负载很大,而写操作相对不频繁的场景,可kafka不属于那样的场景13;同步机制:kafka采取pull方式实现同步,因此leader和follower存在不一致的窗口,如果要读取follower就势必要处理消息滞后的问题
如果leader crash时,ISR为空怎么办
kafka在Broker端提供了一个配置参数:unclean.leader.election,这个参数有两个值:13;true(默认):允许不同步副本成为leader,由于不同步副本的消息较为滞后,此时成为leader,可能会出现消息不一致的情况。13;13;false:不允许不同步副本成为leader,此时如果发生ISR列表为空,会一直等待旧leader恢复,降低了可用性。
生产者发送消息是异步调用,怎么知道是否有异常
发送消息配置回调函数即可, 该回调方法会在 Producer 收到 ack 时被调用,为异步调用13;回调函数有两个参数 RecordMetadata 和 Exception,如果 Exception 是 null,则消息发送成功,否则失败
offset的提交方式
自动提交offset问题13;13;没法控制消息是否正常被消费13;适合非严谨的场景,比如日志收集发送
手工提交offset13;13;同步 commitSync 阻塞当前线程 (自动失败重试)13;异步 commitAsync 不会阻塞当前线程 (没有失败重试,回调callback函数获取提交信息,记录日志)13;acknowledge是sping-kafka提供的一个封装方法,当ack-mode为manual_immediate时,实际还是调用上面的2种方法之一。
MANUAL poll()拉取一批消息,处理完业务后,手动调用Acknowledgment.acknowledge()先将offset存放到map本地缓存,在下一次poll之前从缓存拿出来批量提交13;MANUAL_IMMEDIATE 每处理完业务手动调用Acknowledgment.acknowledge()后立即提交13;RECORD 当每一条记录被消费者监听器(ListenerConsumer)处理之后提交13;BATCH 当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后提交13;TIME 当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后,距离上次提交时间大于TIME时提交13;COUNT 当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后,被处理record数量大于等于COUNT时提交13;COUNT_TIME TIME或COUNT满足其中一个时提交
指定了一个offset,Kafka怎么查找到对应的消息?
Kafka 采取了分片和索引机制,将每个partition分为多个segment,每个segment对应2个文件 log 和 index 默认超过1G就会有新的segment13;每个 segment对应两个文件--“.index”文件和“.log”文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic 名称+分区序号。
index文件中并没有为每一条message建立索引,采用了稀疏存储的方式13;每隔一定字节的数据建立一条索引,避免了索引文件占用过多的空间和资源,从而可以将索引文件保留到内存中13;缺点是没有建立索引的数据在查询的过程中需要小范围内的顺序扫描操作。
根据offset值二分法找到对应的index文件。需要定位的offset - segment文件中第一条消息的offset + 1 = 1560140921 - 1560140917 + 1 = 5 这个5就是第一列的序号,然后再取第二列的物理偏移地址,再拿物理偏移地址去log里面直接查找。
消费者根据什么模式从broker获取数据的?为什么是pull模式,而不是broker主动push?
消费者采用 pull 拉取方式,从broker的partition获取数据13;13;pull 模式则可以根据 consumer 的消费能力进行自己调整,不同的消费者性能不一样13;13;如果broker没有数据,consumer可以配置 timeout 时间,阻塞等待一段时间之后再返回13;如果是broker主动push,优点是可以快速处理消息,但是容易造成消费者处理不过来,消息堆积和延迟。
kafka数据一致性保证,最大限度不丢失数据13;13;ACK保障了【生产者】的投递可靠性13;partition的多副本保障了【消息存储】的可靠性13;HW作用:保证消费数据的一致性和副本数据的一致性
Kafka之间副本数据同步是怎样的?一致性怎么保证,数据怎样保证不丢失呢
发送数据一致性保障13;当producer向leader发送数据时,可以通过request.required.acks参数来设置数据可靠性的级别13;13;ack= all(即-1)13;producer只有收到分区内所有副本的成功写入全部落盘的通知才认为推送消息成功13;13;acks=all 就可以代表数据一定不会丢失了吗?13;Partition只有一个副本,也就是一个Leader,任何Follower都没有13;接收完消息后宕机,也会导致数据丢失,acks=all,必须跟ISR列表里至少有2个以上的副本配合使用13;在设置request.required.acks=-1的同时,也要min.insync.replicas这个参数设定 ISR中的最小副本数是多少,默认值为1,改为 >=2,如果ISR中的副本数少于min.insync.replicas配置的数量时,客户端会返回异常
如果在follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复
一个队列的多个parttion分布在不同的机器,怎样保证数据的顺序消费
同一个partition下的消息是有序的13;将消息发送到同一个partition即可实现顺序消费13;指定partitionid,或者使用同样的key 就会发送到同一个partition
消息幂等性解决方案
方式一:Redis的setNX() , 做消息id去重 java版本目前不支持设置过期时间
redis的 Incr 原子操作:key自增,大于0 返回值大于0则说明消费过,(key可以是消息的md5取值, 或者如果消息id设计合理直接用id做key)
设计一个去重表,某个字段使用Message的key做唯一索引,因为存在唯一索引,所以重复消费会失败
当消费者在消费过程突然宕机了,重新恢复后是从哪里消费,会有什么问题
消费者会记录offset,故障恢复后从这里继续消费,这个offset记录在哪里?
老版本记录在zk里面和本地
新版默认将offset保证在kafka的内置topic中,名称是 __consumer_offsets13;13;该Topic默认有50个Partition,每个Partition有3个副本,分区数量由参数offset.topic.num.partition配置13;通过groupId的哈希值和该参数取模的方式来确定某个消费者组已消费的offset保存到__consumer_offsets主题的哪个分区中13;由 消费者组名+主题+分区,确定唯一的offset的key,从而获取对应的值13;三元组:group.id+topic+分区号,而 value 就是 offset 的值
kafka消息推送默认策略
生产者发送消息到partition中,如果没有指定key 或者parttion id 则按照轮询的方式发送
kafka消息默认消费策略
轮询方式
如果同一消费者组内,所订阅的消息是不相同的,在执行分区分配的时候不是轮询分配,可能会导致分区分配的不均匀
范围方式(默认)
【按照主题】进行分配,如果不平均分配,则第一个消费者会分配比较多分区, 一个消费者监听不同主题也不影响,例如7个分区,同组内2个消费者13;13;topic-p0/topic-p1/topic-p2/topic-p3/topic-p4/topic-p5//topic-p613;13;c-1: topic-p0/topic-p1/topic-p2/topic-p313;13;c-2:topic-p4/topic-p5/topic-p613;13;弊端13;13;只是针对 1 个 topic 而言,c-1多消费一个分区影响不大13;如果有 N 多个 topic,那么针对每个 topic,消费者 C-1 都将多消费 1 个分区,topic越多则消费的分区也越多,则性能有所下降
HW
Follower故障13;13;Follower发生故障后会被临时踢出ISR(动态变化),待该follower恢复后,follower会读取本地的磁盘记录的上次的HW,并将该log文件高于HW的部分截取掉,从HW开始向leader进行同步,等该follower的LEO大于等于该Partition的hw,即follower追上leader后,就可以重新加入ISR
Leader故障13;13;Leader发生故障后,会从ISR中选出一个新的leader,为了保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于hw的部分截掉(新leader自己不会截掉),然后从新的leader同步数据
副本数量要小于broker的数量13;消费者的数量小于等于partition的数量
kafka副本默认最大数量是10个。13;且副本的数量不超过broker节点。leader和follwer绝对是在不同的机器上
partition数量是N,那么消费线程也保持为N,这样通常能够达到最大吞吐量
partition的数量 ,理论上越多的partition可以提供更高的吞吐量13;前提是 资源足够的情况下,partition越多速度越快的13;越多的分区 需要打开更多的文件句柄
消息堆积了10小时,有几千万条消息待处理,现在怎么办?13;修复consumer, 然后慢慢消费?也需要几小时才可以消费完成,新的消息怎么办?
核心思想:紧急临时扩容,更快的速度去消费数据13;13;- 修复Consumer不消费问题,使其恢复正常消费,根据业务需要看是否要暂停13;13;- 临时topic队列扩容,并提高消费者能力,但是如果增加Consumer数量,但是堆积的topic里面的message queue数量固定,过多的consumer不能分配到message queue13;13;13;- 编写临时处理分发程序,从旧topic快速读取到临时新topic中,新topic的queue数量扩容多倍,然后再启动更多consumer进行在临时新的topic里消费13;13;- 直到堆积的消息处理完成,再还原到正常的机器数量
kafka的Rebalance
Rebalance可能发生得时机
有新得consumer加入13;旧的consumer挂了13;topic得partition增加13;分区个数得增加,对topic得订阅发生变化,最常遇到得就是 消费组成员得加入或者离开
遇到的reblance问题13;例如70个分区,10个消费者,但是先启动一个消费者,后续再启动一个消费者,这个会怎么分配
Kafka 会进行一次分区分配操作,即 Kafka 消费者端的 Rebalance 操作 ,下面都会发生rebalance操作13;13;当消费者组内的消费者数量发生变化(增加或者减少),就会产生重新分配patition13;分区数量发生变化时(即 topic 的分区数量发生变化时)
Rebalance的影响
13;可能重复消费: Consumer被踢出消费组,可能还没有提交offset,Rebalance时会Partition重新分配其它Consumer,会造成重复消费,虽有幂等操作但耗费消费资源,亦增加集群压力13;集群不稳定:Rebalance扩散到整个ConsumerGroup的所有消费者,因为一个消费者的退出,导致整个Group进行了Rebalance,并在一个比较慢的时间内达到稳定状态,影响面较大13;影响消费速度:频繁的Rebalance反而降低了消息的消费速度,大部分时间都在重复消费和Rebalance
Rebalance的过程
第一步:所有消费成员都向Coordinator发送请求,请求入Consumer Group。一旦所有成员都发送了请求,Coordinator会从中选择一个Consumer担任Leader的角色,并把组成员信息以及订阅信息发给Leader。13;第二步:Leader开始分配消费方案,指明具体哪个Consumer负责消费哪些Topic的哪些Partition。一旦完成分配,leader会将这个方案发给Coordinator。Coordinator接收到分配方案之后会把方案发给各个Consumer,这样组内的所有成员就都知道自己应该消费哪些分区了。13;所以对于Rebalance来说,Coordinator起着至关重要的作用
解决方式
合理设置消费者参数13;未能及时发送心跳而Rebalance13;session.timeout.ms 一次session的连接超时时间13;heartbeat.interval.ms 心跳时间,一般为超时时间的1/3,Consumer在被判定为死亡之前,能够发送至少 3 轮的心跳请求13;13;Consumer消费超时而Rebalance13;max.poll.interval.ms 每隔多长时间去拉取消息。合理设置预期值,尽量但间隔时间消费者处理完业务逻辑,否则就会被coordinator判定为死亡,踢出ConsumerGroup,进行Rebalance13;max.poll.records 一次从拉取出来的数据条数。根据消费业务处理耗费时长合理设置,如果每次max.poll.interval.ms 设置的时间较短,可以max.poll.records设置小点儿,少拉取些,这样不会超时。13;总之,尽可能在max.poll.interval.ms时间间隔内处理完max.poll.records条消息,让Coordinator认为消费Consumer还活着
分区数是越多越好吗?不是
Broker端:有很多组件都在内存中维护了分区级别的缓存,比如 Controller,FetcherManager 等,因此分区数越多,这类缓存的成本就越大。
Producer端:比如参数 batch.size,默认是16KB。它会为每个分区缓存消息,在数据积累到一定大小或者足够的时间时,累积的消息将会从缓存中移除并发往Broker 节点。这个功能是为了提高性能而设计,但是随着分区数增多,这部分缓存所需的内存占用也会更多。
Consumer端:消费者数跟分区数是直接挂钩的,在消费消息时的内存占用、以及为达到更高的吞吐性能需要开启的 Consumer 数也会随着分区数增加而增加。
在 Kafka 的 Broker 中,每个 Partition 都会对应磁盘文件系统中一个目录。在 Kafka 的日志文件目录中,每个日志数据段都会分配三个文件,两个索引文件和一个数据文件。每个 Broker 会为每个日志段文件打开两个 index 文件句柄和一个 log 数据文件句柄。因此,随着 Partition 的增多,所需要保持打开状态的文件句柄数也就越多,最终可能超过底层操作系统配置的文件句柄数量限制。13;13;在 Kafka 中只对「已提交的消息做最大限度的持久化保证不丢失」,因此 Kafka 只有在消息提交之后,才会将消息暴露给消费者。此时如果分区越多那么副本之间需要同步的数据就会越多,假如消息需要在所有 ISR 副本集合列表同步复制完成之后才能进行暴露。因此 ISR 副本集合间复制数据所花时间将是 kafka 端对端延迟的最主要部分。
kakfa日志的清理方式
kafka日志清理13;基于【时间删除】每个日志段文件都维护一个最大时间戳字段,每次日志段写入新的消息时,都会更新该字段13;一个日志段segment写满了被切分之后,就不再接收任何新的消息,最大时间戳字段的值也将保持不变13;kafka通过将当前时间与该最大时间戳字段进行比较,从而来判定是否过期13;13;基于【大小超过阈值】13;log.retention.bytes和log.retention.minutes任意一个达到要求,都会执行删除13;13;13;#清理超过指定时间的消息,默认是168小时,7天,13;#还有log.retention.ms, log.retention.minutes, log.retention.hours,优先级高到低13;log.retention.hours=16813;13;#超过指定大小后,删除旧的消息,下面是1G的字节数,-1就是没限制13;log.retention.bytes=1073741824
产线环境部分参数调整
max.poll.interval.ms 一次处理消息的最大允许时间,同时也是rebalanceTimeoutMs
request.timeout.ms:请求的回应超时时间,rebalance会导致kafka server响应client的请求时间变长,因此参数值要大于max.poll.interval.ms
产线环境如何选择分区数量
Kafka底层存储的理解(页缓存、内核层、块层、设备层)
kafka的事务是怎么实现的
Kafka中有那些地方需要选举?这些地方的选举策略又有哪些?
kafka数据的同步机制
redis
有了哨兵为什么还要集群
搭建了主从,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,人工干预费时费力,还会在造成一段时间内服务不可用,这种情况下就使用了哨兵模式。会自动监听服务,做主从切换13;13;但是Sentinel哨兵模式只是解决了主从架构自动迁移的问题,其中Master主节点的写能力和存储能力依然是受到限制的,所以使用Redis的集群,集群就是为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器。
redis这么快的原因
Redis基于内存存储实现的数据库,相对于数据存在磁盘的MySQL数据库,省去磁盘I/O的消耗
Redis 作为 K-V 型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比如HashMap,通过key就可以直接获取到对应的value。而哈希表的特性,在O(1)时间复杂度就可以获得对应的值。
合理的线程模型 13;I/O多路复用13;13;I/O :网络 I/O13;多路 :多个网络连接13;复用:复用同一个线程。13;IO多路复用其实就是一种同步IO模型,它实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出cpu。
单线程模型13;Redis是单线程模型的,而单线程避免了CPU不必要的上下文切换和竞争锁的消耗。也正因为是单线程,如果某个命令执行过长(如hgetall命令),会造成阻塞。Redis是面向快速执行场景的数据库。,所以要慎用如smembers和lrange、hgetall等命令。13;Redis 6.0 引入了多线程提速,它的执行命令操作内存的仍然是个单线程。
什么是缓存击穿、缓存穿透、缓存雪崩
缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。
接口层增加校验,数据合理性校验13;缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,设置短点的过期时间,防止同个key被一直攻击13;13;SpringCache解决方案:13;空结果也缓存,默认不配置condition或者unless就行13; cache:13; #使用的缓存类型13; type: redis13; #过时时间13; redis:13; time-to-live: 360000013; # 开启前缀,默以为true13; use-key-prefix: true13; # 键的前缀,默认就是缓存名cacheNames13; key-prefix: XD_CACHE13; # 是否缓存空结果,防止缓存穿透,默以为true13; cache-null-values: true
如何避免缓存穿透呢? 一般有三种方法。13;13;1.如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。13;2.如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有有写请求进来的话,需要更新缓存哈,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。(业务上比较常用,简单有效)13;3.使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查。13;布隆过滤器原理:它由初始值为0的位图数组和N个哈希函数组成。一个对一个key进行N个hash算法获取N个值,在比特数组中将这N个值散列后设定为1,然后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。
缓存雪崩: 指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机。13;13;缓存雪崩一般是由于大量数据同时过期造成的,对于这个原因,可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值,5小时+0到1800秒酱紫。13;Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群啦。
预防:13;存数据的过期时间设置随机,防止同一时间大量数据过期现象发生13;设置热点数据永远不过期,定时任务定时更新13;13;SpringCache解决方案:13;设置差别的过时时间13;比如CacheManager配置多个过期时间维度13;配置文件 time-to-live 配置13; cache:13; #使用的缓存类型13; type: redis13; #过时时间13; redis:13; time-to-live: 3600000
缓存击穿: 指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。13;1.使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。13;2. “永不过期”,是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间。
什么是热Key问题,如何解决热key问题
如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务。13;产生原因:13;用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。13;请求分片集中,超过单Redi服务器的性能,比如固定名称key,Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,产生热点Key问题。13;13;如何识别到热点key呢?13;凭经验判断哪些是热Key;13;客户端统计上报;13;服务代理层上报13;13;如何解决热key问题?13;13;Redis集群扩容:增加分片副本,均衡读流量;13;将热key分散到不同的服务器中;13;使用二级缓存,即JVM本地缓存,减少Redis的读请求。
Redis 过期策略和内存淘汰策略
定时过期13;每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期13;只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期13;每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。13;13;expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。
Redis中同时使用了惰性过期和定期过期两种过期策略。
定期删除的扫描并不是遍历所有的键值对,这样的话比较费时而且太消耗系统资源。Redis服务器采用的是随机抽取的形式,每次100MS从过期字典中,取出20个键进行过期监测,过期字典中存储的是所有设置了过期时间的键值对。如果这批随机监查的数据中有25%比例过期,那么会再抽取20个随机键值进行检测和删除,并且会循环这个流程,直到抽取的这批数据中过期键键值小于25%,此次检测才算完成。
如果定期删除漏掉了很多过期的key,然后也没走惰性删除。就会有很多过期key积在内存,直接会导致内存爆的。或者有些时候,业务量大起来了,redis的key被大量使用,内存直接不够了,Redis用8种内存淘汰策略保护自己~13;volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰;13;allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰。13;volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key。13;allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰;13;volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据;。13;allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据。13;volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰;13;noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。13;
Redis 的持久化机制有哪些?优缺点说说
RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。RDB触发机制主要有以下几种:13;手动触发 save (同步,会阻塞)bgsave(异步 redis进程执行fork操作创建子进程)13;自动触发 save m n m秒内数据集存在次修改时,自动触发bgsave13;主从架构13; 从服务器同步数据的时候,会发送sync执行同步操作,master主服务器就会执行bgsave
RDB 的优点13;适合大规模的数据恢复场景,如备份,全量复制等13;数据的一致性和完整性更高13;13;13;RDB缺点13;没办法做到实时持久化/秒级持久化。 在快照持久化期间修改的数据不会被保存,可能丢失数据13; 每次快照是一次全量备份,fork子进程进行后台操作,子进程存在开销
AOF持久化介绍:13; append only file,追加文件的方式,文件容易被人读懂13; 以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令达到恢复数据的目的13; 写入过程宕机,也不影响之前的数据,可以通过 redis-check-aof检查修复问题13;核心原理:13; Redis每次写入命令会追加到aof_buf(缓冲区)13; AOF缓冲区根据对应的策略向硬盘做同步操作13; 高频AOF会带来影响,特别是每次刷盘13;提供了3种同步方式,在性能和安全性方面做出平衡:13;appendfsync always13;每次有数据修改发生时都会写入AOF文件,消耗性能多13;13;appendfsync everysec13;每秒钟同步一次,该策略为AOF的缺省策略。13;13;appendfsync no13;不主从同步,由操作系统自动调度刷磁盘,性能是最好的,但是最不安全
优点:13;数据更加安全13;当Redis AOF文件太大时,Redis能够在后台自动重写AOF13;AOF以易于理解和解析的格式,一个接一个地包含所有操作的日志13;13;缺点:13;AOF文件通常比同一数据集的等效RDB文件大13;根据确切的fsync策略,恢复的时候AOF可能比RDB慢
Redis4.0后开始的rewrite支持混合模式13; 就是rdb和aof一起用,直接将rdb持久化的方式来操作将二进制内容覆盖到aof文件中,rdb是二进制,所以很小13;有写入的话还是继续append追加到文件原始命令,等下次文件过大的时候再次rewrite,默认是开启状态13;13;好处:13; 混合持久化结合了RDB持久化 和 AOF 持久化的优点,采取了rdb的文件小易于灾难恢复13; 同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失13;13;坏处:13;前部分是RDB格式,是二进制,所以阅读性较差13;13;数据恢复:13; 先看是否存在aof文件,若存在则先按照aof文件恢复,aof比rdb全,且aof文件也rewrite成rdb二进制格式13; 若aof不存在,则才会查找rdb是否存在
怎么实现Redis的高可用
主从模式同步方式、原理
Redis的主从复制原理13;保存主节点(master)信息13;这一步只是保存主节点信息,保存主节点的ip和port。13;主从建立连接13;从节点(slave)发现新的主节点后,会尝试和主节点建立网络连接。13;发送ping命令13;连接建立成功后从节点发送ping请求进行首次通信,主要是检测主从之间网络套接字是否可用、主节点当前是否可接受处理命令。13;权限验证13;如果主节点要求密码验证,从节点必须正确的密码才能通过验证。13;同步数据集13;主从复制连接正常通信后,主节点会把持有的数据全部发送给从节点。13;命令持续复制13;接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。
主从同步的方式:13;主从模式中,Redis部署了多台机器,有主节点,负责读写操作,有从节点,只负责读操作。从节点的数据来自主节点,实现原理就是主从复制机制13;主从复制包括全量复制,增量复制两种。一般当slave第一次启动连接master,或者认为是第一次连接,就采用全量复制,全量复制流程如下:13;1.slave发送sync命令到master。13;2.master接收到SYNC命令后,执行bgsave命令,生成RDB全量文件。13;3.master使用缓冲区,记录RDB快照生成期间的所有写命令。13;4.master执行完bgsave后,向所有slave发送RDB快照文件。13;5.slave收到RDB快照文件后,载入、解析收到的快照。13;6.master使用缓冲区,记录RDB同步期间生成的所有写的命令。13;7.master快照发送完毕后,开始向slave发送缓冲区中的写命令;13;8.salve接受命令请求,并执行来自master缓冲区的写命令13;redis2.8版本之后,已经使用psync来替代sync,因为sync命令非常消耗系统资源,psync的效率更高。13;13;slave与master全量同步之后,master上的数据,如果再次发生更新,就会触发增量复制。
哨兵模式
哨兵模式13;主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址。显然,多数业务场景都不能接受这种故障处理方式。Redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。13;13;哨兵模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监视所有的Redis主节点和从节点,并在被监视的主节点进入下线状态时,自动将下线主服务器属下的某个从节点升级为新的主节点。但是呢,一个哨兵进程对Redis节点进行监控,就可能会出现问题(单点问题),因此,可以使用多个哨兵来进行监控Redis节点,并且各个哨兵之间还会进行监控。13;13;哨兵模式就三个作用:13;发送命令,等待Redis服务器(包括主服务器和从服务器)返回监控其运行状态;13;哨兵监测到主节点宕机,会自动将从节点切换成主节点,然后通过发布订阅模式通知其他的从节点,修改配置文件,让它们切换主机;13;哨兵之间还会相互监控,从而达到高可用。
每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他Sentinel实例发送一个 PING命令。13;如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel标记为主观下线。13;如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。13;当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线。13;在一般情况下, 每个 Sentinel 会以每10秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。13;当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次13;若没有足够数量的 Sentinel同意Master已经下线, Master的客观下线状态就会被移除;若Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
集群模式
哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。因此,Cluster集群应运而生,它在Redis3.0加入的,实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它也提供复制和故障转移的功能。13;Redis Cluster集群通过Gossip协议进行通信,节点之前不断交换信息,交换的信息内容包括节点出现故障、新节点加入、主从节点变更信息、slot信息等等。常用的Gossip消息分为4种,分别是:ping、pong、meet、fail
Hash Slot插槽算法
插槽算法把整个数据库被分为16384个slot(槽),每个进入Redis的键值对,根据key进行散列,分配到这16384插槽中的一个。使用的哈希映射也比较简单,用CRC16算法计算出一个16 位的值,再对16384取模。数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点都可以处理这16384个槽。13;13;集群中的每个节点负责一部分的hash槽,比如当前集群有A、B、C个节点,每个节点上的哈希槽数 =16384/3,那么就有:13;13;节点A负责0~5460号哈希槽13;节点B负责5461~10922号哈希槽13;节点C负责10923~16383号哈希槽13;
Redis Cluster集群中,需要确保16384个槽对应的node都正常工作,如果某个node出现故障,它负责的slot也会失效,整个集群将不能工作。13;13;因此为了保证高可用,Cluster集群引入了主从复制,一个主节点对应一个或者多个从节点。当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与 A 通信超时,那么认为主节点 A 宕机了。如果主节点宕机时,就会启用从节点。13;13;在Redis的每一个节点上,都有两个玩意,一个是插槽(slot),它的取值范围是0~16383。另外一个是cluster,可以理解为一个集群管理的插件。当我们存取的key到达时,Redis 会根据CRC16算法得出一个16 bit的值,然后把结果对16384取模。酱紫每个key都会对应一个编号在 0~16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。13;13;虽然数据是分开存储在不同节点上的,但是对客户端来说,整个集群Cluster,被看做一个整体。客户端端连接任意一个node,看起来跟操作单实例的Redis一样。当客户端操作的key没有被分配到正确的node节点时,Redis会返回转向指令,最后指向正确的node,这就有点像浏览器页面的302 重定向跳转。
使用过Redis分布式锁嘛?有哪些注意点呢?
分布式锁的几种坑-(死锁,错删)
1.多个命令之间不是原子性操作,如setnx和expire之间,如果setnx成功,但是expire失败,且宕机了,则这个资源就是死锁
解决:13;使用原子命令:设置和配置过期时间 setnx / setex13;如: set key 1 ex 30 nx13;java里面 redisTemplate.opsForValue().setIfAbsent("seckill_1",1,30,TimeUnit.MILLISECONDS)
2.13;线程A 设置key 30秒过期,先加锁成功,但是执行了40秒13;线程B 设置key 30秒过期,由于线程A加锁过期,所以线程B加锁成功,执行方法50秒。13;这时候线程A执行完成了,但是线程B还没执行完成,结果就是 线程A删除了线程B加的锁
可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁, 那 value 应该是存当前线程的标识或者uuid 13;if(get(key).equals(value)){13; //还存在时间间隔(问题3)13; del(key)13; }13;
3.当线程A获取到正常值时,返回带代码中判断期间锁过期了,线程B刚好重新设置了新值,线程A那边有判断value是自己的标识,然后调用del方法,结果就是删除了新设置的线程B的值
//获取lock的值和传递的值一样,调用删除操作返回1,否则返回013;在finally中加上以下:13;String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";13;13;//Arrays.asList(lockKey)是key列表,uuid是参数13;Integer result = redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class), Arrays.asList(lockKey), uuid);
4.13;原生代码+redis实现分布式锁使用比较复杂,且有些锁续期问题更难处理 推荐使用Redission
使用步骤1.<dependency>13; <groupId>org.redisson</groupId>13; <artifactId>redisson</artifactId>13; <version>3.10.1</version>13;</dependency>13;
使用步骤2. @Bean13; public RedissonClient redissonClient() {13; Config config = new Config();13;13; //单机模式13; //config.useSingleServer().setPassword("123456").setAddress("redis://8.129.113.233:3308");13; config.useSingleServer().setPassword(redisPwd).setAddress("redis://"+redisHost+":"+redisPort);13;13; //集群模式13; //config.useClusterServers()13; //.setScanInterval(2000)13; //.addNodeAddress("redis://10.0.29.30:6379", "redis://10.0.29.95:6379")13; // .addNodeAddress("redis://127.0.0.1:6379");13;13; RedissonClient redisson = Redisson.create(config);13;13; return redisson;13; }13;
使用步骤3:Lock lock = redisson.getLock("lock:coupon:"+couponId);13;//阻塞式等待,一个线程获取锁后,其他线程只能等待,和原生的方式循环调用不一样 默认30秒13;lock.lock();13;13;finally {13; lock.unlock();13; }
Redis锁的过期时间小于业务的执行时间该如何续期?
1.Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。13;2.Redisson中客户端一旦加锁成功,就会启动一个watch dog看门狗。watch dog是一个后台线程,会每隔10秒检查一下,如果客户端还持有锁key,那么就会不断的延长锁key的生存时间13;3.默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定
方式一、13;//多个线程进入,加锁以后默认30秒自动解锁。会阻塞等待释放锁 有watchdog功能,每隔10秒检查一下13;lock.lock();13;方式二、13;// 加锁以后10秒钟自动解锁 没有watchdog功能 无需调用unlock方法手动解锁13;lock.lock(10, TimeUnit.SECONDS);13;方式三、13;// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 13;lock.tryLock(100, 10, TimeUnit.SECONDS);13;
MySQL与Redis 如何保证双写一致性
缓存延时双删13;删除缓存重试机制13;读取biglog异步删除缓存
延时双删流程13;13;先删除缓存13;再更新数据库13;休眠一会(比如1秒),再次删除缓存。
删除缓存重试机制13;因为延时双删可能会存在第二步的删除缓存失败,导致的数据不一致问题。可以使用这个方案优化:删除失败就多删除几次呀,保证删除缓存成功就可以了呀~ 所以可以引入删除缓存重试机制13;13;删除缓存重试流程13;13;写请求更新数据库13;缓存因为某些原因,删除失败13;把删除失败的key放到消息队列13;消费消息队列的消息,获取要删除的key13;重试删除缓存操作
读取biglog异步删除缓存13;重试删除缓存机制还可以吧,就是会造成好多业务代码入侵。其实,还可以这样优化:通过数据库的binlog来异步淘汰key。13;13;以mysql为例吧13;13;可以使用阿里的canal将binlog日志采集发送到MQ队列里面13;然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性
在生成 RDB期间,Redis 可以同时处理写请求么?
可以的,Redis提供两个指令生成RDB,分别是save和bgsave。13;13;如果是save指令,会阻塞,因为是主线程执行的。13;如果是bgsave指令,是fork一个子进程来写入RDB文件的,快照持久化完全交给子进程来处理,父进程则可以继续处理客户端的请求。
Redis的数据恢复?
当Redis发生了故障,可以从RDB或者AOF中恢复数据。13;13;恢复的过程也很简单,把RDB或者AOF文件拷贝到Redis的数据目录下,如果使用AOF恢复,配置文件开启AOF,然后启动redis-server即可。13;13;Redis 启动时加载数据的流程:13;13;AOF持久化开启且存在AOF文件时,优先加载AOF文件。13;AOF关闭或者AOF文件不存在时,加载RDB文件。13;加载AOF/RDB文件成功后,Redis启动成功。13;AOF/RDB文件存在错误时,Redis启动失败并打印错误信息。
?为什么Redis 6.0 之后改多线程呢?
Redis6.0之前,Redis在处理客户端的请求时,包括读socket、解析、执行、写socket等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。
Redis6.0的多线程是用多线程来处理数据的读写和协议解析,但是Redis执行命令还是单线程的。13;并发处理socket连接,多线程处理网络IO,单线程串行执行命令
因为Redis的性能瓶颈都在网络IO不在CPU上,使得多线程提升IO读写效率,从而整体提升Redis性能
布隆过滤器
布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。13;13;优点:13;相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数。另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势13;13;缺点:13;但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣13;13;流量攻击:故意访问不存在的数据,导致程序不断访问DB数据库的数据(缓存穿透-解决方式将空值加到缓存中,防止大量攻击到db)13;13;安全阻截:当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉(提前做ID数据判断是否是合格的)
?Redis的Hash 冲突怎么办
哈希冲突:通过不同的key,计算出一样的哈希值,导致落在同一个哈希桶中。
Redis为了解决哈希冲突,采用了链式哈希。链式哈希是指同一个哈希桶中,多个元素用一个链表来保存,它们之间依次用指针连接。
为了保持高效,Redis 会对哈希表做rehash操作,也就是增加哈希桶,减少冲突。为了rehash更高效,Redis还默认使用了两个全局哈希表,一个用于当前使用,称为主哈希表,一个用于扩容,称为备用哈希表。
聊聊Redis 事务机制
Redis提供了简单的事务,但它对事务ACID的支持并不完备。13;13;multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的:13;127.0.0.1:6379> multi 13;OK13;127.0.0.1:6379> sadd user:a:follow user:b 13;QUEUED 13;127.0.0.1:6379> sadd user:b:fans user:a 13;QUEUED13;127.0.0.1:6379> sismember user:a:follow user:b 13;(integer) 013;127.0.0.1:6379> exec 1) (integer) 113;2) (integer) 113;Redis事务的原理,是所有的指令在 exec 之前不执行,而是缓存在13;服务器的一个事务队列中,服务器一旦收到 exec 指令,才开执行整个事务队列,执行完毕后一次性返回所有指令的运行结果。13;因为Redis执行命令是单线程的,所以这组命令顺序执行,而且不会被其它线程打断13;
Redis事务的注意点有哪些
Redis 事务是不支持回滚的,不像 MySQL 的事务一样,要么都执行要么都不执行;13;13;Redis 服务端在执行事务的过程中,不会被其他客户端发送来的命令请求打断。直到事务命令全部执行完毕才会执行其他客户端的命令。
Redis 事务为什么不支持回滚?
Redis 的事务不支持回滚。13;13;如果执行的命令有语法错误,Redis 会执行失败,这些问题可以从程序层面捕获并解决。但是如果出现其他问题,则依然会继续执行余下的命令。13;13;这样做的原因是因为回滚需要增加很多工作,而不支持回滚则可以保持简单、快速的特性。
锁超时的解决方案
把执行耗时的方法从锁中剔除,减少锁中代码的执行时间,保证锁在超时之前,代码一定可以执行完成13;把锁的超时时间设置的长一点,正常情况下我们使用完锁后,会调用珊瑚粗的方法手动删除锁,因此可以把超时时间设置的长一点13;也可以用Redisson 看门狗机制 续期锁
使用过Redisson嘛?说说它的原理
redis集群中节点挂了或者添加,如何重新分配数据
redis集群模式的原理
为什么要做Redis分区?
分库分表Shardingjdbc
Mycat和ShardingJdbc区别
相同点:13;两者设计理念相同,主流程都是SQL解析-->SQL路由-->SQL改写-->结果归并
sharding-jdbc(推荐)13;13;优:基于jdbc驱动,不用额外的proxy,在本地应用层重写Jdbc原生的方法,实现数据库分片形式13;是基于 JDBC 接口的扩展,是以 jar 包的形式提供轻量级服务的,性能高13;13;缺 :代码有侵入性
Mycat13;13;优点:是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库13;客户端所有的jdbc请求都必须要先交给MyCat,再有MyCat转发到具体的真实服务器13;代码无侵入性13;13;缺点:是效率偏低,中间包装了一层
ES和Mysql的选择使用场景
ES13;优:13;1.非关系型数据库,全文检索引擎首选,适用于数据与数据之间关联相对独立且数据基本只增加不修改的情况;13;2.适用于查询所有表格的所有字段,可认为使用者只需要知道查询的关键字,但不需要知道自己需要查询的表格和字段;13;缺:13;1.多数据存储时,无法保证数据的原子性;13;2.数据修改效率低于MYSQL,且不支持联表查询;13;使用场景:13;ES用来做关键词的搜索13;ES不是数据库,它适合于海量数据、更新频率很低的数据(话单信息,预警结果)13;ES没有事务也不适合处理并行更改数据
Mysql 13;优点:13;1.关系型数据库,顾名思义,适用于结构化数据(数据与数据之间存在强关联)的存储和查询;13;2.适用于复杂的业务逻辑控制、频繁数据更改这样的场景使用;13;3.需要保证数据的原子性,可认为保证多个数据同时成功存储(不存在部分存储成功,部分数据存储失败的情况) 13;缺点:13;1.需要使用者清楚的知道自己所需要查找的数据在哪个表格,并且对内部的字段参数有所了解;13;2.全表全字段检索效率较低,性能消耗大;
分库分表执行流程原理
解析->路由->改写->执行->结果归并
改写:逻辑sql不能直接在真实表中执行,该步骤会将sql改写成可以再真实表中执行的sql
执行的SQL排序、翻页、函数计算问题
问题:13;分库后,数据分布再不同的节点上, 跨节点多库进行查询时,会出现limit分页、order by排序等问题13;13;而且当排序字段非分片字段时,更加复杂了,要在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序(也会带来更多的CPU/IO资源损耗)
解决方式:13;业务上要设计合理,利用好PartitionKey,查询的数据分布同个数据节点上,避免 跨节点多库进行查询时13;比如YJ结果在新建的时候,任务只可以是当前月份 ,不做跨月份预警。这样命中的结果都会再一张库表中。13;sharding-jdbc在结果合并层自动帮我们解决很多问题(流式归并和内存归并)
数据库全局主键重复问题
问题:13;常规表的id是使用自增id进行实现,分库分表后,由于表中数据同时存在不同数据库中,如果用自增id,则会出现冲突问题
解决方式:13;UUID13;自研发号器 redis incr13;雪花算法
跨节点数据库Join关联和多维度查询
join关联
冗余字段13;广播表(类似字典表)13;nosql汇总(mysql小表合并到es大表)
举例:重点是冗余信息,避免跨库13;订单需要用户的基本信息,但是分布在不同库上,就可以进行字段冗余,订单表冗余用户昵称、头像
多维度查询
冗余双写
举例:13;订单表 的partionKey是user_id,用户查看自己的订单列表方便13;但商家查看自己店铺的订单列表就麻烦,分布在不同数据节点13;方法:13;订单冗余存储在es上一份(Canal-server(模拟的从节点,主节点数据增删改都可以监听到,然后将数据推送kafka)-->kafka-->es)13;业务架构流程
分库分表后二次扩容问题
Range范围
时间范围:不用考虑扩容迁移13;区域范围:调整分片粒度,需要全量迁移
Hash取模
业务最多的是hash取模分片,因扩分库分表涉及到rehash过程13;13;分片数量建议可以成倍扩容策略,只需要【迁移部分数据】即可13;13;旧节点的数据,有一半要迁移至一个新增节点中
实施方案
方案一、利用数据库主从
新增两个数据库 A2、A3 作为从库,设置主从同步关系为:A0=>A2、A1=>A3,13;13;开启主从数据同步,早期数据手工同步过去13;发布新程序,某个时间点开始,利用MQ存储CUD操作13;13;关闭数据库实例的主从同步关系13;13;校验数据,消费原先MQ存储CUD操作,配置新分片规则和生效13;13;数据校验和修复:13;依赖gmt_modified字段,所以常规数据表都需要加这个字段13;由数据库自己维护值,根据业务场景,进行修复对应的数据13;13;校验步骤:13;开始迁移时间假如是2022-01-01 00:00:0013;查找 gmt_modified数据校验修复大于开始时间点,就是修改过的数据13;各个节点的冗余数据进行删除13;
缺点:13;同步的很多数据到最后都需要被删除13;一定要提前做,越晚做成本越高,因为扩容期间需要存储的数据更多13;基本都离不开代码侵入,加锁等操作13;13;优点:13;利用mysql自带的主从同步能力13;方案简单,代码量相对少
方案二、对外发布公告,停机迁移
严格一致性要求:比如证券、银行部分数据等13;13;优点:最方便、且安全13;13;缺点:13;会造成服务不可用,影响业务13;根据停机的时间段,数据校验人员有压力
分库分表操作带来的分布式事务问题
MQ+本地Task
Sharding-jdbc分库表策略/表类型
#分库策略 默认13;spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column = user_id13;spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression = ds$->{user_id % 2}
Sharding-Jdbc水平分库+水平分表
分库规则 根据 user_id 进行分库
spring.shardingsphere.datasource.names=ds0,ds1
sharding.tables.product_order.database-strategy.inline.sharding-column=user_id
sharding.tables.product_order.database-strategy.inline.algorithm-expression=ds$->{user_id % 2}
sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
分表规则 根据 product_order_id 订单号进行分表
# 指定product_order表的分片策略,分片策略包括【分片键和分片算法】13;spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=id13;spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{id % 2}
分库表后的查询删除操作
查询操作
有分片键(标准路由)13;userid是分片键,查询条件中含有userid的
无分片键(全库表路由)13;userid是分片键 但是查询条件中没有userid
Sharding-Jdbc广播表
指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致13;适用于数据量不大且需要与海量数据的表进行关联查询的场景13;例如:字典表、配置表
broadcast-tables=ad_config(表名)
Sharding-Jdbc-绑定表
指分片规则一致的主表和子表13;比如product_order表和product_order_item表,均按照order_id分片,则此两张表互为绑定表关系13;绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升
# 指定product_order_item表的数据分布情况13;spring.shardingsphere.sharding.tables.product_order_item.actual-data-nodes=ds$->{0..1}.product_order_item_$->{0..1}13;spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.sharding-column=product_order_id13;spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.algorithm-expression=product_order_item_$->{product_order_id % 2}13;13;#绑定表13;spring.shardingsphere.sharding.binding‐tables[0] = product_order,product_order_item
分片策略
行表达式分片策略 InlineShardingStrategy
只支持【单分片键】使用Groovy的表达式,提供对SQL语句中的 =和IN 的分片操作支持13;13;可以通过简单的配置使用,无需自定义分片算法,从而避免繁琐的Java代码开发13;13;prouduct_order_$->{user_id % 8}` 表示订单表根据user_id模8,而分成8张表,表名称为`prouduct_order_0`到`prouduct_order_7
StandardShardingStrategy 标准分片策略
分表:13; PreciseShardingAlgorithm 精准分片 是必选的,用于处理=和IN的分片13;实现PreciseShardingAlgorith接口,重写doSharding方法13;dataSourceNames 数据源集合13; 在分库时值为所有分片库的集合 databaseNames13; 分表时为对应分片库中所有分片表的集合 tablesNames13;13;shardingValue 分片属性,包括13; logicTableName 为逻辑表,13; columnName 分片健(字段),13; value 为从 SQL 中解析出的分片健的值13;使用配置:13;# 指定product_order表的数据分布情况,配置数据节点,在 Spring 环境中建议使用 $->{...}13;spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}13;13;#指定精准分片算法(水平分表)13;spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.sharding-column=id13;spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.precise-algorithm-class-name=net.wnn.strategy.CustomTablePreciseShardingAlgorithm13;
分库:13;13;#指定全部数据节点,水平分库分表13;spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}13;13;# 分库分片健13;spring.shardingsphere.sharding.tables.product_order.database-strategy.standard.sharding-column=user_id13;# 分库分片算法13;spring.shardingsphere.sharding.tables.product_order.database-strategy.standard.precise-algorithm-class-name=net.wnn.strategy.CustomDBPreciseShardingAlgorithm
RangeShardingAlgorithm 范围分片
RangeShardingAlgorithm 范围分片13;13;用于处理BETWEEN AND语法,没配置的话会报错 Cannot find range sharding strategy in sharding rule.13;主要是会根据 SQL中给出的分片健值范围值处理分库、分表逻辑
Set<String> result = new LinkedHashSet<>();13; // between 起始值13; Long lower = rangeShardingValue.getValueRange().lowerEndpoint();13; // between 结束值13; Long upper = rangeShardingValue.getValueRange().upperEndpoint();13;13; // 循环范围计算分库逻辑13; for (long i = lower; i <= upper; i++) {13; for (String databaseName : dataSourceNames) {13; if (databaseName.endsWith(i % dataSourceNames.size() + "")) {13; result.add(databaseName);13; }13; }13; }13; return result;13; }
#精准水平分表下,增加一个范围分片13;spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.range-algorithm-class-name=net.wnn.strategy.CustomRangeShardingAlgorithm
Hint分片算法
这种分片策略无需配置文件进行配置分片健,分片健值也不再从 SQL中解析,外部手动指定分片健或分片库,让 SQL在指定的分库、分表中执行13;通过Hint代码指定的方式而非SQL解析的方式分片的策略13;Hint策略会绕过SQL解析的,对于这些比较复杂的需要分片的查询,Hint分片策略性能可能会更好13;可以指定sql去某个库某个表进行执行
implements HintShardingAlgorithm<Long> 重写 doSharding13;
@param dataSourceNames 数据源集合13; 在分库时值为所有分片库的集合 databaseNames13; 分表时为对应分片库中所有分片表的集合 tablesNames
@param hintShardingValue 分片属性,包括logicTableName 为逻辑表,13; columnName 分片健(字段),hit策略此处为空 ""13; value 【之前】都是 从 SQL 中解析出的分片健的值,用于取模判断13; HintShardingAlgorithm不再从SQL 解析中获取值,而是直接通过13; hintManager.addTableShardingValue("product_order", 1)参数进行指定
配置# Hint分片算法13;spring.shardingsphere.sharding.tables.product_order.table-strategy.hint.algorithm-class-name=net.xdclass.strategy.CustomTableHintShardingAlgorithm13;spring.shardingsphere.sharding.tables.product_order.database-strategy.hint.algorithm-class-name=net.xdclass.strategy.CustomDBHintShardingAlgorithm13;
@Test13; public void testHint() {13; // 清除掉历史的规则13; HintManager.clear();13; //Hint分片策略必须要使用 HintManager工具类13; HintManager hintManager = HintManager.getInstance();13; // 设置库的分片健,value用于库分片取模,13; hintManager.addDatabaseShardingValue("product_order",3L);13;13; // 设置表的分片健,value用于表分片取模,13; //hintManager.addTableShardingValue("product_order", 7L);13; hintManager.addTableShardingValue("product_order", 8L);13;13; // 如果在读写分离数据库中,Hint 可以强制读主库(主从复制存在一定延时,但在业务场景中,可能更需要保证数据的实时性)13; //hintManager.setMasterRouteOnly();13;13; //对应的value只做查询,不做sql解析13; productOrderMapper.selectList(new QueryWrapper<ProductOrder>().eq("id", 66L));13; }
复合分片算法ComplexShardingStrategy 使用较少
自己实现分片策略的优缺点13;13;优点:可以根据分片策略代码里面自己拼装 真实的数据库、真实的表,灵活控制分片规则13;缺点:增加了编码,不规范的sql容易造成全库表扫描,部分sql语法支持不友好
nginx
nginx的负载均衡模式有哪些
13;负载均衡策略:13;轮询策略(默认): 每次将请求按顺序轮流发送至相应的服务器上13;权重负载均衡策略: 指每次会按照服务器配置的权重进行请求分发,权重高的服务器会受到更多的请求。我们可以将硬件配置高并发能力强的服务器权重设置的高一点,合理利用服务器资源13;最少连接数负载均衡策略: 指每次将请求分发到当前连接数最少的服务器上,也就是Nginx会将请求试图转发给相对空闲的服务器以实现负载均衡13;ip-hash负载均衡策略: 可以根据客户端的IP,将其固定的分配到相应的服务器上
Nginx实现原理
客户端通过访问域名地址发出HTTP请求,访问的域名会被DNS解析为Nginx的IP地址,然后将请求转发至nginx服务器13;Nginx接收到请求后悔通过URL地址和负载均衡的配置,匹配到配置的代理服务器,然后将请求转发给代理服务器13;代理服务器拿到请求之后将处理结果返回给Nginx,Nginx再将结果返回给客户端,这样就完成了一次正常的HTTP交互
代理的服务器宕机了,nginx会怎么处理
1.代理的服务器出现宕机情况,如果被nginx发现,那么nginx就会将其自动标识为不可用,并且在一段时间内禁止入站的请求访问到该服务器上13;2.发现服务宕机的过程就是健康检测的功能了,分为主动检测和被动检测(默认),13;被动检测:13;所谓的被动检测就是访问了发现不可用,将其标识为不可用,并且在一段时间内禁止入站的请求访问到该服务器上,而不是主动以一定频率去检查该服务是否可用13;健康检测的重要参数 max_fails和fail_timeout,当一定时间内,发生了一定次数的服务器不响应事件,那么nginx就会将该服务器标识为不可用的服务器13;max_fails 表示服务不可用的最大尝试次数13;fail_timeout 定义了健康检查的执行时间长13;主动检测:13;商用的nginx plus来配置主动检测13;开源的第三方模块nginx_upstream_check_module来实现主动健康检测
JVM
jvm运行时内存布局分为哪些区域?之间的关系是怎样的?
线程独享13;后面3类区域,生命周期与Thread相同,即线程创建时,相应的区域分配内存;线程销毁时,释放相应内存
程序计数器13;记录每个线程当前执行的指令
虚拟机栈13;记录每个栈帧中的局部变量、方法返回地址等
本地方法栈13;调用操作系统原生本地方法所需要的内存区域
线程共享13;堆和方法区都是在虚拟机启动时候创建,虚拟机退出时候释放
堆 内存区 也是GC垃圾回收的主战场,用于存放类的实例对象
方法区 主要存放类结构、类成员定义 static静态成员等
运行时常量池
哪些区域会产生OOM
除了程序计数器不会抛出StackOverflowError或OutOfMemoryError,其它五个区域当请求分配的内存不足时,均会抛出OOM,其中thread独立的虚拟机栈 以及本地方法栈还会抛出StackOverflowError
jvm把内存划分为栈与堆两大类,为何要这样设计?
跟程序执行逻辑相关的指令数据,这列数据通常不大,而且生命周期短
跟对象实例相关的数据,这类数据可能会很大,而且可以被多个线程长时间反复共用,比如字符串常量、缓存对象这类
将这两类特点不同的数据分开管理,体现软件设计上模块隔离的思想,也更便于内存管理
如何判断对象是垃圾
引用计数法13;应用在python中
在对象中添加一个引用计数器,对象被引用,计数器+1 引用失效 计数器-1,统计计数器为0的对象作为结果13;缺点:难以解决对象之间的相互循环引用
可达性分析13;主流jvm虚拟机中
通过GC Root作为起点13;通过起点向下遍历,走过的路径作为引用链13;仅判定引用链包含的对象作为可达对象
GC Root范围13;虚拟机栈中的引用对象13;方法区中类静态属性引用的对象13;方法区中常量引用的对象13;本地方法中JNI引用的对象13;被synchronize持有的对象
如果A引用B,B引用A(发生循环引用问题),这两个对象是否能被垃圾回收?
关键不是在于A、B之间是否有引用,而是A、B是否可以一直向上追溯到GC Root.如果与GC Root没有关联,则会被回收,否则 将继续存活
哪些内存区域需要GC?
无需GC
范围: 程序计数器13;虚拟机栈13;本地方法栈13;特点:随线程而生,随线程而灭13;原因:编译期间即可知道内存占用大小13;内存的分配和回收都具有确定性
需要GC
范围:虚拟机堆 静态区 常量池13;运行期间动态创建,内存的分配和回收具有不确定性
常用的GC算法及其优缺点
标记清除
优点:13;简单快速13;缺点:13;产生很多内存碎片
标记复制
优点:13;避免了很多内存碎片的问题13;缺点:13;内存浪费严重,相当于只能使用50%的内存
标记整理
将垃圾清理后,同时将剩下的存活对象向一端进行整理挪动,保证它们占用的空间连续13;优点:13;节约了内存,并避免了内存碎片问题13;缺点:13;整理过程会降低GC效率
分代收集算法
分代诞生原因:13;内存中的对象有些生命周期很短,比如一些局部变量、临时对象13;而另外一些则会存活很久,比如websocket长连接中的connection对象13;13;内存被分为三块,年轻代 老年代和永久代 ,其中年轻代被分为 eden 、s0、s1三个区13;jdk1.8后元空间取代了永久代
8种垃圾回收器
新生代
Serial、ParNew、Parellel Scavenge
Serial 单线程,标记 复制算法 ,单线程执行期间 STOP THE WORLD 。早期的机器,大多是单核的 比较实用
ParNew 是Serial的多线程版本,同样也会STOP THE WORLD,适用在多核的机器上
Parallel Scavenge 是ParNew的升级版本,主要区别在于提供了2个参数13;最大垃圾回收停顿时间和垃圾回收时间与总时间占比13;通过这2个参数,可以适当控制回收的节奏,更关注与吞吐,即总时间与垃圾回收时间的比例
老年代
Serial Old、Parallel Old、CMS
Serial Old 单线程的 依然会有STOP THE WORLD 13;因为老年代的对象通常比较多,占用的空间通常也会更大。如果采用复制算法,得留50%空间用于复制,不划算;而且因为对象多,从一个区域复制到另外一个区域,耗时也会较长 所以老年代的手机通常会采用 标记-整理法。
Parallel Old是Serial Old的多线程版本
CMS 这是重点!Concurrent Mark Sweep 并发多线程的 JDK7中广泛使用
CMS收集器工作的整个流程主要分为哪几个阶段、优缺点
初始标记
标记GC Root开始的下一级(仅仅是下一级)对象,这个过程是会STW,但是跟GC Root直接关联的下一级不会很多,因此这个过程其实很快
并发标记
根据上一步的结果,继续向下标识所有关联的对象,直到这条链的最尽头的。这个过程是多线程的,虽然耗时理论上会比较长,但是其它工作线程并不会阻塞,没有STW
再标记
为啥还要再标记?因为第二步并没有阻塞其它工作线程,其它线程在标识过程中,很有可能会产生新的垃圾,这步骤是STW的,但是不会很长 ,因为刚才已经收过一遍了,所以这次的垃圾用不了多长时间
并行清理
这里使用多线程,标记清理算法,把垃圾清理掉,其它工作线程仍然能继续执行,不会造成卡顿
标记清理是有碎片的,但是 也没办法,如果换成标记整理的话,把垃圾清理后,剩下的对象顺便排整理,会导致这些对象的内存地址发生变化。此时其他线程还在工作,如果引用对象的地址变了,是很麻烦的一件事情13;13;以往收集器中最让人头疼的长时间STW,通过上述设计变成二次短暂的STW,所以从整理效果上看,应用在GC期间卡顿的情况会大大改善。
CMS优点:13;支持并发收集,低停顿,因为CMS可以控制将耗时的两个STW操作保持与用户线程并发执行,并且能保证短时间执行完成,这样就达到了近似并发的目的13;CMS缺点:13;CMS收集器对CPU资源敏感,并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分CPU资源,导致在CPU资源不足的情况下应用会有明显的卡顿13;CMS清理后悔产生大量的内存碎片,当有不足以提供整块连续的空间发给新对象、晋升为老年代丢下时又会触发FullGC13;在执行并发清理步骤时候,用户线程也会同时产生一部分可回收对象,但是这部分可回收对象只能在下次执行清理时候才会被回收。如果清理过程预留用用户线程的内存不足,就会出现 concurrent mode failure 。会切换到serial old收集方式。13;13;使用场景:它关注的是垃圾回收最短的停顿时间,在老年代并不频繁GC的场景下,是比较适用的
G1
G1 jdk1.9默认13;初始标记:仅仅标记GCR能直接关联到的对象,修改TAMS指针的值,让下一个阶段用户线程并发运行时,能正确的在可用的Region中分配新对象。这个阶段需要停顿线程,但是 耗时 很短13;而且是借用Minor GC的时候同步完成的,所以G1收集器再这个阶段时间并没有额外的停顿13;并发标记:从GCR开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象扫描完成以后,并发时有引用变动的对象13;会产生漏标的问题,G1中会使用STAB算法来解决。13;最终标记:对用户线程做个短暂的暂停,用于处理并发标记阶段仍遗留下的最后那少量的STAB记录,多个线程同时标记。13;筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集13;然后把决定回收的那一个部分Region的存活对象复制到空的Region中,再清理掉整个旧的Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多个收集器线程并行完成的。13;13;使用场景:实时数据占用超过一半的堆空间,对象分配或者晋升的速度变化大
存在的问题
没有一款垃圾收集器是完美无缺的,只能分场景选择最适合的垃圾收集器,对于于 G1来说,主要存在以下问题:13;13;跨代引用,堆空间通常被划分为新生代和老年代。由于新生代的垃圾收集通常很频繁,如果老年代对象引用了新生代的对象,那么回收新生代的话,需要跟踪从老年代到新生代的所有引用,所以要避免每次 Young GC 时扫描整个老年代,减少开销。13;并发情况下的漏标问题,通过三色标记来分析。
ZGC
动态调整大小得Region13;G1中每个Region的大小都是固定的,创建和销毁Region,可以动态调整大小,内存使用更高效13;不分代,去掉了RSets13;G1中每个Region需要借助额外的RSets来记录是谁引用了我,占用了额外的内存空间,每次对象移动时候,RSets也需要更新,会产生开销
以Hotspot为样例,分析一下GC全过程,并指出其中GC算法的综合运用
一开始对象在eden区域,S0(from)和S1(to)区域几乎是空着的13;随着应用的运行,越来越多的对象被分配到eden区域13;当eden区域放不下的时候,就会发生minor GC (也被称为 young GC)13; 首先是要标识出不可达垃圾对象,然后将可达对象移动到S0区域,将不可达的清理掉。这轮过后,eden区域就是空的了13;随着时间的推移,eden如果又满了,再次触发minor GC,同样还是做标记,这时eden和s0区域可能都有垃圾对象了13; 这时,S1区域是空的,s0和eden区域存活的对象之间搬到s1区域。然后将eden和s0区域的垃圾清理掉,这一轮minor Gc后,eden和s0区域就变成空的了13;继续随着对象的不断分配,eden空可能又满了,这时S0是 空的,所以S0与S1的角色其实是会互换的,即存活的对象,会从eden和s1区向s0区移动13;然后再把eden和s1区域的垃圾清除,这一轮完成后,eden和s1区域变成空的。13;13;对于那些比较长寿的对象,一直在S0和S1之间,每次从一个区移动到另外一个区,年龄+1 ,再年轻代区达到一定的年龄阈值后,将晋升到老年代里面13;如果老年代最终也放满了,就会发生Full GC 。由于老年代的对象通常会比较多,标记-清理-整理的耗时通常也会比较长,会让应用出现卡顿的现场。这也就是为什么很多应用需要优化,尽量避免或者减少Full GC的原因13;如果说分配的新对象比较大,eden放不下,但是old区域可以放下时,会直接放到old区,就是没有晋升,年龄++ 就直接到了Old区
G1和GMS的优缺点
13;比较:13;G1可以控制垃圾回收的时间,建立可停顿的时间模型,选择一组合适的Regions作为回收目标,达到实时收集的目的13;在大对象的处理上,如果一个对象过大,再CMS中进入S1 S2区域的时候发现大于该分配的区域,对象会直接进入老年代。G1处理大对象的时候,会判断对象是否大于Region大小50%13;如果大于50%就会横跨多个Region进行存放
三色标记
针对CMS和G1存在的漏标问题,JVM通过三色标记算法来解决。13;13;在三色标记法之前的算法叫 Mark-And-Sweep(标记清除)。这个算法会设置一个标志位来记录对象是否被使用。最开始所有的标记位都是 0,如果发现对象是可达的就会置为 1,一步步下去就会呈现一个类似树状的结果。等标记的步骤完成后,会将未被标记的对象统一清理,再次把所有的标记位设置成 0 方便下次清理。13;13;这个算法最大的问题是 GC 执行期间需要把整个程序完全暂停,不能异步进行 GC 操作。因为在不同阶段标记清扫法的标志位 0 和 1 有不同的含义,那么新增的对象无论标记为什么都有可能意外删除这个对象。对实时性要求高的系统来说,这种需要长时间挂起的标记清扫法是不可接受的。所以就需要一个算法来解决 GC 运行时程序长时间挂起的问题,那就三色标记法。13;13;三色标记最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个 GC。
根据GC Roots可达性分析算法遍历对象的过程中,按照对象是否访问过这个条件标记成以下三种颜色:13;13;黑色:根对象,或者该对象与它的子对象都被扫描过。13;灰色:对本身被扫描,但是还没扫描完该对象的子对象。13;白色:未被扫描对象,如果扫描完所有对象之后,最终为白色的为不可达对象,既垃圾对象。
存在的问题13;需要注意的是,对象是否需要被回收主要是通过可达性分析来判断的,但是在GC的并发标记过程中,程序还是在跑的状态,因此对象之间的引用可能会发生改变,这样可能出现两种后果:13;13;多标,把原本死亡的对象错误标记为存活,导致的后果是在本次垃圾回收不会收集。13;13;漏标,把原本存活的对象错误标记为已死亡,这就是非常致命的后果了,程序肯定会因此发生错误
针对CMS的解决方案-增量更新13;对于存在的漏标问题,CMS主要是采用增量更新算法(Incremental Update)来解决。13;什么是增量更新?13;当一个白色对象被一个黑色对象引用,将黑色对象重新标记为灰色,让垃圾回收器重新扫描。13;13;有何缺点?13;上面重新扫描的情况是比较简单的,一旦A对象的引用很多,那必定会扫描这个黑色对象的所有引用,耗时增加。
针对G1的解决方案-SATB13;对于G1来讲,主要是通过SATB(snapshot-at-the-beginning)的方式来处理,简单来说就是在标记之前记录下一个快照(可理解为照片),然后再回收之前和前面的快照进行对比即可13;可以简单理解为,当一个灰色对象取消了对白色对象的引用,那么这个白色对象变为灰色下次继续被扫描。这也就回到了上面讲的G1运行过程中的第3步,最终标记13;13;有何缺点?13;如果说在快照对比的时候发现这个白色对象并没有黑色对象去引用它,但是对比之后仍然把它置为灰色,此时本应该是要被回收的,但实际还是没有被回收,在这次的GC存活下来,这就是所谓的浮动垃圾,但相比增量更新来说,只是浪费了一点空间,但是却节约了时间。13;13;为什么会存在漏标对象?13;分析了漏标问题的解决方案,我们可以得出如果产生漏标对象,必然:13;至少有一个黑色对象(也就是被标记的对象)指向了一个白色对象。13;删除了灰色对象到白色对象的直接或间接引用。
为什么G1用SATB(snapshot-at-the-beginning)?CMS用增量更新?
跨代引用13;堆空间通常被划分为新生代和老年代,所谓跨代引用,一般是指老年代对象引用了新生代的对象。如下图的X和Y引用:13;13;新生代的垃圾收集通常很频繁(朝生夕死),如果老年代对象引用了新生代的对象,那么在回收新生代(Young GC)的时候,需要跟踪从老年代到新生代的所有引用
记忆集
跨代引用主要存在于Young GC的过程中,除了常见的GC Roots之外,如果老年代有对象引用了的新生代对象,那么老年代的对象也属于GC Roots(如上图中的老年代对象B和C)对象,但是如果每次进行Young GC我们都需要扫描一次老年代的话,那我们进行垃圾回收的代价实在是太大了,因此收集器在新生代上建立一个全局的称为记忆集的数据结构来记录这种引用关系。13;13;Rset(Remember Set),简单来说就是一种抽象数据结构用来存老年代对象对新生代的引用(即引用X和Y)。
卡表
卡表(CardTable)在很多资料上被认为是对记忆集的实现(它定义了记忆集的记录精度、与堆内存的映射关系等)13;,由于在Young GC的过程中,需要扫描整个老年代,效率非常低,所以 JVM 设计了卡表,如果一个老年代的卡表中有对象指向新生代,13; 就将它设为 Dirty(标志位 1,反之设为0),下次扫描时,只需要扫描卡表上是 Dirty 的内存区域即可。13; 而卡表和记忆集的关系可以理解为一个HashMap
类加载过程
我们知道JVM是执行class文件的,而我们的Java程序是一个.java文件,实际上每个.java文件编译后(包括类或接口等)都对应一个 .class 文件。当Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。具体如下:13;13;加载,类的加载是指把类的.class 文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不可用。13;连接,当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。13;初始化,最后JVM 对类进行初始化,包括:如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;如果类中存在初始化语句,就依次执行这些初始化语句。13;类加载完成之后,就是使用了,用完之后自然就是卸载。13;加载 验证 准备 解析 初始化 使用 卸载
并发垃圾回收器存在的问题
漏标和跨代引用13;漏标-->三色标记方式,CMS用增量更新 G1快照SATB13;跨代引用用记忆集RSet 卡表CardTable
项目中遇到过cpu过高的情况,是怎么排查的
宿主机器问题 内存和CPU问题,通过简单的top命令,可以查看实时的CPU使用情况。也可以查看最近一段时间的CPU使用情况
top查看占比高得程序,然后top -p 4337 单独监控整个程序13;然后在该界面输入 H,则展示该进程下所有的线程信息 ,再找到具体哪个很大的线程13;执行 jstack 对当前的进程做 dump,输出所有的线程信息 jstack 进程的PID13;可以看到它会输出所有线程的堆栈信息,并且存在一些 16 进制的 nid13;13;一般情况下我们的项目在运行过程中是不会开启 GC 日志的,我们可以jstat –gc pid 来统计,达到类似的效果,如下:13;13;jstat -gc PID 毫秒数 刷新次数13;13;例如:jstat -gc 11103 5000 513;13;查看jmap –heap 4337 内存占用情况13;我们可以通过 jmap导出堆的快照(即 dump 文件),但是导出 dump 文件过大会对程序造成影响,因此我们可以先jmap -histo pid,它可以打印每个 class 的实例数目,内存占用,类全名信息13;//把JVM中的对象全部打印出来, 但是这样太多了,那么我们选择前 20 的对象展示出来13;jmap –histo 4337 | head -20
产线环境遇到的问题13;13;最大线程数设置为 50,而我们的任务数为 100,任务数多于线程数,那么任务会进入阻塞队列,由于任务数一直多于线程数,所以每 0.1s 就会有 50 个任务进入阻塞队列,没有执行,但同时这些对象又被线程池对象 Executor 引用,且Executor 是一个 GCroots,所以堆中有 60 万个对象(UserInfo),阻塞队列中有 60 万个任务(ScheduledFutureTask),并且这些对象还无法回收。13;总结13;在 JVM 出现性能问题的时候,表现上是 CPU100%,实际是内存一直占用导致。13;13;如果 CPU 的 100%,要从两个角度出发,一个有可能是业务线程疯狂运行,比如说很多死循环。还有一种可能性,就是 GC 线程在疯狂的回收,因为 JVM 中垃圾回收器主流也是多线程的,所以很容易导致 CPU 的 100%。13;在遇到内存泄漏的问题的时候,一般情况下我们要查看系统中哪些对象占用得比较多,在实际的业务代码中,通过分析找到对应的对象,分析对应的类,找到为什么这些对象不能回收的原因(可达性分析算法)。
Arthas
jad13;反编译指定已加载类的,可以查看代码是否提交更新或者在排错的时候查看别人写的代码。13;命令:jad 指定的类路径,可用tab补齐。jad com.bxc.arthasdemo.controller.FanbianyiController13;将类信息 写入指定路径 jad --source-only com.bxc.arthasdemo.controller.FanbianyiController > D:/FanbianyiController.java13;然后再通过 mc D:/FanbianyiController.java -d D:/ 进行反编译13;然后再通过redefine D:/com/bxc/arthasdemo/controller/FanbianyiController.class 重新加载进去13;13;13;使用 trace 命令可以跟踪统计方法耗时。13;命令:trace 类路径 方法名13;13;monitor13;方法执行监控。如每 5 秒统计一次某个类的某个方法的方法执行情况。13;命令:monitor -c 5 类路径 方法名13;13;watch13;观察方法的入参出参信息。如:返回值、抛出异常、入参。13;watch cn.javatv.demo.controller.TraceController getOrder '{params[0],returnObj}'13;13;dashboard13;用arthas连接到该应用,先使用dashboard查看下当前内存使用情况:13;使用arthas生成内存分区的火焰图:13; profiler start --event alloc
java基础
锁
AQS
AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。13;它是一个Java提高的底层同步工具类,比如CountDownLatch、ReentrantLock,Semaphore,13;ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的
简单来说:是用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态对象13;一个是 state(用于计数器,类似gc的回收计数器)13;一个是线程标记(当前线程是谁加锁的),13;一个是阻塞队列(用于存放其他未拿到锁的线程
acquire(int arg)好比加锁lock操作13;tryAcquire()尝试直接去获取资源,如果成功则直接返回,AQS里面未实现但没有定义成abstract,因为独占模式下只用实现tryAcquire-tryRelease,而共享模式下只用实现tryAcquireSharedtryReleaseShared,类似设计模式里面的适配器模式13;addWaiter() 根据不同模式将线程加入等待队列的尾部,有Node.EXCLUSIVE互斥模式、13;Node.SHARED共享模式;如果队列不为空,则以通过compareAndSetTail方法以CAS将当前线程节点加入13;到等待队列的末尾。否则通过enq(node)方法初始化一个等待队列13;acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回,如果在等待过程中被中断,则返回true,否则返回false13;release(int arg)好比解锁unlock13;独占模式下线程释放指定量的资源,里面是根据tryRelease()的返回值来判断该线程是否已经完成释13;放掉资源了;在自义定同步器在实现时,如果已经彻底释放资源
说说Synchronize关键字的用法
synchronize关键之可以用于修饰普通方法、静态方法以及普通代码块,分别锁住的是:13;修饰普通方法:锁住对象的实例(new 出多少个对象,各个对象之间不会相互竞争)13;修饰静态方法:锁住整个类(new 出多少个对象,各个对象之间会相互竞争)13;修饰代码块: 锁住一个对象 synchronized (object) 即synchronized后面括号里的对象
synchroized和ReentrantLock的实现原理和区别
synchroized独占式悲观锁,通过JVM隐式实现,只允许同一个时刻只有一个线程操作资源13;只能是非公平锁13;可以用于修饰方法,修饰代码块等13;无需手动释放锁13;不知道是否成功获得了锁13;不支持中断锁
ReentranTLock 是Lock的默认实现方式之一,是基于AQS实现的,默认通过非公平锁13;可设置为公平锁13;只能修饰代码块13;需要手动枷锁和释放锁,如果忘记释放锁,则会造成资源被永久占用13;可以知道是否成功获得了锁13;支持中断锁
Synchronized与Lock的区别
synchronized的锁可重入、不可中断、非公平,每次使用会自动释放锁13;Lock锁可重入、可中断、可公平,每次使用需要自己加锁以及释放锁,一般情况下需放在finally里进行锁的释放,否则,可能因为未能正确释放锁而导致程序出问题。13;Lock可以实现有限时间内的获取锁的等待,在指定的时间内如果获取不到锁,会返回false
知道ReentrantReadWriteLock吗?和ReentrantLock有啥不同?
ReentrantReadWriteLock13;1、读写锁接口ReadWriteLock接口的一个具体实现,实现了读写锁的分离,13;2、支持公平和非公平,底层也是基于AQS实现13;3、允许从写锁降级为读锁13;流程:先获取写锁,然后获取读锁,最后释放写锁;但不能从读锁升级到写锁13;4、重入:读锁后还可以获取读锁;获取了写锁之后既可以再次获取写锁又可以获取读锁13;核心:读锁是共享的,写锁是独占的。 读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,13;主要是提升了读写的性能
ReentrantLock是独占锁且可重入的,相比synchronized而言功能更加丰富也更适合复杂的并发场景,但13;是也有弊端,假如有两个线程A/B访问数据,加锁是为了防止线程A在写数据, 线程B在读数据造成的数据不13;一致; 但线程A在读数据,线程C也在读数据,读数据是不会改变数据没有必要加锁,但是还是加锁了,降低13;了程序的性能,所以就有了ReadWriteLock读写锁接口
锁的大致分类
13;悲观锁:它认为线程很容易把数据修改掉,因此在整个数据被修改的过程中都会采取锁定的状态,直到线程使用完,其它才可以使用,举例:synchronized13;乐观锁:和悲观锁的概念相反,乐观锁认为一般情况下数据在修改时不会出现冲突,所以在数据访问之前不会加锁,只是在数据提交更改时候,才会对数据进行检测,举例 ReadWriteLock 读写锁13;可重入锁:也叫递归锁,指的是同一个线程在外面的函数获取了锁之后,那么内层的函数也可以继续获得此锁 举例 ReentrantLock 和 synchronized 都是可重入锁13;独占锁和共享锁:只能被单线程持有的锁叫独占锁,可以被多线程持有的锁叫共享锁 ,独占锁指的是任何时候最多只能有一个线程持有该锁,比如ReentrantLock就是独占锁 ,而ReadWriteLock读写锁允许同一个时间段有多个线程进行读操作,属于共享锁
线程
13;线程的状态
NEW 新建,线程被创建出来,但是尚未启动时候的线程状态13;Runnable 就绪,表示可以运行的线程状态,可能正在运行或者是在排队等待操作系统给它分配CPU资源13;Blocked 阻塞,阻塞等待锁的线程状态,表示出于阻塞状态的线程正在等待监视器锁,比如等待执行的synchronized代码块或者synchroized标记的方法13;Waiting 等待,一个处于等待状态的线程正在等待另外一个线程执行某个特定的动作,比如调用了wait join方法13;Terminated 终止,线程执行完成
Blocked和Waiting区别
13;1.二者状态形成的调用方法不同,其次Block可以理解为当前线程还处于活跃状态,只是在阻塞等待其他线程使用完某个锁资源13;2.而Waiting则是因为自身调用了wait或者join进入了等待状态,只能等待其他线程执行某个特定的动作才能被继续唤醒
Start 和Run区别
1.从执行效果来说,start方法可以开启多线程,让线程从new状态转换到Runnable状态,而run方法只是一个普通方法13;2.调用次数不同,start方法不能被调用多次,否则会抛出IllegalStateException,而run方法可以进行多次调用,因为它只是一个普通方法
sleep与wait的区别
wait只能在同步块(synchronize)环境中被调用,而sleep不需要。13;wait方法在进入wait状态的时候会释放对象的锁,但是sleep方法不会。13;进入wait状态的线程能够被notify和notifyAll线程唤醒,但是进入sleeping状态的线程不能被notify方法唤醒,到期之后自动继续执行
13;线程的优先级
1.线程的优先级可以理解为线程抢占CPU时间片的概率,优先级越高的线程优先执行的概率越大,但是并不能保证优先级高的线程一定先执行13;2.可以通过Thread.setPriority()来设置优先级 最小是1 最大是10 默认是5
13;线程常用方法
1.join 在一个线程中调用other.join(),这时候当前线程会让出执行权给other线程,直到Other线程执行完了或者过了超时时间后再继续执行当前线程,join方法的底层还是通过wait来实现的13;2.yield方法表示给线程调度器一个当前线程愿意出让CPU使用权的按时,但是线程调度器可能会忽略这个暗示,代码中加入yield测试会发现每次执行结果都不一样,这是因为yield执行不稳定,调度器不一定UI采纳13;yield出让CPU使用权的建议
线程池
好处:重用存在的线程,减少对象创建销毁的开销,有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,且可以定时定期执行、单线程、并发数控制,配置任务过多任务后的拒绝策略等功能
常用线程池
newFixedThreadPool13;一个定长线程池,可控制线程最大并发数13;newCachedThreadPool13;一个可缓存线程池13;newSingleThreadExecutor13;一个单线程化的线程池,用唯一的工作线程来执行任务13;newScheduledThreadPool13;一个定长线程池,支持定时/周期性任务执行
execute()和submit()区别
execute()不能接收返回值13;submit()方法可以接收线程池执行的返回值,返回的是Future<>,可通过future.get()
线程池不允许使用 Executors 去创建,要通过 ThreadPoolExecutor的方式原因
Executors创建的线程池底层也是调用 ThreadPoolExecutor,只不过使用不同的参数、队列、拒绝策略等,如果使用不当,会造成资源耗尽问题;13;直接使用ThreadPoolExecutor让使用者更加清楚线程池允许规则,常见参数的使用,避免风险
newFixedThreadPool和newSingleThreadExecutor:13;队列使用LinkedBlockingQueue,队列长度为 Integer.MAX_VALUE,可能造成堆积,导致OOM13;newScheduledThreadPool和newCachedThreadPool:13;线程池里面允许最大的线程数是Integer.MAX_VALUE,可能会创建过多线程,导致OOM
线程池拒绝策略
Abortpolicy 默认的拒绝策略,终止策略,线程池会抛出异常并终止进行。13;CallerRunsPolicy把任务交给当前线程来执行13;DiscardPolicy 忽略此任务13;DiscardOldestPolicy 忽略最早的任务
自定义拒绝策略,只需要新建一个RejectedExceutionHandler对象,然后重写它的rejectedExecution方法即可
?spring
SpringBoot有哪些优点?它和Spring有什么区别?
SpringBoot本质上是Spring框架的延伸和扩展,是基于Spring的,使用它可以不再依赖Spring应用程序中的XML配置,可以更快更高效的开发Spring
SpringBoot只需要添加一个Starter模块支持后,在项目构建期,就会把所有其他依赖项自动添加到项目中13;比如测试库,如果是Spring项目,我们通常要添加Spring-Test,JUint,Mockito库,而如果是SpringBoot项目的话,只需要添加spring-boot-starter-test即可,它会自动帮我们把其他依赖项添加到项目中
SpringBoot内嵌默认的容器 Tomcat,可以通过修改pom.xml来移除内嵌Tomcat更换为其它容器
SpringBoot启动流程
1.创建并启动计时监控类13;2.声明应用上下文对象和异常报告集合13;3.设置系统属性headless的值13;4.创建所有spring运行监听器,并发布启动事件13;5.初始化默认应用的参数类13;6.准备环境 ,创建配置并且绑定环境,通过property source和profiles等配置文件13;7.创建Banner的打印类13;8.创建应用上下文,根据不同的应用类型来创建不同的ApplicationContext上下文对象13;9.实例化异常报告器13;10.准备应用上下文13;11.刷新应用上下文13;12.应用上下文刷新之后的事件处理13;13.停止计时监控类13;14.输出日志信息13;15.发布应用上下文启动完成事件13;16.执行所有Runner运行器13;17.发布应用上下文就绪事件13;18.返回应用上下文对象
HashMap
什么是加载因子?为什么是0.75?
假如加载因子是0.5,hashmap的初始化容量是16,那么,当hashmap中有16*0.5=8个元素的时候,就hashmap就会扩容13;13;之所以是0.75而不是0.5 这其实是出于容量和性能之间平衡的结果。当加载因子变大的时候,扩容门槛就提高了,此时发生hash冲突的几率就会上升。需要更复杂的数据结构来存储元素,这样对元素的操作时间就会增加,运行效率降低13;而当加载因子比较小的时,扩容的门槛会比较低,因此会占用更多的空间,此时元素存储就比较稀疏,发生Hash冲突的可能性就比较小,因此可操作性能会比较高13;
get操作源码步骤
对Key进行hash操作13;先判断table是否非空,然后判断第一个元素是否是要查询的元素,是就返回,不是就继续下一个节点非空判断13;判断第一个节点是树结构,是则使用getTreeNode直接获取相应的数据13;非树结构的话,循环节点判断do while ,判断到hash相等且key相同 则返回此节点
put方法源码步骤
对Key进行hash操作13;判断hash表是否为空,为空则进行扩容创建13;不为空,进行hash分析命中的那个桶中是否有值,没有则直接保存插入数据13;有值则判断key值是否一样,一样则覆盖。13;不一样则判断是否为红黑树节点,是树节点则直接插入13;不是树节点则遍历链表插入数据,插入的时候判断表长度是否大于8,size大于64 ,是则转为红黑树13;最后判断是否要扩容,否就结束插入流程 ,是就进行扩容
0 条评论
下一页