TCP是主机对主机层的传输控制协议,提供可靠的连接服务:
位码即tcp标志位
,有6种标示:SYN(synchronous建立联机) 、ACK(acknowledgement 确认) 、PSH(push传送)、 FIN(finish结束) 、RST(reset重置) 、URG(urgent紧急)、Sequence number(顺序号码) 、Acknowledge number(确认号码)。
三次握手
握手过程中传送的包里不包含数据
,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP连接都将被一直保持下去。
四次握手
可以使用list来进行限流操作
使用分布式锁
当redis占用内存超过最大限制时,可采用如下策略,让redis淘汰一些数据,以腾出空间继续提供读写服务:
在了解覆盖索引之前我们先大概了解一下什么是聚集索引(主键索引)和辅助索引(二级索引)
覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。**如果一个索引包含了(或覆盖了)满足查询语句中字段与条件的数据就叫做覆盖索引。**当一条查询语句符合覆盖索引条件时,sql只需要通过索引就可以返回查询所需要的数据,这样避免了查到索引后再返回表操作,减少I/O提高效率。使用覆盖索引Innodb比MyISAM效果更好----InnoDB使用聚集索引组织数据,如果二级索引中包含查询所需的数据,就不再需要在聚集索引中查找了。
覆盖索引是一种非常强大的工具,能大大提高查询性能,只需要读取索引而不需要读取数据,有以下优点:
【注】遇到以下情况,执行计划不会选择覆盖查询
信号量,管道,队列,共享内存
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倍,以上就是动态扩容的原理。
Java是一个面向对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
另外,当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装器类了。
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;
}
https://blog.csdn.net/yuzhiqiang666/article/details/11969015
(1)大家都以为“ 静态方法常驻内存,实例方法不是,所以静态方法效率高但占内存。” 事实上,他们都是一样的,在加载时机和占用内存上,静态方法和实例方法是一样的,在类型第一次被使用时加载。调用的速度基本上没有差别。(2)大家都以为“ 静态方法在堆上分配内存,实例方法在堆栈上” 事实上所有的方法都不可能在堆或者堆栈上分配内存,方法作为代码是被加载到特殊的代码内存区域,这个内存区域是不可写的。方法占不占用更多内存,和它是不是static没什么关系。因为字段是用来存储每个实例对象的信息的,所以字段会占有内存,并且因为每个实例对象的状态都不一致(至少不能认为它们是一致的),所以每个实例对象的所以字段都会在内存中有一分拷贝,也因为这样你才能用它们来区分你现在操作的是哪个对象。但方法不一样,不论有多少个实例对象,它的方法的代码都是一样的,所以只要有一份代码就够了。因此无论是static还是non-static的方法,都只存在一份代码,也就是只占用一份内存空间。同样的代码,为什么运行起来表现却不一样?这就依赖于方法所用的数据了。主要有两种数据来源,一种就是通过方法的参数传进来,另一种就是使用class的成员变量的值……
(3)大家都以为“实例方法需要先创建实例才可以调用,比较麻烦,静态方法不用,比较简单” 事实上如果一个方法与他所在类的实例对象无关,那么它就应该是静态的,而不应该把它写成实例方法。所以所有的实例方法都与实例有关,既然与实例有关,那么创建实例就是必然的步骤,没有麻烦简单一说。当然你完全可以把所有的实例方法都写成静态的,将实例作为参数传入即可,一般情况下可能不会出什么问题。从面向对象的角度上来说,在抉择使用实例化方法或静态方法时,应该根据是否该方法和实例化对象具有逻辑上的相关性,如果是就应该使用实例化对象 反之使用静态方法。这只是从面向对象角度上来说的。如果从线程安全、性能、兼容性上来看 也是选用实例化方法为宜。我们为什么要把方法区分为:静态方法和实例化方法 ?如果我们继续深入研究的话,就要脱离技术谈理论了。早期的结构化编程,几乎所有的方法都是“静态方法”,引入实例化方法概念是面向对象概念出现以后的事情了,区分静态方法和实例化方法不能单单从性能上去理解,创建c++,java,c#这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题,而是为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。拿别人一个例子说事:比如说“人”这个类,每个人都有姓名、年龄、性别、身高等,这些属性就应该是非静态的,因为每个人都的这些属性都不相同;但人在生物学上属于哪个门哪个纲哪个目等,这个属性是属于整个人类,所以就应该是静态的——它不依赖与某个特定的人,不会有某个人是“脊椎动物门哺乳动物纲灵长目”而某个人却是“偶蹄目”的
在最原始的BIO通信时,我们服务端与客户端建立连接accept是一个阻塞状态,连接connect获取客户端的数据调用read方法也是一个阻塞的。那个时候,我们使用多线程,每一次将一个新的connect放入一个新的线程里面。这样可以保证我们的服务端一直处于一个监听新的连接的状态。
缺点:
资源浪费,每一次的新建线程,都会进行一次系统调用,而问题的根源在于读的时候是阻塞的。
我们让read操作成为一个非阻塞的状态,然后就可以通过单线程来完成服务端监听新连接与read操作。此时我们将这个程序降为了单线程的程序。
缺点:
假如我们已经建立了10W个连接,那么在单线程中,我们遍历所有的连接,来判断有没有connect传输数据了,那么此时,我们将需要进行10W次的内核调用。这样就是非常浪费资源的。
在上面我们遍历connect是否有数据传输过来时,我们是在用户工作空间中进行调用的。这个时候,我们每次遍历一个connect时,就会产生一次内核的调用,十分浪费资源。所以此时我们使用多路复用的技术
,将10w个connet都使用select,扔到内核中去进行遍历。在内核中进行遍历的时候,只会返回一个io的状态
,然后在用户空间中,完成io的读写
。所以遍历io状态和io的读写是在不同的地方完成的两件不同的事情
。
缺点:
这种情况下,内核相当于在遍历10w个connect,来查看有没有客户端向我们的服务端发送数据。这个时候也是比较浪费的,比如10w个connect中,如果只有1W个connect在进行发送数据的工作。那么我相当于有9w个connect的遍历都是浪费的。
epoll的全程叫做event poll,事件驱动
。我们在使用多路复用connect扔进内核中的时候,我们使用增量复制的方法,在内核中开辟一块空间,存放所有的connect,然后每次使用多路复用向内核中扔connect的时候,我们只将新增的connet放到内核中。并且在发生监听到有新的客户端在传输数据的时候,我们才将这些有数据通信的connect给存放到内核的另一个地方,让内核进行处理。所以,此时我们的内核处理io属于事件驱动的方式
,当我们有新的数据传输时,才会将内核资源分配给connect进行处理。
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;
}
}
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;
}
}
没有将评论缓存在Redis中,需要取出评论的时候,直接去数据库中搜索获取即可
主要聊一下线程池的7大参数,4大拒绝策略。
手写一个单例
https://blog.csdn.net/weixin_42168958/article/details/106462506
未初始化的全局变量和静态变量
的一块内存区域。BSS段属于静态分配,程序结束后静态变量资源由系统自动释放。已初始化的全局变量
的一块内存区域。数据段也属于静态内存分配。程序执行代码
的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量。函数的参数值、局部变量
等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。堆的效率比栈要低的多
。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接口的子类实例
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,这避免了加锁。
Redis中关注与被关注。
重做
。https://juejin.im/post/5e6f42ebe51d452700568abb
kafak如何保证宕机的情况下依旧可以避免重复消费,比如消费者已经消费,在传回给服务器的过程中宕机了,重启之后,如何保证不被重复消费。
生产者(Producer) 调用send
方法发送消息之后,消息可能因为网络问题并没有发送过去。所以,我们不能默认在调用send
方法发送消息之后消息消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果
。但是要注意的是 Kafka 生产者(Producer) 使用 send
方法发送消息实际上是异步的操作,我们可以通过 get()
方法获取调用结果,但是这样也让它变为了同步操作。一般不推荐这么做!可以采用为其添加回调函数的形式。
如果消息发送失败的话,我们检查失败的原因之后重新发送即可
!另外这里推荐为 Producer 的retries
(重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你3次一下子就重试完了。
我们知道消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。偏移量(offset)表示 Consumer 当前消费到的 Partition(分区)的所在的位置。Kafka 通过偏移量(offset)可以保证消息在分区内的顺序性。
当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset
。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。
解决办法也比较粗暴,我们手动关闭闭自动提交 offset,每次在真正消费完消息之后之后再自己手动提交 offset
。但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次
。**对于这种重复消费的问题:我们可以在消费者端建立一个去重表,避免重复消费。**
我们知道 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。
进程:进程是指一个具有一定独立功能
的程序在一个数据集合
上的一次动态执行
过程。
一个进程包含了正在运行的一个程序的所有的状态信息
进程的特点:
进程的5个状态
线程:线程是进程的一部分,描述指令流执行状态。他是进程中的指令执行流的最小单元,是CPU调度
的基本单位。
所以,线程 = 进程 - 共享资源
两者的比较
: