对比面向过程,是两种不同的处理问题角度
面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象),及各自需要做什么。
比如:实现一个模块功能
面向过程会将任务拆解成一系列的步骤(函数),1,书写技术文档 2,排期 3,代码编写 4,测试 5,上线
面向对象会拆除技术架构师 java开发工程师 测试工程师 运维工程师
架构师:调研 技术文档编写
开发工程师:代码编写
测试工程师:测试
运维工程师:上线
面向过程比较直接高效,而面向对象更容易复用、扩展和维护
面向对象
封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项
内部细节对外部调用透明,外部调用无需修改或者关心内部实现
eg: javabean的getset方法对外访问,因为属性的赋值智能由javabean本身决定。而不能由外部胡乱修改。
private String name;
public void setName(String name){
this.name = "tuling_"+name;
}
public String getName(){
retrun this.name;
}
该name有自己的命名规则,明显不能由外部直接赋值
继承基类的方法,并做出自己的改变或扩展
子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需要扩展自己个性化的
基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同
==对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象地址。
equeals: object中默认也是采用==比较,通常会重写。
public boolean equals(Object obj) {
return (this == obj);
}
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public static void main(String[] args) {
String aa="aa";
String a2=new String("aa");
String a3=a2;
System.out.println(aa==a2); //false
System.out.println(aa==a3); //false
System.out.println(a2==a3); //true
System.out.println(aa.equals(a2));//true
System.out.println(aa.equals(a3));//true
System.out.println(a2.equals(a3)); //true
}
hashCode()的作用是获取哈希码(散列码),实际返回一个int整数。
作用是确定该对象在哈希表中的索引位置。hashCode()定义在JDK的Object.java中,java中的任何类都包含有hashCode()函数。
散列表存储的键值对(key-value),它的特点是:能根据键快速检索出对应的value,这其中就用到了散列码。
为什么要有hashCode:
以“HashSet如何检查重复”为例子来说明为什么要有hashCode。
对象加入HashSet时,HashSet会先计算出对象的HashCode值来判断对象加入的位置,看该位置时候有值,如果没有,HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals()方法来检查两个对象是否真的相同,HashSet就不会让其加入操作成功。
如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。
重载:发生在同一个类中,方法名必须相同,参数类型不同,个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法修饰符为private则子类就不能重写该方法。
最终的
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器
1)启动类加载器(Bootstarp ClassLoader)用来加载java核心类库,无法被java程序直接引用。
2)扩展类加载器(ExtensionsClassLoader)用来加载java的扩展库。Java虚拟机的实现会提供一个扩展库目录。
该类加载器在此目录里面查找并加载类。
3)系统类加载器(System class loader)也叫应用类加载器:它根据Java应用的类路径(ClassPath)来加载Java类。一般来说,Java应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClasssLoader()来获取它。
4)用户自定义类加载器,通过继承java.lang.ClassLoader类的方式实现。
轻量级的开源J2EE框架。它是一个容器框架,用来装javabean(java对象)。
Spring是一个轻量级的控制翻转IOC和面向切面AOP的容器架构
系统是由许多不同的组件组成的,每一个组件各负责一块特定的功能。除了实现自身的核心功能外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常称为横切关注点,因为他们会跨越系统的多个组件。
当我们需要分散的对象引入公共行为的时候,OOP则显的无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。
日志代码往往水平的散步在所有的对象层次中,而它所散列到的对象的核心功能毫无关系。
在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP:将程序中的交叉业务逻辑(比如安全、日志、事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以执行某个方法之前额外的做一些事情,在某个方法执行之后额外做的一些事情。
容器概念、控制翻转、依赖注入
IOC容器:实际上就是个map(key,value),里面存的各种对象(在xml里配置的bean节点、@repository@service@controller@compoment),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类目使用反射创建对象放到map里、扫描到有上述注解的类还是通过反射创建对象放到map里。
这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入(autowired,resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性根据id注入,也会扫描这些注解,根据类型或id注入,id就是对象名)
引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变成了被动行为,控制权颠倒过来了,这就是“控制反转”这个名词的由来。
全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个ioc,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
零拷贝指的是,应用程序在需要把内核中的一块区域数据转移到另外一块区域去时,不需要经过先复制到用户空间,再转移到目标内核区域去了,而直接实现转移。
依赖于TransationListener接口
prepare:将消息(消息上带有事务标识)投递到一个名为RMS_SYS_TRANS_HALC_TOPIC的topic中,而不是投递到真正的topic中。
commit/rollback:producer在通过TransactionListeer的executorLocalTransaction方法执行本地事务,当producer的localTranstion处理成功或失败后,producer会向broker发送commit或rollback命令,如果是commit,则broker会将投递到RMQ_SYS_TRANS_HALF_TOPIC中的消息投递到真实的topic中,然后再投递一个标识删除的消息RMQ_SYS_TRANS_HALF_TOPIC中,表示当前事务已经完成。
如果是rollback,则没有投递到真实的topic的过程,只需要投递表示删除的消息到RMQ_SYS_TRANS_OP_HALF_TOPIC。最后,消费者和普通消息一样消费事务消息。
事务状态的检查有两种情况:
@RocketMQTransactionListener
class TransactionListenerImpl implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// ... local transaction process, return bollback, commit or unknown
System.out.println("executeLocalTransaction");
return RocketMQLocalTransactionState.UNKNOWN;
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
// ... check transaction status and return bollback, commit or unknown
System.out.println("checkLocalTransaction");
return RocketMQLocalTransactionState.COMMIT;
}
}
默认是不能保证的,需要程序保证发送和消费的是同一个queue,多线程消费也无法保证
发送顺序:发送端自己业务逻辑保证先后,发往一个固定的queue,生产者可以在消费体上设置消息的顺序
发送者实现MessageQueueSeletor接口,选择一个queue进行发送,也可使用rocketmq提供的默认实现。
SelectMessageQueueByHash:按参数的hashcode与可选队列进行求余选择
SelectMessageQueueByRandom:随机选择
mq:queue本身就是顺序追加写,只需保证一个队列同一时间只有一个consumer消费,通过加锁实现,consumer上的顺序消费有一个定时任务,每隔一定时间向broker发送请求延长锁定。
消费端:
pull模式:消费者需要自己维护需要拉取queue,一次拉取的消息都是顺序的,需要消费端自己保证顺序消费。
push模式:消费实例实现自MQPushConsumer接口,提供注册监听的方法消费消息,registerMessageListener、重载方法。
MessageListenerConcurrently:并行消费
MessageListenerOrderly:串行消费,consumer会把消息放入本地队列并加锁,定时任务保证锁的同步。
commitLog:日志数据文件,被所有的queue共享,大小为1G,写满之后重新生成,顺序写
consumeQueue:逻辑queue,消息先到达commitLog、然后异步转发到consumeQueue,包含queue在commitLog中的物理位置偏移量Offset,消息实体内容的大小和MessageTag的hash值。大小约为600w个字节,写满之后重新生成,顺序写。
indexFile:通过key或者时间区间来查找CommitLog中的消息,文件名以创建的时间戳命名,固定的单个IndexFile大小为400M,可以保存2000W个索引。
所有队列共用一个日志数据文件,避免了kafka的分区数过多、日志文件过多导致磁盘IO读写压力较大造成的性能瓶颈,rocketmq的queue只存储少量的数据、更加轻量化,对于磁盘的访问是串行化避免磁盘竞争,缺点在于:写入是顺序写,但读是随机的,先读ConsumeQueue,再读CommitLog,会降低消费读的效率。
消息发送到broker后,会被写入commitLog,写之前加锁,保证顺序写入。然后转发到consumeQueue。
消息消费时先从consumeQueue读取消息在CommitLog中的起始物理偏移量Offset,消息大小和消息的tag的HashCode值。在重commitLog读取消息内容。
生产者:
broker:
同步刷盘、集群模式下采用同步复制,会等待slave复制完成才会返回确认。
消费者:
offset手动提交,消息消费保证幂等。
生产者向消息队列里写入消息,不同的业务场景需要生产者采用不同的写入策略。比如同步发送,异步发送,Oneway发送,延迟发送,发送事务消息等。默认使用的是DefaultMQProducer类,发送消息要经过五个步骤。
1)设置Producer的GroupName.
2)设置InstanceName,当一个jvm需要启动多个Producer的时候,通过设置不同的InstanceName来区分,不设置的话系统使用默认名称“Default”。
3)设置发送失败重试次数,当网络出现异常的时候,这个次数影响消息的重复投递次数。想保证不丢消息,可以设置重试几次。
4)设置NameServer地址。
5)组装消息并发送。
消息发生返回状态(SendResule#SendStatus)有如下四种
Flush_Dish_timeout
Flush_slave_timeout
Slave_not_available
send_ok
不同状态在不同的刷盘策略和同步策略的配置下含义是不同的。
1,Flush_Dish_Timeout:表示没有在规定时间内完成刷盘(需要Broker的刷盘策略被设置成Sync_flush才会报这个错)。
2,Flush_slave_Timeout:表示在主备方式下,并且Broker被设置成Sync_Master方式,没有在设定时间内完成主从同步。
3,Slave_not_available:这个状态产生的场景和Flush_slave_timeout类似,表示在主备方式下,并且Broker被设置成Sync_master,但是没有找到被设置成slave的broker。
4,Send_ok:表示发送成功,发送成功的具体含义,比如消息是否已经被存储到磁盘?消息是否被同步到Slave上?消息在Slave上是否被写入磁盘?需要结合所配置刷盘策略,主从策略来定。这个状态可以理解为,没有发生上面列出的三个问题状态就是Send_Ok.
写一个高质量的生产者程序,重点在于对发送结果的处理,要充分考虑各种异常,写清对应的处理逻辑。
提升写入性能
发送一条消息处刑曲需要经过三步。
1,客户端发送请求到服务器
2,服务器处理该请求。
3,服务器向客户端返回应答。
一次消息的发送耗时是上述三个步骤的总和。
在一些对速度要求高,但是可靠性要求不高的场景下,比如日志收集类应用,可以采用Oneway方式发送。
Oneway方式发送请求不等待应答,即将数据写入客户端的Socket缓冲区就返回,不等待对方返回结果。
用这种方式发送消息的耗时可以缩短到毫秒级。
另一种提高发送速度的方法是增加Producer的并发量,使用多个Producer同时发送,我们不担心多Producer同时写会降低消息写磁盘的效率,RocketMq引入了一个并发窗口,在窗口内消息可以并发地写入DirectMem中,然后异步地将连续一段无空洞的数据刷入文件系统中。
顺序写CommitLog可以让rocketMq无论在HDD还是SSD磁盘情况下都能保持较高的写入性能。
目前在阿里内部经过调优的服务器上,写入性能达到90w+的tps,我们可以参考这个数据进行系统优化。
简单总结消费的几个要点:
1,消息消费方式(pull和push)
2,消息消费的模式(广播模式和集群模式)
3,流量控制(可以结合sentinel来实现,后面单独讲)
4,并发线程数设置
5,消息的过滤(tag,key)taga||tagb||tagc*null
当consumer的处理速度跟不上消息的产生速度,会造成越来越多的消息积压,这个时候可以查看消费逻辑本身有没有优化的空间,除此之外还有三种方法可以提高Consumer的处理能力。
1,提高消费并行度
在同一个ConsumerGroup下(Clustering方式),可以通过增加Consumer实例的数量来提高并行度。
通过加机器,或者在已有的机器启动多个Consumer进程都可以增加Consumer是实例数。
注意:总的consumer数量不要超过topic的read queue数量,超过的consumer实例接收不到消息。
此外,通过提高单个Consumer实例中的并行处理的线程数,可以在同一个Consumer内增加并行度来提高吞吐量(设置方法是修改consumerThreadMin和consumeThreadMax)
2,以批量方式进行消费
某些业务场景下,多条消息同时处理的时间会大大小于逐个处理的时间总和,比如消费消息中涉及update某个数据库,一次update10条的时间会大大小于10次update1条数据的时间。
可以通过批量方式消费来提高消费的吞吐量。实现方法是设置Consumer的consumerMessageBatchMaxSize这个参数,默认是1,如果设置N,在消息多的时候每次收到长度为N的消息链表。
3,检测延时情况,跳过非重要消息
consumer在消费过程中,如果发现由于某种原因发生严重的消息堆积,短时间无法消除堆积,这个时候可以选择丢弃不重要的消息,使Consumer尽快追上Producer的进度。
索引用来快速地寻找那些具有特定值得记录。如果没有索引,一般来说执行查询时遍历整张表。
索引得原理:就是把无序得数据变成有序得查询。
1,把创建了索引得列得内容进行排序
2,对排序结果生成倒排表
3,在查询得时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体得数据
在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要是运维在做,会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢的原因是什么?是查询没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化是针对这三个方向来的。
首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析和重写。
分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
当一个SQL想利用索引是,就一定要提供索引所对应的字段中最左边的字段,也就是排在前面的字段,比如针对a,b,c三个字段建立了一个联合索引,那么在写一个sql时就一定要提供a字段的条件,这样才能用到联合索引,这是由于在建立a,b,c三个字段的联合索引时,底层的B+树是按照a,b,c三个字段从左往右去比较大小进行排序,所以如果想利用B+树进行快速查找也得符合这个规则。
innodb通过Buffer Pool,LogBuffer,Redo Log,UndoLog来实现事务
1,Innodb在收到一个update语句后,会先根据条件找到数据所在的页,并将该页缓存在Buffer Pool中
2,执行update语句,修改buffer pool中的数据,也就是内存中的数据
3,针对update语句生成一个redolog对象,并存入LogBuffer中
4,针对update语句生成undolog日志,用于事务回滚
5,如果事务提交,那么则把redolog对象进行持久化,后续还有其他机制将buffer pool中所修改的数据页持久化到磁盘中。
6,如果事务回滚,则利用undolog日志进行回滚。
redolog 重做日志:
确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入到磁盘,在重写mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性
我们都知道,事务的四大特性里面有一个持久性,具体来说就是只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来状态。
那么mysql是如何保证一致性呢?最简单的做法是在每次事务提交的时候,将该事务涉及修改的数据页全部刷新到磁盘中。但是这么做会有严重的性能问题,主要体现在两个方面。
1,因为innodb是以 页为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,太浪费资源了
2,一个事务可能涉及修改多个数据页,并且这些数据在物理上并不连续,使用随机IO写入性能太差。
因此mysql设计了redolog ,具体来说就是只记录事务对数据页做了哪些修改,这样就能完美地解决性能问题了。
undolog 回滚日志
保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。
zxid:事务id sid:节点id
先对比zxid,在对比节点id,先投自己,选票内容(zxid,sid),遇强改投。
投票箱:每个节点在本地维护自己和其他节点的投票信息,改投时需要更新信息,并广播节点状态。
1,首先集群启动时,会先进行领导者选举,确定哪个节点时Leader,哪些节点时Follower和Observer
2,然后Leader会和其他节点进行数据同步,采用发送快照和发送Diff日志的方式
3,集群在工作过程中,所有的写请求都会交给Leader节点来进行处理,从节点只能处理读请求。
一般脑裂都是出现在集群环境中。指的是一个集群环境中出现了多个master节点 ,导致严重的数据问题,数据不一致等等。
出现的原因:可能就是网络环境有问题如断开,假死等等,导致一部分slave节点会重新进入崩溃恢复模式,重新选举新的master节点,然后对外提供事务服务。
例如机房A和机房B通信,一个6个节点,选举机房A的一个节点为master节点。
当机房A和机房B出现网络通信故障,如下图会导致机房重新选举master。在网络恢复后就会出现两个master。
解决办法
1,采用冗余心跳通信
2,采用过半原则
节点之间采用多路心跳来保证单心跳链路的不稳定性。这里着重介绍下过半原则
位图:int[10],每个int类型的整数是4*8=32个bit,则int[10]一共有320bit,每个bit非0即1,初始化时都是0
添加数据时,将数据进行hash得到hash值,对应到bit位,将该bit改为1,hash函数可以定义多个,则一个数据添加会将多个(hash函数个数)bit改为1,多个hash函数的目的是减少hash碰撞的概率
查询数据时:hash函数计算得到hash值,对应到bit中,如果有一个为0,则说明数据不在bit中,如果都为1,则该数据可能在bit中。
优点:
缺点:
zk的数据模型时一种树形结构,具有一个固定的根节点(/),可以
#Redis
RDB:redis DataBase 将某一个时刻的内存快照(Snapshot),以二进制的方式写入磁盘。
手动触发:
save命令,使Redis处于阻塞状态,直到RDB持久完成,才会响应其他客户端发来的命令,所以在生产环境一定要慎用。
bgsave命令:fork出一个子进程执行持久化,主进程只在fork过程中有短暂的阻塞,子进程创建之后,主进程就可以响应端请求了。
自动触发:
save m n:在m秒内,如果有n个健发生改变,则自动触发持久化,通过bgsave执行,如果设置多个、只要满足其一就会触发,配置文件有默认配置(可以注释掉)
flushall:用于清空redis所有的数据库,flushdb清空当前redis所在库数据(默认是0号数据库),会清空RDB文件,同时会生成dump.rdb、内容为空。
主从同步:全量同步时会自动触发bgsave命令,生成rdb发送给从节点
优点:
1,整个Redis数据库将只包含dump.rdb,方便持久化。
2,容灾性好,方便备份。
3,性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所有是IO最大化。使用单独子进程进行持久化,主进程不会进行任何IO操作,保证了redis的高性能。
4,相对于数据集大时,比AOF的启动效率更高。
Redis基于Reactor模式开发了网络事件处理器、文件事件处理器file event handler。它是单线程的,所以Redis才叫做单线程模式,它采用IO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程模块进行对接,保证了Redis内部的线程模型的简单性。
文件事件处理器的结构包含4个部分:多个Socket、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)
多个Socket可能并发的产生不同的事件,IO多路复用程序会监听多个Socket,会将socket放入一个队列中排队,每次从队列中有序、同步取出一个Socket事件分派器,事件分派器把Socket给对应的事件处理器。
然后一个Socket的事件处理完之后,IO多路复用程序才会将队列中的下一个Socket给事件分派器。文件事件分派器会根据每个socket当前产生的事件,来选择对应的事件处理器来处理。
1,Redis启动初始化时,将连接应答处理器跟AE_READABLE事件关联
2,若一个客户端发起连接,会产生一个AE_READBLE事件,然后由连接应答器负责和客户端建立连接,创建客户端对应的socket,同时将这个socket的AE_READBLE事件和命令请求处理器关联,使得客户端可以向主服务器发送命令请求。
3,当客户端向Redis发请求时(不管读还是写请求),客户端socket都会产生一个AE_READABLE事件,触发命令请求处理器。处理器读取客户端的命令内容,然后传给相关程序执行。
4,当Redis服务器准备好给客户端的响应数据后,会将socket的AE_WRITABLE事件和命令回复处理器关联,当客户端准备好读取响应数据时,会在socket产生一个AE_WRITABLE事件,由对应命令回复处理器,即将准备好的响应数据写入socket,供客户端读取。
5,命令回复处理器全部写完到socket后,就会删除该socket的AE_WRITABLE事件和命令回复处理器的映射。
单线程快的原因:
1)纯内存操作
2)核心是基于非阻塞的IO多路复用机制
3)单线程反而避免了多线程的频繁上下文切换带来的性能问题
哨兵模式
sentinel,哨兵是redis集群中非常重要的一个组件,主要有以下功能:
哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
3.0版本开始正式提供。采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询发送的正确的节点上执行。
方案说明:
在redis cluster架构下,每个redis要开放两个端口号,比如一个是6379,另外一个是16379.
16379端口号是用来进行节点间通信的,也就是cluster bus的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus用了另外一种二进制的协议,gossip协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间
优点:
基于业务逻辑拆分
是从业务维度进行拆分。标准是按照业务的关联程度来决定,关联比较密切的业务适合拆分为一个微服务,而功能相对比较独立的业务适合独立拆分为一个微服务。
以社交App为例。你可以认为首页信息流是一个服务,评论是一个服务,消息通知是一个服务,个人主页也是一个服务。
是从公共且独立功能维度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源不与其他业务耦合。
将系统中的业务模块按照稳定性排序,将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务。稳定的服务粒度可以粗一些,即使逻辑上没有强关联的服务,也可以放在同一个子系统中,例如将“日志服务”和“升级服务”放在同一个子系统中;不稳定的服务粒度可以细一些,但也不要太细,始终记住要控制服务的总数量。
将系统中的业务模块按照优先级排序,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用
避免非核心的业务故障影响核心业务
核心服务高可用方案可以更简单
能够降低高可用成本
基于性能拆分
其实G1如果要做到这一点,他就必须要追踪每个Region里的回收价值。
回收价值:必须清楚每个Region里的对象有多少垃圾,如果对这个Region 进行垃圾回收,需要耗费多长时间,可以回收掉多少垃圾。
总结:G1可以做到让你来设定垃圾回收对系统的影响,他自己通过把内存拆分为大量小Region,以及追踪每个Region,以及追踪每个Region中可以回收的对象大小和预估时间,最后在垃圾回收的时候,尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,同时在有限的时间内尽量回收竟可能多的垃圾对象。
G1对应的是一大堆的Region内存区域,每个Region的大小是一致的。
问题:到底有多少个Region?每个Region的大小是多少?
其实这个默认情况下是自动计算和设置的,我们可以给整个内存设置一个大小,比如说用“-Xms”和“-Xmx”来设置堆内存的大小。
因为jvm最大可以有2048个region,然后region的大小必须是2的倍数,比如1MB,2MB,4MB之类的
并发清理阶段用户线程还在运行,这段时间就可能产生新的垃圾,新的垃圾在此次GC无法清除,只能等到下次清理。这些垃圾有个专业名词:浮动垃圾。
这个浮动垃圾如何理解?难道不是在本次GC重新标记remark的过程被发现然后清理吗?为何还要等到下次GC才能清理?
remark过程标记活着的对象,从GCRoot的可达性判断对象活着,但无法标记“死亡”的对象。
如果在初始化标记阶段标记为活着,并行运行过程中“死亡”,remarck过程无法纠正,因此变为浮动垃圾,需等待下次gc的到来。
重新标记(Remark)
之前在并发标记时,因为是GC和用户程序是并行执行的,可能导致一部分已经标记为从GC Roots不可达的对象,因为用户程序的(并行)运行又可达了。remark的作用就是将这部分对象又标记为可达对象。
至于浮动垃圾,因为CMS在并发标记时是并发的,GC线程和用户线程并发执行,这个过程当然可能会因为线程交替执行而导致新产生的垃圾(即浮动垃圾)没有被标记到,而重新标记的作用只是修改并发标记所获得的的不可达,所以是没有办法处理“浮动垃圾”的。
tcp是面向连接,传输可靠,以字节流的形式传输,传输效率慢。
udp面向无连接,传输不可靠,以数据报文的形式传输,传输效率快。
udp在传输之前不需要建立连接,远地主机在收到UDP报文后,不需要给出任何确认。在某些情况下UDP确是一种最有效的工作方式(一般用于即使通信),比如qq语音,直播
tcp提供面向连接的服务。在传送之前必须先建立连接3次握手,数据传送结束后释放连接。(tcp的可靠性体现在tcp在传输数据之前,会有三次握手来建立连接,而且在数据传送时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如:确认,流量,计时器以及连接管理。
应用数据被分割成TCP认为最适合发送的数据块。
tcp给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
校验和:tcp将保持它首部和数据的校验和。这是一个端到端的校验和,目的是检测数据在传输过程中的任何变化。如果收到段的校验和有差错,TCP将丢弃这个报文和不确认收到此报文段和不确认收到此报文段。
流量控制:TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端接收端缓存区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。(TCP利用滑动窗口实现流量控制)
拥塞控制:当网络拥塞时,减少数据的发送。
ARQ协议:也是为了实现可靠性传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
超时重传:当tcp发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
问岗位:
这个岗位最大的挑战是什么?
在这个岗位会接触到哪些类型的项目?
请问此岗位所隶属的团队成员有多少人?目前团队的核心工作是哪些?如果我有幸入职,我的工作内容会涉及哪些?
问团队:
这个团队在公司的角色是什么?
团队的基本情况?
可以介绍下跟我的介绍下我的领导吗?
问公司:
员工的晋升机制是什么样的? 我这个岗位的晋升机会如何?
这个岗位所在的团队如何支持公司目标的实现?
有关工作潜力:
这个职位未来一段时间内的职业发展是什么?
开闭原则是指软件实体如类、模块、函数对扩展开放,对修改关闭
强调的是用抽象构建框架,用实现扩展细节。
比如在一个稳定的软件系统中增加新的功能,若对原有的功能进行修改,那么可能带来很多的隐患,甚至降低原系统的稳定性。而如果只是原有功能的基础上进行扩展,将会有效的降低原有功能的影响,且有利于后续的维护。
提高软件系统的可复用性、可维护性。
指的是高层模块不应该直接依赖于底层模块,二者都应该依赖于其抽象。
抽象不依赖于实现,实现依赖于抽象。
具体实现可以通过依赖注入的方式进行使用。
这样可以有效的减少类与类之间的耦合,提高代码的可读性、可维护性,并能够降低修改程序带来的风险。
尽量避免一种以上可能导致软件中的类、对象、方法进行变更的因素。
可以有效的降低代码的复杂度,提高代码的可读性、可维护性。
接口的定义与使用应遵循的原则,尽量使用多个专用的接口,而不是单个大量不相关的方法组成的接口,使实现减少依赖不需要的接口。
符合高内聚、低耦合的设计思想,可以有效的提高代码的可读性,可扩展性、可维护性。
指一个对象应对其他对象保持最少的了解。
eg:你是一家花店的老板,当你想指导当天的营业额时,无需你自己对当天售出的鲜花价格、数量等进行计算,只需要询问负责收银的员工得到结果,那么就降低了你想得到营业额的复杂度,由对应的人去处理对应的事务。对象之间也是如此,当你想要得到某个结果时,可以使用对应的处理其的对象,而无需自身进行计算,lishi可以有效的降低代码的复杂程度。
可以有效的降低类之间的耦合度
所有使用父类的地方都能透明的使用其子类替换,并且不会影响到原有的功能和逻辑。
强调了子类可以对父类的功能进行扩展,但是不能修改原有的功能。
合成复用原则强调的是尽量使用的对象组合(has-a)或对象聚合(contains-a),而不是使用继承关系来达到复用的目的。
避免滥用继承关系,可以使系统更加的灵活,降低类之间的耦合度,并且在修改其中的类时对其他类的影响会较小。
+1的原因是:即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能保证CPU的时钟周期不会被浪费。
比如平均每个线程CPU运行时间为0.5秒,而线程等待时间(非CPU运行时间,比如IO)为1.5s,cpu核心数为8,那么根据上面这个公式估算得到:
((0.5+1.5)/0.5)*8=32。
这个公式进一步转化为:最佳线程数目=(线程等待时间与线程CPU时间之比+1)*CPU数目。
eg:
在windows中,在cmd命令中输入“wmic”,然后再出现的新端口中输入“CPU get”即可查看物理CPU数,
其中:
name:表示物理cpu数
numberOfCores:表示CPU核心数
NumberOfLogicalProcessors:表示CPU线程数
1)cpu的线程数概念仅仅只针对Intel的cpu才有用,因为它是通过Intel超线程技术来实现的
2)如果没有超线程技术,一个CPU核心对应一个线程。所以,对于AMD的CPU来说,只有核心数的概念,没有线程数的概念