秋招面经三(作业帮、新浪、阿里云)

作业帮

一面(2020-7-20)

1、http报文头格式

图片说明

图片说明

图片说明

2、tcp/ip三次握手和四次挥手的状态转移?

图片说明

TCP是主机对主机层的传输控制协议,提供可靠的连接服务:

位码即tcp标志位,有6种标示:SYN(synchronous建立联机) 、ACK(acknowledgement 确认) 、PSH(push传送)、 FIN(finish结束) 、RST(reset重置) 、URG(urgent紧急)、Sequence number(顺序号码) 、Acknowledge number(确认号码)。

三次握手

  • 第一次握手:客户端发送syn包(syn=x)的数据包到服务器,并进入SYN_SEND状态,等待服务器确认;

  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

  握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP连接都将被一直保持下去。

四次握手

  • 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可以接受数据。

  • 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。

  • 第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。

  • 第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。

3、Redis中如果有大量请求怎么办?

可以使用list来进行限流操作

4、多线程对Redis写操作,如何处理?

使用分布式锁

5、Redis的淘汰过期策略

当redis占用内存超过最大限制时,可采用如下策略,让redis淘汰一些数据,以腾出空间继续提供读写服务:

  • noeviction:对可能导致增大内存的命令返回错误(大多数写命令,del除外)默认方式
  • volatile-ttl:在设置了过期时间的key当中,选择剩余寿命最短的key,将其淘汰
  • volatile-lru:在设置了过期时间的key当中,选择最少使用的key(LRU算法),将其淘汰
  • volatile-random:在设置了过期时间的key当中,随机选择一些key,将其淘汰
  • allkeys-lru:在所有的key当中,选择最少使用的key,将其淘汰
  • allkeys-random:在所有的key当中,随机选择一些key,将其淘汰

6、mysql覆盖索引

在了解覆盖索引之前我们先大概了解一下什么是聚集索引(主键索引)和辅助索引(二级索引)

  • 聚集索引(主键索引):聚集索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的即为整张表的记录数据。聚集索引的叶子节点称为数据页,聚集索引的这个特性决定了索引组织表中的数据也是索引的一部分。

  • 辅助索引(二级索引):非主键索引,叶子节点=键值+书签。Innodb存储引擎的书签就是相应行数据的主键索引值。

覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。如果一个索引包含了(或覆盖了)满足查询语句中字段与条件的数据就叫做覆盖索引。当一条查询语句符合覆盖索引条件时,sql只需要通过索引就可以返回查询所需要的数据,这样避免了查到索引后再返回表操作,减少I/O提高效率。使用覆盖索引Innodb比MyISAM效果更好----InnoDB使用聚集索引组织数据,如果二级索引中包含查询所需的数据,就不再需要在聚集索引中查找了。

覆盖索引是一种非常强大的工具,能大大提高查询性能,只需要读取索引而不需要读取数据,有以下优点:

  • 索引项通常比记录要小,所以MySQL访问更少的数据。

  • 索引都按值得大小存储,相对于随机访问记录,需要更少的I/O。

  • 数据引擎能更好的缓存索引,比如MyISAM只缓存索引。

  • 覆盖索引对InnoDB尤其有用,因为InnoDB使用聚集索引组织数据,如果二级索引包含查询所需的数据,就不再需要在聚集索引中查找了。

【注】遇到以下情况,执行计划不会选择覆盖查询

  • select选择的字段中含有不在索引中的字段 ,即索引没有覆盖全部的列。
  • where条件中不能含有对索引进行like的操作。

7、进程间通信的方式

信号量,管道,队列,共享内存

二面(2020-7-20)

1、ArrayList的扩容和收缩的规则

ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10 ;之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15(如下图二);当添加第16个数据时,继续扩容变为15 * 1.5 =22个。

如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时,才真正分配容量。每次按照1.5倍(位运算)的比率通过copeOf的方式扩容。 在JKD1.6中实现是,如果通过无参构造的话,初始数组容量为10,每次通过copeOf的方式扩容后容量为原来的1.5倍,以上就是动态扩容的原理。

2、为什么要有包装类

Java是一个面相对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

另外,当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装器类了。

3、线程状态

图片说明

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called {@code Object.wait()}
         * on an object is waiting for another thread to call
         * {@code Object.notify()} or {@code Object.notifyAll()} on
         * that object. A thread that has called {@code Thread.join()}
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

4、单例模式的优势以及单例模式与静态类的区别

https://blog.csdn.net/yuzhiqiang666/article/details/11969015

(1)大家都以为“ 静态方法常驻内存,实例方法不是,所以静态方法效率高但占内存。”
事实上,他们都是一样的,在加载时机和占用内存上,静态方法和实例方法是一样的,在类型第一次被使用时加载。调用的速度基本上没有差别。
(2)大家都以为“ 静态方法在堆上分配内存,实例方法在堆栈上”
事实上所有的方法都不可能在堆或者堆栈上分配内存,方法作为代码是被加载到特殊的代码内存区域,这个内存区域是不可写的。
方法占不占用更多内存,和它是不是static没什么关系。
因为字段是用来存储每个实例对象的信息的,所以字段会占有内存,并且因为每个实例对象的状态都不一致(至少不能认为它们是一致的),所以每个实例对象的所以字段都会在内存中有一分拷贝,也因为这样你才能用它们来区分你现在操作的是哪个对象。
但方法不一样,不论有多少个实例对象,它的方法的代码都是一样的,所以只要有一份代码就够了。因此无论是static还是non-static的方法,都只存在一份代码,也就是只占用一份内存空间。
同样的代码,为什么运行起来表现却不一样?这就依赖于方法所用的数据了。主要有两种数据来源,一种就是通过方法的参数传进来,另一种就是使用class的成员变量的值……

(3)大家都以为“实例方法需要先创建实例才可以调用,比较麻烦,静态方法不用,比较简单”
事实上如果一个方法与他所在类的实例对象无关,那么它就应该是静态的,而不应该把它写成实例方法。所以所有的实例方法都与实例有关,既然与实例有关,那么创建实例就是必然的步骤,没有麻烦简单一说。
当然你完全可以把所有的实例方法都写成静态的,将实例作为参数传入即可,一般情况下可能不会出什么问题。
从面向对象的角度上来说,在抉择使用实例化方法或静态方法时,应该根据是否该方法和实例化对象具有逻辑上的相关性,如果是就应该使用实例化对象 反之使用静态方法。这只是从面向对象角度上来说的。
如果从线程安全、性能、兼容性上来看 也是选用实例化方法为宜。
我们为什么要把方法区分为:静态方法和实例化方法 ?
如果我们继续深入研究的话,就要脱离技术谈理论了。早期的结构化编程,几乎所有的方法都是“静态方法”,引入实例化方法概念是面向对象概念出现以后的事情了,区分静态方法和实例化方法不能单单从性能上去理解,创建c++,java,c#这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题,而是为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。
拿别人一个例子说事:
比如说“人”这个类,每个人都有姓名、年龄、性别、身高等,这些属性就应该是非静态的,因为每个人都的这些属性都不相同;但人在生物学上属于哪个门哪个纲哪个目等,这个属性是属于整个人类,所以就应该是静态的——它不依赖与某个特定的人,不会有某个人是“脊椎动物门哺乳动物纲灵长目”而某个人却是“偶蹄目”的

5、spring里面jar,war包启动方式的区别

  • jar包:直接通过内置tomcat运行,不需要额外安装tomcat。如需修改内置tomcat的配置,只需要在spring boot的配置文件中配置。内置tomcat没有自己的日志输出,全靠jar包应用输出日志。但是比较方便,快速,比较简单。

  • war包:传统的应用交付方式,需要安装tomcat,然后放到waeapps目录下运行war包,可以灵活选择tomcat版本,可以直接修改tomcat的配置,有自己的tomcat日志输出,可以灵活配置安全策略。相对打成jar包来说没那么快速方便。

新浪

一面(2020-7-23)

1、NIO

1.1 最原始的BIO

在最原始的BIO通信时,我们服务端与客户端建立连接accept是一个阻塞状态,连接connect获取客户端的数据调用read方法也是一个阻塞的。那个时候,我们使用多线程,每一次将一个新的connect放入一个新的线程里面。这样可以保证我们的服务端一直处于一个监听新的连接的状态。

缺点:资源浪费,每一次的新建线程,都会进行一次系统调用,而问题的根源在于读的时候是阻塞的。

图片说明

1.2 然后进行发展

我们让read操作成为一个非阻塞的状态,然后就可以通过单线程来完成服务端监听新连接与read操作。此时我们将这个程序降为了单线程的程序。

缺点:假如我们已经建立了10W个连接,那么在单线程中,我们遍历所有的连接,来判断有没有connect传输数据了,那么此时,我们将需要进行10W次的内核调用。这样就是非常浪费资源的。

图片说明

1.3 多路复用

在上面我们遍历connect是否有数据传输过来时,我们是在用户工作空间中进行调用的。这个时候,我们每次遍历一个connect时,就会产生一次内核的调用,十分浪费资源。所以此时我们使用多路复用的技术,将10w个connet都使用select,扔到内核中去进行遍历。在内核中进行遍历的时候,只会返回一个io的状态,然后在用户空间中,完成io的读写。所以遍历io状态和io的读写是在不同的地方完成的两件不同的事情

缺点:这种情况下,内核相当于在遍历10w个connect,来查看有没有客户端向我们的服务端发送数据。这个时候也是比较浪费的,比如10w个connect中,如果只有1W个connect在进行发送数据的工作。那么我相当于有9w个connect的遍历都是浪费的。

图片说明

1.4 使用epoll进行被动中断

epoll的全程叫做event poll,事件驱动。我们在使用多路复用connect扔进内核中的时候,我们使用增量复制的方法,在内核中开辟一块空间,存放所有的connect,然后每次使用多路复用向内核中扔connect的时候,我们只将新增的connet放到内核中。并且在发生监听到有新的客户端在传输数据的时候,我们才将这些有数据通信的connect给存放到内核的另一个地方,让内核进行处理。所以,此时我们的内核处理io属于事件驱动的方式,当我们有新的数据传输时,才会将内核资源分配给connect进行处理。

图片说明

2、数据结构,写一个pow函数

public double helper(double x, int n){
    if(n ` 0){
        return 1.0*1.0;
    }
    double half = helper(x,n/2);
    if(n%2 ` 0 ){
        return half * half;
    }else {
        return half * half * x;
    }
}

3、AOP解释一下

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

//@Component
//@Aspect
public class AlphaAspect {

    @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
    public void pointcut(){

    }

    /**
     * 在切入点之气执行
     */
    @Before("pointcut()")
    public void before(){
        System.out.println("before......");
    }

    /**
     * 在切入点之后执行
     */
    @After("pointcut()")
    public void after(){
        System.out.println("after......");
    }

    /**
     * 在返回结果前执行
     */
    @AfterReturning("pointcut()")
    public void afterReturn(){
        System.out.println("afterReturn......");
    }

    /**
     * 在抛出异常前执行
     */
    @AfterThrowing("pointcut()")
    public void afterThrowing(){
        System.out.println("afterThrowing......");
    }

    /**
     * 在切入点前后都执行
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("around before......");
        Object obj = joinPoint.proceed();
        System.out.println("around after......");
        return obj;
    }
}

4、十大排序算法手撕一下

图片说明

5、项目中的对帖子的回复。Redis中缓存了热帖的评论么?

没有将评论缓存在Redis中,需要取出评论的时候,直接去数据库中搜索获取即可

6、多线程

主要聊一下线程池的7大参数,4大拒绝策略。

7、利用UDP实现TCP

8、TCP的拥塞控制

9、设计模式

手写一个单例

10、操作系统中程序的内存的结构

https://blog.csdn.net/weixin_42168958/article/details/106462506

  • BSS:(未初始化数据段)通常用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS段属于静态分配,程序结束后静态变量资源由系统自动释放。
  • 数据段:存放程序中已初始化的全局变量的一块内存区域。数据段也属于静态内存分配。
  • 代码段:存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量。
  • 栈区:由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。
  • 堆区:用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的申请释放内存造成内存空间的不连续,产生碎片。当申请堆空间时库函数(malloc,对应释放是free)是按照一定的算法搜索可用的足够大的空间。new(对应释放是delete)是关键字,会调用对象的构造和析构函数。因此堆的效率比栈要低的多

阿里云

一面(2020-7-23)

1、AOP底层原理

https://blog.csdn.net/qq_24693837/article/details/54909477

Spring中的AOP底层实现原理:动态代理。

动态代理,就是在不修改原有类对象方法的源代码基础上,通过代理对象实现原有类对象方法的增强,也就是拓展原有类对象的功能。

JDK动态代理中包含一个类和一个接口:

InvocationHandler接口:

public interface InvocationHandler { 
    public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 
} 

参数说明:

Object proxy:指最终生成的代理实例,一般不会用到。
Method method:要调用的方法
Object[] args:方法调用时所需要的参数

可以将InvocationHandler接口的子类想象成一个代理的最终操作类

Proxy类:

Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 

参数说明:

ClassLoader loader:类加载器
Class<?>[] interfaces:得到全部的接口
InvocationHandler h:得到InvocationHandler接口的子类实例

2、concurrentHashMap

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

其中的node数据结构

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
}

可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next 引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。

3、项目中使用到的事务

Redis中关注与被关注。

4、数据一致性,redo.log和undo.log的原理

  • redo.log:在将数据写入磁盘之前,我们先将命令写入到redo.log文件中,然后在启动时,会检查系统数据库中的数据状态和redo.log文件中的状态是否一致,如果不一致,那么就重新执行redo.log文件中的指令,也就是重做

  • undo.log:每次执行对数据库有所改变的命令,将其逆命令(比如 insert--->delete 、delete ----> insert )存储在undo.log文件中,这样使得当事务回滚的时候,依次执行undo.log文件中属于该事务范围的命令即可。

5、kafka如何避免重复消费

https://juejin.im/post/5e6f42ebe51d452700568abb

kafak如何保证宕机的情况下依旧可以避免重复消费,比如消费者已经消费,在传回给服务器的过程中宕机了,重启之后,如何保证不被重复消费。

5.1 生产者丢失消息的情况

生产者(Producer) 调用send方法发送消息之后,消息可能因为网络问题并没有发送过去。所以,我们不能默认在调用send方法发送消息之后消息消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。但是要注意的是 Kafka 生产者(Producer) 使用 send 方法发送消息实际上是异步的操作,我们可以通过 get()方法获取调用结果,但是这样也让它变为了同步操作。一般不推荐这么做!可以采用为其添加回调函数的形式。

如果消息发送失败的话,我们检查失败的原因之后重新发送即可!另外这里推荐为 Producer 的retries(重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你3次一下子就重试完了。

5.2 消费者丢失消息

我们知道消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。偏移量(offset)表示 Consumer 当前消费到的 Partition(分区)的所在的位置。Kafka 通过偏移量(offset)可以保证消息在分区内的顺序性。

当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。

解决办法也比较粗暴,我们手动关闭闭自动提交 offset,每次在真正消费完消息之后之后再自己手动提交 offset 。但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次**对于这种重复消费的问题:我们可以在消费者端建立一个去重表,避免重复消费。**

5.3 kafka弄丢了消息

我们知道 Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。

试想一种情况:假如 leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被 follower 副本的同步的话,就会造成消息丢失。

(1)设置acks = all

解决办法就是我们设置 acks = all。acks 是 Kafka 生产者(Producer) 很重要的一个参数。

acks 的默认值即为1,代表我们的消息被leader副本接收之后就算被成功发送。当我们配置 acks = all 代表则所有副本都要接收到该消息之后该消息才算真正成功被发送。

(2)设置 replication.factor >= 3

为了保证 leader 副本能有 follower 副本能同步消息,我们一般会为 topic 设置 replication.factor >= 3。这样就可以保证每个分区(partition) 至少有 3 个副本。虽然造成了数据冗余,但是带来了数据的安全性。

(3)设置 min.insync.replicas > 1

一般情况下我们还需要设置 min.insync.replicas> 1 ,这样配置代表消息至少要被写入到 2 个副本才算是被成功发送min.insync.replicas 的默认值为 1 ,在实际生产中应尽量避免默认值 1。但是,为了保证整个 Kafka 服务的高可用性,你需要确保 replication.factor > min.insync.replicas 。为什么呢?设想一下加入两者相等的话,只要是有一个副本挂掉,整个分区就无法正常工作了。这明显违反高可用性!一般推荐设置成 replication.factor = min.insync.replicas + 1

6、进程和线程

进程:进程是指一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。

一个进程包含了正在运行的一个程序的所有的状态信息

  • 代码
  • 数据
  • 状态寄存器:CPU状态CR0、指令指针IP
  • 通用寄存器:AX、BX、CX
  • 进程占用系统资源:打开文件、已分配内存

进程的特点:

  • 动态性:可动态的创建、结束进程
  • 并发性:进程可以被独立调度并占用处理器运行
  • 独立性:不同进程的工作不相互影响
  • 制约性:因为访问共享数据/资源或进程间同步而产生制约

进程的5个状态

图片说明

线程:线程是进程的一部分,描述指令流执行状态。他是进程中的指令执行流的最小单元,是CPU调度的基本单位。

  • 进程的资源分配角色:进程由一组相关资源构成,包括地址空间(代码段、数据段)、打开的文件等各种资源
  • 线程的处理机调度角色:线程描述在进程资源环境中的指令流执行状态。

所以,线程 = 进程 - 共享资源

  • 线程的优点:
    • 一个进程中可以同时存在多个线程
    • 各个线程之间可以并发的执行
    • 各个线程之间可以共享地址空间和文件等资源
  • 线程的缺点:
    • 一个线程崩溃,会导致其所属进程的所有线程都奔溃。

两者的比较

  • 进程是资源分配单位,线程是CPU调度单位。
  • 进程拥有一个完整的资源平台,而线程只独享指令流执行的必要资源,如寄存器和栈。
  • 线程具有就绪、等待和运行三种基本状态和状态建的装换关系。
  • 线程能减少并发执行的时间和空间开销
    • 线程的创建时间比进程端。
    • 线程的终止时间比进程端。
    • 同一进程内的线程切换时间比进程短。
    • 由于同一进程的各个线程间共享内存和文件资源,可以不通过内核直接进行直接通信。
全部评论

相关推荐

不愿透露姓名的神秘牛友
11-21 17:16
科大讯飞 算法工程师 28.0k*14.0, 百分之三十是绩效,惯例只发0.9
点赞 评论 收藏
分享
在评审的大师兄很完美:像这种一般就是部门不匹配 转移至其他部门然后挂掉 我就是这样被挂了
点赞 评论 收藏
分享
尊嘟假嘟点击就送:加v细说,问题很大
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务