打赏

美团点评面试20190515

1. 自我介绍

2. 项目介绍,项目难点

3. 笔试题研究过吗?

4. Mybatis多参数传递   

//方法1:顺序(索引)传参法
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{0} and dept_id = #{1}
</select>
// #{}里面的数字代表你传入参数的顺序。
// 这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。

// 方法2:@Param注解传参法
public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);
<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>
// #{}里面的名称对应的是注解@Param括号里面修饰的名称。
// 这种方法在参数不多的情况还是比较直观的,推荐使用。

// 方法3:Map传参法
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>
// #{}里面的名称对应的是Map里面的key名称。
// 这种方法适合传递多个参数,且参数易变能灵活传递的情况。


// 方法4:Java Bean传参法
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="com.test.User" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>
// #{}里面的名称对应的是User类里面的成员属性。


//方法5:集合遍历  数组,map,list(多个的话需要@Param注解传参法)
<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

5. 数据库隔离级别

  

并行事务的四大问题:
    1.更新丢失:和别的事务读到相同的东西,各自写,自己的写被覆盖了。(谁写的快谁的更新就丢失了)
    2.脏读:读到别的事务未提交的数据。(万一回滚,数据就是脏的无效的了)
    3.不可重复读:两次读之间有别的事务修改。
    4.幻读:两次读之间有别的事务增删。幻行,顾名思义就是突然蹦出来的行数据。指的就是某个事务在读取某个范围的数据,但是另一个事务又向这个范围的数据去插入数据,导致多次读取的时候,数据的行数不一致。

 对应隔离级别
    1.READ UNCOMMITTED:读未提交,不处理,会出现脏读,不可重复读,幻读。
    2.READ COMMITTED:读已提交,只读提交的数据,无脏读,但这种级别会出现读取旧数据的现象,不可重复读,大多数数据库系统的默认隔离级别。
    3.REPEATABLE READ:可重复读,加行锁,两次读之间不会有修改,无脏读无重复读;保证了每行的记录的结果是一致的。但是无法解决幻读
    4.SERIALIZABLE: 串行化,加表锁,强制事务串行执行,无所有问题,不会出现脏读,不可重复读,幻读。由于他大量加上锁,导致大量的请求超时,因此性能会比较低下,在需要数据一致性且并发量不需要那么大的时候才可能考虑这个隔离级别。
  隔离级别原理
隔离级别原理

READ_UNCOMMITED 的原理:
    1,事务对当前被读取的数据不加锁2,事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级共享锁,直到事务结束才释放。  
   // 1,事务1读取某行记录时,事务2也能对这行记录进行读取、更新;当事务2对该记录进行更新时,事务1再次读取该记录,能读到事务2对该记录的修改版本,即使该修改尚未被提交。
   // 2,事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。

READ_COMMITED 的原理:
    1,事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁;
    2,事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。
   // 1,事务1读取某行记录时,事务2也能对这行记录进行读取、更新;当事务2对该记录进行更新时,事务1再次读取该记录,读到的只能是事务2对其更新前的版本,要不就是事务2提交后的版本。
   // 2,事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。

REPEATABLE READ 的原理:
    1,事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加 行级共享锁,直到事务结束才释放2,事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。 
    // 1,事务1读取某行记录时,事务2也能对这行记录进行读取、更新;当事务2对该记录进行更新时,事务1再次读取该记录,读到的仍然是第一次读取的那个版本。
    // 2,事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。

SERIALIZABLE 的原理:
    1,事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放2,事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。
    // 1,事务1正在读取A表中的记录时,则事务2也能读取A表,但不能对A表做更新、新增、删除,直到事务1结束。
    // 2,事务1正在更新A表中的记录时,则事务2不能读取A表的任意记录,更不可能对A表做更新、新增、删除,直到事务1结束。

  数据库引擎

MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制所有的锁都是绑定在数据库的索引机制上的1.InnoDB(MySQL默认存储引擎 从版本5.5.5开始)
支持事务,行级锁,以及外键,拥有高并发处理能力。既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。 但是在创建索引和加载数据时,比MyISAM慢。默认的隔离级别是Repeatable Read(可重复读)

2.MyISAM
采用的是表级锁(table-level locking),不支持事务和行级锁。所以速度很快,性能优秀。可以对整张表加锁,支持并发插入,支持全文索引。

3.MEMORY
支持Hash索引,内存表,Memory引擎将数据存储在内存中,表结构不是存储在内存中的,查询时不需要执行磁盘I/O操作,所以要比MyISAM和InnoDB快很多倍,但是数据库断电或是重启后,表中的数据将会丢失,表结构不会丢失。

  数据库锁

// 数据库锁出现的目的:处理并发问题

锁分类
从数据库系统角度分为三种:排他锁、共享锁、更新锁。 
从程序员角度分为两种:一种是悲观锁,一种乐观锁。
悲观锁按使用性质划分:排他锁、共享锁、更新锁。
悲观锁按作用范围划分:行锁、表锁。
乐观锁实现方式:版本号,时间戳。

数据库规定同一资源上不能同时共存共享锁和排他锁。

一、悲观锁(Pessimistic Lock)
顾名思义,很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人拿这个数据就会block(阻塞),直到它拿锁。传统的关系数据库里用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。

1. 共享锁(Share Lock)
S锁,也叫读锁,用于所有的只读数据操作。共享锁是非独占的,允许多个并发事务读取其锁定的资源。 
性质 
    1. 多个事务可封锁同一个共享页; 
    2. 任何事务都不能修改该页; 
    3. 通常是该页被读取完毕,S锁立即被释放。
// 在SQL Server中,默认情况下,数据被读取后,立即释放共享锁。 
// 例如,执行查询语句“SELECT * FROM my_table”时,首先锁定第一页,读取之后,释放对第一页的锁定,然后锁定第二页。这样,就允许在读操作过程中,修改未被锁定的第一页。 
// 例如,语句“SELECT * FROM my_table HOLDLOCK”就要求在整个查询过程中,保持对表的锁定,直到查询完成才释放锁定。

2. 排他锁(Exclusive Lock)
X锁,也叫写锁,表示对数据进行写操作。如果一个事务对对象加了排他锁,其他事务就不能再给它加任何锁了。
性质 
    1. 仅允许一个事务封锁此页; 
    2. 其他任何事务必须等到X锁被释放才能对该页进行访问; 
    3. X锁一直到事务结束才能被释放。
// 产生排他锁的SQL语句如下:select * from ad_plan for update;


3. 更新锁
U锁,在修改操作的初始化阶段用来锁定可能要被修改的资源,这样可以避免使用共享锁造成的死锁现象。
// 因为当使用共享锁时,修改数据的操作分为两步: 1. 首先获得一个共享锁,读取数据, 2. 然后将共享锁升级为排他锁,再执行修改操作。 这样如果有两个或多个事务同时对一个事务申请了共享锁,在修改数据时,这些事务都要将共享锁升级为排他锁。这时,这些事务都不会释放共享锁,而是一直等待对方释放,这样就造成了死锁。
// 如果一个数据在修改前直接申请更新锁,在数据修改时再升级为排他锁,就可以避免死锁。
性质 
    1. 用来预定要对此页施加X锁,它允许其他事务读,但不允许再施加U锁或X锁; 
    2. 当被读取的页要被更新时,则升级为X锁; 
    3. U锁一直到事务结束时才能被释放。

4. 行锁
锁的作用范围是行级别。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
5. 表锁 
锁的作用范围是整张表。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

6. 页面锁 

页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

// 数据库能够确定那些行需要锁的情况下使用行锁,如果不知道会影响哪些行的时候就会使用表锁。
// 举个例子,一个用户表user,有主键id和用户生日birthday。 
// 当你使用update … where id=?这样的语句时,数据库明确知道会影响哪一行,它就会使用行锁; 
// 当你使用update … where birthday=?这样的的语句时,因为事先不知道会影响哪些行就可能会使用表锁。

二、乐观锁(Optimistic Lock)
顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以,不会上锁。但是在更新的时候会判断一下在此期间别人有没有更新这个数据,可以使用版本号等机制。

1. 版本号(version)
版本号(记为version):就是给数据增加一个版本标识,在数据库上就是表中增加一个version字段,每次更新把这个字段加1,读取数据的时候把version读出来,更新的时候比较version,如果还是开始读取的version就可以更新了,如果现在的version比老的version大,说明有其他事务更新了该数据,并增加了版本号,这时候得到一个无法更新的通知,用户自行根据这个通知来决定怎么处理,比如重新开始一遍。这里的关键是判断version和更新两个动作需要作为一个原子单元执行,否则在你判断可以更新以后正式更新之前有别的事务修改了version,这个时候你再去更新就可能会覆盖前一个事务做的更新,造成第二类丢失更新,所以你可以使用update … where … and version=”old version”这样的语句,根据返回结果是0还是非0来得到通知,如果是0说明更新没有成功,因为version被改了,如果返回非0说明更新成功。

2. 时间戳(使用数据库服务器的时间戳)
时间戳(timestamp):和版本号基本一样,只是通过时间戳来判断而已,注意时间戳要使用数据库服务器的时间戳不能是业务系统的时间。

3. 待更新字段
待更新字段:和版本号方式相似,只是不增加额外字段,直接使用有效数据字段做版本控制信息,因为有时候我们可能无法改变旧系统的数据库表结构。假设有个待更新字段叫count,先去读取这个count,更新的时候去比较数据库中count的值是不是我期望的值(即开始读的值),如果是就把我修改的count的值更新到该字段,否则更新失败。java的基本类型的原子类型对象如AtomicInteger就是这种思想。

4. 所有字段 
所有字段:和待更新字段类似,只是使用所有字段做版本控制信息,只有所有字段都没变化才会执行更新。

6. spring数据库隔离级别

spring 7大事务传播行为类型:
    propagation_required:当前没有事务,新建一个事务,如果存在事务,则加入到该事务。
    propagation_supports:支持当前事务,如果当前没有事务,就以非事务方式执行。
    propagation_mandatory:使用当前的事务,如果当前没有事务,则抛出异常。
    propagation_requires_new:新建事务,如果当前没有事务,把当前事务挂起。
    propagation_not_supported:以非事务方式操作,如果当前存在事务,就把当前事务挂起。
    propagation_never:以非事务方式执行,如果当前存在事务,则抛出异常。
    propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。要求:底层数据源必须基于JDBC3.0,并且实现者需要支持保存点事务机制

事务熟悉:
readOnly:表示对应的事务应该被最优化为只读事务。
Timeout:指定事务超时时间,单位:秒。xml配置写法:Timeout_11  ---设置超时时间11秒

spring 5大事务隔离级别:
    1、isolation_default:这是一个platfromTransactionManager默认隔离级别,使用数据库默认的事务隔离级别。
    2、isolation_read_uncommitted:最低事务隔离级别,允许另一个事务读取这个事务未提交的数据
    3、isolation_read_committed:保证一个事务修改数据并提交后,另外一个事务才能读取。
    4、isolation_repeatable_read:这种事务隔离级别可以防止脏读,不可重复读,但可能出现幻读
    5、isolation_serializable:这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。

3大问题:
    脏读:当一个事务正再访问数据,并且对数据进行修改,而这种修改还没提交到数据库,这是另外一个事务也可以访问这个数据,并进行使用,因为前一个事务还未提交到数据库,那么另外一个事务读到这个数据是脏数据。
    不可重复读:在一个事务内,多次读同一数据,在这个事务还没有结束时,另外一个事务也访问该数据,那么在第一事务中的两次读取数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能不一样。
    幻读:当事务不是独立执行发生,例:当第一个事务对表中数据进行修改,而这种修改是针对全表数据行,同事第二个事务向表中插入一条新数据,那么在操作发生后,第一个事务用户发现表中还有没有修改的数据行。

  配置方式 

  

Spring事务的配置五种不同的方式:
第一种方式:每个Bean都有一个代理
第二种方式:所有Bean共享一个代理基类
第三种方式:使用拦截器
第四种方式:使用tx标签配置的拦截器
第五种方式:全注解,DAO上需加上@Transactional注解

第一种方式:每个Bean都有一个代理
    <bean id="userDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">   
           <!-- 配置事务管理器 -->
        <property name="transactionManager" ref="transactionManager" />      
        <property name="target" ref="userDaoTarget" />   
        <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" /> 
        <!-- 配置事务属性 -->
        <property name="transactionAttributes">   
            <props>   
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props>   
        </property>   
    </bean>

第二种方式:所有Bean共享一个代理基类 <bean id="transactionBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true" abstract="true">   <!-- 配置事务管理器 -->   <property name="transactionManager" ref="transactionManager" />   <!-- 配置事务属性 -->   <property name="transactionAttributes">   <props>    <prop key="*">PROPAGATION_REQUIRED</prop>    </props>    </property>   </bean> <!-- 配置DAO --> <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="userDao" parent="transactionBase" > <property name="target" ref="userDaoTarget" /> </bean>

第三种方式:使用拦截器 <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager" /> <!-- 配置事务属性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Dao</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean>

第四种方式:使用tx标签配置的拦截器 <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="interceptorPointCuts" expression="execution(* com.bluesky.spring.dao.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" /> </aop:config>

第五种方式:全注解,DAO上需加上@Transactional注解 <tx:annotation-driven transaction-manager="transactionManager"/>

  补充 

spring 什么情况下进行事务回滚?

Spring、EJB的声明式事务默认情况下都是在抛出unchecked exception后才会触发事务的回滚

unchecked异常,即运行时异常runntimeException 回滚事务;
checked异常,即Exception可try{}捕获的不会回滚.当然也可配置spring参数让其回滚.

一般不需要在业务方法中catch异常,如果非要catch,在做完你想做的工作后(比如关闭文件等)一定要抛出runtime exception,否则spring会将你的操作commit,这样就会产生脏数据.所以你的catch代码是画蛇添足。

7. mysql建索引的几大原则

索引的优点
    1.通过创建唯一索引,可以保证数据库每一行数据的唯一性
    2.可以大大提高查询速度
    3.可以加速表与表的连接
    4.可以显著的减少查询中分组和排序的时间。

索引的缺点
    1.创建索引和维护索引需要时间,而且数据量越大时间越长
    2.创建索引需要占据磁盘的空间,如果有大量的索引,可能比数据文件更快达到最大文件尺寸
    3.当对表中的数据进行增加,修改,删除的时候,索引也要同时进行维护,降低了数据的维护速度

建立索引的原则
    1、对于查询频率高(用于查询条件)的字段创建索引;
    2、对排序、分组、联合查询频率高的字段创建索引,提高搜索速度;
    3、索引的数目不宜太多原因:a、每创建一个索引都会占用相应的物理空间;b、过多的索引会导致insert、update、delete语句的执行效率降低;
    4、若在实际中,需要将多个列设置索引时,可以采用多列索引,在经常存取的多个列上建立复合索引,但要注意复合索引的建立顺序要按照使用的频度来确定;
    5、选择唯一性索引,数据本身具备唯一性的时候,建立唯一性索引,可以保证定义的列的数据完整性,以提高查询熟度
    6、尽量使用数据量少的索引。如果索引的值很长,那么查询的速度会受到影响。
    7、尽量使用前缀来索引。如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
    8、删除不再使用或者很少使用的索引.
    9.数据量小的表最好不要建立索引;包含大量的列并且不需要搜索非空值的时候可以考虑不建索引.数据量超过300w的表应该有索引
    10. 常更新的表越少越好,对于经常存取的列避免建立索引;
    11. 在不同值较少的字段上不必要建立索引,如性别字段;
    12. 用于联接的列(主健/外健)上建立索引;
    13. 缺省情况下建立的是非簇集索引,但在以下情况下最好考虑簇集索引,如:含有有限数目(不是很少)唯一的列;进行大范围的查询;充分的利用索引可以减少表扫描I/0的次数,有效的避免对整表的搜索。当然合理的索引要建立在对各种查询的分析和预测中,也取决于DBA的所设计的数据库结构。
    14. 复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替:        
        A、正确选择复合索引中的主列字段,一般是选择性较好的字段;
        B、复合索引的几个字段是否经常同时以AND方式出现在Where子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引;
        C、如果复合索引中包含的字段经常单独出现在Where子句中,则分解为多个单字段索引;
        D、如果复合索引所包含的字段超过3个,那么仔细考虑其必要性,考虑减少复合的字段;
        E、如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;
        

8. B+树和B树

B树 
    每个节点都存储key和data,所有节点组成这棵树,并且叶子节点指针为null。
    B树优点在于,由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。

B+树 
    只有叶子节点存储data,叶子节点包含了这棵树的所有键值,叶子节点不存储指针。所有非终端节点看成是索引,节点中仅含有其子树根节点最大(或最小)的关键字,不包含查找的有效信息。B+树中所有叶子节点都是通过指针连接在一起。

   B+ 树的优点在于:
    由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
    B+树的叶子结点都是相链的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

B和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。

总结:为什么使用B+树?
    1.文件很大,不可能全部存储在内存中,故要存储到磁盘上 
    2.索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数(为什么使用B-/+Tree,还跟磁盘存取原理有关,具体看下边分析) 
    3.局部性原理与磁盘预读,预读的长度一般为页(page)的整倍数,(在许多操作系统中,页得大小通常为4k) 
    4.数据库系统巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样 每个节点只需要一次I/O 就可以完全载入,(由于节点中有两个数组,所以地址连续)。而红黑树这种结构, h 明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性。

为什么B+树比B树更适合做索引?
    1.B+树磁盘读写代价更低: B+的内部结点并没有指向关键字具体信息的指针,即内部节点不存储数据。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
    2.B+-tree的查询效率更加稳定: 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

在MySQL中,最常用的两个存储引擎是MyISAM和InnoDB,它们对索引的实现方式是不同的。
    MyISAM data存的是数据地址。索引是索引,数据是数据。
    InnoDB data存的是数据本身。索引也是数据。

9. 排序算法

10. linux进程间有几种通信方式,各种方式的优缺点 

linux使用的进程间6大通信方式
1.管道(pipe),流管道(s_pipe)和有名管道(FIFO)
2.信号(signal)
3.消息队列
4.共享内存
5.信号量
6.套接字(socket)

   

管道( pipe )
管道这种通讯方式有两种限制,一是半双工的通信,数据只能单向流动,二是只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
流管道s_pipe: 去除了第一种限制,可以双向传输.
命名管道:name_pipe克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信; 信号量( semophore ) 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段
信号 ( singal )
信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;主要作为进程间以及同一进程内不同线程之间的同步手段
消息队列( message queue ) 
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。


共享内存( shared memory )

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。 使得多个进程可以访问同一块内存空间。

套接字( socket )
套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信,更为一般的进程间通信机制。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
  各种通信方式的比較和优缺点
各种通信方式的比較和优缺点

管道:速度慢。容量有限,仅仅有父子进程能通讯
FIFO:不论什么进程间都能通讯,但速度慢
信号量:不能传递复杂消息,仅仅能用来同步
信号:假设用户传递的信息较少或是须要通过信号来触发某些行为.前文提到的软中断信号机制不失为一种简捷有效的进程间通信方式.
消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。可是信息的复制须要额外消耗CPU的时间,不适宜于信息量大或操作频繁的场合。
共享内存区:能够非常easy控制容量,速度快,信息量大,高效双向通信,但要保持同步,比方一个进程在写的时候。还有一个进程要注意读写的问题,相当于线程中的线程安全。 

socket:即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。

  

11. TCP为什么不两次握手而要三次握手?

1. 三次握手保证了客户端和服务器的接受消息能力和发送消息的能力没问题,保证可靠。
2. 三次握手保证了网络拥塞情况下延迟请求问题,不浪费资源。【“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”  OR  为了解决“网络中存在延迟的重复分组”的问题。】

两次握手具体解释:
第一种情况:
    1. 服务器收到了客户端的消息,服务器知道了客户端是可以发送消息的,但由于没有第三次握手,所以服务器不知道客户端是否具有接受消息的能力;
    2. 客户端从服务器接受到了消息,客户端知道了服务器接受到了我的消息才回复,说明服务器的接受消息能力和发送消息的能力没问题;
    3. 综上所述,客户端确保了服务器的接受发送没问题,但是服务器仅仅只知道客户端的发送消息没问题,这并不是可靠的,所以两次握手不可以

第二种情况:
    1. 假设客户端和服务器进行TCP连接,然后第一次发送的TCP连接请求A发生了阻塞。
    2. 于是由于客户端没有收到服务器的应答报文,客户端认为这个TCP连接请求丢失了,于是重新发送了TCP连接请求B。这次没有阻塞,成功连接了,因为是讨论的两次握手,所以只进行两次连接就可以进行通信了。
    3. 通信结束,然后就断开了连接B。
    4. 阻塞一段时间网络又畅通了,于是TCP连接请求A成功到达了服务器,服务器又以为是客户端又要进行数据传输,于是服务器就又对这个连接请求进行应答,两次握手,于是又成功建立了TCP连接A。
    5. 但是由于客户端它以为这个连接请求已经丢失了,所以不会利用这个建立的连接请求进行数据通信,虽然服务器分配给了资源给客户端,但是客户端并不进行数据传输,这样就白白浪费了服务器的资源。


为什么三次握手可以解决以上拥塞问题呢?
    第三次握手如果没有发生,服务器过了很长时间(规定好的时间和客户端)都没有收到回复,于是也不会为客户端分配资源,这次连接就放弃了,就不会浪费资源。

  三次握手和四次挥手

三次握手:
A:“喂,你听得到吗?”A->SYN_SEND

B:“我听得到呀,你听得到我吗?”应答与请求同时发出 B->SYN_RCVD | A->ESTABLISHED

A:“我能听到你,今天balabala……”B->ESTABLISHED


四次挥手: A:“喂,我不说了。”A
->FIN_WAIT1 B:“我知道了。等下,上一句还没说完。Balabala…..”B->CLOSE_WAIT | A->FIN_WAIT2 B:”好了,说完了,我也不说了。”B->LAST_ACK A:”我知道了。”A->TIME_WAIT | B->CLOSED A等待2MSL,保证B收到了消息,否则重说一次”我知道了”,A->CLOSED

  

三次握手建立连接:

    第一次握手:客户端发送syn包(seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认;
    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
    // 握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

传输数据过程:
a.超时重传 超时重传机制用来保证TCP传输的可靠性。每次发送数据包时,发送的数据报都有seq号,接收端收到数据后,会回复ack进行确认,表示某一seq 号数据已经收到。发送方在发送了某个seq包后,等待一段时间,如果没有收到对应的ack回复,就会认为报文丢失,会重传这个数据包。 b.快速重传 接受数据一方发现有数据包丢掉了。就会发送ack报文告诉发送端重传丢失的报文。如果发送端连续收到标号相同的ack包,则会触发客户端的快速重 传。比较超时重传和快速重传,可以发现超时重传是发送端在傻等超时,然后触发重传;而快速重传则是接收端主动告诉发送端数据没收到,然后触发发送端重传。 c.流量控制 这里主要说TCP滑动窗流量控制。TCP头里有一个字段叫Window,又叫Advertised
-Window,这个字段是接收端告诉发送端自己 还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。 滑动窗可以是提高TCP传输效率的一种机制。 d.拥塞控制 滑动窗用来做流量控制。流量控制只关注发送端和接受端自身的状况,而没有考虑整个网络的通信情况。拥塞控制,则是基于整个网络来考虑的。考虑一下这 样的场景:某一时刻网络上的延时突然增加,那么,TCP对这个事做出的应对只有重传数据,但是,重传会导致网络的负担更重,于是会导致更大的延迟以及更多 的丢包,于是,这个情况就会进入恶性循环被不断地放大。试想一下,如果一个网络内有成千上万的TCP连接都这么行事,那么马上就会形成“网络风 暴”,TCP这个协议就会拖垮整个网络。为此,TCP引入了拥塞控制策略。 拥塞策略算法主要包括:慢启动,拥塞避免,拥塞发生,快速恢复。 四次握手断开连接: 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当 然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但此时主动关闭方还可以接受数据。 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。 第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。 第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。主动关闭方等待2MSL以后,没有收到B传来的任何消息,知道B已经收到自己的ACK了,主动关闭方就关闭链接,B也关闭链接了。 A为什么等待2MSL,从TIME_WAIT到CLOSE? 在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。 MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。 如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

 为什么连接的时候是三次握手,关闭的时候却是四次握手?

   因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。


 如果已经建立了连接,但是客户端突然出现故障了怎么办?

  TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

 

抄录网址

  1. 数据库的隔离级别以及悲观锁和乐观锁详解
  2. S、X、IS、IX数据库锁机制 很详细的教程,简单易懂
  3. 数据库锁分类和总结
  4. 为什么Java开发人员必须要了解数据库锁?
  5. 数据库隔离级别及实现原理
  6. 数据库隔离级别的原理解析
  7. Spring事务传播机制和数据库隔离级别 
  8. Spring事务传播机制和数据库隔离级别
  9. Spring 事务配置的几种方式
  10. Spring配置事务的五种方式
  11. 浅谈mysql的索引设计原则以及常见索引的区别
  12. mysql 建立索引的原则(转)
  13. MySQL索引篇,索引的优缺点,分类及设计原则
  14. mysql创建索引的原则
  15. MySQL系列—建索引的几大原则和使用索引优化查询
  16. 数据库索引以及索引的实现(B+树介绍,和B树,区别)
  17. 浅谈算法和数据结构: 十 平衡查找树之B树
  18. Linux进程间通信的几种方式总结--linux内核剖析(七)
  19. 【漫画】TCP连接为什么是三次握手,而不是两次握手,也不是四次握手?
  20. tcp为什么要三次握手,而不能二次握手?
  21. 6张动态图轻松学习TCP三次握手和四次挥手
  22. 网络TCP建立连接为什么需要三次握手而结束要四次
  23. TCP协议:三次握手过程详解
  24. TCP的三次握手与四次挥手理解及面试题(很全面)
posted @ 2019-05-16 15:03  海米傻傻  阅读(682)  评论(0编辑  收藏  举报