新鲜出炉java后端高频面经总结-持续更新中(万字长文,助君青云)(下)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 新鲜出炉java后端高频面经总结-持续更新中(万字长文,助君青云)(下)

ConcurconrentHashMap底层原理


1.7版本ConcurconrentHashMap底层采用的是分段锁,具体来说是一个 Segment 数组(默认长度为16),每个 Segment 又包含了一个 HashEntry 数组,所以可以看做一个 HashMap, Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 Segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。


1.8版本取而代之的是 Node 数组 + CAS + synchronized + volatile 的新设计方式,只调用synchronized锁住首节点的方式,使得锁更加灵活,同时锁粒度也更加小


不仅数据结构变得更简单了(与JDK 1.8 的HashMap类似),锁的粒度也更小了,锁的单位从 Segment 变成了 Node 数组中的桶(科普:桶就是指数组中某个下标位置上的数据集合,这里可能是链表,也可能是红黑树)。说到红黑树,必须提一下,在JDK 1.8 的 HashMap 和ConcurrentHashMap 中,如果某个数组位置上的链表长度过长(大于等于8),就会转化为红黑树以提高查询效率


get 操作过程

可以发现源码中完全没有加锁的操作,因为使用 volatile 关键字已经足以保证线程在读取数据时不会读取到脏数据,所以没有加锁的必要。


首先计算hash值,定位到该table索引位置,如果是首节点符合就返回

如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回

以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null

put 操作过程

第一次 put 元素会初始化 Node 数组 (initTable)

put 操作又分为 key (hash 碰撞) 存在时的插入和 key 不存在时的插入

put 操作可能会引发数组扩容 (tryPresize) 和链表转红黑树 (treeifyBin)

扩容会使用到数据迁移方法 (transfer)

CAS 操作简要介绍

CAS 操作是新版本 ConcurrentHashMap 线程安全实现原理的精华所在,如果说其共享变量的读取全靠 volatile 实现线程安全的话,那么存储和修改过程除了使用少量的 synchronized 关键字外,主要是靠 CAS 操作实现线程安全的。


ConcurrentHashMap的底层结构和HashMap一样,都是数组+链表+红黑树


ConcurrentHashMap是同步的HashMap,读写都加锁


缓存


Redis


Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。 键的类型只能为字符串,值支持的五种类型数据类型为:字符串(String)、列表(List)、集合(Set)、有序集合(Zset)、散列表(Hash)。 Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。


数据结构:字典和跳跃表


跳跃表是有序集合的底层实现之一。 跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。

跳跃表与红黑树等平衡树相比,跳跃表具有以下优点

插入速度非常快速,因为不需要进行旋转等操作来维护平衡性;

更容易实现;

支持无锁操作。


使用场景


计数器 :可以对 String 进行自增自减运算,从而实现计数器功能。 Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。


缓存: 将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。


查找表: 例如 DNS 记录就很适合使用 Redis 进行存储。 查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因 为缓存不作为可靠的数据来源。


消息队列: List 是一个双向链表,可以通过 lpop 和 lpush 写入和读取消息。 不过最好使用 Kafka、RabbitMQ 等消息中间件。


会话缓存: 在分布式场景下具有多个应用服务器,可以使用 Redis 来统一存储这些应用服务器的会话信息。 当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器。


分布式锁实现: 在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。 可以使用 Reids 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。


其它: Set 可以实现交集、并集等操作,从而实现共同好友等功能。 ZSet 可以实现有序性操作,从而实现排行榜等功能。


Redis与Memcache


两者都是非关系型内存键值数据库,主要有以下不同:


数据类型: Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。


数据持久化: Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。


分布式: Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需 要先在客户端计算一次数据所在的节点。 Redis Cluster 实现了分布式的支持。


内存管理机制: 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。 Memcached 将内存分割成特定长度的块来存储数据,来解决内存碎片的问题,但是这种方式会使内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。


键的过期时间


Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。

对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间


数据淘汰策略


Redis可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略。

Reids 具体有 6 种淘汰策略:


从已设置过期时间的数据集中挑选最近最少使用的数据淘汰

从已设置过期时间的数据集中挑选将要过期的数据淘汰

从已设置过期时间的数据集中任意选择数据淘汰

从所有数据集中挑选最近最少使用的数据淘汰

从所有数据集中任意选择数据进行淘汰

禁止驱逐数据

作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法实际实现上并非针对所有 key,而是抽样一小 部分并且从中选出被淘汰的 key。


使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,启用 最近最少使用的数据淘汰策略。


Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。


持久化


Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。


RDB 持久化: 将某个时间点的所有数据都存放到硬盘上。 可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。 如果系统发生故障,将会丢失最后一次创建快照之后的数据。 如果数据量很大,保存快照的时间会很长。


优点


快照保存数据极快,还原数据极快

适用于灾难备份

缺点


小内存及其不适合使用

符合快照条件才会进行快照,意外宕机会丢失最后一次快照后的所有修改

AOF 持久化: 将写命令添加到 AOF 文件(Append Only File)的末尾。 使用 AOF 持久化需要设置同步选项,从而确保写命令什么时候会同步到磁盘文件上。这是因为对文件进行写入并 不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。同步选项:每个写命令都同步、每秒同步一次 、no 让操作系统来决定何时同步


优点


持久化比RDB更好,不会丢失任何的修改

缺点


持久化文件会变的越来越大

重复命令很多

Redis事务:Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。


缓存击穿、雪崩


缓存击穿(不存在的数据,可以在redis中存null)


 数据库水平切分,或者读写分离


 分布式锁,读数据库,然后把数据加载到缓存


 熔断、降级


 布隆过滤器


缓存雪崩


 过期时间加上一个随机数


 备份缓存:缓存a有超时,缓存b没有超时


 分布式锁:每次只能使用限个数据库连接


缓存并发


 分布式的话:用分布式锁


 单个服务器的话:用synchronize,lock等保证线程安全


redis集群


redis集群有三种模式:主从模式、哨兵模式、Redis cluster(redis集群)

三种模式主从复制原理基本一致,主从模式不支持高可用(机器故障需要手动操作);哨兵模式解决高可用问题,但扩容需求无法满足;


主从模式


d8fa3a3b749f44beb684f7dfea2a9185.png



集群介绍


1)主从模式里一个redis实例作为主机(master),其余多个实例作为备份机(slave);


2)master支持数据的写入和读取操作,而slave支持读取及master的数据同步;


3)在整个架构里,master和slave实例里的数据完全一致;


主从复制原理


全量同步

当从节点启动时,会向主节点发送SYNC(同步)命令;

主节点接收到SYNC命令后,在后台执行保存快照的命令生成RDB文件,并使用缓冲区记录此后执行的所有写命令;

主节点快照完成后,将快照文件和所有缓存命令发送给集群内的从节点,并在发送期间继续记录被执行的写命令;

主节点快照发送完毕后开始向从节点发送缓冲区中的写命令;

从节点载入快照文件后,开始接收命令请求,执行接收到的主节点缓冲区的写命令。

增量同步

主从复制中因网络等原因造成数据丢失场景,当从节点再次连上主节点。如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。


主节点故障处理方式


主从模式中,每个客户端连接redis实例时都指定了ip和端口号。如果所连接的redis实例因为故障下线了,则无法通知客户端连接其他客户端地址,因此只能进行手动操作。


不支持高可用


主从模式很好地解决了数据备份的问题,但是主节点因为故障下线后,需要手动更改客户端配置重新连接,这种模式并不能保证服务的高可用。


哨兵模式

c18064b81aab4eedaa9a949062d4d5fd.png



集群介绍


哨兵模式中增加了独立进程(即哨兵)来监控集群。客户端在连接集群时,首先连接哨兵,通过哨兵查询主节点的地址,然后再去连接主节点进行数据交互。


如果master异常,则会进行master-slave切换,将最优的一个slave切换为主节点。同时,哨兵持续监控挂掉的主节点,待其恢复后,作为新的从节点加入集群中。


主节点故障处理方式/哨兵工作方式


每个哨兵每秒向集群中的master、slave以及其他哨兵发送一个PING命令;

如果某个实例距离最后一次有效回复ping命令的时间超过一定值,则会被标记为主观下线;

如果master被标记为主观下线,那么其他正在监视master的哨兵以每秒的频率确认其确实进入主观下线状态,且数量达到一定值时,master会被标记为下线,然后通知其他的从服务器,修改配置文件,让它们切换主机;

客户端在master节点发生故障时会重向哨兵要地址,此时会获得最新的master节点地址。

扩容问题


哨兵模式的出现虽然解决了主从模式中master节点宕机不能自主切换(即高可用)的问题。但是,随着业务的逐渐增长,不可避免需要对当前业务进行扩容。


常见的扩容方式有垂直和水平扩容两种方式:


垂直扩容:通过增加master内存来增加容量;

水平扩容:通过增加节点来进行扩容,即在当前基础上再增加一个master节点。

虽然垂直扩容方式很便捷,不需要添加多余的节点,但是机器的容量是有限的,最终还是需要通过水平扩容方式来解决。而水平扩容涉及到数据的迁移,且迁移过程中又要保证服务的可用性。因此,数据能不迁移就尽量不要迁移。


Redis cluster 模式

9938505b9f604de291e3737a16a888ed.png



集群介绍


**redis cluster模式采用了无中心节点的方式来实现,每个主节点都会与其它主节点保持连接。**节点间通过gossip协议交换彼此的信息,同时每个主节点又有一个或多个从节点;

客户端连接集群时,直接与redis集群的每个主节点连接,根据hash算法取模将key存储在不同的哈希槽上;

在集群中采用数据分片的方式,将redis集群分为16384个哈希槽。

每个节点会保存一份数据分布表,节点会将自己的slot信息发送给其他节点,节点间不停的传递数据分布表;

客户端连接集群时,通过集群中某个节点地址进行连接。客户端尝试向这个节点执行命令时,比如获取某个key值,如果key所在的slot刚好在该节点上,则能够直接执行成功。如果slot不在该节点,则节点会返回MOVED错误,同时把该slot对应的节点告诉客户端,客户端可以去该节点执行命令。

主节点故障处理方式


redis cluster中主节点故障处理方式与哨兵模式比较相似,当约定时间内某节点无法与集群中的另一个节点顺利完成ping消息通信时,则将该节点标记为主观下线状态,同时将这个信息向整个集群广播。


如果一个节点收到某个节点失联的数量达到了集群的大多数时,那么将该节点标记为客观下线状态,并向集群广播下线节点的fail消息。然后立即对该故障节点进行主从切换。等到原来的主节点恢复后,会自动成为新主节点的从节点。如果主节点没有从节点,那么当它发生故障时,集群就将处于不可用状态。


扩容问题


当集群中加入新节点时,会与集群中的某个节点进行握手,该节点会把集群内的其它节点信息通过gossip协议发送给新节点,新节点与这些节点完成握手后加入到集群中,然后集群中的节点会各取一部分哈希槽分配给新节点

当集群中要删除节点时,只需要将节点中的所有哈希槽移动到其它节点,然后再移除空白(不包含任何哈希槽)的节点就可以了


数据库


复杂sql语句,遇到的复杂SQL语句,group by、行转列(小表涉及)等


mysql


mysql引擎(myisam与innodb)区别以及应用


1、innodb支持事务ACID,myisam不支持


2、innodb支持行级锁,myisam支持表级锁


3、innodb支持mvcc(多版本并发控制),myisam不支持


4、MyIASM支持全文类型索引,而InnoDB不支持全文索引


5、MyIASM相对简单,效率上要优于InnoDB,小型应用可以考虑使用MyIASM


6、MyIASM表保存成文件形式,跨平台使用更加方便


应用:1、MyIASM管理非事务表,提供高速存储和检索以及全文搜索能力,如果再应用中执行大量select操作,应该选择MyIASM


2、InnoDB用于事务处理,具有ACID(原子性、一致性、隔离性、持久性)事务支持等特性,如果在应用中执行大量insert和update操作,应该选择InnoDB


数据库事务


数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑的数据库操作遵循:要么全部执行成功,要么全部不执行,关系型数据库具有ACID特性


原子性(Atomicity) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

一致性(Consistency): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;

隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

持久性(Durabilily): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。


mysql四个事务隔离级别


这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题,讨论隔离级别的场景,主要是在多个事务并发 的情况下


读未提交-Read uncommitted ----最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读


**举例:**公司发工资了,领导把5000元打到A的账号上,此时该事务并未提交,而A正好查看账户,发现工资已经到账,是5000元整。领导发现发给A的工资金额不对,应该是2000元,于是迅速回滚了事务,修改金额后,将事务提交,最后A实际的工资只有 2000元,A空欢喜一场。

出现上述情况,即我们所说的脏读 ,两个并发的事务,“事务A:领导给A发工资”、“事务B:A查询工资账户”,事务B读取了事务A尚未提交的数据。

读提交-Repeatable read ---- 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。(大多数数据库的默认级别就是读提交,比如Sql Server , Oracle)


**举例:**A去消费,系统读取到卡里有2000元,而此时她的老婆也正好在转账,把A工资卡的2000元转到另一账户,并在 A之前提交了事务,当A扣款消费时,系统检查到A的工资卡已经没有钱

出现上述情况,即我们所说的不可重复读 ,两个并发的事务,“事务A:A员工消费”、“事务B:A的老婆网上转账”,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

可重复读-Read committed ---- 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。(Mysql的默认隔离级别是可重复读)


**举例:**当A拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),A的老婆就不可能对该记录进行修改,也就是A的老婆不能在此时转账。

序列化- Serializable ----最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,该级别可以防止脏读、不可重复读以及幻读。


同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行

### 脏读(读取未提交数据)


### 不可重复读(前后多次读取,数据内容不一致)


### 幻读(前后多次读取,数据总量不一致)


不可重复读和幻读到底有什么区别呢?


(1)不可重复读是读取了其他事务更改的数据,针对insert与update操作


解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。


(2)幻读是读取了其他事务新增的数据,针对insert与delete操作


解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。


MySQL 的隔离级别是基于锁实现的吗?


MySQL 的隔离级别基于锁和 MVCC (多版本并发控制)机制共同实现的。


序列化隔离级别,是通过锁来实现的。除了序列化隔离级别,其他的隔离级别都是基于 MVCC 实现。


不过, 序列化之外的其他隔离级别可能也需要用到锁机制,就比如 读提交 在当前读情况下需要使用加锁读来保证不会出现幻读


Mysql锁


表级锁和行级锁


MyISAM 仅仅支持表级锁,锁整张表,在并发写的情况下性能非常差。


InnoDB 不光支持表级锁,还支持行级锁,默认为行级锁。行级锁的粒度更小,仅对相关的记录上锁即可,所以对于并发写入操作来说, InnoDB 的性能更高。


表级锁和行级锁对比 :

表级锁: MySQL 中锁定粒度最大的一种锁,是针对非索引字段加的锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM 和 InnoDB 引擎都支持表级锁。

行级锁: MySQL 中锁定粒度最小的一种锁,是针对索引字段加的锁,只针对当前操作的记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁

行级锁的使用注意事项:InnoDB 的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁。当我们执行 更新或删除语句时,如果 WHERE条件中字段没有命中唯一索引或者索引失效的话,就会导致扫描全表对表中的所有记录进行加锁。


共享锁和排他锁

不论是表级锁还是行级锁,都存在共享锁和排他锁这两类:


共享锁(S 锁) :又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。

排他锁(X 锁) :又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。

排他锁与任何的锁都不兼容,共享锁仅和共享锁兼容。


B+树索引


是MySQL 存储引擎的默认索引类型,便于查找两个值之间的多个元素


B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。


与B树的区别:


每个叶子结点都存有相邻叶子结点的指针

父节点存有右孩子的第一个元素的索引。

内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。

B+树有两种类型的节点:内部结点(也称索引结点)和叶子结点。内部节点就是非叶子节点,内部节点不存储数据,只存储索引,数据都存储在叶子节点。


B+树与红黑树的比较:


红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,主要有以下两个原因:


(一)更少的查找次数(红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,检索的次数也就更多)


(二)利用计算机预读特性(预读过程中,磁盘进行顺序读取,数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点,并且可以利用预读特性,相邻的节点也能够被预先载入)


大量数据排序的方法


先进行适当分批次,然后在批次内进行堆排序


数据库中查询的时间复杂度


使用二叉搜索树(BST),只需 log(N) 次运算,查找特定值好用

定义:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树

一条sql语句执行到结果经历了什么?


SQL优化方法


在实现功能的基础上,尽量减少对数据库的访问次数;

检查是否使用了索引

需要使用多个列作为条件查询时,使用多列索引比使用多个单列索引性能更好

尽量把使用的索引放在选择的首列,让选择性最强的索引列放在前面;

在查询时,不要过多地使用如SELECT * 语句;

尽量减少子查询,使用关联查询(left join,right join,inner join)替代

减少使用IN或者NOT IN ,使用exists,not exists或者关联查询语句替代,因为要对子查询的表进行全表扫描。

通过搜索参数,尽量减少对表的访问行数,最小化结果集,减轻网络负担;

能够分开的操作尽量分开处理,提高每次的响应速度;

算法的结构尽量简单;

在可能的情况下尽量限制结果集行数如:SELECT TOP 300 COL1,COL2,COL3 FROM T1,因为某些情况下用户是不需要那么多的数据的。

or 的查询尽量用 union或者union all 代替,索引列上的or操作会造成全表扫描。union具有去重的操作,增加了计算时间。union all不需要去重,但会包含相同记录。

合理的增加冗余的字段(减少表的联接查询)

增加中间表进行优化(这个主要是在统计报表的场景,后台开定时任务将数据先统计好,尽量不要在查询的时候去统计)

建表的时候能使用数字类型的字段就使用数字类型(type,status…),数字类型的字段作为条件查询比字符串的快


索引失效情况


sort() 函数的工作原理


是归并排序,把问题拆分为小问题,通过解决小问题来解决最初的问题


mysql语句


创建数据库:create database 数据库名;

创建数据库时设置字符编码:create database 数据库名 character set utf8;默认是latin1 (单字节编码)

查看数据库信息:show create database 数据库名;


关系型数据库和非关系型数据库


关系型数据库:采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,以便于用户理解,关系型数据库这一系列的行和列被称为表,一组表组成了数据库。用户通过查询来检索数据库中的数据,而查询是一个用于限定数据库中某些区域的执行代码。关系模型可以简单理解为二维表格模型,而一个关系型数据库就是由二维表及其之间的关系组成的一个数据组织。

常见的关系型数据库:mysql,oracle,SQL Server

存储方式:行存储,一个表里每一个对象的记录存储一行,一行里包括了该记录的所有特征


关系型优缺点以及应用场景:


优点:


1)复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。


2)事务支持使得对于安全性能很高的数据访问要求得以实现。


缺点:


1)不擅长大量数据的写入处理


2)不擅长为有数据更新的表做索引或表结构(schema)变更


3) 字段不固定时应用不方便


4)不擅长对简单查询需要快速返回结果的处理


使用场景:


1)需要做复杂处理的数据;


2)数据量不是特别大的数据;


3)对安全性要求高的数据;


4)数据格式单一的数据;


非关系型数据库


NoSQL,泛指非关系型的数据库。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用。

常见的非关系型数据库:


(1)键值对存储(key-value):Redis键值对存储,优势:快速查询,缺点:存储数据缺少结构化。


(2)列存储:Hbase,优势:快速查询,扩展性强。缺点:功能相对于局限。


(3)文档数据库存储:MongoDB,早起应用多。优势:要求不特别的严格。缺点:查询性不高,缺少统一查询语法。


(4)图形数据库存储:应用于社交网络,优势:利用图结构相关算法。缺点:需要整个图计算才得出结果,不容易做分布式集群方案。

存储方式:

以列为单位进行数据的存储,一列作为一个记录,每个对象的记录会存储多行,各行相对独立;


非关系优缺点以及使用场景


优点:


1)nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。


2)nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。


3)nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。


4)不支持Join处理,各个数据都是独立设计的,很容易把数据分散在多个服务器上,故减少了每个服务器上的数据量,即使要处理大量数据的写入,也变得更加容易,数据的读入操作当然也同样容易。


缺点:


1)无法对表进行复杂的计算,不支持join等功能。


使用场景:


1)海量数据存储;


2)多格式的数据存储;


3)对查询速度要求快的数据存储;

https://blog.csdn.net/weixin_46043015/article/details/107896330


Tomcat


Spring


spring帮我们做了什么,spring mvc做了什么,分发,静态资源配置,aop,IOC,设计模式用了什么

轻量级框:Spring是轻量级框架,基本的版本大约2M

Spring Bean 的生命周期:实例化 -> 属性赋值 -> 初始化 -> 销毁


控制反转IOC


依赖:类之间的方法调用

依赖倒置:把调用类改为调用这个类的接口

依赖注入DI:就是如何获得请求的对象的,这个对象是怎样的注入到你的类的

控制反转:我想要用筷子(向容器发出请求),接着筷子就会”注入“到的手上,而在这个过程当中,你不再是控制方,反而演变成一名请求者(虽然本身还是调用者),依赖于容器给予你资源,控制权坐落到了容器身上

Autowired和Resource是用来修饰字段,构造函数,或者设置方法,并做注入的,自动从spring的上下文找到合适的bean来注入。而Service,Controller,Repository,Component则是用来修饰类,标记这些类要生成bean。


面相切面的编程 AOP(动态代理)@Aspect


Spring支持面相切面的编程,并且把应用业务逻辑和系统分开,可以很方便的实现对程序进行权限拦截和运行监控等功能


spring aop中@Around @Before @After三个注解的区别@Before是在所拦截方法执行之前执行一段逻辑。@After 是在所拦截方法执行之后执行一段逻辑。@Around是可以同时在所拦截方法的前后执行一段逻辑。


spring实现注解


是框架会扫描注解,通过反射方法读出注解,然后执行对应的方法


SpringMVC


为什么用SpringMVC?

很多应用程序的问题在于处理业务数据的对象和显示业务数据的视图之间存在紧密耦合,通常,更新业务对象的命令都是从视图本身发起的,使视图对任何业务对象更改都有高度敏感性。而且,当多个视图依赖同一个业务对象时是没有灵活性的。


SpringMVC基于java,实现了web MVC设计模式,请求驱动类型的轻量级web框架,使用了MVC架构模式思想,将web层进行职责解耦。基于请求驱动指的是使用请求-响应模型。


SpringMVC运行原理


客户端请求提交到DispatcherServlet(调度程序)

由DispatcherServlet控制器查询一个或多个HandlerMapping

DispatcherServlet将请求提交到Controller

Controller调用业务逻辑处理后,返回ModelAndView

DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图

视图负责将结果显示到客户端


SpringMVC缺点

依赖的jar需要一个个配置,非常繁琐


Spring Boot


Spring Boot:基于spring,为了解决使用spring框架时配置繁多、部署流程复杂、开发效率低等问题,可以创建独立的应用程序,嵌入tomcat、jetty等,可以直接启动应用程序而不需要外部的容器。同时,spring boot可以自动配置spring应用


springboot中典型的设计模式



工厂模式

最典型的就是BeanFactory,通过bean名称我们可以获取bean实例

代理模式

AOP的实现本质即是代理模式,通过JDK或CGLIB新建一个类代理原来的类,在保证原来类的功能的基础上进行增强


网络

- tcp/ip协议

- http和https区别,优缺点,保证内容不被篡改,传输的都是密文,不担心被看到,80,https慢一些,有加密解密过程,端口号443,配置https


TCP和UDP的区别,使用场景?

• TCP面向连接,可靠传输,相对于UDP效率低,http的get,post等方法使用的就是TCP连接传输数据

• UDP没有连接,不可靠,但是传输速度快,用于视频会议等场合


TCP拥塞控制算法

TCP拥塞控制有四种算法:慢开始,拥塞避免,快重传,快恢复。


慢开始:以拥塞窗口cwnd=1为初始值,如果发送的包没有超时重传,则cwnd每次翻倍,直到cwnd>ssthresh(慢开始门限值初始值为ssthresh=16),慢开始结束,进入拥塞避免阶段。

拥塞避免:当cwnd>ssthresh时,采用拥塞避免算法控制。拥塞避免的过程为:cwnd=16,发送一个数据包,没有超时重传,cwnd=cwnd+1=16+1=17,再次发送数据包,依旧没有超时,cwnd=cwnd+1,以此类推,假设在cwnd=24时,发送的数据包出现超时重传,说明了网络出现拥塞,拥塞避免的算法为:把cwnd设置为1,ssthresh设置为cwnd/2=24/2=12,这样又以慢开始传输,当拥塞窗口大小超过慢开始门限,又采用拥塞避免控制。

快重传:如果接收方收不到数据包是因为数据包丢失,而不是网络拥塞,如果把cwnd设置为1,ssthresh设置为原来cwnd的一半,那网络传输的效率就会低很多。针对这一问题的解决方法是采用快重传算法,其核心是:如果接收方连续三次发送相同的确认包,发送方收到三次确认包后就认为该包丢失,于是重新发送丢失的包给接收端。

快恢复:当发送方连续收到三个重复确认时,把慢开始门限设置为cwnd/2(24/2 拥塞窗口值的一半),拥塞窗口的值设置为慢开始门先减半后的值,即cwnd=12,然后开始执行拥塞避免算法。


长连接&短连接

长连接:连接->传输数据->保持连接 -> 传输数据-> …->直到一方关闭连接,客户端关闭连接。

长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。


短连接:连接->传输数据->关闭连接。

比如HTTP是无状态的的短链接,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。因为连接后接收了数据就断开了,所以每次数据接受处理不会有联系。这也是HTTP协议无状态的原因之一。

应用场景:


长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个TCP都需要三次握手,处理速度会降低很多,所以每个操作完后都不断开,处理时直接发送数据包就OK了,不用建立TCP连接。


例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

短连接:像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,资源情况和系统压力难以想象。


所以并发量大,但每个用户无需频繁操作情况下需用短连好。


TCP长&短链接

短链接:我们模拟一下TCP短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起close操作。


短连接一般只会在client/server间传递一次读写操作

短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段。

长链接:模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。


在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。


粘包拆包

粘包拆包场景


因为TCP是面向字节流的操作,没有边界,操作系统在发送TCP数据时,会通过缓冲区进行优化


粘包:如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送。


拆包:如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送


为什么UDP没有粘包?


粘包拆包问题在数据链路层、网络层以及传输层都有可能发生。日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中。


常见的解决方案


发送端将每个包都封装成固定的长度,比如100字节大小。如果不足100字节可通过补0或空等进行填充到指定长度;

发送端在每个包的末尾使用固定的分隔符,例如\r\n。如果发生拆包需等待多个包发送过来之后再找到其中的\r\n进行合并;例如,FTP协议;

将消息分为头部和消息体,头部中保存整个消息的长度,只有读取到足够长度的消息之后才算是读到了一个完整的消息;

通过自定义协议进行粘包和拆包的处理。


Netty对粘包和拆包的处理

Netty对解决粘包和拆包的方案做了抽象,提供了一些解码器(Decoder)来解决粘包和拆包的问题。如:

LineBasedFrameDecoder:以行为单位进行数据包的解码;

DelimiterBasedFrameDecoder:以特殊的符号作为分隔来进行数据包的解码;

FixedLengthFrameDecoder:以固定长度进行数据包的解码;

LenghtFieldBasedFrameDecode:适用于消息头包含消息长度的协议(最常用,适用高并发大流量);

http状态码


1xx-信息提示

2xx-成功标志

3xx-重定向

4xx-客户端错误

5xx服务端错误


请求头Header中怎么清除信息?

可以扩展HttpServletRequestWrapper并覆盖getHeaderNames();,其中您可以返回另一个枚举,只添加了必需的标题。


设计模式

单例

单例怎么避免反射构造对象:增加一个标识位,如通过增加一个布尔类型的 ideal 标识,保证只会执行一次,更安全的做法,可以进行加密处理,保证其安全性


Linux

linux基本命令,加分项是线上确定日志确定问题在哪里,项目日志文件里面的,vim的命令,查找关键信息


grep “” *.log

tail -200 *.log

tail -200f *.log

tail -200 *.log | grep -a3 -b3 “”

ps -ef | grep “java”


架构

log4j2,配置日志

扩展

服务器降级


降级的目的是为了保证核心服务可用


降级可以有几个层面的分类:自动降级,人工降级;按照功能可以分为:读服务降级和写服务降级;


1.对一些非核心服务进行人工降级,在大促之前通过降级开关关闭那些推荐内容,评价等对主流程序没有影响的功能


2.故障降级,比如调用的远程服务挂了,网络故障,或者RPC服务返回异常。那么可以直接降级,降级的方案比如设置默认值,采用兜底数据(系统推荐的行为广告挂了,可以提前准备静态页面做返回)等等


3.限流降级,在秒杀这种流量比较集中并且流量特别大的情况下,因为突发访问量特别大可能导致系统支撑不了。这个时候可以采用限流来限制访问量。当达到阈值时,后续的请求被降级,比如进入排队页面,比如跳转到错误页面(活动火爆,请稍后重试)


秒杀思路


秒杀系统与原有电商系统分开部署


商品页面 进行静态化缓存,只访问服务器中商品的动态数据


读写分离是用来解决数据库的读性能瓶颈的(主从集群)

但是解决读性能问题首选缓存,因为读写分离存在(主从一致性;如何实现故障自动转移)问题

商品列表页 进行缓存

下订单时,查看是否重复下单,查看redis,key为userId,value为用户下单的商品id集合


如果已经买过,就不让在买

如果没有买过,检查redis缓存的商品库存是否为0

如果库存不为0,则在redis中预减库存,然后把购买请求加入到rabbitmq中,把并发请求串行化

如果库存为0,则告诉用户卖完了

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
14天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
46 3
|
14天前
|
小程序 前端开发 算法
|
1月前
|
NoSQL 安全 Java
Java后端基础自测
Java后端基础自测
60 12
|
1月前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
【10月更文挑战第8天】本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
34 5
|
1月前
|
前端开发 小程序 Java
java基础:map遍历使用;java使用 Patten 和Matches 进行正则匹配;后端传到前端展示图片三种情况,并保存到手机
这篇文章介绍了Java中Map的遍历方法、使用Pattern和matches进行正则表达式匹配,以及后端向前端传输图片并保存到手机的三种情况。
19 1
|
1月前
|
算法 Java Linux
java制作海报五:java 后端整合 echarts 画出 折线图,项目放在linux上,echarts图上不显示中文,显示方框口口口
这篇文章介绍了如何在Java后端整合ECharts库来绘制折线图,并讨论了在Linux环境下ECharts图表中文显示问题。
39 1
|
1月前
|
算法 搜索推荐 Java
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
这篇文章介绍了如何使用Java后端技术,结合Graphics2D和Echarts等工具,生成包含个性化信息和图表的海报,并提供了详细的代码实现和GitHub项目链接。
104 0
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
|
6天前
|
存储 SQL API
探索后端开发:构建高效API与数据库交互
【10月更文挑战第36天】在数字化时代,后端开发是连接用户界面和数据存储的桥梁。本文深入探讨如何设计高效的API以及如何实现API与数据库之间的无缝交互,确保数据的一致性和高性能。我们将从基础概念出发,逐步深入到实战技巧,为读者提供一个清晰的后端开发路线图。
|
5天前
|
JSON 前端开发 API
后端开发中的API设计与文档编写指南####
本文探讨了后端开发中API设计的重要性,并详细阐述了如何编写高效、可维护的API接口。通过实际案例分析,文章强调了清晰的API设计对于前后端分离项目的关键作用,以及良好的文档习惯如何促进团队协作和提升开发效率。 ####
|
7天前
|
存储 SQL 数据库
深入浅出后端开发之数据库优化实战
【10月更文挑战第35天】在软件开发的世界里,数据库性能直接关系到应用的响应速度和用户体验。本文将带你了解如何通过合理的索引设计、查询优化以及恰当的数据存储策略来提升数据库性能。我们将一起探索这些技巧背后的原理,并通过实际案例感受优化带来的显著效果。
23 4