JAVA基础2

项目中如何是多线程的?
写日志、发短信、比较耗时间都可以使用多线程。 技术:线程池

你在项目中是否遇到多线程安全问题?
lock或者syn

java中的锁

lock或者syn 
备注:高级可能还会问到lock锁的原理(aqs)和syn原理


java集合 HashMap1.7 和 1.8 ConcurrentHashMap

HashMap与HashTable区别
HashMap加载因子为什么是0.75
HashMap1.7版本扩容存在哪些问题 --扩容死循环,在多线程环境下

HashMap底层如何实现
为什么HashMap使用红黑树
红黑树数据结构时间复杂度是多少


二,应用框框

springIOC原理 aop原理

springIOC原理:反射
springbean生命周期

SpringBean的生命周期总结:
源码分析:
1. 执行refresh();刷新方法
2. finishBeanFactoryInitiallization()
3. preInstantiateSingletons()
4. getBean()-> doGetBean()->creatBean()->doCreateBean()->createBeanInstance(实例对象)
     默认情况下使用java反射机制初始化对象,也可以通过CGLIB实例初始化
5. initializeBean()
6. invokeAwareMethods() 判断是有aware接口依赖信息
7. applyBeanPostProcessorsBeforeInitialization() 执行前置处理器
8. invokeIninMethods() 执行init方法
9. applyBeanPostProcesorsAfterInitlization 执行后置增强


SpringBean在初始化的时候如何实现增强?
后置处理器

SpringAop作用:对我们目标方法实现增强 事务、日志、异常

SpringAop的原理:代理模式
代理模式分两种:JDK动态与Cglib

那你说下,SpringAop用的是jdk还是Cglib?
根据判断代理类有实现接口 就采用jdk
如果没有实现接口就是用cglib

springboot原理

springboot是一个快速的整合第三方框架,内嵌入了Tomcat服务器,完全采用注解形式

1. 快速整合第三方框框 原理靠的已封装好依赖
2. 内嵌入Tomcat服务器 原理:因为本身tomcat就是java语言编写的,支持内嵌到我们的引用程序
3. 完全采用注解形式(本身spring就是支持注解方式启动)
4. 自定义stared组件

springcloud组件及原理
误区:springcloud不是rpc远程调用框架,它是微服务全家桶解决框架


eureka和zookeeper区别
相同点:都是可以实现服务注册中心
不同点:
zookeeper 核心保证CP(必须保证数据一致性),当zk领导角色在某种原因下出现宕机的话,会重新选举新的领导角色,
                    在重新选举新的领导角色过程中,那么整个zk环境是不可用的。那么可能会导致整个注册中心不可以使用。
eureka 核心是保证AP(必须保证可用性),集群完全是去除中心思想,每个节点都是相等的,没有主从之分,几个节点
             宕机之后不影响整个eureka集群环境的使用,最终能够保证一个eureka节点在,就可以保证服务注册中心的使用。
总结:
zookeeper必须保证数据一致性、有中心思想
eureka必须保证可用性、完全去除中心思想(相互注册)

Redis面试题

A. Redis和MySql如何保证数据一致性问题

1. 传统的解决方案:直接清理Redis缓存 清理缓存的过程存在时间差 (方案已淘汰)
2. 使用alibaba Canal框架+kafka解决(基于MQ解决、基于Canal解决 binlog)

数据库知识点
每次做update add del 日志记录起来,binlog

消息中间件面试题

B. 消息中间件在高并发情况下,如何保证消息不丢失

1. 如果消费者每次启动,消息会不会丢失,不会
2. 消息中间件如果宕机呢,消息会丢失吗? 不会 默认持久化的
3. 如果消息中间件存放消息满的情况下,达到消息队列容量阈值 采用拒绝策略
4. 生产者往中间件投递消息,中间件突然宕机怎么办? 消息的确认机制,确认成功投递到消息中间件
5. 消费者会把消息中间件所有内容都消费掉吗?消费者有消费阈值限制。帮助解决高并发问题,流量削封。
     生产者同时投递1万条消息,消费者限制同时消费100条。
6. 消费者如果消费消息失败的情况下,手动实现ACK. 1。日志记录下来,后续做补偿。2。放到死信队列(备胎)

秒杀:消费者加集群

HashMap面试题

HashMap和HashTable有啥区别

HashTable 线程安全 是否允许key为空?不允许,通过key的hash计算存储位置 方法上加有同步锁 syn
HashMap 线程不安全 允许存放key为空,注意:key为空没有hashcode,无法计算hash值

那么HahMap key为空存放数组哪个位置呢?

存放到下标为0的位置,也就是第一个链表。只允许存放一个key为空

static final int hash(Object key){
    int h;
    return (key ==null) ? 0 : (h = key.hashcode()) ^(h>>>16);
}

HashMap是否可以存放自定义对象作为key? 可以,key是Object类型


HashMap1.7 与 1.8之间的区别?

HashMap1.7 底层采用数组+链表实现
HashMap1.8 底层采用数组+链表+转换红黑树实现

备注:可能面试官问HashMap底层使用哪些数据结构?答:1.7数组+链表, 1.8数组+链表+红黑树

HashMap是如何解决Hash冲突的问题?
使用链表存放hash值相等且内容不等 存放到同一个链表中

HashMap的put方法底层是如何实现的?
1. 判断key如果为空的情况下,存放数组0
2. 默认HashMap的初试容量为16、加载因子大小 16*0.75 = 12 每次扩容*2倍
3. 根据key计算hash值,存放到数组下标位置
4. 如何hash值相等,但是内容不等的情况下,存放到同一个链表中
5. 如果当前size>加载因子阈值 开始*2倍扩容

备注:1.8中链表长度如果大于8的情况下,开始将链表转换成红黑树

HashMap 加载因子的值为什么是0.75 而不是其他的?
折中的方案,空间利用率高、冲突少 0.75 最合适


HashMap1.7中数组扩容死循环的问题 有了解过吗?
肯定了解过,有bug,死循环问题
因为HashMap数组中的扩容的链表采用头插入法,在多线程的情况下操作HashMap的话,导致死循环的问题,代码在586行,1.8已解决

备注:
头插入法相当于让最新的冲突节点存放到最前面
尾插入法相当于一直存放到最后next


HashMap根据key查询时间复杂度是多少?
HashMap根据key直接计算index值,直接从数组中查询数据
分两种场景:
如果index没有产生冲突,可以直接获取值,时间复杂度为 O1
如果index产生了冲突,遍历链表 时间复杂度变为 On

HashMap1.8的HashMap改进1.7哪些地方
1. HashMap1.8中采用尾插入法
2. 解决1.7HashMap扩容死循环问题
3. 链表长度>8情况下 转换成红黑树
4. 1.8比1.7的代码书写更加牛逼

HashMap1.8为什么需要引入到红黑树呢?
如果index冲突过多,导致链表过长,因为链表时间复杂度为On 为了解决链表查询效率慢的问题,这时候链表长度>8的情况下,
并且数组的容量大于64的情况下就开始将链表转换为红黑树存放,这时候时间复杂度从O(n)变为O(logn)

HashMap线程不安全,有其他哪些替代方案吗?
1. ConcurrentHashMap 分段锁 16段,分成了16个小的HashTable
   在多线程不大于16的情况下,效率高,ConcurrentHashMap1.8的时候引入CAS 效率更高
2. 使用Conections.synchronizedMap() 将线程不安全HashMap转换为线程安全HashMap

红黑树与链表时间复杂度分别是多少呢?

红黑树时间复杂度其实就是 O(logn) 。O(logn) 表示平方不断除以2,排除2,跟树一样。
链表时间复杂度为 O(n) 。 O(n) 表示从头查到尾

微服务专题面试

微服务架构和SOA架构区别
1. 微服务架构基于SOA架构演变过来,继承SOA架构的优点,在微服务架构中去除SOA架构中的ESB消息总线,
     采用http+json(restful) 进行传输。
2. 微服务架构比SOA架构粒度会更加精细,让专业的人去做专业的事,目的提高效率,每个服务与服务之间互不影响,
   微服务架构中,每个服务必须独立部署,微服务架构更加轻巧,轻量级。
3. SOA架构中可能数据库存储会发生共享,微服务强调每个独立服务都是单独数据库,保证每个服务与服务之间互不影响。
4. 项目体现特征微服务架构比SOA架构更加适合与互联网公司敏捷开发、快速迭代版本,因为粒度非常精细。


SOA:Service Oriented Architecture面向服务的架构。也就是把工程都拆分成服务层工程、表现层工程。
服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。工程都可以独立部署。 

订单系统    会员系统 前台系统 后台系统 搜索系统

服务器层:
订单服务    会员服务    搜索服务 商品服务 内容服务 xx服务

缓存

数据库


1. 服务注册与发现原理 在任何rpc远程框架中,都会有一个注册中心
2. 注册中心概念:存放服务地址相关信息(接口地址)


1. 首先启动注册中心(eureka注册中心)
2. 启动会员服务
3. 会员服务在启动的时候,会把当前服务基本信息比如服务地址和端口,以别名方式注册到注册中心上去
     serverid(key):app_member 服务集群(value):127.0.0.1:8080,127.0.0.1:8081
4. 消费者在调用接口的时候,使用服务别名也就是serverid(key) 去注册中心上获取实际rpc远程调用地址
   消费者(订单服务)获取到实际rpc远程调用地址之后,首先缓存到本地jvm内存中,默认情况下eureka每隔30秒更新一次服务调用地址
5. 如果消费者获取到实际rpc远程调用地址之后,再使用本地HttpClient技术实现调用

微服务负载均衡:本地负载均衡。127.0.0.1:8080,127.0.0.1:8081

服务注册:将服务信息注册到注册中心
服务发现:从注册中心上获取服务信息


服务提供者:提供服务接口的意思
服务消费者:调用别人接口进行使用
一个服务即可以作为提供者,也可以作为消费者

springcloud中支持以下三种注册中心:Eureka、consul(go语言编写)、zookeeper
dubbo支持常用两种:redis和zookeeper


springcloud简介
springcloud是基于springboot基础之上开发的微服务框架,springcloud是一套目前非常完整的微服务解决方案框架,其内容包含服务治理、
注册中心、配置管理、断路器、智能路由、微代理、控制总线、全局锁、分布式会话等。

springcloud包含众多子项目:
    config  分布式配置中心
    netflix 核心组件
    eureka  服务治理 注册中心
    hystrix 服务保护框架
    ribbon  客户端负载均衡器
    feign   基于ribbon和hystrix的声明式服务调用组件
    zuul    网关组件,提供智能路由,访问过滤等功能

为什么要使用springcloud
因为springcloud出现,对微服务技术提供了非常大帮助,因为springcloud提供了一套完整的微服务解决方案,不像其他框架只解决了微服务
中的某个问题。

服务治理:阿里巴巴开源的Dubbo和当当网在其基础上扩展的Dubbox、Eureka、Apache的Consul等
分布式配置中心:百度的discof、Netfix的Archaius、360的QConf、springcloud自带的config、携程的阿波罗等
分布式任务:xxl-job、elastic-job、springcloud的task等
服务跟踪:京东的hyra、springcloud的sleuth等

因为springcloud是目前来说,是一套比较完整的微服务解决方案框架。
不像其他rpc远程调用框架,只是解决某个微服务的问题。可以把springcloud理解为一条龙微服务解决方案。
微服务全家桶 -- springcloud比较完善。
微服务中包含:
    分布式配置中心
    分布式锁
    分布式跟踪
    分布式服务治理
    分布式任务调到平台
    。。。


1. 网关API(接口) Gateway(网关 Zuul) -- 接口网关注意:接口没有界面
     网关概念:相当于客户端请求统一先请求到网关服务器上,再由网关服务器进行转发到实际服务器地址上。类似于nginx
    
2. 接口在什么背景下产生?在面向服务架构和微服务背景下产生 目的都是为了解耦。rpc远程调用中产生
3. 接口如何分类
开放接口:
    其他机构合作伙伴进行调用(必须在外网访问)蚂蚁开发平台、微信公众号开发需要通过appid+appsocet生成
    appessToken进行通讯。对接支付开发、微信开发。目的可以授权一些接口权限OAuth2.0协议方式 第三方联合登录
内部接口
一般只能在局域网中进行访问,服务与服务之间调用关系都是在同一个微服务系统中,目的是为了保证安全问题。

现在让你去设计一套公司项目接口,你会如何设计?
考虑:接口权限(开放接口 | 内部接口)、考虑幂等性、安全性(采用HTTPS)、防止篡改数据(验证签名)、
            使用网关拦截接口实现黑名单和白名单、
            接口使用http协议+json格式restful目的为了跨平台(http底层采用socket技术,socket转换二进制,二进制是任何语言都支持)、
            考虑高并发对接口服务实现服务保护 服务降级、熔断、隔离之类


1. Zuul网关:
     可以拦截客户端所有请求,对该请求进行权限控制、负载均衡、日志管理、接口调用监控登录。
2. 过滤器与网关区别是什么?
   过滤器是拦截单个tomcat服务器请求
     网关是拦截整个微服务所有请求

网关分为内网网关(内部平台)和外网网关(专门针对开放平台)

Nginx与Zuul区别?
相同点:
Zuul和Nginx都可以实现负载均衡、反向代理、过滤请求、实现网关效果。
不同点:
Nginx采用C语言编写
Zuul采用java语言编写

Zuul负载均衡实现:采用ribbon+eureka实现本地负载均衡。
Nginx负载均衡实现:采用服务器端实现负载均衡。

Nginx比Zuul实现功能会更加强大,因为Nginx可以整合一些脚本语言(Nging+Lua)

Nginz适合于服务器端负载均衡,也可以实现网关
Zuul适合微服务中网关,而且使用技术是java语言。

最好建议nginx+zuul实现网关
nginx作用实现反向代理,转发到zuul,zuul针对微服务实现网关拦截


为什么放弃Dubbo,而采用SpringCloud框架(属于架构师)
相同点:
    都可以实现rpc远程调用框架,都可以实现服务治理与发现。
不同点:
  从框架架构层面:
        Dubbo内部实现功能没有SpringCloud(全家桶)更加强大,目前来说SpringCloud比较完善微服务框架。
        Dubbo核心只是实现服务治理,缺少一些对分布式解决方案的整合(比如:分布式配置中心、消息总线、链路、网关),
        需要整合其他一些分布式解决方案框架。

    从版本更新迭代:
        Dubbo目前更新迭代的速度没有SpringCloud更新迭代速度快,而且在SpringCloud2.0整合SpringBoot功能变得越来越完善和稳定。
        
    从框架开发背景:
        Dubbo的开发背景是阿里巴巴集团,阿里巴巴提供非常多优秀的开源框架,但是SpringCloud开发背景是Spring家族。
        Spring家族是专门提供企业级开源框架,而且在中国,或者在整个世界上Spring的产品应用更加广泛。

    总结:
        如果学习Dubbo的话,学习其他分布式解决方案需要自己组装,反而如果学习SpringCloud,它已经把整个常用分布式框架都整合好了。

Dubbo架构原理  SOA架构
角色分区:
1.Provider:暴露服务的服务提供方(生产者)
2.Consumer: 调用远程服务的服务消费方(消费者)
3.Registry: 服务注册与发现的注册中心(zookeeper)
4.Monitor:  统计服务的调用次数和调用时间的监控中心

调用流程:
1. 服务容器负责启动,加载,运行服务提供者。
2. 服务提供者在启动时,像注册中心注册自己提供的服务。
3. 服务消费者在启动时,像注册中心订阅自己所需要的服务。
4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

一. 生产者注册的过程
a. 当生产者启动的时候,会将自己的服务信息注册到注册中心上去。
b. 将当前服务的接口class完整路径作为key,value为实际dubbo协议调用地址,以临时节点+持久节点方式存放到Zookeeper路径上。
   案例:com.demo.api.service.MemberService作为key存放在Zookeeper的节点,value有子节点Provider,存放多个服务实际接口地址。

二. 消费者订阅(利用zookeeper的事件通知原理)
1. 消费者采用订阅的方式获取服务接口地址

三. zookeeper通知消费者
      当节点发生变化的时候,通知给消费者(一直长连接,而eureka每隔一段时间定时去查,默认30秒)
四. 消费者在获取地址之后,然后再采用本地rpc远程调用技术调用生成者接口
五. 消费者调用生产者的时候,都会在监控中心Monitor做个记录,记录调用成功、失败次数。(Dubbu-Admin平台)

思考:
Zookeeper核心:节点和事件通知
事件通知:当节点发生变化的时候(删除、修改、新增),都会通知订阅者。

软负载别名为本地负载均衡。

Dubbo优缺点
优点:
1. 透明化的远程方法调用
     像调用本地方法一样调用远程方法,只需要简单配置,没有任何API侵入(外部很难调用,采用内部dubbo协议),面向接口开发
2. 软负载均衡及容错机制
     可在内网替代nginx、lvs等硬件负载均衡器
3. 服务注册中心自动注册&配置管理
   不需要些死服务器提供者地址,注册中心基于接口名自动查询提供者ip
     使用类似zookeeper等分布式服务作为注册中心,可以将绝大部分项目配置移入zookeeper集群
4. 服务接口监控与治理
     Dubbo-admin与Dubbo-monitor提供了完善的服务接口管理与监控功能,针对不同应用的不同接口,可以进行多版本、多协议、多注册中心管理。
缺点:
只支持java语言

Mysql数据库相关问题

问题1. 京东搜索商品是查询的ElasticSearch还是数据库? 查询ElasticSearch,ES查询效率比数据库快
问题2. 商品详情是查询ElasticSearch还是数据库还是查询缓存? 查询缓存
问题3. 为什么搜索商品查询ES,而商品详情查询缓存? 
         因为搜索数据量太多,模糊条件数据量太多,要进行分词查询;商品详情可以通过商品id直接定位到数据。
问题4. 为什么京东商品详情页是以.html后缀的? https://item.jd.com/100008348542.html 
       作用:目的是为了搜索引擎更好的收录,做静态化,这个页面是个模板。
问题5. 搜索时,不只是在搜索一个字段,把标题或者是描述,或者评论里,包含该字段的都能够搜索到,这是怎么实现的?
       ES聚合查询 
问题6. 考虑ES如何与数据库实现同步?
       使用logstash插件,定时调度以分为单位。


数据库集群会产生哪些问题?

1. 自增id问题
2. 数据库关联查询问题(水平拆分数据库)
3. 数据库同步问题

数据库集群的话,如果自动增长id产生重复的话?如何解决?
背景:数据库A: id:1,2,3 数据库B: id:1,2,3 。两个数据库id重复。
解决:
1.UUID形式,该方式没有排序,不是自增的(不是很推荐)
2.设置数据库的步长。数据库A:起始值1,数据库B:起始值2,两台数据库的步长:2。效果:数据库A:1,3,5.. 数据库B:2,4,6..
  缺点:在开始设计数据库的时候,应该定好数据库集群的数量。如果新增了一台服务器,步长会发生改变。
    修改自增步长: 注意:会改变数据库所有表
    set @@auto_increment_increment=10; 
    修改起始值
    set @@auto_increment_offset=5;
3.其他:雪花算法或者redis

读写分离:主数据库提供写操作,从数据库提供读操作,这样有效减轻单台数据库压力。主数据库进行写操作后,数据及时同步到所读的数据库,保证读、写数据库一直。MySQL主从复制、Oracle的data grard、SQL Server的复制订阅。

读的数据库权限:只可以做select
写的数据库权限:select/update/create/insert/delect

思考问题:既然想实现读写分离?如何路由到不同数据库进行访问?使用MyCat插件。 
MyCat原理:会拦截客户端的所有jdbc请求,根据SQL语句判断转发到不同的数据库执行。类似Nginx.MyCat可以实现数据库反向代理,隐藏真实数据库ip地址。
           

联合索引为什么需要遵循左前缀原则?
create table user_details(
    id int(11) default null;
    user_name value(50) default null;
    user_phone value(11) default null;
    primary_key (id,user_name)
)engine=InnoDB default charset=utf8;


联合主键索引:id,user_name
insert into user_details value(1,"aaa",111);
insert into user_details value(1,"bbb",111); // 这条可以插入,user_name值不一样
insert into user_details value(1,"bbb",111); // 这条可以不插入,数据库已包含相同的id和user_name
只要id+user_name 保证是唯一即可

左前缀指的就是第一个索引,id

执行计划查询索引使用情况
explain select * from user_details where id = 1 ; //走索引
explain select * from user_details where id = 1 and user_naem = 'aaa'; //走索引
explain select * from user_details where user_naem = 'aaa'; //全表扫描

MySQL如何定位慢查询?
mysql数据库配置慢查询
参数说明:
slow_query_log 慢查询开启状态
slow_query_log_file 慢查询日志存放位置(一般设置为MySQL的数据库存放目录)
long_query_time 查询超过多少秒才记录

1. 慢查询查询配置
show variables like 'slow_query%'
2. 查询慢查询限制时间
show variables like 'long_query_time'
3. 将slow_query_log全局变量设置为"NO"状态,开启。默认OFF关闭
set global slow_query_log = 'NO'
4. 查询超过1秒就记录
set global long_query_time = 1;
5. 查询执行慢的SQL语句: cat /var/lib/mysql/localhost-show.log
6. 重启mysql:service mysqld restart

阿里云数据库自带慢查询监控或者运维人员有查看权限。

MySQL主从复制原理:
MySQL的主从复制是MySQL本身自带的一个功能,不需要额外的第三方软件就可以实现。其复制功能不是copy文件来实现的,而是借助binlog日志文件里面的SQL命令实现的主从复制。可以理解为再master端执行了一条SQL命令,那么在salve端同样会执行一遍,从而达到主从复制的效果。
从库生成两个线程,一个I/O线程,一个SQL线程:
i/o线程去请求主库的binlog,并将得到的binlog日志写到relaylog(中继日志)文件中;
SQL线程,会读取relay log文件中的日志,并解析成具体操作,来实现主从的操作一直,从而最终数据一致。

主库会生成一个log dump线程,用来给从库i/o线程传binlog;

误区:
MySQL主从复制是MySQL本身自带的功能
MyCat是做读写分离
先有主从复制,再有读写分离

客户端insert/update/delete -> masterMysql ->将SQL语句写入到二进制日志文件(binlog)
salveMysql 开启IO线程请求获取binlog <- 长连接 -> masterMysql开启logDump线程发送binlog给salveMysql  

从节点SQL线程作用:就是IO线程获取到二进制执行文件之后,通过SQL线程进行执行该文件。

从节点同步主节点网络产生延迟时,如果误差超过几秒,数据产生脏读,需要优化:半行和并行。


动态切换数据源

学习编程思想:学习编程如果掌握了思维的话,实际上就是一个公式。

动态数据源核心配置
在springboot2.0中引入了AbstractRoutingDataSource,该类充当了DataSource的路由中介,能在运行时,根据某种key值来动态切换到真正的DataSource上。
1. 项目中需要集成多个数据源分别为读和写的数据源,绑定不同的key。
2. 采用aop技术进行拦截业务逻辑层方法,判断方法前缀是否需要写或者读的操作。
3. 如果方法的前缀是写操作的时候,直接切换为写的数据源,反之切换为读的数据源,也可以自己定义注解进行封装。


动态数据源与多数据源的区别:

多数据源:以分包形式区分
动态数据源:在jvm进行不断的切换。动态数据源包含多数据源,多数据源并不是动态数据源。

1,首先在项目中,有两个数据源 分别为读和写的数据源,都要注册到RoutingDataSource,(map形式)。
2. 使用aop技术拦截业务逻辑层方法的前缀,判断该方法是否需要读或者写的操作。前缀比如 select get find
3. 如果是写的操作,传的一个key给RoutingDataSource指明写的数据源

流程图:
业务逻辑层(AOP拦截,区分前缀) --》根据前缀传递对应的数据源key到RoutingDataSource -》根据不同的数据源key调用不同的数据源 读数据源/写数据源。

读数据源:selectDataSource
写数据源:updateDataSource

数据结构可视化(旧金山大学工具):https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

为什么mysql底层使用b+数
b+数相比b数,新增叶子节点与非叶子节点关系,叶子节点中包含了key和value,非叶子节点中只包含了key.
所有相邻的叶子节点包含非叶子节点,使用链表进行结合,有一定顺序,从而范围查询效率非常高。

Myisam与InnoDB B+数的区别,都采用B+Tree。
Myisam:使用叶节点value存放行数的地址,在通过地址定位到数据。
InnoDB: 使用叶节点value存放行的data数据,相比Myisam效率高,但占硬盘大小。

InnoDB一棵B+树可以存放多少行数据?约2千万

索引文件如何查看
默认数据与索引文件位置 /var/lib/mysql
.myd 即my data,表数据文件
.myi 即my index,索引文件
.log 日志文件

索引为什会失效?应该注意哪些事项?

1. 索引无法存储null值
2. 如果条件中有or,即使其中有条件带索引也不会使用(尽量少用or),要想使用or,有想让索引生效,只能将or条件中的每个列都加上索引
3. 对应多列索引,不是使用第一部分,则不会使用索引
4. like查询以%开头
5. 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不要使用索引


explain 执行计划, type:ALL 全表扫描,rows:扫描数据

explain select * user_details where user_name like '%aa' 全表扫描
explain select * user_details where user_name like 'aa%' 索引扫描

替代like:
1. SELECT `column` FROM `table` WHERE LOCATE('keyword', `field`)>0
2. SELECT `column` FROM `table` WHERE POSITION('keyword' IN `filed`)
3. SELECT `column` FROM `table` WHERE INSTR(`field`, 'keyword' )>0 


分布式解决方案

1. SpringCloud Config 分布式配置文件中心
   目前没有公司使用SpringCloud自带分布式配置文件中心,设计不是很人性化。
2. 为什么要使用分布式配置中心
   产生背景:在微服务当中,可能有几百个服务,如果使用传统的方式管理配置文件,管理起来非常复杂。如果生产环境配置文件,需要发生改变的时候,需要重新打war包,重启服务器重新部署。
3. 什么是分布式配置中心
   在微服务中使用同一个服务器管理所有服务配置文件信息,能够实现后台可管理。当服务器正在运行的时候,如果配置文件需要发生改变,可以实现不需要重启服务器,实时更改配置文件信息。
4. 分布式配置中心框架有哪些?
   阿波罗(携程) 有图形界面后台可管理配置文件信息,配置文件信息存放在数据库里面
     SpringCloud Config 没有后台可管理配置文件信息,配置文件信息存放在版本控制器里面(git|svn)。
   使用Zookeeper实现分布式配置中心,持久节点信息+事件通知

热部署,其实底层还是会重启服务器,不适合生产环境,只适合本地开发测试。


1. SpringCloud Config分布式配置中心原理 

   提交配置文件信息 -》 git|svn服务器(存放持久化配置文件信息)《- configServer服务器(获取git环境上配置文件信息)
     configClient1,2,3集群从configServer服务器获取配置文件信息后缓存到客户端 -》configServer服务器

2. 首先分析分布式配置中心需要哪些组件
   2.1 web管理系统 -- 后台可以使用图形界面管理配置文件。SpringCloud Config没有图形化管理配置文件的后台
   2.2 存放分布式配置文件服务器(持久化存储服务器) -- 使用版本控制器存放配置文件信息,使用git环境。
   2.3 ConfigServer缓存配置文件服务器(临时缓存存放),不能每次都从git环境。
     2.4 ConfigClient读取ConfigServer配置文件信息 

SpringCloud把微服务所有的解决方案都整合好了,全家桶
分布式配置中心
分布式锁
分布式任务调到平台
分布式事务
分布式日志收集...

分布式日志收集系统ELK+Kafka 实现分布式日志收集原理

使用SpringAop进行日志收集,然后通过kafka将日志发送给logstash,logstach再将日志写入elasticsearch,这样elasticsearch就有了日志数据了,最后,则用kibana将存放在elasticsearch中日志数据显示出来,并且可以做实时的数据图表分析等。

为什么在分布式情况下,需要实现分布式日志收集?
在生产环境下,服务器采用集群的话,查询日志不方便,查询日志非常繁琐,效率非常低。

疑问:为什么需要使用elk+kafka实现分布式日志收集?

输入过程(采集过程):logstash读取本地日志文件
Tomcat1,2,3的日志文件 使用aop拦截日志,发布消息到生产者 --》kafka(Topic)《-- logstash1,2,3(从kafka订阅消息,格式化后存放到ES中) --》 elasticsearch1,2,3 《----  kinbana展示数据(读取ES数据形成报表分析)

创建项目 basics-elk-kafka

ELK: 
elasticsearch(存放日志) 可以存储足够多的日志数据。
logstash(采集,搬运工) 做日志对接,接收应用系统log,然后将其写入到elasticsearch,并且支持多种log渠道。输入、以json格式输出、过滤器功能。
kibana(展示数据) 对存放在elasticsearch中的log数据进行:数据展现、报表展现、并且是实时的。


设计模式相关

策略模式:有共同的抽象行为,具体不同的行为称为不同的策略,最终可以使用Context上下文获取对应策略。
应用场景:解决多重if判断问题、聚合支付平台、第三方联合登录、调用不同短信接口等。

责任链模式:每一个业务模块之间相互依赖,比较有关联,每个关联模块称为handler(处理器),使用上一个handler引用到下一个handler,实现一个链表。
应用场景:权限控制、网关权限控制、审批、风控系统等。

模板方法:提前定义好整体的骨架,不同的行为让子类实现,相同的行为直接定义在抽象类中复用。
应用场景:支付异步回调、servlet实现

装饰模式:在不改变原有对象的基础上附加功能,相比子类更灵活。
应用场景:IO流

代理模式:为其他对象提供一个代理,以便控制这个对象的访问。在方法之前和之后做一些处理,实现AOP通知。
应用场景:AOP、事务、日志、权限控制

观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象收到通知并自动更新。其实就是发布订阅模式、发布者发布消息,订阅者获取消息。订阅了就能收到消息,没有订阅就收不到消息。
应用场景:发布订阅、事件通知、Zookeeper、事件监听

门面模式:该模式就是把一些复杂的流程封装成一个接口提供给外部用户。

状态模式:状态模式与策略模式本质上没有很大区别,主要根据行为决定,如果有共同抽象行为使用策略模式,没有共同行为就使用状态模式。

适配器模式:将一个类的方法接口转换成客户希望的另一个接口。
应用场景:mybatis日志收集、提供接口转换。

单例模式:保证在一个jvm中只能有一个实例。
面试谈一下:单例中七种写法,七种写法对比,反射机制可以破解单例、最靠谱单例:枚举、枚举底层是如何实现、为什么反射不能破解枚举有参构造函数。25k


java单例优缺点:
1. 单例类只有一个实例
2. 共享资源,全局使用
3. 节省创建时间,提供性能

缺点:可能存在线程不安全问题

单例七种写法:饿汉、懒汉(非线程安全)、懒汉(线程安全)、双重校验锁、静态内部类、枚举和容器类管理。


Spring相关面试题

BeanFactory和ApplicationContext区别

Spring有两个核心的接口:BeanFactory和ApplicationContext,其中ApplicationContext是BeanFactory的子接口。它们都可以代表Spring容器,Spring容器是生成Bean实例的工厂,并且管理容器中的Bean。

BeanFactory和ApplicationContext的作用和区别?
作用:
1. BeanFactory负责读取bean配置文档,管理bean的加载,实例化,维护bean之间的依赖关系,负责bean的生命周期。
2. ApplicationContext 继承BeanFactory,除了上述BeanFactory所能提供的功能之外,还提供了更完善的框架功能:
   a. 国际化支持
     b. 资源访问:Resource rs = ctx.getResource("classpath:config.properties");
     c. 事件传递:通过实现ApplicationContextAware接口
3. 常用获取ApplicationContext的方法
   FileSymtemXmlApplicationContext: 从文件系统或者指定url的xml配置文件创建,参数为配置文件名或文件名数组。
     ClassPathXmlApplicationContext: 从classspath的xml配置文件创建,可以从jar包中读取配置文件。
     WebApplicationContextUtils: 从web应用的根目录读取配置文件,需要先在web.xml中配置,可以配置监听器或者servlet来实现。
     AnnotationConfigApplicationContext: 是基于注解来使用的,它不需要配置文件,采用java配置类和各种注解来配置,是比较简单的方式,也是大势所趋。

SpringBean生命周期
1. 进入到刷新的方法 refresh();
2. finishBeanFactoryInitialization(); 初始化都有单例对象  
3. preInstantiateSingletons(); 初始化所有的单例对象,注意:是非懒加载 
4. getBean(); -> doGetBean(); 先检查该对象是否有过初始化,没有的话就创建注册到IOC容器中。
5. createBean(); 创建对象 判断对象如果是单例的情况下调用该方法
6. doCreateBean(); 创建IOC对象
7. createBeanInstance(); 使用java反射技术实例化对象 
8. populateBean(); 给对象set方法属性赋值
9. invokeAwareMethods(); 判断bean的类型是否Aware相关依赖,如果存在的情况下回调方法。
10.initializeBean(); 执行初始化的方法(也可以自己自定义初始化的方法。这个方法初始完之后,这个对象一定new成功了)
11. applyBeanPostProcessorsBeforeInitialization() 在初始化方法之前执行处理(增强)
12. invokeInitMethods() 调用自定义init方法(使用java的反射技术)
13. applyBeanPostProcessorsAfterInitialization()  在初始化方法之后执行处理(增强)
14. 正常是用户使用该bean对象
15. 销毁bean

问题:到底是对象的无参构造函数先执行,还是自定义的init方法(9.initializeBean)先执行? 
答:先实例化,再执行自定义方法,对象无参构造函数先执行。
public MemberServiceImpl implements InitializeBean,MemberService{
    public MemberServiceImpl() {
        System.out.println("1.先执行无参构造函数...");
    }
    public void afterPropertiesSet() throws Exception {
      System.out.println("2.再执行自定义bean的init方法...");
    }
}

对象到底通过反射,还是通过其他方式处理的? 反射机制
1. System.out.println("1.先执行无参构造函数..."); 这里断点,F8继续
2. ReflectionUtils.makeAccessible(ctor); 反射工具类 F8继续
3. BeanUtils.instantiateClass(constructorToUse); 反射执行无参构造函数
   instantiateWithMethodInjection(bd,beanName,owner); 也可以通过CGLIB创建对象,但是有前提条件
4. beanInstance = getInstantiatioStrategy().instantiate(mdb,beanName,parent); 通过反射初始化对象
5. BeanWrapper bw = new BeanWrapperImpl(beanInstance); 对象初始化完之后封装成BeanWrapper F8继续
6. instantiateBean(beanName,mbd); 调用这个方法初始化对象 F8继续
7. instanceWrapper = createBeanInstance(beanName,mdb,args); 创建并实例化bean对象
8. exposedObject = initializeBean(beanName,exposedObject,mbd); 执行自定义init方法

@Service和@Component注解区别?
@Service就是在用@Component注解,@Service表示service,@Repository表示dao
使用自定义注解@YzService加上@Component效果一样,一样可以注入bean到spring容器,注意要添加扫包范围@ComponentScan。

@bean和@import 适合导入外部jar包注入bean对象

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    @AliasFor(annotation = Component.class)
    String value() default "";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
    @AliasFor(annotation = Component.class)
    String value() default "";
}


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface YzService {
    @AliasFor(annotation = Component.class)
    String value() default "";
}

SpringAop源码分析
1. 首先启动SpringAop时,会使用@EnableAspectJAutoProxy注解
2. 将@Import(AspectJAutoProxyRegistrar.class) 注入SpringIOC容器中
3. AspectJAutoProxyRegistrar中会注册对象
   BeanId: org.springframework.aop.config.internalAutoProxyCreator
     BeanClass: AnnotationAwareAspectJAutoProxyCreator
4. AnnotationAwareAspectJAutoProxyCreator最为核心:使用后置通知在bean的对象初始化的时候,实现对代理对象的增强。
   AnnotationAwareAspectJAutoProxyCreator 祖宗:AbstractAutoProxyCreator 祖宗是 BeanPostProcessor
5. 被代理对象在初始化的时候,AbstractAutoProxyCreator 经过这样一个类拦截,判断该被代理对象是否有被实现的接口,如果有实现的接口就使用jdk动态代理,如果没有实现的接口则使用cglib动态代理。

JDK动态代理:只能对实现了接口的类产生代理。(实现接口默认JDK动态代理,底层自动切换) UserServiceImpl implements UserService
Cglib动态代理:对没有实现接口的类产生代理对象。生成子类对象。class UserService

如果目标对象实现了接口,如何强制使用CGLIB实现AOP?
 (1)添加CGLIB库,SPRING_HOME/cglib/*.jar
 (2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

Spring声明式事务失效原因

添加@Transactional 注解后走 TransactionAspectSupport 类

TransactionAttributeSource tas = getTransactionAttributeSource(); // 表示bean去获取事务
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);        // 表示拿到了事务,表示手动begin开启事务
    Object retVal;
    try {
        retVal = invocation.proceedWithInvocation();        // 表示执行目标方法 order.addOrder(); F9跳转到order.addOrder()
    }
    catch (Throwable ex) {
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    }
    finally {
        cleanupTransactionInfo(txInfo);
    }
    commitTransactionAfterReturning(txInfo);
    return retVal;
}


当执行到int i = 1/0 时,TransactionAspertSupport catch捕获异常:
completeTransactionAfterThrowing(txInfo,ex);

回滚当前事务:
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());

completeTransactionAfterThrowing(txInfo,ex)执行完毕,抛出异常 throw ex;

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDao orderDao;

    @Transactional
    public void addOrder() {
        order.addOrder();
        // 这里异常,测试事务
        int i = 1/0; 
    }
}


如果下面的order.addOrder() try catch 会不会回滚?不会,目标方法抛出异常才会回滚事务
如果service没有将异常抛出的话,TransactionAspectSupport 不会捕获异常,直接往下走commit, commitTransactionAfterReturning(txInfo);

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDao orderDao;

    @Transactional
    public void addOrder() {
      try{
            order.addOrder();
            int i = 1/0; 
        } catch(Exceptinon e){
        
        }
    }
}


Spring声明式事务源码分析
1. 开启@EnableTransactionManagement 开启事务注解
2. @Import(TransactionManagementConfigurationSelector.class)
3. 继续加载AutoProxyRegistrar、ProxyTransactionManagementConfiguration对象
4. 会将该类InfrastructureAdvisorAutoProxyCreator 注册到IOC容器
5. InfrastructureAdvisorAutoProxyCreator祖宗是BeanPostProcessor,所以得知bean对象初始化的时候该对象与AOP实现一样的功能。
6. IOC容器中注册InfrastructureAdvisorAutoProxyCreator
7. ProxyTransactionManagementConfiguration 配置类的对象注册到IOC容器中。TransactionInterceptor 祖宗是 MethodInterceptor
8. 当SpringAop在调用的时候,会执行到TransactionInterceptor的invoke
9. TransactionInterceptor 封装了事务的开启、提交、回滚。


SpringBean循环依赖

依赖注入:赋值
循环依赖:A依赖B,B依赖A

为什么A对象创建的时候,B对象要先创建?因为A对象要等B对象初始化完了之后,才能赋值给A。
反过来B再创建的时候,要先创建A对象,这样就形成循环,死锁现象。循环依赖注入。

@Service 
pubilc class AService {
    @Autowired
    pubilc BService bService;
}

@Service 
pubilc class BService {
    @Autowired
    pubilc AService aService;
}


解决使用三级缓存,从缓存中获取。
1. doGetBean创建bean对象
2. getSingleton(beanName) 获取缓存对象
注意: 完整对象概念:对象已经实例化成功并且所有属性已经赋值。
singletonObjects 一级缓存完整对象
earlySingletonObjects 二级缓存 缓存婴儿对象(对象初始化完成,属性未赋值)
singletonFactories:三级缓存,也是存放婴儿对象

理解概念:
1. 完整对象表示该对象实例化完成并且所有的属性已经赋值。
2. 婴儿对象(提前对象)对象已经实例化完成,但是属性没有赋值的。

既然要创建对象,先反射走无参构造函授,必须对象先实例化完成,才可以给对象属性赋值

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    才能够查询二级缓存
}
isSingletonCurrentlyInCreation: 标记该对象开始创建

第一次返回singletonObject null.

3. doGetBean 判断bean null走else,执行getSingleton(String beanName, ObjectFactory<?> singletonFactory) 重载方法
执行:beforeSingletonCreation(beanName); 

this.singletonsCurrentlyInCreation.add(beanName) 标记该对象开始创建了

4. 开始执行createBean() 创建对象 -》继续调用doCreateBean()方法创建对象 -》createBeanInstance()反射实例化对象,这里婴儿对象,属性还没赋值。
   populateBean(); 表示给属性赋值 -》initializeBean() 这步执行完之后,才是完整对象

5. addSingletonFactory 将婴儿对象(不完整对象,也就是只是实例化完成,但是属性没有赋值) 存放到三级缓存

6. A对象已经存放到三级缓存中,开始给的对象属性赋值的时候,需要创建B对象,B对象(婴儿对象)实例创建好后,给属性赋值时,从三级缓存取A对象(婴儿对象,还未初始化完毕)。

7. ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。
Spring在获取ClassA的实例时,不等ClassA完成创建就将其曝光加入正在创建的bean缓存中。
在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,
则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。


createBean (AbstractAutowireCapableBeanFactory) 
// 返回创建好的对象实例
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
    logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;

这里将对象注册到一级缓存(DefaultSingletonBeanRegistry)
if (newSingleton) {
    addSingleton(beanName, singletonObject);
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
      // 注册到一级缓存
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

// 存放婴儿对象singletonsCurrentlyInCreation.add
protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

// 判断是否有婴儿对象 isSingletonCurrentlyInCreation()
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    // 是婴儿对象执行该方法
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
          // 如果一级缓存没有该对象的情况下,将该对象存放到三级缓存中,(存放婴儿对象,对象实例化完成,属性未赋值)
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

DefaultSingletonBeanRegistry

@Override
public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

SpringBean循环依赖源代码分析:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
  // 1. 判断一级缓存对象集合(缓存完整对象) 对象已经创建成功,并且所有属性都已经赋值成功
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
          // 2. 如果一级缓存没有 查询二级缓存是否有缓存对象
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
              // 3. 如果二级缓存也没有,查询三级缓存
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                  // 4. 如果三级缓存有的情况下,将数据放入到二级缓存中
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}


模拟Spring循环依赖:

// 缓存婴儿对象
Map<String,Object> singletonFactories = new HashMap<>();
singletonFactories.put("aService",new AService());
singletonFactories.put("bService",new BService());
// 从三级缓存查询并赋值
AService aService = (AService)singletonFactories.get("aService");
BService bService = (BService)singletonFactories.get("bService");
// java引用传递
aService.bService = bService;
bService.aService = aService;

项目中单例情况下,循环依赖不报错。

如果项目中对象必须是多例,而且必须要循环依赖怎么解决?(正常多例循环依赖会报错)  使用set ,并指定

@Service 
@Scope("prototype")
pubilc class AService {
    pubilc BService bService;
    pubilc void setBService(BService bService){
        this.bService = bService;
    }
}

@Service 
@Scope("prototype")
pubilc class BService {
    pubilc AService aService;
    pubilc void setAService(AService aService){
        this.aService = aService;
    }
}

AService aService = applicationContext.getBean("AService",AService.class);
BService bService = applicationContext.getBean("BService",BService.class);
aService.setBService(bService);
bService.setAService(aService);


SpringMvc:

如果证明Servlet线程不安全?
在servlet中定义变量,所有的线程都可能使用这些变量


SpringMvc执行流程:
1. 客户端请求被DispatcherServlet(前端控制器)接收
2. DispatcherServlet请求HandlerMapping查询Handler
3. HandlerMapping根据请求URL查找Handler,将Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回给DispatcherServlet
4. DispatcherServlet请求HandlerAdapter执行Handler
5. HandlerAdapter调用Handler的方法做业务逻辑处理
6. HandlerAdapter处理完Handler会生成一个ModelAndView对象
7. 将ModelAndView对象返回给DispatcherServlet
8. DispatcherServlet将获取的ModelAndView对象传给ViewResolver视图解析器,请求进行视图解析
9. ViewResolver将逻辑视图解析成物理视图View,返回给DispatcherServlet
10.DispatcherServlet根据View进行视图渲染(将模型数据填充到视图中)
11.DispatcherServlet将渲染后的视图响应给客户端

DispatcherServlet是springMvc非常核心的类,客户端所有的请求都会转发到DispatcherServlet实现执行,最终能够执行请求定义方法。
DispatcherServlet 继承FrameworkServlet,祖先是HttpServlet,其实就是一个Servlet类,包装了根据url,能够映射找到SpringMvc定义的请求方法。

面向对象基本思想 重写。先走父类,再走子类。源码分析先看父类HttpServlet,再找到最后的子类DispatcherServlet。

请求进来先执行HttpServlet.service()方法,判断方法类型,是get类型调用doGet(); post类型调用doPost();

流程:
HttpServlet Service判断请求如果Get请求,先执行自己的doGet,该方法是重写,再执行子类FrameworkServlet doGet方法
FrameworkServlet doGet中调用子类DispatcherServlet doService方法。
也就是说springmvc所有请求都先进入DispatcherServlet.doService方法,该方法中调用doDispatch()方法,SpingMvc最核心的代码

HttpServlet Service -》判断请求如果Get请求 -》FrameworkServlet doGet方法 -》DispatcherServlet doService() -》DispatcherServlet doDispatch()

HttpServlet
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        if (method.equals(METHOD_GET)) {
                long lastModified = getLastModified(req);
                if (lastModified == -1) {
                        doGet(req, resp);
                } else {
                        long ifModifiedSince;
                        try {
                                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                        } catch (IllegalArgumentException iae) {
                                ifModifiedSince = -1;
                        }
                        if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                                maybeSetLastModified(resp, lastModified);
                                doGet(req, resp);
                        } else {
                                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                        }
                }
        } else if (method.equals(METHOD_HEAD)) {
                long lastModified = getLastModified(req);
                maybeSetLastModified(resp, lastModified);
                doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
                doPost(req, resp);
        } else if (method.equals(METHOD_PUT)) {
                doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
                doDelete(req, resp);
        } else if (method.equals(METHOD_OPTIONS)) {
                doOptions(req,resp);
        } else if (method.equals(METHOD_TRACE)) {
                doTrace(req,resp);
        } else {
                String errMsg = lStrings.getString("http.method_not_implemented");
                Object[] errArgs = new Object[1];
                errArgs[0] = method;
                errMsg = MessageFormat.format(errMsg, errArgs);
                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
}

FrameworkServlet
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    processRequest(request, response);
}

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    initContextHolders(request, localeContext, requestAttributes);
    try {
        doService(request, response);        // DispatcherServlet.doService()方法
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }
    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

DispatcherServle
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
    if (this.flashMapManager != null) {
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }
    try {
        doDispatch(request, response); // SpingMvc最核心的代码
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}


MyBatis 

如果想看懂源码需要哪些基础?
java反射机制、泛型、自定义注解、设计模式、面向对象思想:封装、继承、多态,断点调试等

SSM框架源码: 代理模式、适配器、策略、工厂、建造者等


osi七层网络模型
应用层:Http协议、文件服务器、邮件服务器
表示层:数据转换解决不同系统之间的兼容问题
会话层:建立与应用程序的会话连接
传输层:提供端口号和传输的协议(TCP和UDP)
网络层:为我们的数据实现路由 路由器、交换机
数据链路层:传输的地址的帧以及错误的检测
物理层:以二进制的形式,在物理机器上实现传输(光纤、专线、各种物理介质实现)


传输层非常核心的内容:socket技术

什么是Socket技术?
任何的编程语言都是支持socket(网络编程的技术)技术开发,目的就是解决两个应用程序之间通讯的问题。
注意:socket不属于某种协议,只是网络编程技术。

socket技术支持两种协议(TCP和UDP)

TCP和UDP区别:

TCP 协议是一个面向连接可靠的协议,因为建立连接的时候必须通过三次握手才可以实现数据传输,所以数据不会丢失。
    应用场景:http协议、rpc协议
UDP 协议是面向无连接协议,udp在通讯的时候不需要接受对方是否存在,属于不可靠传输,可能会存在数据丢包的问题

白话文描述tcp三次握手
1. 第一次握手:客户端会像服务器端 发送消息问:你在不在?
2. 第二次握手:服务器端收到了客户端咨询的("你在不在"),并回复客户端,就说:"我在呢"。
3. 第三次握手:客户端收到服务器端("我在呢")的回复,就会给服务器端发送消息:"好的,我们开始传输数据"。
三次握手成功之后就开始建立传输数据。

Syn(建立连接)、Ack(确认标记)、fin(终止标记)

1. 第一次握手:客户端会像服务器端 发送代码 syn=1 并生成一个随机数 seq=x(随机数) 发送到服务。
2. 第二次握手:服务器端确认收到syn和x值,回复给客户端 ack=x+1 和seq=y(随机数) 发送给客户端。
3. 第三次握手:客户端收到syn、ack、y的值之后向服务器发送ack=y+1 此包发送完毕之后就可以开始建立连接
三次握手成功之后,就开始传输数据

三次握手主要目的:确保连接可靠
四次挥手 可靠关闭连接

白话文翻译四次挥手:
第一次挥手:客户端向服务端发送一个释放连接的通知
第二次挥手:服务端接受到释放通知之后,告诉客户端说等一下,因为可能存在其他的数据没有发送完毕,等数据全部传输完毕之后就开始 关闭连接。
第三次挥手:服务器端所有的数据发送完毕之后,就告诉客户端说现在可是释放链接了。
第四次挥手:客户端确认是最终释放连接通知,ok 就开始向服务器端发送我们可以开始关闭连接啦。

关闭连接:
第一次挥手:客户端向服务器端发送释放的报文,停止发送数据 fin=1,生成一个序列号seq=u。
第二次挥手:服务器端接收到释放的报文之后,发送ack=u+1; 随机生成seq=v给客户端,当前状态为关闭等待状态。
            客户端收到了服务器端确认通知之后,此时客户端就会进入到终止状态,等待服务器发送释放报文。
第三次挥手:服务器端最后数据发送完毕之后,就向客户端发送连接释放报文,fin=1,ack=u+1 当前为半关闭状态,随机生成一个随机数据seq=w。
第四次挥手:客户端必须发出确认,ack=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。
            注意:此时TCP连接还没有释放,必须是2**MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入closed状态。


Http协议是一种超文本传输协议基于TCP/IP协议实现的包装,应用广泛:img、css、html
Http协议  默认端口号 80  明文传输
Https协议 默认端口号 443 加密传输
Https协议比Http协议安全 ssl+证书实现传输

Http协议特征:
1. 无状态,没有记忆的会话。想有状态使用:token令牌、jwt
2. 请求(request)与响应(response)模型
3. 简单快捷
4. 灵活,可以传输任何类型

请求
    请求头
    请求类型
    请求方法
响应
    响应头
    响应体

你刚才请求的数据是:
GET / HTTP/1.1
Host: 127.0.0.1:8888
Connection: keep-alive        --> 表示长连接
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

Http协议版本
Http协议1.0 : 规定客户端与服务器端传输完成之后,立马关闭连接;请求比较多的情况下频繁关闭,消耗时间。 短连接
Http协议1.1 : Http协议1.1开始就支持长连接。长连接

长连接与短连接

长连接:客户端与服务器端建立连接之后,不会立马关闭连接,会保持一定复用机制。默认情况下在300s空闲情况下自动断开。
短连接:客户端与服务器端发送消息之后,立马关闭连接,如果频繁发送Http请求,可能消化服务器端资源;

Http协议基于tcp包装,tcp包括三次握手和四次挥手。


Redis是如何单线程能非常好的支持并发?
Redis的底层采用Nio中的IO复用机制,能够非常好的支持这样的并发,从而保证线程安全问题。
但是Nio在不同的操作系统上实现的方式有所不同:
在windows操作系统使用select,实现轮训时间复杂度是o(n),而且还存在空轮训的情况,效率非常低,其次是默认对轮训的数据有一定限制,所以支持上万的tcp连接是非常难的。
在Linux操作系统采用epoll实现事件驱动回调,不会存在空轮训的情况,只对活跃的socket连接实现主动回调,这样性能上有大大的提升,所以时间复杂度是o(1)
注意:windows操作系统没有epoll,只有Linux系统才有epoll.
所以,为什么nginx、redis都能够非常好的支持高并发,最终都是Linux中IO多路复用机制epoll.

阻塞式:
    当没有获取到数据的时候,整个应用可能会产生阻塞,放弃了cpu执行,无法去做其他的事情。
非阻塞式:
  不管是否有获取到数据,都立马必须告诉一个结果,如果没有获取到数据的情况下,返回一个错误标记,根据错误的标记不断的轮训(重试)。


Bio 就是一个阻塞式一个io操作

Main(主)线程单线程情况下通讯模式
                 阻塞                  当执行IO操作的时候整个程序阻塞
客户端   socket.accept()方法  socket.getInputStream().read(bytes);


使用多线程实现伪异步io优缺点
伪异步IO:
客户多 -》main函数(socket.accept()服务器)-》Thread1,2,3
缺点:
有并发来了的时候,处理并发操作都靠线程,不管怎样复用线程池,cpu频繁调度情况下,非常消耗服务器资源。
假如一万个请求,就创建一万个线程,对服务器造成内存溢出。使用线程池技术复用,最大线程数100

Redis是一个单线程,但是能够支持高并发。


Nio (no blooking io)同步的,非阻塞式io:
java语言在jdk1.4版本推出一个io方法,就是对原来bio(阻塞式 悲观的)实现了优化。

nio核心:面向于缓冲区、基于通道实现非阻塞式io、多路io复用实现(选择器)

Bio与Nio区别(都是同步):
Bio是一个阻塞式的io,它是面向于流传输,也就是根据每个字节实现传输,效率非常低。喝水一滴一滴喝。
Nio是一个非阻塞式io,它是面向于缓冲区传输。最大亮点:io多路复用机制。喝水拿杯子(缓冲区)喝。

IO多路复用:
多路:实际指的就是多个不同的tcp连接。复用:一个线程可以维护多个不同的io操作。好处:占用cpu资源非常小,保证线程安全问题


Nio核心组件:
Channel  通道 数据传输都是经过管道
Selector 选择器 channel都会统一注册到Selector
Buffer   缓冲区

                                                            (服务器端)
客户端1,2,3 -》Channel管道 - Selector(选择器) -》Buffer(缓存区) -》Thread1,2,3

Redis是单线程为什么能够支持高并发?
Redis底层采用nio多路io复用机制,实现对多个不同的连接(tcp)实现io的复用。
备注:给面试官解释下多路复用,使用一个线程维护多个不同的io操场,原理使用nio的选择器,将不同的Channel统一交给Selector(选择器)

nio的实现在不同的操作系统上存在差别:
windows使用select实现轮训机制。
linux使用epoll实现轮训机制。
备注:windows操作系统没有epoll,select实现轮训机制从头查到尾,时间复杂度是o(n),而且也会存在空轮训,效率非常低,其次,对轮训有一定限制,所以很难支持上万tcp连接。 
所以linux操作系统就出现epoll,实现事件驱动回调形式通知,不会存在空轮训的情况,只是对活跃的socket实现主动回调,不会存在空轮训,这样性能有很大提升,所以时间复杂度是o(1)。

所以为什么Nginx、redis能够支持非常高的并发,最终都是靠的linux系统的io多路复用机制epoll


NIO多路复用实现原理

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * ClassName: NioServerTcp
 * Description: 实现 多路io复用入门demo,一个线程可以实现维护多个不同的io操作 要求:必须非阻塞式
 * Author: yz
 * Date: Created in 2019/11/1 15:48
 */
public class NioServerTcp {
    private static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    // 多路复用集合,存放多个不同的tcp连接
    private static List<SocketChannel> socketChannelList = new ArrayList<>();
    public static void main(String[] args) throws IOException {
        // 1. 创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 2. 绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(8080));
        // 3. 非阻塞式
        serverSocketChannel.configureBlocking(false);
        // 4. 非阻塞式(不会阻塞)
        while (true){
            SocketChannel socketChannel = serverSocketChannel.accept();
            // 非阻塞式:不管是否有获取到数据,都必须立马返回一个结果,如果没有数据的情况下返回错误码
            // socketChannel==null 不断循环重试
            if(socketChannel !=null ){
                socketChannel.configureBlocking(false);//设置非阻塞
                socketChannelList.add(socketChannel); // 将socketChannel存到到集合
            }
            // 主动的轮训检查连接是否有数据,这样可以实现一个线程可以维护多个不同的io操作
            for (SocketChannel scl : socketChannelList) {
                int read = scl.read(byteBuffer);
                if(read>0){
                    byteBuffer.flip();
                    Charset charset = Charset.forName("UTF-8");
                    String receiveText = charset.newDecoder().decode(byteBuffer.asReadOnlyBuffer()).toString();
                    // 这里都是main主线程,一个线程管理过个io
                    System.out.println(Thread.currentThread().getName()+" receiveText:"+receiveText);
                                        socketChannelList.remove(scl);
                }
            }
        }
    }
}


import java.io.IOException;
import java.net.*;
import java.util.Scanner;

/**
 * ClassName: ChientTcpSocket
 * Description: 测试NioServerTcp,启动多个ChientTcpSocket,同时执行,检测NioServerTcp效果。
 * Author: yz
 * Date: Created in 2019/10/31 22:38
 */
public class ChientTcpSocket {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket();
        SocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),8080);
        socket.connect(address);
        while (true){
                        // 输入内容
            Scanner scanner = new Scanner(System.in);
            socket.getOutputStream().write(scanner.next().getBytes());
        }
    }
}

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * ClassName: NIOServer
 * Description: java原生nio实现,启动 ChientTcpSocket 测试。 nio在最后都是被netty封装,性能没有netty好
 * Author: yz
 * Date: Created in 2019/11/1 16:34
 */
public class NIOServer {
    // 创建一个选择器
    private Selector selector;

    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8080);
        server.listen();
    }

    public void initServer(int port) throws IOException {
        // 获得一个ServerSocketChannel通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 设置通道为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 将该通道对应的ServerSocket绑定到port端口
        serverSocketChannel.bind(new InetSocketAddress(port));
        // 获得一个通道管理器
        this.selector = Selector.open();
        // 将管道注册到选择器,并注册OP_ACCEPT事件 accept:接收
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    public void listen() throws IOException {
        System.out.println("服务器启动成功!");
        // 轮训访问selector
        while (true){
            // 当注册事件到达时,方法返回,否则,该方法一直阻塞,可设置超时时间
            int select = selector.select();
            if(select == 0){
                continue;
            }
            // 发出一个tcp两种方式:建立连接 发送消息
            // 获取selector中选中项的迭代器,选中的项为注册的事件
            Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()){
                SelectionKey key =  ite.next();
                // 删除已选的key,以防止重复处理
                ite.remove();
                // 发出一个消息
                if(key.isAcceptable()){
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    // 获得和客户端连接的通道
                    SocketChannel channel = server.accept();
                    // 设置成非阻塞
                    channel.configureBlocking(false);
                    // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限
                    channel.register(this.selector,SelectionKey.OP_READ);
                }else if (key.isReadable()){ // 获取到了可读事件
                    read(key);
                }
            }
        }
    }

    public void read(SelectionKey key) throws IOException {
        // 服务器可读取消息,得到事件发生的socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(512);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg =  new String(data).trim();
        System.out.println("服务器收到消息:"+ msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("utf-8"));
        channel.write(outBuffer);
    }
}


Netty入门

回顾nio:
nio核心设计思想:非租塞式、io多路复用原则(选择器)、缓冲区(提高读写效率)

为什么放弃java原生NIO选用Netty框架?

为什么不选择Java原生NIO编程的原因:
1.NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
2.需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序。
3.可靠性能力补齐,工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大。
4.JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有被根本解决。
5.由于上述原因,在大多数场景下,不建议大家直接使用JDK的NIO类库,除非你精通NIO编程或者有特殊的需求。在绝大多数的业务场景中,我们可以使用NIO框架Netty来进行NIO编程,它既可以作为客户端也可以作为服务端,同时支持UDP和异步文件传输,功能非常强大。

为什么选择Netty:
Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架Avro就使用了Netty作为底层通信框架,其他还有业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。

通过对Netty的分析,我们将它的优点总结如下。
◎  API使用简单,开发门槛低;
◎  功能强大,预置了多种编解码功能,支持多种主流协议;
◎  定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
◎  性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
◎  成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
◎  社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;
◎  经历了大规模的商业应用考验,质量得到验证。Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。
正是因为这些优点,Netty逐渐成为了Java NIO编程的首选框架。

Netty执行流程(两个线程池):

客户端1,2,3 -》 boosGroup线程池(只负责接收请求,不做业务处理)-》accepe -》workGroup线程池(负责处理客户端请求io操作)-》handler1,2,3 

如果boosGroup线程池一边接收,一遍处理请求,会导致整个性能不是很好,所以,采用两个线城池分工明确,效率高。

比如nginx启动时,也会启动两个线程,marst、worker。
1、Nginx 在启动后,会有一个master进程和多个相互独立的worker进程。
2、接收来自外界的信号,向各worker进程发送信号,每个进程都有可能来处理这个连接。
3、master进程能监控 worker进程的运行状态(专门处理请求),当 worker 进程退出后(异常情况下),会自动启动新的 worker 进程。

在nio中,服务器和客户端建立通道连接时,服务器端serversocketchannel、客户端socketchannel是两个不同的概念?
这个其实非常好理解,在nio通讯中所有的客户端的数据必须通过channel管道发送到selector选择器统一管理和维护,服务器端selector选择器管理多个不同的客户端的socketchannel。服务器接受channel就是serversocketchannel。可以这么理解serversocketchannel就是管理所有客户端的socketchannel。

Nio执行流程:
客户端1,2,3 -》Channel管道 -》Selector选择器 -》Buffer缓冲区 -》Thread1,2,3

Channel管道:对应Netty socketchannel
Selector选择器:对应Netty serversocketchannel


<dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.42.Final</version>
</dependency>

基于Netty创建服务器端

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * ClassName: NettyServer
 * Description: 基于Netty创建服务器端
 * Author: yz
 * Date: Created in 2019/11/1 22:58
 */
public class NettyServer {
    private static int inetPort = 8080;
    public static void main(String[] args) {
        // 使用netty创建服务器的时候 采用两个线程池
        // boos线程池 负责接收请求
        NioEventLoopGroup boosGroup = new NioEventLoopGroup();
        // 工作线程池 处理请求读写操作
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        // 创建serverBootstrap
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // NioServerSocketChannel 标记当前是服务器端
        serverBootstrap.group(boosGroup,workGroup).channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 处理每个请求handler
                        sc.pipeline().addLast(new ServerHandler());
                    }
                });
        try {
            // 绑定端口号
            ChannelFuture channelFuture = serverBootstrap.bind(inetPort).sync();
            System.out.println("服务器启动成功:"+inetPort);
            // 等待监听请求
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 优雅的关闭线程池
            workGroup.shutdownGracefully();
            boosGroup.shutdownGracefully();
        }
    }
}

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

/**
 * ClassName: ServerHandler
 * Description: 处理每个请求handler
 * Author: yz
 * Date: Created in 2019/11/2 13:45
 */
public class ServerHandler extends SimpleChannelInboundHandler {
    /**
     * 获取数据
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("request:" + request);
        // 响应代码 服务端回复
        ctx.writeAndFlush(Unpooled.copiedBuffer("你好,这里是服务端",CharsetUtil.UTF_8));
    }
}


import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * ClassName: SocketClient
 * Description: 测试NettyServer
 * Author: yz
 * Date: Created in 2019/11/2 13:57
 */
public class SocketClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",8080);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("你好啊,我是客户端".getBytes());
        while (true){
            // netty服务端采用长连接,这里close,那里报错。
        }
//        outputStream.close();
//        socket.close();
    }
}

基于Netty创建客户端

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

/**
 * ClassName: NettyClient
 * Description: 基于Netty创建客户端
 * Author: yz
 * Date: Created in 2019/11/2 14:06
 */
public class NettyClient {
    public static void main(String[] args) {
        // 创建netty线程池 NioEventLoopGroup
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group).channel(NioSocketChannel.class)
                .remoteAddress(new InetSocketAddress("127.0.0.1",8080))
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        sc.pipeline().addLast(new ChientHandler());
                    }
                });
        try {
            // 发起同步连接
            ChannelFuture channelFuture = bootstrap.connect().sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 关闭netty线程池
            group.shutdownGracefully();
        }
    }
}


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

/**
 * ClassName: ChientHandler
 * Description: 客户端handerl
 * Author: yz
 * Date: Created in 2019/11/2 14:11
 */
public class ChientHandler extends SimpleChannelInboundHandler {

    /**
     * 活跃的通道
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 向服务器端发送请求
        ctx.writeAndFlush(Unpooled.copiedBuffer("你好,这里是客户端,请求着陆",CharsetUtil.UTF_8));
    }

    /***
     * 读写消息
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String response = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("客户端获取服务器端响应的消息:" + response);
    }
}


启动NettyServer、NettyClient进行测试。

Netty拆包、粘包

ChientHandler修改代码:

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 现在客户端发送10条消息,那么客户端已要收到10条消息
        for (int i = 0; i < 10; i++) {
                // 向服务器端发送请求
                ctx.writeAndFlush(Unpooled.copiedBuffer("你好,这里是客户端,请求着陆",CharsetUtil.UTF_8));
        }
}

启动NettyServer、NettyClient进行测试:

NettyServer收到消息(10条消息粘在了一起):request:你好,这里是客户端,请求着陆你好,这里是客户端,请求着陆你好,这里是客户端,请求着陆你好,这里是客户端,请求着陆你好,这里是客户端,请求着陆你好,这里是客户端,请求着陆你好,这里是客户端,请求着陆你好,这里是客户端,请求着陆你好,这里是客户端,请求着陆你好,这里是客户端,请求着陆

NettyServer收到消息(1条,应该收到10条):客户端获取服务器端响应的消息:你好,这里是服务端,请等待


什么是粘包(msgmsg)、拆包(msgm sg)?

原因造成:
因为现在的tcp协议采用长连接的形式实现通讯,客户端发送完数据之后,服务器端不会马上关闭连接。

msg
客户端与服务器端建立连接,客户端发送一条消息,服务器马上关闭连接。不会存在粘包、拆包问题。
msg msg
客户端与服务器端建立连接,客户端发送多条消息,服务器马上关闭连接。存在粘包、拆包问题。

什么是粘包:
多次发送的消息,客户端一次合并读取(它既可能由发送方造成,也可能由接收方造成)。 msg msg = msgmsg
什么是拆包:
多次发送的消息,第一次完整的+第二次不完整的第二次不完整 msg msg = msgm sg


为什么会造成粘包与拆包?
Tcp协议和缓冲区造成的。
Tcp协议为了能够高性能传输、发送和接收采用缓存区。
1. 当客户端发送的数据消息 < 服务器端读取的缓存区大小 会发生粘包
2. 当客户端发送的数据消息 > 服务器端读取的缓冲区大小 会发生拆包
3. 服务端不够及时的获取缓存区的数据,会发生粘包。
4. 进行mss(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度 > mss 的时候,会发生拆包。


粘包:
客户端发送两条数据,每条8个字节(16) -》 服务器缓存区,大小1024字节 -》Server一次读取缓存区全部内容,包含客户两次请求。
拆包:
客户端发送两条数据,每条2048个字节(4096) -》 服务器缓存区,大小1024字节 -》Server一次读取缓存区全部内容,包含客户两次请求,读取不完整。


解决粘包和拆包的思路:
1. 以固定的长度发送数据到缓存区,但是,每次消息内容不可能都是1024大小,治标不治本
2. 可以在数据之间设置边界 \n 客户端发送消息时添加\n,服务器端读取的时候以\n进行消息拆分。
3. 使用Netty LineBaseDFrameDecoder编码器解决

设置边界方式解决,服务端、客户端发送消息都添加\n,并且都以\n拆分消息,代码修改(太复杂):

ServerHandler 

/**
 * 获取数据
 * @param ctx
 * @param msg
 * @throws Exception
 */
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        String[] split = request.split("\n");
        for (String s : split) {
                System.out.println("request:" + s);
                // 响应代码 服务端回复
                ctx.writeAndFlush(Unpooled.copiedBuffer("你好,这里是服务端,请等待\n",CharsetUtil.UTF_8));
        }
}


ChientHandler 

/**
 * 活跃的通道
 * @param ctx
 * @throws Exception
 */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 现在客户端发送10条消息,那么客户端已要收到10条消息
        for (int i = 0; i < 10; i++) {
                // 向服务器端发送请求
                ctx.writeAndFlush(Unpooled.copiedBuffer("你好,这里是客户端,请求着陆\n",CharsetUtil.UTF_8));
        }
}

/***
 * 读写消息
 * @param ctx
 * @param msg
 * @throws Exception
 */
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String response = byteBuf.toString(CharsetUtil.UTF_8);
        String[] split = response.split("\n");
        for (String s : split) {
                System.out.println("客户端获取服务器端响应的消息:" + s);
        }
}


使用Netty LineBaseDFrameDecoder编码器解决粘包、拆包。服务端、客户端发送消息一样添加\n,代码修改(代码简洁):

NettyServer

serverBootstrap.group(boosGroup,workGroup).channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 设置编码器,解决粘包、拆包 对发送的数据设置边界 \n \r
                        sc.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        sc.pipeline().addLast(new StringEncoder());
                        // 处理每个请求handler
                        sc.pipeline().addLast(new ServerHandler());
                    }
                });


ServerHandler

/**
 * 获取数据
 * @param ctx
 * @param msg
 * @throws Exception
 */
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("request:" + request);
        // 响应代码 服务端回复
        ctx.writeAndFlush(Unpooled.copiedBuffer("你好,这里是服务端\n",CharsetUtil.UTF_8));
}

NettyClient

bootstrap.group(group).channel(NioSocketChannel.class)
                .remoteAddress(new InetSocketAddress("127.0.0.1",8080))
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 设置编码器,解决粘包、拆包 对发送的数据设置边界 \n \r
                        sc.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        sc.pipeline().addLast(new StringEncoder());
                        sc.pipeline().addLast(new ChientHandler());
                    }
                });

ChientHandler

/**
 * 活跃的通道
 * @param ctx
 * @throws Exception
 */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 现在客户端发送10条消息,那么客户端已要收到10条消息
        for (int i = 0; i < 10; i++) {
                // 向服务器端发送请求
                ctx.writeAndFlush(Unpooled.copiedBuffer("你好,这里是客户端,请求着陆\n",CharsetUtil.UTF_8));
        }
}

/***
 * 读写消息
 * @param ctx
 * @param msg
 * @throws Exception
 */
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String response = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("客户端获取服务器端响应的消息:" + response);
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值