2021-9-29
1、NIO的实现方式有哪些?以及各自区别?
Java支持三种网络编程模型的IO模式:BIO(同步阻塞IO)、NIO(同步非阻塞IO)、AIO(异步非阻塞IO)
BIO:服务器实现模式一个连接一个线程,客户端有连接请求时服务器就需要启动一个线程进行处理。
NIO:实现模式为一个线程处理多个请求,即客户端发送请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求就进行处理。
2、
2021-9-22
1、系统设计:企业支付业务场景选型,包含B2B、B2C。
B2B解决方案:1、利用类似商企付这种大厂金融产品,公司A通过平台跳转支付页面付款给公司B,付款成功后通知到平台。2、使用银行的母子账号方案。公司A打款给平台母账号,平台将钱划拨给公司B的子账号。
2、做过的涉及高并发的项目?
2021-9-15
1. 零拷贝原理?
实现零拷贝技术的方式通常有两种:mmap+write、sendfile
原来 8 张图,就可以搞懂「零拷贝」了_小林coding-CSDN博客
2. MySQL的索引有哪些?
MySQL-索引原理_dukay2021的博客-CSDN博客
3. 设计模式用过哪些?
2021-9-8
1. 报表类应用如何设计缓存?
业务痛点是多维度,查询较慢,考虑使用ES,起一个搜索引擎服务。
ES 是支持更新的,并且ES的索引很适合区分维度建立,如果分的好,整个索引数据可以直接删除重建。
2. 延时双删的加锁阶段是在B进行查询到更新缓存这个过程吗?
在数据库更新这步加锁,当处理完数据库的更新,才释放锁。B线程开始读缓存,也会被阻塞,一直等到数据库更新完。之后B线程,再拿到锁,去读缓存读不到,再去读数据库,已经是对的数据了。
如果b线程在更新数据库之前,进行了查询缓存、查询数据库,并且更新了缓存,将缓存刷成了错误的数据。此时就需要延时删除缓存,再一次清除缓存,使后面的线程查询时,更新到最新的数据。
3. 一个服务挂了配多长时间重试?
两种场景:1、自己的服务宕机;2、依赖的外部服务挂了。
针对第一种,无法重试,属于线上服务故障,需要及时引发报警,进行及时的问题排查以及重启;针对第二种,我的服务,依赖外部服务的接口,此时会考虑两个问题:a.是否需要重试b.重试的话,按照什么维度:是频率,1s、2s、3s、5s;还是次数,1秒一次,共3次,还是5秒1次,共2次;
4. 分表后怎么分页查?
方案一:通过中间件;通过不同的分库规则,实现多个分片,然后每个分片分别进行分页查询,再在内存中进行汇总,最后再进行一次分页查询,就可以得出最终的结果。分页的总条数,是另一次查询,不在上面的步骤中计算。
方案二:
引入搜索引擎ES。大数据量前提下(至少达到需要分库分表了),实时性要求高的话,选择es。实时性要求相对不高,可考虑大数据相关的数据库。
2021-8-25
1.MySQL索引设计原则?
为唯一性的字段设置唯一索引,可以更快确定某条记录,尽量使用主键索引;
为经常查询、区分度高的字段设置索引;
对于经常排序、分组、联合查询的字段建立索引;
如果索引字段的值很长,最好使用值的前缀来索引;
能合并的组合索引就不要单独为字段建立索引,遵循最左前缀原则;
对于经常增删改的字段不使用索引。
2.MySQL垂直拆分和水平拆分适用于那些场景?达到什么数据量可以考虑水平拆分?拆分后采用数据库中间件时SQL语法不兼容如何处理?
垂直拆分:由于表的字段过多导致单个表大,将表字段拆分到不同的表中;或者表数量过多导致的单个库过大,将不同的表拆分到不同的库中。一般指垂直分库。
水平拆分:由于单表记录过多导致单个(表)库大,将表拆分到多个(表)库中。一般是根据某个字段将数据映射到多个库中。
如果列数超过某一数量(30)考虑垂直拆分;考虑SQL优化,当有性能瓶颈再考虑水平拆分。
中间件主要有Sharding-JDBC和Mycat。
Sharding-JDBC:ShardingSphere的产品之一。以jar包形式使用,在java的JDBC层提供额外服务。支持不同的数据库类型。不支持跨语言(java)。
Mycat:强大的数据库中间件,可以看作是一个数据库代理,开发无感知。增删节点无须重启。支持跨语言(java、php)。不支持跨数据库jdbc。
3. 私有化的paas平台licence加密问题(网络隔离,离线licence),选型公私钥加密,是licence方持有私钥好还是商户端持有好?
4.自定义注解实现的逻辑很多,如何在使用注解过程中,将某些功能做成开关形式?
启动时读取:拦截器中读取配置文件开关参数。
启动后实时读取:定时任务平台 elasticjob
2021-8-18
1. Redo Log工作原理?
Redo Log是InnoDB引擎持独有的,实现的是事务的久性。以恢复操作为目的,在数据库发生意外时重现操作。包括了内存中的Log Buffer和磁盘上的Redo Log File。
随着事务操作的执行生成Redo Log,事务提交时会将产生的Redo Log写入Log Buffer,并更新内存数据。Log Buffer日志缓冲区满时会自动刷新到磁盘。通过参数innodb_flush_log_at_trx_commit定义刷新磁盘行为。等事务操作的脏页写入磁盘后,Redo Log使命完成,占用空间可以被覆盖。
Redo Log File采用循环方式写入,多个文件组成一个闭环,写满时回溯第一个文件。如果Write Pos追上checkPoint表示写满,这时候不能再执行新的更新操作,得停下来擦掉一些记录,使checkPoint推进。Redo Log固定大小,通过innodb_log_file_size查看。
2、MySQL最大列数,组合索引列数限制?索引多了会有什么问题?
官方约定,InnoDB表的最大列数为1017。多列索引最多允许16列。
因为数据的更新操作需要额外进行索引树的维护,所以索引过多可能会使数据库性能降低,同时索引文件也会占用更多的磁盘空间。
3、MySQL行数据大小限制?
65535byte。text或者blob等大字段 innoDB只会存放前768byte在数据页中,剩余的数据会存储在溢出段中,然后通过一个偏移量指针将二者关联,这就是行溢出机制。
大字段能否做索引?
3、MySQL有2000w数据,Redis只能存20w数据,如何保证Redis中的是热点数据?
方案一:设置Redis最大占用内存,Redis会根据自身的淘汰策略,留下热数据。设置Redis淘汰策略为volatile-lru和allkeys-lru。
方案二:设置延长时间。命中加过期时间。
4、Redis设计分布式锁?和zookeeper区别?
Redis:
使用Redis的SETNX命令加锁,解锁使用lua脚本;
使用现成集成框架Redisson。
zk:
建立数据节点/exclusive_lock,代表排他锁;
需要获取锁到节点下注册⼀个临时子节点/exclusive_lock/lock,其他需要获取锁的客户端实时监听lock节点的变更状态。
区别:
Redis实现难度简单,zk相对复杂;
Redis应用广泛,zk应用广度一般;
5、如何设计和实现TPS和QPS统计?
通过服务层记录业务日志分析统计;
Nginx或者Tomcat请求日志分析统计;
Redis的高级数据结构HyperLogLog, 解决海量数据统计,提供不精确的去重计数方案。
2021-8-11
1. 接口设计版本化问题
方案一:url携带版本号,比如/v1/user
方案二:请求参数携带版本,比如/user?version=1.0
2. MySQL数据迁移方案
方案一:采用增量日志解析、增量数据同步工具Canel。
a、在主库中开启Binlog功能,
- 查看状态:show variables like 'log_bin';
- 开启:set global log_bin=mysqllogbin;
- 配置文件my.cnf
- 查看binlog日志文件列表:show binary logs;
b、部署Canel服务端,设置MySQL服务器,指定Binlog日志信息;
c、Canel客户端监听Binlog来同步数据。实际项目可以配合MQ模式,把数据发送到消息队列中,通过消费者进行处理。
方案二:Logstash
利用logstash-input-jdbc插件,配置数据库信息和需要同步的sql语句,运行配置文件完成同步数据。
二者比较:
Logstash通过读取数据库数据完成同步,对数据库又压力,延时性较高,对数据处理有局限;
Canal基于Binlog,对数据库无压力,准实时,延时性低,可接入业务服务清洗数据,场景灵活。
3. MySQL日志用于数据恢复,将入误删数据,如何找回恢复?
Binlog记录了数据库表结构变更以及表数据修改的二进制日志,以事件形式记录。
日志应用场景:主从复制和数据恢复。
文件记录模式:statement、row、mixed
日志内容Log event:表示修改操作事件的数据结构
使用 mysqlbinlog命令恢复数据:
- 按事件位点恢复
/usr/bin/mysqlbinlog --start-position=154 --stop-position=500 mysqlbinlog.000002 | mysql -uroot -p1234
- 按指定时间恢复
/usr/bin/mysqlbinlog --start-datetime="2021-08-13 18:00:00" --stop-datetime="2021-08-13 21:00:00" mysqlbinlog.000002 | mysql -uroot -p1234
* 查看位点命令:show binlog events in 'mysqlbinlog.000002'
* 使用mysqlbinlog命令:mysqlbinlog "文件名" > "test.sql"
4. SQL调优
索引失效情况:
- like '%a', %加在参数前面;
- or条件中有未建立索引的列;
- where子句使用的不是组合索引中的第一部分(最左);
- 字符串字段不加'',条件的类型不一致;
- 使用!=,例外:如果是主键则会走索引
- 使用>,例外:如果是主键或者索引是整数类型则会走索引;
- order by ,例外:如果是主键或者索引是整数类型则会走索引;
- 使用函数,做条件判断;
- 估计使用全表扫描比使用索引快;
SQL书写建议:
- 尽量不使用select *,可能导致索引回表,磁盘排序等问题,使用覆盖索引;
- where子句使用组合索引列需满足最左前缀原则;
- 对于可能null值的列设置列的默认值;
- 提高索引过滤性,避免数据种类较少的索引。
explain查看执行计划
参数:
select_type: 表示语句的查询类型。
- SIMPLE 查询语句不包含子查询或union
- PRIMARY 表示此查询时最外层查询
- UNION 表示此查询时union的第二个或后续的查询
- SUBQUERY:SELECT子查询语句
type: 表示存储引擎查询数据时采用的方式。可以用来判断是全表扫描还是基于索引的部分扫描。以下属性值效率依次增强。
- ALL: 表示全表扫描 。
- index:表示基于索引的全表扫描,先扫描索引再扫描全表数据。
- range:表示使用索引范围查询。使用>、 >=、 <=、 in等。
- ref:表示使用非唯一索引进行单值查询。
- eq_ref:表示前面表的每一个记录都只能匹配后面表的一行结果,一般出现在多表join查询。
- const:表示使用主键或唯一索引做等值查询,常量查询。
- NULL:表示不用访问表,速度最快。
key:表示查询时真正使用到的索引名称。
rows:表示SQL查询需要扫描多少行记录,越少效率越高。
key_len:表示索引使用字节数,可以用来判断是否全部使用组合索引。
Extra:表示额外信息
- Using where :表示查询需要通过索引回表;
- Using index:表示查询通过索引就能满足;
- Using filesort:表示查询出来的结果需要额外排序,涉及内存或者磁盘排序,建议优化;
- Using temprorary:表示使用到临时表,一般出现在去重,分组操作。
数据量大的数据表如何优化?
分库分表
更换数据源,例如大数据数据源,ES等。
预处理数据,定时加载并处理数据,使用缓存。
2021-8-4
1. ThreadLocal内存泄漏
问题分析:并发编程笔记—ThreadLocal_dukay2021的博客-CSDN博客
2. 锁的使用
并发编程- 锁总结 待补充_dukay2021的博客-CSDN博客
3. 缓存一致性问题及其解决方案
先更新数据库再更新缓存或者先更新缓存再更新数据库,当其他线程读取数据发生在更新两个操作之间,则会发生缓存不一致。
方案一:延时双删,为了避免更新数据库的时候,其他线程从缓存中读取不到数据,就在更新完数据库之后,再sleep一段时间,然后再次删除缓存。sleep时间大于读写缓存的时间。
- 线程1删除缓存,然后去更新数据库
- 线程2来读缓存,发现缓存已经被删除,所以直接从数据库中读取,这时候由于线程1还没有更新完成,所以读到的是旧值,然后把旧值写入缓存
- 线程1,根据估算的时间sleep,由于sleep的时间大于线程2读数据+写缓存的时间,所以缓存被再次删除
- 如果还有其他线程来读取缓存的话,就会再次从数据库中读取到最新值
方案二:分布式锁
利用分布式锁把缓存操作和数据库操作封装为逻辑上的一个操作可以保证数据的一致性,具体流程为:每个想要操作缓存和数据库的线程都必须先申请分布式锁,如果成功获得锁,则进行数据库和缓存操作,操作完毕释放锁。如果没有获得锁,根据不同业务可以选择阻塞等待或者轮训,或者直接返回的策略。
利用分布式锁是解决分布式事务的一种方案,但是在一定程度上会降低系统的性能,而且分布式锁的设计要考虑到down机和死锁的意外情况。
方案三:
还有一个是基于binlog+mq的方案,使用binlog中间件(比如canal)监控mysql的DML(update,insert,delete),如果有变更会通过发送到MQ,MQ消费者更新REDIS。
2021-07-28
1. 什么是回表?
1) InnoDB索引:
-
聚集索引(clustered index)
-
普通索引(secondary index)
2) 聚集索引和普通索引区别?
- InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引:
如果表定义了PK,则PK就是聚集索引;
如果表没有定义PK,则第一个not NULL unique列是聚集索引;
否则,InnoDB会创建一个隐藏的row-id作为聚集索引;
- InnoDB普通索引的叶子节点存储主键值。注意,不是存储行记录头指针,MyISAM的索引叶子节点存储记录指针。那普通索引的查询过程是怎么样的呢?
先通过普通索引定位到主键值;再通过聚集索引定位到行记录;这就是所谓的回表。
3)哪些情况会触发回表?
例1:表user(id,a, b, c),id为主键,其他业务字段均有索引
select id from user where id = 1; //不回表 直接查询的聚集索引
select * from user where id=1; //不回表
select * from user where a = 3; //回表
select id from user where a =3; //不回表
例2:count(*),count(id),count(1),count(a)回表情况?
count(a),当a字段上有索引时能够命中a索引,索引叶子节点存储了主键id,通过a的索引树即可获取id和a,无需回表,符合索引覆盖。
count(*),count(id),count(1)在只有主键索引的情况下会走主键索引,如果有二级索引,mysql优化器就会选择使用二级索引,这是在mysql5.7.18版本后有的优化,因为二级索引只存主键值,而没存真实数据,所以二级索引树比主键索引树更小,其次直接扫描二级索引树获取数据量也避免了回表操作,所以mysql优化器会选择成本更小的二级索引。
2. TPS或者QPS达到5万10万或更高有哪些处理方法?
TPS:Transactions Per Second
也就是事务数/秒。
QPS:Queries Per Second
是每秒查询率。 QPS = 并发量 / 平均响应时间
常见的解决方法:
系统拆分:根据服务化拆分系统,数据库分库分表等,系统拆分实现了流量分担。
流量削峰:从本质上是更多的延缓用户请求,采用消息队列等中间件把同步的调用转换为异步间接推送。
服务限流:目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待。
服务降级:对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心服务正常运作或高效运作。
3. AQS如何保证可见性?
AbstractQueuedSynchronizer被称为抽象队列同步器,是Java编程锁的底层实现。实现原理:使用volatile修饰的int值标识锁状态;利用LockSupport工具类的park和unpark方法完成阻塞和唤醒线程的操作;获取不到锁的线程会进入AQS的队列等待。
锁的lock和unLock方法之间的操作,可见性的保证取决于happens-before规则。锁在释放锁的最后写volatile变量state,在获取锁线程首先读这个volatile变量。根据volatile的happens-before规则,释放锁的线程在写volatile变量之前可见的共享变量,在获取锁的线程读取同一个volatile变量后将立即变得对获取锁的线程可见。从而保证了代码段中变量(变量主要是指共享变量,存在竞争问题的变量)的可见性。
4. CopyOnWriteArrayList、ConcurrentHashMap、ThreadLocal在实际开发中都适用于那些场景,会产生什么问题吗
CopyOnWriteArrayList:copyOnWrite 的意思是说,当容器需要被修改的时候,不直接修改当前容器,而是先将当前容器进行 Copy,复制出一个新的容器,然后修改新的容器,完成修改之后,再将原容器的引用指向新的容器。这样就完成了整个修改过程。
1) 写数组的拷贝,支持高效率并发且是线程安全的,读操作无锁的ArrayList。所有可变操作都是通过对底层数组进行一次新的复制来实现。
2)适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变Array引用。CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差。
3)适合读多写少的场景,不过这类慎用 ,因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。
ConcurrentHashMap:线程安全的HashMap,Java 7 采用 Segment 分段锁来保证安全,而 Segment 是继承自 ReentrantLock。 Java 8 中放弃了 Segment 的设计,采用 Node + CAS + synchronized 保证线程安全。
ThreadLocal: 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全。主要做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的。
场景:用户权限信息、多数据源场景下数据源连接信息