深入笔记(为了记录一些图片)

目录:
1.Mysql相关
2.Spring相关

 

 


1.Mysql相关

>>> mybatis加载过程
mybatis底层还是采用原生的jdbc对数据库进行操作
解析配置文件,创建sqlSessionFactory建造者模式,
初始化Configuration对象,解析mappers节点时,会读取该节点下所有的mapper文件,
然后进行解析,并将解析后的结果存到configuration对象中。
对于每一次请求,创建一个sqlSession,然后对应mybatis里的

配置文件的基本结构
configuration —— 根元素
properties —— 定义配置外在化
settings —— 一些全局性的配置
typeAliases —— 为一些类定义别名
typeHandlers —— 定义类型处理,也就是定义java类型与数据库中的数据类型之间的转换关系
objectFactory
plugins —— Mybatis的插件,插件可以修改Mybatis内部的运行规则
environments —— 配置Mybatis的环境
environment
transactionManager —— 事务管理器
dataSource —— 数据源
databaseIdProvider
mappers —— 指定映射文件或映射类

下面我们简单分析下其中4个Builder:
1⃣️XMLConfigBuilder
解析mybatis中configLocation属性中的全局xml文件,内部会使用XMLMapperBuilder解析各个xml文件。
2⃣️XMLMapperBuilder
遍历mybatis中mapperLocations属性中的xml文件中每个节点的Builder,比如user.xml,内部会使用XMLStatementBuilder处理xml中的每个节点。
3⃣️XMLStatementBuilder
解析xml文件中各个节点,比如select,insert,update,delete节点,内部会使用XMLScriptBuilder处理节点的sql部分,遍历产生的数据会丢到Configuration的mappedStatements中。
4⃣️XMLScriptBuilder
解析xml中各个节点sql部分的Builder。

mybatis sql执行流程:
Configuration MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
MappedStatement MappedStatement维护一条<select|update|delete|insert>节点的封装
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息

>>> mybatis缓存:
一级缓存是SqlSession级别的缓存,每个SqlSession对象都有一个哈希表用于缓存数据,不同SqlSession对象之间缓存不共享。同一个SqlSession对象对象执行2遍相同的SQL查询,在第一次查询执行完毕后将结果缓存起来,这样第二遍查询就不用向数据库查询了,直接返回缓存结果即可。MyBatis默认是开启一级缓存的。
二级缓存是mapper级别的缓存,二级缓存是跨SqlSession的,多个SqlSession对象可以共享同一个二级缓存。不同的SqlSession对象执行两次相同的SQL语句,第一次会将查询结果进行缓存,第二次查询直接返回二级缓存中的结果即可。MyBatis默认是不开启二级缓存的,可以在配置文件中使用如下配置来开启二级缓存:

>>> 索引
优点:可以快速检索,减少I/O次数,加快检索速度;根据索引分组和排序,可以加快分组和排序;
缺点:索引本身也是表,因此会占用存储空间,索引表的维护和创建需要时间成本;

分类:主键索引 唯一索引 普通索引 全文索引 联合索引;
类型:BTree索引(B+Tree索引),哈希索引(无法进行范围查找),全文索引;

BTree是多路搜索树,查找复杂度为h*log(n),一般来说树的高度是很小的,一般为3左右;
B+Tree中的非叶子结点不存储数据,只存储键值;
B+Tree的叶子结点没有指针,所有键值都会出现在叶子结点上,且key存储的键值对应的数据的物理地址;
非叶子节点只存key,大大滴减少了非叶子节点的大小,那么每个节点就可以存放更多的记录,树更矮了,I/O操作更少了。所以B+Tree拥有更好的性能。
B树:一棵m阶B树是一棵平衡的m路搜索树
B+树:一棵m阶B树是一棵平衡的m路搜索树,同时数据节点只存在于叶子节点中,且叶子节点间增加了横向的指针


>>> mysql优化
mysql优化:mysql服务器的优化,各种参数常量设定,查询语句优化,主从复制,软硬件升级,容灾备份
什么样的sql不走索引?
1⃣️索引参与计算/使用函数计算
2⃣️like前%
3⃣️类型不一致
4⃣️存在or
5⃣️is null not null


MySQL有三种锁的级别:页级、表级、行级。
>>> mysql锁
可以显式的关闭Gap Lock:
1:把事务隔离级别改成:Read Committed,提交读、不可重复读。SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
2:修改参数:innodb_locks_unsafe_for_binlog 设置为1。


>>> MyIsam InnoDb区别
MyISAM存储引擎的mysql对表会存储三个文件,后缀名 MYI(索引文件),后缀名MYD(数据文件),后缀名frm(表字段结构文件)。
在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址读取相应的数据记录

而对于InnoDB存储引擎的表会存储两个文件,后缀名frm(表字段结构文件),后缀名ibd(数据文件)。
在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。

当数据库一条记录里包含多个字段时,一棵B+树就只能存储主键,如果检索的是非主键字段,则主键索引失去作用,又变成顺序查找了。
这时应该在第二个要检索的列上建立第二套索引。 这个索引由独立的B+树来组织。有两种常见的方法可以解决多个B+树访问同一套表数据的问题,一种叫做聚簇索引(clustered index ),一种叫做非聚簇索引(secondary index)。这两个名字虽然都叫做索引,但这并不是一种单独的索引类型,而是一种数据存储方式。
对于聚簇索引存储来说,行数据和主键B+树存储在一起,辅助键B+树只存储辅助键和主键,主键和非主键B+树几乎是两种类型的树。
对于非聚簇索引存储来说,主键B+树在叶子节点存储指向真正数据行的指针,而非主键。

 

>>> 特性ACID:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),Durability(持久性)

>>> 事务
Spring对事务的支持,确实很强大,但是从本质上来讲:事务是否生效取决数据库底层是否支持(比如MySQL的MyISAM引擎就不支持事务,Spring能奈何!),同时一个事务的多个操作需要在同一个Connection上。事务也往往是在业务逻辑层来控制。

在方法执行前后进行拦截,然后在目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务。
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。DataSource(sqlSessionFactory)、TransactionManager(事务管理器)这两部分只是会根据数据访问方式有所变化。
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给持久化机制所提供的相关平台框架的事务来实现。

关于Connection和Session的区别:
Connection(连接)是一个物理概念,是指一个通过网络建立的客户端和专有服务器或调度器之间的一个网络连接
Session(会话)是一个逻辑概念,他存在于实例中,一个Connection可以拥有多个Session,也可以没有Session,同一个Connection上的多个Session之间不会相互影响
datasource数据源是注入给sessionfactory的,然后关联到session中。

事务(接口简介):

这里写图片描述
在方法执行前后进行拦截,然后在目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务。
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。DataSource(sqlSessionFactory)、TransactionManager(事务管理器)这两部分只是会根据数据访问方式有所变化。

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给持久化机制所提供的相关平台框架的事务来实现。

 

 

事务隔离级别:
1⃣️脏读(未提交读):事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2⃣️不可重复读(已提交读):事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
3⃣️幻读(可重复读):系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
4⃣️串行;
注:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

事务隔离级别针对的是读这种情况,如果是两个事务同时update,则一个事务是会被阻塞的,直到另一个事务提交才解决,所以可以认为update底层是串行执行的?
可重复读情况下,另一个事务就算提交了,这个事务里前后读取的值也不会变化。
select @@tx_isolation


事务传播行为:
1.PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
2.PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
3.PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
4.PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
5.PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6.PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
7.PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作


事务readonly:不能更新数据
数据库连接池:
DBCP(spring推荐)、C3P0(Hibernate推荐)、HikariCP、BoneCP、Proxool、JBoss DataSource、Druid。

Druid
替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池
可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。

 

>>> mysql大数据量使用limit分页,随着页码的增大,查询效率越低下
对limit分页问题的性能优化方法
利用表的覆盖索引来加速分页查询
我们都知道,利用了索引查询的语句中如果只包含了那个索引列(覆盖索引),那么这种情况会查询很快。
因为利用索引查找有优化算法,且数据就在查询索引上面,不用再去找相关的数据地址了,这样节省了很多时间。另外Mysql中也有相关的索引缓存,在并发高的时候利用缓存就效果更好了。

1⃣️在我们的例子中,我们知道id字段是主键,自然就包含了默认的主键索引。现在让我们看看利用覆盖索引的查询效果如何:
这次我们之间查询最后一页的数据(利用覆盖索引,只包含id列),如下:
select id from product limit 866613, 20 0.2秒
相对于查询了所有列的37.44秒,提升了大概100多倍的速度

2⃣️那么如果我们也要查询所有列,有两种方法,一种是id>=的形式,另一种就是利用join,看下实际情况:
SELECT * FROM product WHERE ID > =(select id from product limit 866613, 1) limit 20
查询时间为0.2秒,简直是一个质的飞跃啊,哈哈

3⃣️另一种写法
SELECT * FROM product a JOIN (select id from product limit 866613, 20) b ON a.ID = b.id
查询时间也很短,赞!
其实两者用的都是一个原理嘛,所以效果也差不多

 

>>> MySQL的复制原理以及流程
1. 主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中;
2. 从:io线程——在使用start slave 之后,负责从master上拉取 binlog 内容,放进 自己的relay log中;
3. 从:sql执行线程——执行relay log中的语句;


>>> Mysql redo、undo、bin、relay log 区别
事务的简化过程
假设有A、B两个数据,值分别为1,2,现在修改为3、4
1. 事务开始。
2. 记录A=1到undolog。
3. 修改A=3。
4. 记录A=3到redolog。
5. 记录B=2到undolog。
6. 修改B=4。
7. 记录B=4到redolog,将redolog写入磁盘。
8. 事务提交。

既然redo log可以实现数据重做,那为什么要使用binlog?
因为redo log是Innodb实现的物理日志,一旦涉及到多种存储引擎,无法进行重做。

relay(中继) log:
从节点单独进程会将binlog 拷贝至本地 relaylog中。
从节点定时重放relay log。


>>> 事务是如何通过日志来实现基本流程如下:
因为事务在修改页时,要先记 undo,在记 undo 之前要记 undo 的 redo, 然后修改数据页,再记数据页修改的 redo。 Redo(里面包括 undo 的修改) 一定要比数据页先持久化到磁盘。 当事务需要回滚时,因为有 undo,可以把数据页回滚到前镜像的
状态,崩溃恢复时,如果 redo log 中事务没有对应的 commit 记录,那么需要用 undo把该事务的修改回滚到事务开始之前。 如果有 commit 记录,就用 redo 前滚到该事务完成时并提交掉。


>>> MySQL binlog的几种日志录入格式以及区别
1.Statement:每一条会修改数据的sql都会记录在binlog中。
2.Row:不记录sql语句上下文相关信息,仅保存哪条记录被修改。
3.Mixedlevel: 是以上两种level的混合使用 ,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则 采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,

查看慢sql日志是否开启:show variables like '%slow_query_log%'

如何监控你们的数据库的?你们的慢日志都是怎么查询的?
监控的工具有很多,例如zabbix,lepus,我这里用的是lepus(天兔),袋鼠云,druid。

是否做过主从一致性校验,如果有,怎么做的,如果没有,你打算怎么做?
主从一致性校验有多种工具 例如checksum、mysqldiff、pt-table-checksum等

 

线上mysql版本5.6
数据库内存24000MB
最大连接数2000

 

 

 

 


2.Spring
Spring是一个开源Java开发框架,有很好的扩展性。
优点:1⃣️轻量级基础版本只有2MB 2⃣️IOC松耦合 3⃣️AOP 4⃣️容器 5⃣️MVC框架;
模块:core、bean、context、JDBC、ORM、JMS等;
核心容器:context(beanFactory)应用上下文,beanFactory使得Spring成为一个容器;
Spring有以下主要的命名空间:context、beans、jdbc、tx、aop、mvc和aso。
Spring作用域:single(默认)、prototype、request、session、globalSession

>>> 加载bean流程

ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
ac.getBean(XXX.class);

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {

   super(parent);
   setConfigLocations(configLocations);
   if (refresh) {
      refresh();
   }
}

 

 

注:refresh()方法是整个Spring Bean加载的核心,方法是加锁的,这么做的原因是避免多线程同时刷新Spring上下文;
DefaultListableBeanFactory beanFactory = createBeanFactory();
1⃣️加载解析配置文件
2⃣️扫描需要的文件类,并加载
3⃣️初始化扫描出来的类,并放入IOC容器
4⃣️依赖注入

总结:
Spring有两个核心接口:BeanFactory和ApplicationContext,其中ApplicationContext是BeanFactory的子接口。他们都可以代表Spring容器。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("");
1⃣️ResourceLoader加载XML配置信息后,由BeanDefinitionReader读取配置信息文件,把每个<bean>解析成BeanDefinition对象保存在注册表中( 然后放到Factory的map中(这一步就是所谓的注册),这样以后程序就可以直接从Factory中拿Bean信息了)。

2⃣️容器首先扫描注册表取出工厂后处理器,对注册表中的BeanDefinition进行加工处理。
3⃣️Spring容器接着从注册表中取出加工过的BeanDefinition开始着手bean实例化的事情。
4⃣️实例化时,首先由BeanWapper对bean进行封装,配置属性。最后利用注册表中的Bean后处理器装饰打扮,装配出一个准备就绪的Bean来。(注册表就类似于一个Map<K,V>,把所有的bean,不管是业务bean,还是spring自己的bean,都放到注册表里,用的时候取出来)。

以上便是Spring对bean解析注册的全过程,总结一下大致步骤:
1. 加载XML文件,封装成Resource对象
2. 调用Reader对象方法读取XML文件内容,并将相关属性放到BeanDefinition实例
3. 将BeanDefinition对象放到BeanFactory对象


>>> Java类的加载与初始化
加载(双亲委派)->验证->准备->解析(set)->初始化(创建类的实例)->使用->卸载
解析也有可能是发生在初始化在后,为了支持Java语言的运行时绑定(也称为动态绑定)

加载:
1、获取.class文件的二进制流
2、将类信息、静态变量、字节码、常量这些.class文件中的内容放入方法区中
3、在内存中生成一个代表这个.class文件的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。一般这个Class是在堆里的,不过HotSpot虚拟机比较特殊,这个Class对象是放在方法区中的

验证:确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
1、文件格式验证
2、元数据验证
3、字节码验证
4、符号引用验证


准备:准备阶段是正式为类变量分配内存并设置其初始值的阶段,这些变量所使用的内存都将在方法区中分配
不被final修饰的static变量,在准备阶段被赋值为0,直到初始化才会被具体赋值;若被final修饰,会在准备阶段就被赋值;

解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化:
虚拟机会保证类的初始化在多线程环境中被正确地加锁、同步,即如果多个线程同时去初始化一个类,那么只会有一个类去执行这个类的<clinit>()方法,其他线程都要阻塞等待,直至活动线程执行<clinit>()方法完毕

>>> 自定义类加载器
继承ClassLoader,并覆盖findClass方法

>>> 双亲委派:
(1)当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
(2)如果没有找到,就去委托父类加载器去加载(如代码c = parent.loadClass(name, false)所示)。父类加载器也会采用同样的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托父类的父类去加载,一直到启动类加载器。因为如果父加载器为空了,就代表使用启动类加载器作为父加载器去加载。
(3)如果启动类加载器加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用拓展类加载器来尝试加载,继续失败则会使用AppClassLoader来加载,继续失败则会抛出一个异常ClassNotFoundException,然后再调用当前加载器的findClass()方法进行加载。

 好处:
(1)主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。
(2)同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的 ClassLoader加载就是不同的两个类。

 


>>> 解释Spring框架中bean的生命周期
Spring容器读取XML文件中bean的定义并实例化bean。
Spring根据bean的定义设置属性值。
如果该Bean实现了BeanNameAware接口,Spring将bean的id传递给setBeanName()方法。
如果该Bean实现了BeanFactoryAware接口,Spring将beanfactory传递给setBeanFactory()方法。
如果任何bean BeanPostProcessors 和该bean相关,Spring调用postProcessBeforeInitialization()方法。
如果该Bean实现了InitializingBean接口,调用Bean中的afterPropertiesSet方法。
如果bean有初始化函数声明,调用相应的初始化方法。
如果任何bean BeanPostProcessors 和该bean相关,调用postProcessAfterInitialization()方法。
使用
如果该bean实现了DisposableBean,调用destroy()方法。

Bean自身的方法 配置文件中的init-method和destroy-method配置的方法、Bean对象自己调用的方法
Bean级生命周期接口方法 BeanNameAware、BeanFactoryAware、InitializingBean、DiposableBean等接口中的方法
容器级生命周期接口方法 InstantiationAwareBeanPostProcessor、BeanPostProcessor等后置处理器实现类中重写的方法

>>> SpringMVC流程
DispatchSerlet -> HandleMapping -> HandleApater -> controller -> ModelView


>>> 自定义注解
@Documented : JavaDoc文档
@Target:标志此注解可以修饰在哪些地方,类,成员变量,方法...
@Retention:Annotation的生命周期,一般情况下,我们自定义注解的话,显然需要在运行期获取注解的一些信息。

 

 

 

 

 

 

 

 

3.Spring代理
概念:面向切面编程,通过动态代理给方法做增强处理。
在运行期间生成代理(静态代理),在运行期字节码加载前修改字节码(jdk/cglib),在运行期字节码加载后动态创建代理类的字节码(自定义加载器)

Spring会为每一个Bean创建一个对应的ProxyFactoryBean的FactoryBean来创建某个对象的代理对象。
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib(Code Generation Library)动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
字节码的动态生成,会继承目标对象,所以不能用final修饰

 

AOP术语:连接点Joinpoint,切入点Pointcut,通知Advice(前中后),切面Aspect

1⃣️jdk代理

class Proxy implements Invocation{

private Object object;

invoke(Object proxy, Method method, Object[] args){
  return method.invoke(object,args);
}

}

 

 

new newProxyInstance(classLoader.getClass().getClassLoader(),interfaces.getClass().getInterfaces(),Proxy);

2⃣️cglib代理

class Proxy implements methodInterceptor{
private Object object;

public Object createProxyObject(Object targetObject) { 
  this.targetObject = targetObject; 
  Enhancer enhancer = new Enhancer(); 
  enhancer.setSuperclass(this.targetObject.getClass()); 
  enhancer.setCallback(this); 
  return enhancer.create(); 
}

intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy){
  return method.invoke(object,args);
}
}

new Proxy().createProxyObject(new A());

 

4.JUC包下面的方法
BlockingQueue线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。
ArrayBlockingQueue / LinkedBlockingQueue / SynchronousQueue

抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, unit)
1)add:把Object加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则招聘异常
2)offer:表示如果可能的话,将Object加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.
3)put:把Object加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
4)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null
5)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止


CAS的缺点:
1⃣️可以解决原子性问题,但是如果CAS失败,则会一直循环尝试,循环时间长开销很大;
2⃣️只能保证一个共享变量的原子操作;
3⃣️ABA问题,中途被其他线程改变过,但是又改回来了,使用要考虑是否有风险.


ReentrantReadWriteLock可重入,锁降级(允许写锁降级为读锁),中断锁,支持Condition
AQS的state表示写锁和读锁的个数,state的高16位表示读锁的个数,低16位表示写锁的个数
线程池、阻塞队列、CountDownLatch、Semaphore、Exchanger CyclicBarrier、Callable、Future、FutureTask
通过Semaphore控制并发并发数的方式和通过控制线程数来控制并发数的方式相比,粒度更小


异步@Async
spring异步线程池的接口类,其实质是Java.util.concurrent.Executor

 

 

 

 

 

 

 

 

 

 

5.多线程
Thread类也是实现的Runnable接口

>>> interrupt()方法的作用:在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞状态。
换句话说,没有被阻塞的线程,调用interrupt()方法是不起作用的。
原理:通过无限轮询自己的中断标识位,中断了则打印、退出,否则一直运行
总结:中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。

>>> join()方法会使调用join()方法的线程所在的线程无限阻塞,直到调用join()方法的线程销毁为止
join()方法内部使用的是wait(),因此会释放锁


>>> synchronized
1⃣️对象锁
2⃣️锁重入
3⃣️异常自动释放锁

>>> ReentrantLock
ReentrantLock持有的是对象监视器,但是和synchronized持有的对象监视器不是一个意思,虽然我也不清楚两个持有的对象监视器有什么区别,不过把methodB()方法用synchronized修饰,methodA()不变,两个方法还是异步运行的。

>>> volatile
加上了volatile的意思是,每次读取isRunning的值的时候,都先从主内存中把isRunning同步到线程的工作内存中,再当前时刻最新的isRunning。
可见性,禁止重排序,但是并不保证原子性
A、原子性 :对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
B、可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。

>>> ThreadLocal
方法:set(T value)、get()、remove()
1、每个线程都有一个自己的ThreadLocal.ThreadLocalMap对象
2、每一个ThreadLocal对象都有一个循环计数器
3、ThreadLocal.get()取值,就是根据当前的线程,获取线程中自己的ThreadLocal.ThreadLocalMap,然后在这个Map中根据第二点中循环计数器取得一个特定value值


ThreadLocalMap getMap(Thread t){
return t.threadLocals; //t.threadLocals实际上就是访问Thread类中的ThreadLocalMap这个成员变量
}


>>> 线程池
ExecutorService extends Executor,ExecutorService底层是通过ThreadPoolExecutor实现;
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

corePoolSize:核心池的大小。在创建了线程池之后,默认情况下,线程池中没有任何线程,而是等待有任务到来才创建线程去执行任务。默认情况下,在创建了线程池之后,线程池钟的线程数为0,当有任务到来后就会创建一个线程去执行任务
maximumPoolSize:池中允许的最大线程数,这个参数表示了线程池中最多能创建的线程数量,当任务数量比corePoolSize大时,任务添加到workQueue,当workQueue满了,将继续创建线程以处理任务,maximumPoolSize表示的就是wordQueue满了,线程池中最多可以创建的线程数量
workQueue:存储还没来得及执行的任务
threadFactory:执行程序创建新线程时使用的工厂
handler:由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序

>>> 四种线程池
newSingleThreadExecutos() 单线程线程池,线程池中运行的线程数肯定是1
newFixedThreadPool(int nThreads) 固定大小线程池
newCachedThreadPool() 无界线程池,采用了SynchronousQueue
newScheduleThreadPool()

>>> 拒绝策略
AbortPolicy直接抛出一个RejectedExecutionException,这也是JDK默认的拒绝策略
CallerRunsPolicy尝试直接运行被拒绝的任务,如果线程池已经被关闭了,任务就被丢弃了
DiscardOldestPolicy移除最晚的那个没有被处理的任务,然后执行被拒绝的任务。同样,如果线程池已经被关闭了,任务就被丢弃了
DiscardPolicy不能执行的任务将被删除


>>> qps
并发量
io线程数
计算线程数:一来服务器CPU核数有限,同时并发的线程数是有限的

 

 

 

 

 

 

6.消息队列
作用:异步,解藕,峰值处理,可恢复,顺序,扩展性

 

 

 

 

 

7.redis(REmote DIctionary Server)
单进程单线程,性能高,同时,Redis所有操作都是原子性的,也支持对几个操作合并后原子性的执行。
另外,Redis有丰富的扩展特性,它支持publish/subscribe,通知,key过期等等特性。

与其他key-value缓存框架相比:
1⃣️Redis不仅仅支持key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储;
2⃣️Redis支持数据持久化,可以将内存中的数据保存到磁盘,重启的时候可以再加载使用;
3⃣️Redis支持数据的备份,即master-slave模式的数据备份。


>>> 存储方式
Redis内部使用一个redisObject对象来表示所有的key和value。
redisObject最主要的信息:
1⃣️type代表一个value对象具体是何种数据类型(5种数据类型)
2⃣️encoding是不同数据类型在redis内部的存储方式,比如:
type=string代表value存储的是一个普通字符串,那么对应的encoding可以是raw或者是int,
如果是int则代表实际redis内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如:"123" "456"这样的字符串。
3⃣️vm字段,只有打开了Redis的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。

 

缓存穿透:给不命中的null也做缓存,但是失效时间缩短
缓存雪崩:集群

Redis发展过程中的三种模式:主从、哨兵、集群
Redis保证数据可靠性、高可用的思路

redis 提供 6种数据淘汰策略:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据

redis.conf中的maxmemory这个值来开启内存淘汰功能
maxmemory-policy noeviction


Redis与Memcached的区别与比较
1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
2 、Redis支持数据的备份,即master-slave模式的数据备份。
3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中
4、 redis的速度比memcached快很多
5、Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。
附加功能:pub/sub,脚本支持,支持事务


Redis与ehcache
Redis:属于独立的运行程序,需要单独安装后,使用JAVA中的Jedis来操纵。因为它是独立,所以如果你写个单元测试程序,放一些数据在Redis中,然后又写一个程序去拿数据,那么是可以拿到这个数据的。,

ehcache:与Redis明显不同,它与java程序是绑在一起的,java程序活着,它就活着。譬如,写一个独立程序放数据,再写一个独立程序拿数据,那么是拿不到数据的。只能在独立程序中才能拿到数据。

 

 

Redis 大量数据插入
使用协议,卢克(Use the protocol, Luke)
原本使用netcat:(cat data.txt; sleep 10) | nc localhost 6379 > /dev/null,但是不能检查错误
pipe mode:cat data.txt | redis-cli --pipe,会有反馈


>>> Redis集群搭建
第一步: 新建redis-cluster文件夹
第二步:复制redis实例
第三步:修改配置文件,修改bin目录下的redis.conf配置文件
修改端口,cluser_enabled = yes
第四部:开启

主从:slaveof

 

>>> 缓存穿透
一般的缓存系统,都是按照key值去缓存查询,如果不存在对应的value,就应该去DB中查找。由于缓存不命中,每次都要查询持久层。从而失去缓存的意义。
这个时候,如果请求的并发量很大,就会对后端的DB系统造成很大的压力。这就叫做缓存穿透。
关键词:缓存value为空;并发量很大去访问DB

造成的原因:1.业务自身代码或数据出现问题;2.一些恶意攻击、爬虫造成大量空的命中,此时会对数据库造成很大压力。

解决方法
1.设置布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,
从避免了对底层存储系统的查询压力。
2. 如果一个查询返回的数据为空,不管是数据不存在还是系统故障,我们仍然把这个结果进行缓存,但是它的过期时间会很短
最长不超过5分钟。


>>> 缓存雪崩
因为缓存层承载了大量的请求,有效的保护了存储 层,但是如果缓存由于某些原因,整体不能够提供服务,于是所有的请求,就会到达存储层,存储层的调用量就会暴增,造成存储层也会挂掉的情况。缓存雪崩的英文解释是奔逃的野牛,指的是缓存层当掉之后,并发流量会像奔腾的野牛一样,大量后端存储。

2.解决方法
(1)设置redis集群和DB集群的高可用,如果redis出现宕机情况,可以立即由别的机器顶替上来。这样可以防止一部分的风险。
(2)使用互斥锁
在缓存失效后,通过加锁或者队列来控制读和写数据库的线程数量。比如:对某个key只允许一个线程查询数据和写缓存,其他线程等待。单机的话,可以使用synchronized或者lock来解决,如果是分布式环境,可以是用redis的setnx命令来解决。
(3)不同的key,可以设置不同的过期时间,让缓存失效的时间点不一致,尽量达到平均分布。
(4)永远不过期:redis中设置永久不过期,这样就保证了,不会出现热点问题,也就是物理上不过期。
(5)资源保护:使用netflix的hystrix,可以做各种资源的线程池隔离,从而保护主线程池。
四种方案,没有最佳只有最合适, 根据自己项目情况使用不同的解决策略。


>>> 缓存与数据库数据保持一致性
1、分别处理
   针对某些对数据一致性要求不是特别高的情况下,可以将这些数据放入Redis,请求来了直接查询Redis,例如近期回复、历史排名这种实时性不强的业务。而针对那些强实时性的业务,例如虚拟货币、物品购买件数等等,则直接穿透Redis至MySQL上,等到MySQL上写入成功,再同步更新到Redis上去。这样既可以起到Redis的分流大量查询请求的作用,又保证了关键数据的一致性。

2、高并发情况下
   此时如果写入请求较多,则直接写入Redis中去,然后间隔一段时间,批量将所有的写入请求,刷新到MySQL中去;如果此时写入请求不多,则可以在每次写入Redis,都立刻将该命令同步至MySQL中去。这两种方法有利有弊,需要根据不同的场景来权衡。

3、基于订阅binlog的同步机制
   阿里巴巴的一款开源框架canal,提供了一种发布/ 订阅模式的同步机制,通过该框架我们可以对MySQL的binlog进行订阅,这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。值得注意的是,binlog需要手动打开,并且不会记录关于MySQL查询的命令和操作。
   其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。而canal正是模仿了slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。如下图就可以看到Slave数据库中启动了2个线程,一个是MySQL SQL线程,这个线程跟Matser数据库中起的线程是一样的,负责MySQL的业务率执行,而另外一个线程就是MySQL的I/O线程,这个线程的主要作用就是同步Master 数据库中的binlog,达到数据备份的效果。而binlog就可以理解为一堆SQL语言组成的日志。


场景一:先更新数据库,再删除缓存
先删除缓存,然后在更新数据库,如果删除缓存失败,那就不要更新数据库,如果说删除缓存成功,而更新数据库失败,那查询的时候只是从数据库里查了旧的数据而已,这样就能保持数据库与缓存的一致性。

场景二:在高并发的情况下,如果当删除完缓存的时候,这时去更新数据库,但还没有更新完
可以用队列的去解决这个问,创建几个队列,如20个,根据商品的ID去做hash值,然后对队列个数取摸,当有数据更新请求时,先把它丢到队列里去,当更新完后在从队列里去除,如果在更新的过程中,遇到以上场景,先去缓存里看下有没有数据,如果没有,可以先去队列里看是否有相同商品ID在做更新,如果有也把查询的请求发送到队列里去,然后同步等待缓存更新完成。
这里有一个优化点,如果发现队列里有一个查询请求了,那么就不要放新的查询操作进去了,用一个while(true)循环去查询缓存,循环个200MS左右,如果缓存里还没有则直接取数据库的旧数据,一般情况下是可以取到的。

 

redis内存管理方式,支持tcmalloc,jemalloc,malloc三种内存分配,memcache使用slabs,malloc等内存分配方式。
简单点,就是redis,是边用边申请,使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配;
memcache使用预分配的内存池的方式,使用slab和大小不同的chunk来管理内存,Item根据大小选择合适的chunk存储,内存池的方式可以省去申请/释放内存的开销

优化的参数:
1.设置下redis.conf中的maxmemory选项,我的经验是当你的redis物理内存使用超过内存总容量的3/5时就会开始比较危险了。
2.需要将vm.overcommit设置为1,这参数在大量写入时,很有用
overcommit_memory=0,默认,智能超发,每次要求分配内存时,kernel都会比较请求的空间和空余的空间是否足以分配
overcommit_memory=1,请求分配内存时,永远假装还有足够的内存
overcommit_memory=2,不允许超发内存,即允许分配的大小小于
3.确保设置了一定量的swap,最好和内存一样大,否则内核的OOM(out-of-memory)killer会干掉Redis进程


>>> 数据结构
Redis内部使用一个redisObject对象来表示所有的key和value
redisObject主要的信息包括数据类型(type)、编码方式(encoding)、数据指针(ptr)、虚拟内存(vm)等。
type代表一个value对象具体是何种数据类型,encoding是不同数据类型在redis内部式。

struct sdshdr {
int len; //len表示buf中存储的字符串的长度。
int free; //free表示buf中空闲空间的长度。
char buf[]; //buf用于存储字符串内容。
};

实际上type=string代表value存储的是一个普通字符串,那么对应的encoding可以是raw或者是int,如果是int则代表实际redis内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如"20"这样的字符串,当遇到incr、decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。如果你试图对name进行incr操作则报错。

redis的Hash数据类型的key(hash表名称)对应的value实际的内部存储结构为一个HashMap,因此Hash特别适合存储对象。
将整个对象存储在Hash类型中会占用更少内存。
当前HashMap的实现有两种方式:当HashMap的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,这时对应的value的redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。

 


>>> 分布式锁
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>

public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

if (LOCK_SUCCESS.equals(result)) {
  return true;
}
return false;

}

public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

  String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

  if (RELEASE_SUCCESS.equals(result)) {
    return true;
  }
  return false;

}

 

 

 

 

 

 

 


8.分布式
优点:
单机处理能力存在瓶颈
出于稳定性和可用性的考虑

>>> CAP 定理(布鲁尔定理)
C (一致性):对某个指定的客户端来说,读操作能返回最新的写操作。
对于数据分布在不同节点上的数据来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。
A (可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)可用性的两个关键一个是合理的时间,一个是合理的响应。
合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回 50,而不是返回 40。
P (分区容错性):当出现网络分区后,系统能够继续工作。打个比方,这里集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。


>>> 分布式缓存:
redis高可用体现在:主从 / 哨兵 / 集群
常用命令
1⃣️根据配置文件启动redis: src/redis-server redis.confg
2⃣️启动redis客户端:redis-cli -p port
3⃣️Slaver连接Master:slaveof host:ip (测试时使用slaveof手动连接master,正式环境使用配置文件)
4⃣️关闭Redis:shutdown
5⃣️查看主从信息:info Replication

主节点挂掉后,手动将S1子节点升级为Master(命令:slaveof no one 手动将Slaver节点升级为Master节点)

>>> 哨兵
使用哨兵模式,自动监视Master节点,当前挂掉后,自动将Slaver节点变为Master节点
a) sentinel.conf配置文件,修改sentinel monitor host6379 127.0.0.1 6379 1,其它使用默认即可
host6379 主机名称,随便起 主机IP 端口 1表示选举,某个slaver得到超过1票则成成为Master节点
b) 启动sentinel: ./redis-sentinel ../sentinel.conf

 

>>> 分布式调度(基于Quartz和Zookeeper及其客户端Curator进行二次开发)
第一台服务器上线触发主服务器选举。主服务器一旦下线,则重新触发选举,选举过程中阻塞,只有主服务器选举完成,才会执行其他任务。
某作业服务器上线时会自动将服务器信息注册到注册中心,下线时会自动更新服务器状态。
主节点选举,服务器上下线,分片总数变更均更新重新分片标记。
定时任务触发时,如需重新分片,则通过主服务器分片,分片过程中阻塞,分片结束后才可执行任务。如分片过程中主服务器下线,则先选举主服务器,再分片。
通过上一项说明可知,为了维持作业运行时的稳定性,运行过程中只会标记分片状态,不会重新分片。分片仅可能发生在下次任务触发前。
每次分片都会按服务器IP排序,保证分片结果不会产生较大波动。
实现失效转移功能,在某台服务器执行完毕后主动抓取未分配的分片,并且在某台服务器下线后主动寻找可用的服务器执行任务。

 

 

>>> 分布式事务:
数据库事务的几个特性:原子性(Atomicity)、一致性(Consistency)、隔离性或独立性(Isolation)和持久性(Durabilily),简称就是ACID。
XA:XA协议,规定事务管理器和资源管理器接口,采用二阶段提交协议。

解决分布式事务的问题:
1⃣️补偿事务,提供回滚接口,需要更多的代码笨重冗余;
2⃣️两阶段提交事务(协调者 / 参与者):
1.请求阶段(commit-request phase,或称表决阶段,voting phase)
在请求阶段,协调者将通知事务参与者准备提交或取消事务,然后进入表决过程。
在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)。

2.提交阶段(commit phase)
在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。
当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。
参与者在接收到协调者发来的消息后将执行响应的操作。

缺点:多个节点网络通信时间长,事务时间变长,再资源等待变长;
通过事务管理器TC
开源软件atomikos

 

3⃣️本地消息表,存消息数据表,轮巡执行
核心思想是将分布式事务拆分成本地事务进行处理
基本思路:
消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。
消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中 有现成的解决方案。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

4⃣️MQ事务消息
有一些第三方的MQ是支持事务消息的,比如RocketMQ
以阿里的 RocketMQ 中间件为例,其思路大致为:
第一阶段Prepared消息,会拿到消息的地址。
第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。


上述保存消息的方式使得消息数据和业务数据紧耦合在一起,从架构上看不够优雅,而且容易诱发其他问题。为了解耦,可以采用以下方式。
1)支付宝在扣款事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送,只有消息发送成功后才会提交事务;
2)当支付宝扣款事务被提交成功后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送该消息;
3)当支付宝扣款事务提交失败回滚后,向实时消息服务取消发送。在得到取消发送指令后,该消息将不会被发送;
4)对于那些未确认的消息或者取消的消息,需要有一个消息状态确认系统定时去支付宝系统查询这个消息的状态并进行更新。为什么需要这一步骤,举个例子:假设在第2步支付宝扣款事务被成功提交后,系统挂了,此时消息状态并未被更新为“确认发送”,从而导致消息不能被发送。

优点:消息数据独立存储,降低业务系统与消息系统间的耦合;
缺点:一次消息发送需要两次请求;业务处理服务需要实现消息状态回查接口。


5⃣️事务补偿

 

 


>>> 分布式消息队列:
解藕、异步、削峰、消息通讯(发布订阅)
可靠消息传输、通过消息总线将不同的服务联系起来
高吞吐量、高性能、延时低
类型:ActiveMQ、RabbitMQ、RocketMQ、Kafka
ActiveMQ 和 RabbitMQ 都是AMQP 的一种具体实现,默认情况下,消息会存储到磁盘中,可以保证消息队列重启时数据的一致,避免消息的丢失。
不过ActiveMQ只支持简单的JSON格式文本


源码部分主要可以分为
rocketmq-broker,rocketmq-client,rocketmq-common,rocketmq-filterSrv,rocketmq-namesrv和rocketmq-remoting等模块,
通信框架就封装在rocketmq-remoting模块中


RocketMQ不保证消息的顺序性,
RocketMQ(消息可靠传输)重试机制:网络问题:生产者会无限次重试 / 会是无限次的 业务逻辑:延迟重试
处理消息重复:保持幂等,消费者通过日志记录(消息ID),大事务 = 小事务 + 异步
RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。细心的你可能又发现问题了,如果确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事物消息,这时候发现了Prepared消息,它会向消息发送者确认,Bob的钱到底是减了还是没减呢?如果减了是回滚还是继续发送确认消息呢?RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

 

Broker

/ | \
/ | \
Produce - Namesrv - Consume

1,启动Namesrv,Namesrv起来后监听端口,等待Broker、Produer、Consumer连上来,相当于一个路由控制中心
(Namesrv压力不会太大,平时主要开销是在维持心跳和提供Topic-Broker的关系数据)
2,Broker启动,跟所有的Namesrv保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有topic信息。注册成功后,namesrv集群中就有Topic跟Broker的映射关系。
3,收发消息前,先创建topic,创建topic时需要指定该topic要存储在哪些Broker上。也可以在发送消息时自动创建Topic。
4,Producer发送消息,启动时先跟Namesrv集群中的其中一台建立长连接,并从Namesrv中获取当前发送的Topic存在哪些Broker上,然后跟对应的Broker建长连接,直接向Broker发消息。


>>> Broker( 集群最核心模块,主要负责Topic消息存储、管理和分发等功能)
RocketMQ的Broker集群部署模式还挺多的,比如单Master模式、多Master模式、多Master多Slave模式(异步复制)、多Master多Slave模式(同步双写)等。明确个概念,RocketMQ Slave不可以写,可以读,类似于MySQL的主从机制。

1⃣️单Master模式:
无需多言,一旦单个broker重启或宕机,一切都结束了!很显然,线上不可以使用。

2⃣️多Master模式:
全是Master,没有Slave。当然,一个broker宕机了,应用是无影响的,缺点在于宕机的Master上未被消费的消息在Master没有恢复之前不可以订阅。

3⃣️多Master多Slave模式(异步复制):
多对Master-Slave,高可用!采用异步复制的方式,主备之间短暂延迟,MS级别。Master宕机,消费者可以从Slave上进行消费,不受影响,但是Master的宕机,会导致丢失掉极少量的消息。

4⃣️多Master多Slave模式(同步双写):
和上面的区别点在于采用的是同步方式,也就是在Master/Slave都写成功的前提下,向应用返回成功,可见不论是数据,还是服务都没有单点,都非常可靠!缺点在于同步的性能比异步稍低。


>>> 消息失败重试机制
消息失败,无非涉及到2端:从生产者端发往MQ的失败;消费者端从MQ消费消息的失败;

生产者端的消息失败:比如网络抖动导致生产者发送消息到MQ失败。
可以设置重试次数,setRetryTimesWhenSendFailed(3);

消费者端的失败,分为2种情况,一个是timeout,一个是exception
timeout,比如由于网络原因导致消息压根就没有从MQ到消费者上,在RocketMQ内部会不断的尝试发送这条消息,直至发送成功为止!(比如集群中一个broker失败,就尝试另一个broker)
exception,消息正常的到了消费者,结果消费者发生异常,处理失败了。这里涉及到一些问题,需要我们思考下,比如,消费者消费消息的状态有哪些定义?如果失败,MQ将采取什么策略进行重试?假设一次性批量PUSH了10条,其中某条数据消费异常,那么消息重试是10条呢,还是1条呢?而且在重试的过程中,需要保证不重复消费吗?

消息消费的状态,有2种,一个是成功(CONSUME_SUCCESS),一个是失败&稍后重试(RECONSUME_LATER)
RocketMQ为我们提供了这么多次数的失败重试,但是在实际中也许我们并不需要这么多重试,比如重试3次,还没有成功,我们希望把这条消息存储起来并采用另一种方式处理,而且希望RocketMQ不要在重试呢,因为重试解决不了问题了!


>>> 集群消费 AND 广播消费
RocketMQ的消费方式有2种,在默认情况下,就是集群消费,也就是上面提及的消息的负载均衡消费。另一种消费模式,是广播消费。广播消费,类似于ActiveMQ中的发布订阅模式,消息会发给Consume Group中的每一个消费者进行消费。

 

>>> 顺序性,消费的消息发往同一个broker的同一个队列上!其次消费者端采用有序Listener即可。
注意在以前普通消费消息时设置的回调是MessageListenerConcurrently,而顺序消费的回调设置是MessageListenerOrderly。
RocketMQ底层是如何做到消息顺序消费的,看一看源码你就能大概了解到,至少来说,在多线程消费场景下,一个线程只去消费一个队列上的消息,那么自然就保证了消息消费的顺序性,同时也保证了多个线程之间的并发性。也就是说其实broker并不能完全保证消息的顺序消费,它仅仅能保证的消息的顺序发送而已!


>>> RocketMQ消息队列集群中的几个角色:
NameServer:在MQ集群中做的是做命名服务,更新和路由发现 broker服务;
Broker-Master:broker 消息主机服务器;
Broker-Slave:broker 消息从机服务器;
Producer:消息生产者;
Consumer:消息消费者。

 

NameServer相当于配置中心,维护Broker集群、Broker信息、Broker存活信息、主题与队列信息等。
NameServer彼此之间不通信,每个Broker与集群内所有的Broker保持长连接。
多Master多Slave的好处在于,即便集群中某个broker挂了,也可以继续消费,保证了实时性的高可用,但是并不是说某个master挂了,slave就可以升级master,开源版本的rocketmq是不可以的。也就是说,在这种情况下,slave只能提供读的功能,将失去消息负载的能力。

其中,RocketMQ集群的一部分通信如下:
Broker启动后需要完成一次将自己注册至NameServer的操作;随后每隔30s时间定期向NameServer上报Topic路由信息;
消息生产者Producer作为客户端发送消息时候,需要根据Msg的Topic从本地缓存的TopicPublishInfoTable获取路由信息。如果没有则更新路由信息会从NameServer上重新拉取;
消息生产者Producer根据所获取的路由信息选择一个队列(MessageQueue)进行消息发送;Broker作为消息的接收者接收消息并落盘存储。


对于Producer端RocketMQ采用了轮询的方式保证了负载均衡,
Consumer端通常采用cluster集群方式消费消息,我们可以自己定义消息在消息端的分配方式。


netty通信
有自己的消息协议,会encode和decode
使用netty原因:
Netty的编程API使用简单,开发门槛低,无需编程者去关注和了解太多的NIO编程模型和概念;
对于编程者来说,可根据业务的要求进行定制化地开发,通过Netty的ChannelHandler对通信框架进行灵活的定制化扩展;
Netty框架本身支持拆包/解包,异常检测等机制,让编程者可以从JAVA NIO的繁琐细节中解脱,而只需要关注业务处理逻辑;
Netty解决了(准确地说应该是采用了另一种方式完美规避了)JDK NIO的Bug(Epoll bug,会导致Selector空轮询,最终导致CPU 100%);
Netty框架内部对线程,selector做了一些细节的优化,精心设计的reactor多线程模型,可以实现非常高效地并发处理;
Netty已经在多个开源项目(Hadoop的RPC框架avro使用Netty作为通信框架)中都得到了充分验证,健壮性/可靠性比较好。


>>> 消息队列和dubbo的差异:
在架构上,RPC和Message Queue的差异点是,Message Queue有一个中间结点Message Queue(broker),可以把消息存储。


ActiveMq / RabbitMq
【AMQP】高级消息队列协议,以字节为流
【JMS】相当于一个API


>>> 分布式RPC框架dubbo
远程通讯:提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型、序列化、"请求-响应"模式的信息交换方案
集群容错:提供基于借口方法的透明远程过程调用,包括多协议支持、软负载均衡、失败容错、地址路由、动态配置等集群支持
自动发现:基于注册中心目录服务,使服务消费方能动态地查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器
透明化,就像本地调用远程方法一样,没有任何APi侵入;负载均衡容灾错;基于注册中心自动发现。

连接方式:长连接
传输协议:TCP
传输方式:NIO异步传输
序列化:Hessian二进制序列化


>>> nginx异步非阻塞
负载均衡的算法 upstream

nginx实现负载均衡:
轮询、轮询是默认的,每一个请求按顺序逐一分配到不同的后端服务器,如果后端服务器down掉了,则能自动剔除
ip_hash、个请求按访问IP的hash结果分配,这样来自同一个IP的访客固定访问一个后端服务器,有效解决了动态网页存在的session共享问题。
weight、weight是设置权重,用于后端服务器性能不均的情况,访问比率约等于权重之比


>>> tomcat
1个server,多个service,一个service里面有多个connector和1个container(engine/host等)
connector会把请求和返回封装成request 和 response
container里面采用的是责任链方式,一步步请求处理

tomcat是socket服务(必然也基于BIO,NIO,AIO),但是也支持HTTP
一个tomcat可以为多个web应用提供服务

 

>>> 宕机容灾

>>> 分库分表
垂直拆分 / 水平拆分

跨节点Join的问题:解决这一问题的普遍做法是分两次查询实现


拆分工具:Mycat 分布式数据库的中间件
关键词:“拦截”,
它拦截了用户发送过来的SQL语句,首先对SQL语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,
然后将此SQL发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。

 

逻辑库
逻辑表
分片表
全局表
ER表(一对多关系)
非分片表

server.xml
schema.xml
rule.xml

Mycat和MySQL的区别(Mycat的核心作用):
当我们的应用只需要一台数据库服务器的时候我们并不需要Mycat,而如果你需要分库甚至分表,这时候应用要面对很多个数据库的时候,这个时候就需要对数据库层做一个抽象,来管理这些数据库,而最上面的应用只需要面对一个数据库层的抽象或者说数据库中间件就好了,这就是Mycat的核心作用。所以可以这样理解:数据库是对底层存储文件的抽象,而Mycat是对数据库的抽象。


>>> 分布式幂等性
幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品使用约支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...

 

 

 

 

 


9.zookeeper 追求强一致
分布式协调,A系统通知B系统处理结果;
负载均衡;
分布式锁;
元数据/配置信息管理;
HA高可用性。
为分布式应用提供一致性服务的软件,包括:配置维护、域名服务、分布式同步、组服务等。
zookeeper通过心跳机制可以检测挂掉的机器并将挂掉机器的ip和服务对应关系从列表中删除。

特点:高性能可扩展,读写速度快,客户端的更新顺序与它们被发送的顺序相一致,原子性,高可用性 & 可恢复性。
测试环境可以用单个zookeeper服务器,但是不能保证高可用性 & 可恢复性。
高可用性:可以通过复制,ZooKeeper它所做的就是确保对Znode树的每一个修改都会被复制到集合体中超过半数的机器上。超过半数-所有服务器数一般取奇数。

zookeeper 存在leader,follower,observer三种角色,这三种角色在实际服务集群中都是服务节点。
leader:处理所有请求,为客户的提供读和写服务
follower:只提供读服务,有机会通过选举成为leader
observer:只提供读服务

Paxos算法就是通过投票、全局编号机制。

原理:核心是原子广播机制,这个机制保证了各个server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式和广播模式。
(1) 恢复模式
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和server具有相同的系统状态。
(2) 广播模式(有点类似两段式提交事务)
一旦Leader已经和多数的Follower进行了状态同步后,他就可以开始广播消息了,即进入广播状态。这时候当一个Server加入ZooKeeper服务中,它会在恢复模式下启动,发现Leader,并和Leader进行状态同步。待到同步结束,它也参与消息广播。ZooKeeper服务一直维持在Broadcast状态,直到Leader崩溃了或者Leader失去了大部分的Followers支持。

 

分布式锁
(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

 

 

 

 

 

10.maven命令行创建工程
mvn archetype:create -DgroupId=com.leqee -DartifactId=report -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeCatalog=Internal
mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.leqee
-DartifactId=report -DpackageName=com.leqee -Dversion=1.0-SNAPSHOT -DinteractiveMode=false
maven创建步骤http://www.cnblogs.com/leiOOlei/p/3361633.html

 

 

 

 

 

 


11.jvm
jmap -dump:format=b,file=a.bin 13228

堆分区:年轻代(Eden/Survivor)-年老代-永久区
对象实例都是在堆上创建。一些类信息,常量,静态变量等存储在方法区。堆和方法区都是线程共享的
从内存回收一个对象之前会调用对象的finalize()方法

当Eden区满时,触发Minor GC;
什么时候触发full gc?
1⃣️System.gc()
2⃣️老年代空间不足
3⃣️方法去空间不足
4⃣️通过Minor GC后进入老年代的平均大小大于老年代的可用内存
5⃣️由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小


jps主要用来输出JVM中运行的进程状态信息;
jstack主要用来查看某个Java进程内的线程堆栈信息;
jmap用来查看堆内存使用状况,一般结合jhat使用;
jmap -heap pid查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况;
jstat(JVM统计监测工具)


>>> GC收集器
1⃣️Serial收集器(新生代)
Serial收集器是最基本的收集器,这是一个单线程收集器,它“单线程”的意义不仅仅是说明它只用一个线程去完成垃圾收集工作,更重要的是在它进行垃圾收集工作时,必须暂停其他工作线程,直到它收集完成。Sun将这件事称之为”Stop the world“。
没有一个收集器能完全不停顿,只是停顿的时间长短。
虽然Serial收集器的缺点很明显,但是它仍然是JVM在Client模式下的默认新生代收集器。它有着优于其他收集器的地方:简单而高效(与其他收集器的单线程比较),Serial收集器由于没有线程交互的开销,专心只做垃圾收集自然也获得最高的效率。在用户桌面场景下,分配给JVM的内存不会太多,停顿时间完全可以在几十到一百多毫秒之间,只要收集不频繁,这是完全可以接受的。

2⃣️ParNew收集器(新生代 - 并行)
ParNew是Serial的多线程版本,在回收算法、对象分配原则上都是一致的。ParNew收集器是许多运行在Server模式下的默认新生代垃圾收集器,其主要在于除了Serial收集器,目前只有ParNew收集器能够与CMS收集器配合工作。

3⃣️Parallel Scavenge收集器(新生代 - 并行 - 复制)
Parallel Scavenge收集器是一个新生代垃圾收集器,其使用的算法是复制算法,也是并行的多线程收集器。
Parallel Scavenge 收集器更关注可控制的吞吐量,吞吐量等于运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)。直观上,只要最大的垃圾收集停顿时间越小,吞吐量是越高的,但是GC停顿时间的缩短是以牺牲吞吐量和新生代空间作为代价的。比如原来10秒收集一次,每次停顿100毫秒,现在变成5秒收集一次,每次停顿70毫秒。停顿时间下降的同时,吞吐量也下降了。
停顿时间越短就越适合需要与用户交互的程序;而高吞吐量则可以最高效的利用CPU的时间,尽快的完成计算任务,主要适用于后台运算。

4⃣️Serial Old收集器(老年代 标记-整理)
Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,采用“标记-整理算法”进行回收。其运行过程与Serial收集器一样。

5⃣️Parallel Old收集器(老年代 标记-整理 并行)
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收。其通常与Parallel Scavenge收集器配合使用,“吞吐量优先”收集器是这个组合的特点,在注重吞吐量和CPU资源敏感的场合,都可以使用这个组合。


6⃣️CMS 收集器(老年代 标记-清除 最短停顿时间)
CMS(Concurrent Mark Sweep)收集器是一种以获取最短停顿时间为目标的收集器,CMS收集器采用标记--清除算法,运行在老年代。主要包含以下几个步骤:
· 初始标记
· 并发标记
· 重新标记
· 并发清除

其中初始标记和重新标记仍然需要“Stop the world”。初始标记仅仅标记GC Root能直接关联的对象,并发标记就是进行GC Root Tracing过程,而重新标记则是为了修正并发标记期间,因用户程序继续运行而导致标记变动的那部分对象的标记记录。
由于整个过程中最耗时的并发标记和并发清除,收集线程和用户线程一起工作,所以总体上来说,CMS收集器回收过程是与用户线程并发执行的。虽然CMS优点是并发收集、低停顿,很大程度上已经是一个不错的垃圾收集器,但是还是有三个显著的缺点:

· CMS收集器对CPU资源很敏感。在并发阶段,虽然它不会导致用户线程停顿,但是会因为占用一部分线程(CPU资源)而导致应用程序变慢。
· CMS收集器不能处理浮动垃圾。所谓的“浮动垃圾”,就是在并发标记阶段,由于用户程序在运行,那么自然就会有新的垃圾产生,这部分垃圾被标记过后,CMS无法在当次集中处理它们,只好在下一次GC的时候处理,这部分未处理的垃圾就称为“浮动垃圾”。也是由于在垃圾收集阶段程序还需要运行,即还需要预留足够的内存空间供用户使用,因此CMS收集器不能像其他收集器那样等到老年代几乎填满才进行收集,需要预留一部分空间提供并发收集时程序运作使用。要是CMS预留的内存空间不能满足程序的要求,这是JVM就会启动预备方案:临时启动Serial Old收集器来收集老年代,这样停顿的时间就会很长。
· 由于CMS使用标记--清除算法,所以在收集之后会产生大量内存碎片。当内存碎片过多时,将会给分配大对象带来困难,这是就会进行Full GC。

 

7⃣️G1收集器(标记-整理)
G1收集器与CMS相比有很大的改进:
· G1收集器采用标记--整理算法实现。
· 可以非常精确地控制停顿。
G1收集器可以实现在基本不牺牲吞吐量的情况下完成低停顿的内存回收,这是由于它极力的避免全区域的回收,G1收集器将Java堆(包括新生代和老年代)划分为多个区域(Region),并在后台维护一个优先列表,每次根据允许的时间,优先回收垃圾最多的区域 。


G1优点:
并行与并发
能充分利用多CPU、多核环境下的硬件优势;
可以并行来缩短"Stop The World"停顿时间;
也可以并发让垃圾收集与用户程序同时进行;

分代收集,收集范围包括新生代和老年代
能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
能够采用不同方式处理不同时期的对象;
虽然保留分代概念,但Java堆的内存布局有很大差别;
将整个堆划分为多个大小相等的独立区域(Region);
新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;

 

 

 

 

 

 

 

 

 

 

 

 

13.IO 字节流 / 字符流
一个字节有8bit,一个字符有2个字节
File file = new File(location);
FileOutputStream out = new FileOutputStream(file);
out.write(byte [] );

FileInputStream in = new FileInputStream(file);
in.read(new byte [ file.length ] );


用户空间(常规进程,该部分执行的代码不能直接访问硬件设备)和内核空间(操作系统运行时所使用的程序调度/虚拟内存/连接硬件资源)
所有的内核都直接或者间接的通过内核空间,保证操作系统的稳定性和安全性。
每一次系统调用都会存在两个内存空间之间的相互切换,通常的网络传输也是一次系统调用,通过网络传输的数据先是从内核空间接收到远程主机的数据,然后再从内核空间复制到用户空间,供用户程序使用。这种从内核空间到用户控件的数据复制很费时,虽然保住了程序运行的安全性和稳定性,但是牺牲了一部分的效率。

 

 

>>> Netty
最流行的NIO框架之一,其他还有Mina等

Netty基于事件驱动,当Channel进行I/O操作会产生对应的I/O事件,然后驱动事件在ChannelPipeline中传播,由对应的ChannelHandler对事件进行拦截与处理
Netty提供异步的、事件驱动的网络应用程序框架和工具

NIO的特点:事件驱动模型、单线程处理多任务、非阻塞I/O,I/O读写不再阻塞,而是返回0、基于block的传输比基于流的传输更高效、更高级的IO函数zero-copy、IO多路复用大大提高了Java网络应用的可伸缩性和实用性。基于Reactor线程模型。

在Reactor模式中,事件分发器等待某个事件或者可应用或个操作的状态发生,事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。如在Reactor中实现读:注册读就绪事件和相应的事件处理器、事件分发器等待事件、事件到来,激活分发器,分发器调用事件对应的处理器、事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。

NIO缺点:
NIO的类库和API繁杂,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer。
必须对多线程和网络编程非常熟悉才能写出高质量的NIO程序
JDK NIO的BUG,例如著名的epoll bug,该问题会导致Selector空轮训,最终导致CPU 100%。

Netty优点:
API使用简单、开发门槛低、功能强大、性能高、成熟、稳定,Netty修复了已经发现的所有JDK NIO的BUG
Netty运用了reactor模式,采用了监听线程池和IO线程池分离的思想,数据的流转在Netty中采取了类似职责链的设计模式,因此数据看起来就像在管道中流动一样了。

 

ServerBootstrap(EventLoopGroup group,EventLoopGroup childGroup)

 


Netty使用:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha1</version>
</dependency>


server:
NIO线程组
EventLoopGroup
EventLoopGroup

public class TimeServer {

public void bind(int port) throws Exception {
// NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();

try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());

// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}

private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
arg0.pipeline().addLast(new TimeServerHandler());
}
}

}

 

public class TimeServerHandler extends ChannelHandlerAdapter {

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);

String body = new String(req, "UTF-8");
System.out.println("The time server receive order:" + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";

ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);
}

@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}

}

 


client:
public class TimeClient {

public void connect(int port, String host) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();

b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
};
});

// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
// 等待客户端连接关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}

}


public class TimeClientHandler extends ChannelHandlerAdapter {

private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class);

private final ByteBuf firstMessage;

public TimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
}

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(firstMessage);
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);

String body = new String(req, "UTF-8");
System.out.println("Now is:" + body);
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
LOGGER.warn("Unexcepted exception from downstream:" + cause.getMessage());
ctx.close();
}

}

 

 


>>> NIO
BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理
NIO:一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
BIO是面向流的,NIO是面向缓冲区的;
BIO的各种流是阻塞的。而NIO是非阻塞的;
BIO的Stream是单向的,而NIO的channel是双向的。


NIO的服务端建立过程:Selector.open():打开一个Selector;ServerSocketChannel.open():创建服务端的Channel;bind():绑定到某个端口上。并配置非阻塞模式;register():注册Channel和关注的事件到Selector上;select()轮询拿到已经就绪的事件


第一步,绑定一个服务的端口
第二步,打开通道管理器Selector并在Selector上注册一个事件
第三步,轮循访问Selector,当注册的事件到达时,方法返回
public void listen() throws IOException {
// 轮询访问selector
while (true) {
// 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
selector.select();
// 获得selector中选中的项的迭代器,选中的项为注册的事件
Iterator<?> ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
//删除已选的key,以防重复处理
ite.remove();
         //这里可以写我们自己的处理逻辑
handle(key);
}
}
}

 

 

 

 

 

14.java基础
1⃣️多态作用,解藕,包括重载和重写;
重载:编译时多态,从JVM的角度来讲,这是一种静态分派;
重写:运行时多态,从JVM的角度来讲,这是一种动态分派。
static/final/private,子类不能重写父类

2⃣️final
被final修饰的类不可以被继承
被final修饰的方法不可以被重写
被final修饰的变量不可以被改变(什么不可以被改变呢,是变量的引用?还是变量里面的内容?还是两者都不可以被改变)
引用不可变,引用指向的内容可变

3⃣️static
被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个类来
被static修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要new出一个类来
静态代码块是严格按照父类静态代码块->子类静态代码块的顺序加载的,且只加载一次。
static一般情况下来说是不可以修饰类的,如果static要修饰一个类,说明这个类是一个静态内部类

4⃣️序列化 / 反序列化 / transient
序列化之后保存的是对象的信息
Java为用户定义了默认的序列化、反序列化方法,其实就是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法
进行序列化、反序列化时,虚拟机会首先试图调用对象里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,那么默认调用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的defaultReadObject方法。换言之,利用自定义的writeObject方法和readObject方法,用户可以自己控制序列化和反序列化的过程。

什么时候不会被序列化?
被声明为transient的属性不会被序列化,这就是transient关键字的作用
被声明为static的属性不会被序列化,这个问题可以这么理解,序列化保存的是对象的状态,但是static修饰的变量是属于类的而不是属于对象的,因此序列化的时候不会序列化它

实现方式:
实现序列化接口Serializable

用java原生序列化方式的缺点:
多语言环境下,使用Java序列化方式进行存储后,很难用其他语言还原出结果
占用的字节数比较大,而且序列化、反序列化效率也不高

除了将java对象序列化的方式,还可以通过json或者xml的方式
除了Java原生以流的方法进行的序列化 ------> ObjectInputStream,ObjectOutputStream
还有Json序列化(ObjectMapper,mapper.writeValueAsBytes,mapper.readValue(writeValueAsBytes, User.class))、
FastJson序列化、Protobuff序列化。

5⃣️String、StringBuilder、StringBuffer
String 的 “+” 底层是通过StringBuilder,然后调用append,最后toString
StringBuilder底层是一个char数组,在toString()的时候再通过new String(),缺点是当空间不足的时候需要扩容,而且不是线程安全的


6⃣️集合
ArrayList 允许空,允许重复,有序,非线程安全(Collections.synchronizedList)
LinkedList 允许空,允许重复,有序,非线程安全
Vector 线程安全
HashSet底层是HashMap

7⃣️hasCode
hash是散列的意思,就是把任意长度的输入,通过散列算法变换成固定长度的输出,该输出就是散列值


8⃣️泛型:参数化类型
类型安全。类型错误现在在编译期间就被捕获到了,而不是在运行时当作java.lang.ClassCastException展示出来,将类型检查从运行时挪到编译时有助于开发者更容易找到错误,并提高程序的可靠性
消除了代码中许多的强制类型转换,增强了代码的可读性
为较大的优化带来了可能

9⃣️内部类
一旦编译成功,就会生成两个完全不同的.class文件了
内部类分为四种:成员内部类、局部内部类、匿名内部类、静态内部类
成员内部类:
Outer outer = new Outer(0);
Outer.PublicInner publicInner = outer.new PublicInner();
成员内部类是依附其外部类而存在的
局部内部类:
public static void main(String[] args)
{
final int i = 0;
class A
{
public void print()
{
System.out.println("AAA, i = " + i);
}
}
A a = new A();
}
局部内部类是定义在一个方法或者特定作用域里面的类
局部内部类没有访问修饰符,另外局部内部类要访问外部的变量或者对象,该变量或对象的引用必须是用final修饰的
匿名内部类:
在多线程模块中的代码示例中大量使用,匿名内部类是唯一没有构造器的类
静态内部类:
Outer.staticInner os = new Outer.staticInner();


使用内部类的好处:
1、Java允许实现多个接口,但不允许继承多个类,使用成员内部类可以解决Java不允许继承多个类的问题。在一个类的内部写一个成员内部类,可以让这个成员内部类继承某个原有的类,这个成员内部类又可以直接访问其外部类中的所有属性与方法,是不是相当于多继承了呢?
2、成员内部类可以直接访问其外部类的private属性,而新起一个外部类则必须通过setter/getter访问类的private属性
3、有些类明明知道程序中除了某个固定地方都不会再有别的地方用这个类了,为这个只用一次的类定义一个外部类显然没必要,所以可以定义一个局部内部类或者成员内部类,写一段代码用用就好了
4、内部类某种程度上来说有效地对外隐藏了自己,比如我们常用的开发工具Eclipse、MyEclipse,看代码一般用的都是Packge这个导航器,Package下只有.java文件,我们是看不到定义的内部类的.java文件的
5、使用内部类可以让类与类之间的逻辑上的联系更加紧密


🔟
@RequestParam的要求
- 均支持POST,GET请求
- 只支持Content-Type: 为 application/x-www-form-urlencoded编码的内容。Http协议中,如果不指定Content-Type,则默认传递的参数就是application/x-www-form-urlencoded类型)

@RequestBody
不支持get请求,因为get请求没有HttpEntity
- 必须要在请求头中申明content-Type(如application/json).springMvc通过HandlerAdapter配置的HttpMessageConverters解析httpEntity的数据,并绑定到相应的bean上
- 只能一个@RequestBody。且不能与@RequestParam一起使用

 

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
JSONArray jsonArray = JSONArray.fromObject(tagList);

fastjson
JSON.toJSONString() ;
JSONObject.toJSON(dto);
JSONObject.parseObject(data);
JSONArray.parseArray(data, SingleShipmentReq.class);

 

 

 

 

 

15.设计模式
创建型(5):工厂、抽象工厂、建造者、原型、单例
结构型(7):适配器、装饰、代理、桥接、外观、享元、组合
行为型(11):迭代器、策略、模板、观察者、责任链、命令、备忘录、状态、访问者、中介者、解释器


简单工厂模式:线程池、ThreadFactory
单例模式:饿汉、懒汉、懒汉双检锁、应用(Runtime)
策略模式:jdk代理还是cgllib
装饰器模式:输入流InputStream


饿汉模式是安全的,懒汉模式
懒汉双检锁:
getInstance(){
static volatile A instance = null; // volatile是为了指令重排序
if( instance != null ){
synchronized(A.class){
if(nstance != null){
instance = new A(); // 复杂操作
}
}
}
}
单例模式的好处:
控制资源的使用,通过线程同步来控制资源的并发访问
控制实例的产生,以达到节约资源的目的
控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信
之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存;
之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
用单例和多例的标准只有一个:
    当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例;

Spring中用到的设计模式:
1.单例模式
2.适配器
3.观察者:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
spring中Observer模式常用的地方是listener的实现。如ApplicationListener
4.模板:数据库connection,以及在各种BeanFactory以及ApplicationContext实现中也都用到了;
5.工厂:在各种BeanFactory以及ApplicationContext创建中都用到了
6.迭代器:collection实现了Iterable,所以都可以用迭代器
7.门面:其核心为外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。如slf4j

 

 


16.jdk1.7 1.8
1.  接口的默认方
Java1.8以前,接口里的方法要求全部是抽象方法,java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可。
2.  lambda表达式
它将允许我们将行为传到函数里。在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名类,需要6行代码。而定义行为最重要的那行代码,却混在中间不够突出。Lambda表达式取代了匿名类,取消了模板,允许用函数式风格编写代码。这样有时可读性更好,表达更清晰。
3.  函数式接口
如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。一个函数式接口非常有价值的属性就是他们能够用lambdas来实例化。
4.  方法与构造函数引用
使用关键字来传递方法或者构造函数引用。
5.  Lambda作用域
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。
6.  访问局部变量
可以直接在lambda表达式中访问外层的局部变量。
7.  访问对象字段与静态变量
和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的。
8.  访问接口的默认方法
JDK1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了注解以便能用在lambda上。
Java 8API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。

 

 

 

 

17.运维环境

>>> 高CPU怎么排查
1、top命令:Linux命令。可以查看实时的CPU使用情况。也可以查看最近一段时间的CPU使用情况。
2、PS命令:Linux命令。强大的进程状态监控命令。可以查看进程以及进程中线程的当前CPU使用情况。属于当前状态的采样数据。
3、jstack:Java提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。
4、pstack:Linux命令。可以查看某个进程的当前线程栈运行情况。

>>> 内存溢出怎么排查
1、登陆服务器,立马从LB拉下。
2、快速jps,jstat -gcutil 12345看下。
3、如果是内存溢出再down(jmap -dump:live,format=b,file=heap.bin 32471)出堆分析,同时打出线程栈。
4、重启。
5、拉上f5。

 

tcp支持最大连接数
ulimit -n 查看

 


linux命令
ps对进程进行监测和控制
ps -ef
ps -mp
cat -n X.log | grep ''
more +n X.log


losf -i:8083
netstat -an | grep 8083
使用ps命令,具体用法是 ps -mq PID
这样可以看到指定的进程产生的线程数目

 

 

 

 

 

18.千万级并发分布式架构

 

 


19.拓展
技术:
业务:前瞻性的东西:配置

 

posted @ 2018-11-01 15:33  novalist  阅读(287)  评论(0编辑  收藏  举报