读书工作笔记(二)

9 篇文章 0 订阅
8 篇文章 0 订阅

1.notify()和notifyAll()的本质区别

notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。两者的最大区别在于:

notifyAll使所有原来在该对象上等待被notify的所有线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。

notify则文明得多,它只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。

2.Object类有哪些公用方法?

clone

equals

hashcode

getClass

wait

notify

notifyAll

toString

3.kafka原理,kafka如何避免重复消费、丢失数据

kafka原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Tm3eL7W-1577103209697)(https://i.imgur.com/XWM1pKR.png)]

参考链接:http://www.cnblogs.com/dennyzhangdd/p/7759869.html

1.producer:消息生产者,发布消息到 kafka 集群的终端或服务。

2.broker:kafka 集群中包含的服务器。

3.topic:每条发布到 kafka 集群的消息属于的类别,即 kafka 是面向 topic 的。

4.partition:partition 是物理上的概念,每个 topic 包含一个或多个 partition。kafka 分配的单位是 partition。

5.consumer:从 kafka 集群中消费消息的终端或服务。

6.Consumer group:high-level consumer API 中,每个 consumer 都属于一个 consumer group,每条消息只能被 consumer group 中的一个 Consumer 消费,但可以被多个 consumer group 消费。

7.replica:partition 的副本,保障 partition 的高可用。

8.leader:replica 中的一个角色, producer 和 consumer 只跟 leader 交互。

9.follower:replica 中的一个角色,从 leader 中复制数据。

10.controller:kafka 集群中的其中一个服务器,用来进行 leader election 以及 各种 failover。

11.zookeeper:kafka 通过 zookeeper 来存储集群的 meta 信息。

kafka cluster

https://blog.csdn.net/qingqing7/article/details/80054281

要确定Kafka的消息是否丢失或重复,从两个方面分析入手:消息发送和消息消费

1、消息发送

     Kafka消息发送有两种方式:同步(sync)和异步(async),默认是同步方式,可通过producer.type属性进行配置。Kafka通过配置request.required.acks属性(默认1)来确认消息的生产:

0---表示不进行消息接收是否成功的确认;

1---表示当Leader接收成功时确认;

-1---表示Leader和Follower都接收成功时确认;

综上所述,有6种消息生产的情况,下面分情况来分析消息丢失的场景:

(1)acks=0,不和Kafka集群进行消息接收确认,则当网络异常、缓冲区满了等情况时,消息可能丢失;

(2)acks=1、同步模式下,只有Leader确认接收成功后但挂掉了,副本没有同步,数据可能丢失;

2、消息消费

    Kafka消息消费有两个consumer接口,Low-level API和High-level API:

Low-level API:消费者自己维护offset等值,可以实现对Kafka的完全控制;

High-level API:封装了对parition和offset的管理,使用简单;

如果使用高级接口High-level API,可能存在一个问题就是当消息消费者从集群中把消息取出来、并提交了新的消息offset值后,还没来得及消费就挂掉了,那么下次再消费时之前没消费成功的消息就“诡异”的消失了;

解决办法:

        针对消息丢失:同步模式下,确认机制设置为-1,request.required.acks=-1。即让消息写入Leader和Follower之后再确认消息发送成功;异步模式下,为防止缓冲区满,可以在配置文件设置不限制阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态;request.required.acks=1 ;queue.enqueue.timeout.ms = -1 

        针对消息重复:将消息的唯一标识保存到外部介质中,每次消费时判断是否处理过即可。

针对消息重复

记录offset和恢复offset的方案。理论上记录offset,下一个group consumer可以接着记录的offset位置继续消费。 
简单offset记录方案: 
每次消费时更新每个topic+partition位置的offset在内存中,

Map<key, value>,key=topic+'-'+partition,value=offset

当调用关闭consumer线程时,把上面Map的offset数据记录到 文件中*(分布式集群可能要记录到redis中)。 
下一次启动consumer,需要读取上一次的offset信息,方法是 以当前的topic+partition为key,从上次的Map中去寻找offset。 
然后使用consumer.seek()方法指定到上次的offset位置。 

在consumer消费性能慢的情况下,在指定时间内无法自动提交(5分钟500条数据),如果触发上述条件出现rebalanced,数据重新发送到新的consumer上消费,就会出现数据重复消费问题,如果一直都在间隔时间内无法完成消费,就会出现重复消费但offset不变的死循环。因此,我们需要做到1、保证consumer的消费性能足够快2、尽量让consumer逻辑幂等3、通过配置优化解决重复消费4、不单单做好消息积压的监控,还要做消息消费速度的监控(前后两次offset比较)

Kafka的Leader选举机制

    Kafka将每个Topic进行分区Patition,以提高消息的并行处理,同时为保证高可用性,每个分区都有一定数量的副本 Replica,这样当部分服务器不可用时副本所在服务器就可以接替上来,保证系统可用性。在Leader上负责读写,Follower负责数据的同步。当一个Leader发生故障如何从Follower中选择新Leader呢?

    Kafka在Zookeeper上针对每个Topic都维护了一个ISR(in-sync replica---已同步的副本)的集合,集合的增减Kafka都会更新该记录。如果某分区的Leader不可用,Kafka就从ISR集合中选择一个副本作为新的Leader。这样就可以容忍的失败数比较高,假如某Topic有N+1个副本,则可以容忍N个服务器不可用。

    如果ISR中副本都不可用,有两种处理方法:

	(1)等待ISR集合中副本复活后选择一个可用的副本;
	
	(2)选择集群中其他可用副本;

原文:https://blog.csdn.net/u012050154/article/details/78592854
版权声明:本文为博主原创文章,转载请附上博文链接!

4.kafka和其他MQ区别 ActiveMQ RabbitMQ KafKa对比

ActiveMQ: 历史悠久的开源项目,已经在很多产品中得到应用,实现了JMS1.1规范,可以和spring-jms轻松融合,实现了多种协议,不够轻巧(源代码比RocketMQ多),支持持久化到数据库,对队列数较多的情况支持不好。

RabbitMq:它比kafka成熟,支持AMQP事务处理,在可靠性上,RabbitMq超过kafka,在性能方面超过ActiveMQ。

Kafka:Kafka设计的初衷就是处理日志的,不支持AMQP事务处理,可以看做是一个日志系统,针对性很强,所以它并没有具备一个成熟MQ应该具备的特性。Kafka的性能(吞吐量、tps)比RabbitMq要强,如果用来做大数据量的快速处理是比RabbitMq有优势的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kZCeS0o9-1577103209699)(https://i.imgur.com/GyKEWOy.jpg)]

4.1kafka中Consumer与Partition的关系

如果consumer比partition多,是浪费,因为kafka的设计是在一个partition上是不允许并发的,所以consumer数不要大于partition数

  • 如果consumer比partition少,一个consumer会对应于多个partitions,这里主要合理分配consumer数和partition数,否则会导致partition里面的数据被取的不均匀

  • 如果consumer从多个partition读到数据,不保证数据间的顺序性,kafka只保证在一个partition上数据是有序的,但多个partition,根据你读的顺序会有不同

  • 增减consumer,broker,partition会导致rebalance,所以rebalance后consumer对应的partition会发生变化

  • High-level接口中获取不到数据的时候是会block的

负载低的情况下可以每个线程消费多个partition。但负载高的情况下,Consumer 线程数最好和Partition数量保持一致。如果还是消费不过来,应该再开 Consumer 进程,进程内线程数同样和分区数一致。

消费消息时,kafka client需指定topic以及partition number(每个partition对应一个逻辑日志流,如topic代表某个产品线,partition代表产品线的日志按天切分的结果),consumer client订阅后,就可迭代读取消息,如果没有消息,consumer client会阻塞直到有新的消息发布。consumer可以累积确认接收到的消息,当其确认了某个offset的消息,意味着之前的消息也都已成功接收到,此时broker会更新zookeeper上地offset registry。

5.101个数取中位数 小于nlogn的时间复杂度实现

利用快排思想找到下标为j=length/2的数值返回

6.如何减少索引建立

7.ReentrantReadWriteLock读写锁 公平锁、非公平锁【互斥锁包括公平锁和非公平锁】

读写锁是一种特殊的自旋锁,它把对共享资源对访问者划分成了读者和写者,读者只对共享资源进行访问,写者则是对共享资源进行写操作。读写锁在ReentrantLock上进行了拓展使得该锁更适合读操作远远大于写操作对场景。一个读写锁同时只能存在一个写锁或者存在多个读锁,但不能同时存在写锁和读锁。

如果读写锁当前没有读者,也没有写者,那么写者可以立刻获的读写锁,否则必须自旋,直到没有任何的写锁或者读锁存在。如果读写锁没有写锁,那么读锁可以立马获取,否则必须等待写锁释放。(但是有一个例外,就是读写锁中的锁降级操作,当同一个线程获取写锁后,在写锁没有释放的情况下可以获取读锁再释放读锁这就是锁降级的一个过程)

在java的锁机制中,公平和非公平的参考物是什么,个人而言觉得是相对产生的结果而立,简单的来说,如果一个线程组里,**能保证每个线程都能拿到锁,那么这个锁就是公平锁。**相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。本文围绕ReenTrantLock来讲。

那如何能保证每个线程都能拿到锁呢,队列FIFO是一个完美的解决方案,也就是先进先出,java的ReenTrantLock也就是用队列实现的公平锁和非公平锁。

** jdk1.5并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁。**

公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式

8.重构方法

https://blog.csdn.net/zuoxiaolong8810/article/details/11517943
1.重复代码的提炼

2.冗长代码的分隔

3.嵌套分支的优化【可以使用卫语句:即将不满足某些条件的情况放在方法前面并及时跳出,减少嵌套】

4.去掉一次性的临时变量(影响性能)

5.消除过长的参数列表,传入对象

6.提取类或者继承体系的常量【可以消除魔法数字和字符串常量,方便维护】

7.让类提供应该提供的方法(一些常用方法放到类对象的方法中)

8.拆分冗长的类

9.提取继承体系中重复的属性与方法到父类

hashmap负载因子为什么是0.75

用0.75作为负载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。hash容器指定初始容量尽量为2的幂次方。HashMap负载因子为0.75是空间和时间成本的一种折中。

git merge 和 git rebase区别

参考:https://mp.weixin.qq.com/s?__biz=MzI5NTE1NDQ1MQ==&mid=2650767725&idx=1&sn=5cd775c7878b7e7890d0418415b3f778&chksm=f45cc711c32b4e073286919b0e895f9997e2944d1d6acdaf77143f08b8dae157249558145a73&mpshare=1&scene=1&srcid=0601woZhFp3m0KfELdXyl1Za&key=baf732038d89126b7cb3d97894b809cb06d3749519b1ca9b0ffaae63216394bcb03065a455ca4878081b38af768ccc72074238f29a4b1e39707544ec7ab1854d28251c512251f2d5089e4417ecdf5c26&ascene=0&uin=MjEzMTAwMzgyNQ%3D%3D&devicetype=iMac+MacBookAir7%2C1+OSX+OSX+10.12.4+build(16E195)&version=1202

rebase 特点:将commit历史进行合并

优点:项目历史比较简单,少了merge commit

缺点:当发生冲突时不容易定位问题,因为re-write了history

The Golden Rule of Rebasing rebase 的黄金法则:never use it on public branches(不要在公共分支上使用)

结论:

想要干净的历史,少了merge commit的线性历史树,选择git rebase
而如果你想保留历史记录,并且想要避免重写commit history的风险,使用git merge

master分支合并到feature分支最简单的办法就是用下面这些命令:

git checkout feature
git merge master

或者,你也可以把它们压缩在一行里。

git merge master feature

作为merge的替代选择, 你可以像下面这样将feature分支并入master分支:

git checkout feature
git rebase master

反过来将master分支并入feature分支就会出问题。

linux vim使用 统计数据

vi的使用
基本上vi可以分为三种状态,分别是一般模式、编辑模式和命令行模式,各模式的功能区分如下:

一般模式:
以vi打开一个文件就直接进入一般模式了(这是默认的模式)。在这个模式中, 你可以使用上下左右按键来移动光标,你可以使用删除字符或删除整行来处理文件内容, 也可以使用复制、粘贴来处理你的文件数据。

编辑模式:
在一般模式中可以进行删除、复制、粘贴等的操作,但是却无法编辑文件的内容,只有当到你按下【i, I, o, O, a, A, r, R】等任何一个字母之后才会进入编辑模式。这时候屏幕的左下方会出现【INSERT或 REPLACE】的字样,此时才可以进行编辑。而如果要回到一般模式时, 则必须要按下【Esc】即可退出编辑模式。

命令行模式:
输入【 : / ? 】三个中的任何一个,就可以将光标移动到最底下那一行。在这个模式中, 可以提供查找、读取、存盘、替换字符、离开vi、显示行号等的动作则是在此模式中完成的!
查找与替换
【/word】 向光标向下寻找一个名称为word的字符串
【?word】 向光标向上寻找一个名称为word的字符串

dd:删除光标所在的一整行。

ndd:删除光标所在的向下n行。

yy:复制光标所在的一行。

nyy:复制光标所在的向下n行。

Linux文件中查找 反向过滤查找

grep常用用法

[root@www ~]# grep [-acinv] [--color=auto] '搜寻字符串' filename
选项与参数:
-a :将 binary 文件以 text 文件的方式搜寻数据
-c :计算找到 '搜寻字符串' 的次数
-i :忽略大小写的不同,所以大小写视为相同
-n :顺便输出行号
-v :反向选择,亦即显示出没有 '搜寻字符串' 内容的那一行!
--color=auto :可以将找到的关键词部分加上颜色的显示喔!

git操作

git commit、git push、git pull、 git fetch、git merge 的含义与区别

git commit:是将本地修改过的文件提交到本地库中;

git push:是将本地库中的最新信息发送给远程库;

git pull:是从远程获取最新版本到本地,并自动merge;

git fetch:是从远程获取最新版本到本地,不会自动merge;

git merge:是用于从指定的commit(s)合并到当前分支,用来合并两个分支;

$ git merge test // 指将 test 分支合并到当前分支

git pull 相当于 git fetch + git merge。

在本地新建分支并推送到远程

git checkout -b test

git push origin test 这样远程仓库中也就创建了一个test分支

创建分支: $ git branch mybranch

切换分支: $ git checkout mybranch

创建并切换分支: $ git checkout -b mybranch

本地分支与远程分支相关联

github上已经有master分支 和dev分支

在本地

git checkout -b dev 新建并切换到本地dev分支

git pull origin dev 本地分支与远程分支相关联

在本地新建分支并推送到远程

git checkout -b test

git push origin test 这样远程仓库中也就创建了一个test分支

java 类加载机制

双亲委派模型

所谓双亲委派是指每次收到类加载请求时,先将请求委派给父类加载器完成(所有加载请求最终会委派到顶层的Bootstrap ClassLoader加载器中),如果父类加载器无法完成这个加载(该加载器的搜索范围中没有找到对应的类),子类尝试自己加载。

好处:
通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

类加载过程
类加载分为三个步骤:加载,连接,初始化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fp3LEPmZ-1577103209699)(https://i.imgur.com/6ivcMoO.png)]

加载

根据一个类的全限定名(如cn.edu.hdu.test.HelloWorld.class)来读取此类的二进制字节流到JVM内部;将字节流所代表的静态存储结构转换为方法区的运行时数据结构(hotspot选择将Class对象存储在方法区中,Java虚拟机规范并没有明确要求一定要存储在方法区或堆区中)转换为一个与目标类型对应的java.lang.Class对象;

连接

验证

验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证;

准备

为类中的所有静态变量分配内存空间,并为其设置一个初始值(由于还没有产生对象,实例变量将不再此操作范围内);

解析

将常量池中所有的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法)。这个阶段可以在初始化之后再执行。

初始化

在连接的准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员自己写的逻辑去初始化类变量和其他资源,举个例子如下:

public static int value1  = 5;
public static int value2  = 6;
static{
    value2 = 66;
}

在准备阶段value1和value2都等于0;

在初始化阶段value1和value2分别等于5和66;

所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里头,存放到一个特殊的方法中,这个方法就是方法,即类/接口初始化方法,该方法只能在类加载的过程中由JVM调用;

编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量;

如果超类还没有被初始化,那么优先对超类初始化,但在方法内部不会显示调用超类的方法,由JVM负责保证一个类的方法执行之前,它的超类方法已经被执行。

JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。(所以可以利用静态内部类实现线程安全的单例模式)
如果一个类没有声明任何的类变量,也没有静态代码块,那么可以没有类方法;
何时触发初始化

为一个类型创建一个新的对象实例时(比如new、反射、序列化)调用一个类型的静态方法时(即在字节码中执行invokestatic指令)调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式。调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法)初始化一个类的派生类时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外)JVM启动包含main方法的启动类时。

获取反射中的Class对象

第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

Class clz = Class.forName(“java.lang.String”);
第二种,使用 .class 方法。

这种方法只适合在编译前就知道操作的 Class。

Class clz = String.class(User.class);
第三种,使用类对象的 getClass() 方法。

String str = new String(“Hello”);
Class clz = str.getClass();

invoke

反射中Method method 调用方法

AOP

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bHc8VrJX-1577103209700)(https://i.imgur.com/ljpLW45.jpg)]

服务熔断 降级 限流

参考链接:
https://blog.csdn.net/llianlianpay/article/details/79768890

服务熔断

**雪崩效应:**假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。

熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。

在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。

熔段解决如下几个问题:

当所依赖的对象不稳定时,能够起到快速失败的目的 
快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复

服务降级

https://blog.csdn.net/ityouknow/article/details/81230412

当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。

自动降级

超时降级 —— 主要配置好超时时间和超时重试次数和机制,并使用异步机制探测恢复情况

失败次数降级 —— 主要是一些不稳定的API,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况

故障降级 —— 如要调用的远程服务挂掉了(网络故障、DNS故障、HTTP服务返回错误的状态码和RPC服务抛出异常),则可以直接降级

限流降级 —— 当触发了限流超额时,可以使用暂时屏蔽的方式来进行短暂的屏蔽

当我们去秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时开发者会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)。

服务熔断和服务降级区别

触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;

管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)

实现方式不太一样;服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一般称为自我熔断。

服务限流

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流。

常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。

令牌桶和漏桶区别:
漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出
令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.

应用级限流

如果你使用过Tomcat,其Connector其中一种配置有如下几个参数:
acceptCount:如果Tomcat的线程都忙于响应,新来的连接会进入队列排队,如果超出排队大小,则拒绝连接;

maxConnections:瞬时最大连接数,超出的会排队等待;

maxThreads:Tomcat能启动用来处理请求的最大线程数,如果请求处理量一直远远大于最大线程数则可能会僵死。

限流使用AtomicLong或者LongAdder

LongAdder可以代替AtomicLong吗?

话有说回来啊,JDK8并没有把AtomicLong标记为过期,所以肯定还是很多用武之地的。
从LongAdder的Api可以看出,提供的方法还是挺少的。它更多地用于收集统计数据,而不是细粒度的同步控制。
LongAdder只提供了add(long)和decrement()方法,想要使用CAS更全面的方法还是要选择AtomicLong。
因此如果你只需要做形如count++的操作,推荐使用LongAdder代替AtomicLong吧(阿里开发手册就是这么推荐的)

基于Redis功能的实现限流

参考:https://www.cnblogs.com/exceptioneye/p/4783904.html
简陋的设计思路:假设一个用户(用IP判断)每分钟访问某一个服务接口的次数不能超过10次,那么我们可以在Redis中创建一个键,并此时我们就设置键的过期时间为60秒,每一个用户对此服务接口的访问就把键值加1,在60秒内当键值增加到10的时候,就禁止访问服务接口。在某种场景中添加访问时间间隔还是很有必要的。

Linux反向过滤查找

1.grep 是查找含有指定文本行的意思,比如grep test 就是查找含有test的文本的行

2.grep -v 是反向查找的意思,比如 grep -v grep 就是查找不含有 grep 字段的行

mycat 分库分表

zset数据结构

sorted set-有序集合在redis中有两种实现

1.ziplist,压缩双向链表,相关链接

2.skiplist,跳表实现

五、参数控制

redis配置文件中用来控制zset到底是使用ziplist(压缩双向链表)还是skiplist(跳表)的参数:

zset-max-ziplist-entries 128

zset-max-ziplist-value 64

zset-max-ziplist-entries zset使用ziplist存储的时候,最大限制存储entries的个数
zset-max-ziplist-value zset使用ziplist存储的时候,每个节点最大存储字节数

违反上述两个限制条件,均会导致zset将ziplist的数据结构切换为skiplist数据结构

而zset使用ziplist的原因,主要是出于在零散数据量少的时候,节省内容的占用

原文:https://blog.csdn.net/qq_16097611/article/details/79939748

生产者-消费者和发布-订阅者区别

生产者消费者模式是两边都在不停的去查,主动触发
而发布订阅者是发布者主动触发,监听者被动触发

登录验证

流程大致如下:
1、收到用户请求,读取SessionID,去Redis获取登录信息,没取到,告诉用户未登录
2、用户成功登录后,以SessionID为key,一些关键信息为value,存入Redis,最好设置一个过期时间,比如1天

这样这个用户被负载均衡分配到其它服务器,它的SessionID还是第1步那个,能从Redis读取到登录信息,自然认为是登录状态,

QPS TPS

TPS: (每秒事务处理量(TransactionPerSecond))
过程包括:

客户端请求服务端
服务端内部处理
服务端返回客户端
QPS:每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
计算关系:
QPS = 并发量 / 平均响应时间
并发量 = QPS * 平均响应时间

Qps基本类似于Tps,但是不同的是,对于一个页面的一次访问,形成一个Tps;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入“Qps”之中
例:访问一个页面会请求服务器3次,一次放,产生一个“T”,产生3个“Q”

redis相关原理及面试官由浅到深必问的15大问题(高级)

https://blog.csdn.net/zlc3323/article/details/80836881
https://blog.csdn.net/zlc3323/article/details/80836881

缓存穿透 缓存雪崩 缓存击穿

缓存穿透指的是查询一个根本不存在的数据,缓存层不命中,又去查存储层,又不命中。但如果有大量这种查询不存在的数据的请求过来,会对存储层有较大压力。

缓存雪崩,是指在某一个时间段,缓存集中过期失效。

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

RPC实现原理

不管是什么样的RPC框架,总体思路都是服务提供方暴露服务,消费方通过服务方提供的接口使用动态代理获取代理对象,然后调用代理对象的方法,代理对象在内部进行远程调用,获得计算结果。

参考链接:https://www.jianshu.com/p/d3c7a5bbca09

Java8中JVM内存模型,hashmap concurrentHashMap不同版本区别

Java8中JVM内存模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OMrR8H3X-1577103209700)(https://i.imgur.com/UhntIYi.png)]

参考链接:https://blog.csdn.net/u011552404/article/details/80306316

JVM的内存被划分5个区域:
堆区、方法区——这两个区域的数据共享
虚拟机栈、本地方法栈、程序计数器——这三个区域的数据私有隔离,不可共享
接下来详细叙述一下各个区域的作用:

1、堆区

堆区是JVM中最大一块内存区域,存储着各类生成的对象、数组等,JVM8中把运行时常量池、静态变量也移到堆区进行存储。堆区被细化可以分为年轻代、老年代,而年轻代又可分为Eden区、From Survivor、To Survivor三个区域,比例是8:1:1。一个对象从生成到结束将会有机会经历堆区的不同区域完成“使命”。根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可

2、方法区

方法区主要是存储类的元数据的,如虚拟机加载的类信息、编译后的代码等。JDK8之前方法区的实现是被称为一种“永久代”的区域,这部分区域使用JVM内存,但是JDK8的时候便移除了“永久代(Per Gen)”,转而使用“元空间(MetaSpace)”的实现,而且很大的不同就是元空间不在共用JVM内存,而是使用的系统内存

3、虚拟机栈

我们通常所说的“方法入栈”、“栈区”其实指代的就是虚拟机栈。但是实际上这个并不准确,我们所说的“栈区”等称呼确切的说指代的是虚拟机栈中局部变量表的部分

4、本地方法栈

从英文中我们可以很容易的看出,其实这部分区域是专门为Native方法来实现的!由于java需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解java应用之外的繁琐的细节 ,一个Native Method就是一个java调用非java代码的接口

5、程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

JDK7和JDK8中HashMap区别

Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。

根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。

为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)

我们根据数组元素中,第一个节点数据类型是 Node 还是 TreeNode 来判断该位置下是链表还是红黑树的。【p instanceof TreeNode 】

JDK7和JDK8中concurrentHashMap区别

Java8 对 ConcurrentHashMap 进行了比较大的改动。对于 ConcurrentHashMap,Java8 也引入了红黑树。

Unsafe与CAS

在ConcurrentHashMap中,随处可以看到U, 大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。这一点与乐观锁,SVN的思想是比较类似的。

它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。

在Java8的ConcurrentHashMap中,分段锁仅用来处理对象流。

Java7中,Segment继承于ReentrantLock使用了显示锁,在Segment的实例方法中,每个更新操作内部又使用Unsafe来处理更新。这显然是一种浪费。显示锁、Unsafe 这二者都是可以保证对对象的原子操作。使用一个即可。

java7中,ConcurrentHashMap的内部类Segment.

Segment继承了ReetrantLock,利用了显示锁,同时在更新操作中也使用了Unsafe.双管齐下来保证线程安全。
Java8中,ConcurrentHashMap较之前版本有了很大的改变。

使用Node数组替代了Segment数组来存储数据。Node数组中不再使用显示锁,而是Unsafe的乐观锁机制。

Segment予以保留,仅用来处理对象流的读写。

从如下Java8版本的ConcurrentHashMap$Segment源码来看,分段锁,基本弃用了。

CAS是什么

CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。

CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

NIO BIO有哪些实现类

NIO BIO
参考链接:https://www.jianshu.com/p/ef418ccf2f7d

悲观锁实现方式

2使用悲观锁来实现:
在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。

注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

我们可以使用命令设置MySQL为非autocommit模式:
set autocommit=0;

设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:
//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;

注:上面的begin/commit为事务的开始和结束,因为在前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交,在这里就不细表了。

上面的第一步我们执行了一次查询操作:select status from t_goods where id=1 for update;
与普通查询不一样的是,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

注:需要注意的是,在事务中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT … 则不受此影响。拿上面的实例来说,当我执行select status from t_goods where id=1 for update;后。我在另外的事务中如果再次执行select status from t_goods where id=1 for update;则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,但是如果我是在第二个事务中执行select status from t_goods where id=1;则能正常查询出数据,不会受第一个事务的影响。

补充:MySQL select…for update的Row Lock与Table Lock
上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。

参考链接:https://www.cnblogs.com/cyhbyw/p/8869855.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nRWRwTc1-1577103209701)(https://i.imgur.com/RSQ7EAn.png)]

springboot 和 spring 区别

springboot 是整合了spring的一些常用功能和配置,比如整合了tomcat容器,xml配置文件等只需要 run 运行即可启动服务,如果是web服务,则可以通过 localhsot:{应用名}/{path}访问他将一些功能全都整合到了 starert里面,所以只需要在pom.xml中加入starter依赖即可springboot和redi,elsticsearch等的整合

1、引入spring-boot-starter-redis依赖
2、在java应用中@AutoWired StringRedisTemplate/RedisTemplate即可
3、如果要自定义的修改一些配置,则直接在生成的applicaiton.properties文件中配置

框架特点:

1:创建独立的spring应用。

2:嵌入Tomcat, Jetty Undertow 而且不需要部署他们。

3:提供的“starters”poms来简化Maven配置

4:尽可能自动配置spring应用。

5:提供生产指标,健壮检查和外部化配置

6:绝对没有代码生成和XML配置要求

MySQL中添加一个字段在指定字段后面

ALTER TABLE t_monthly_statement  ADD COLUMN month_consignor_fee DOUBLE   NOT NULL DEFAULT 0  COMMENT '每月运费' AFTER recv_province_num ;

jar包和war包区别

war 包:
WEB 模块,可 以是一个web 应用,如一个网站,可以直接部署到容器中运行起来。它一定有一个 WEB-INFO目录,其他有一个web.xml文件和class目录。web.xml是这个应用的配置文件,而classes目录下则包含编译好的Servlet类和Jsp或Servlet所依赖的其它类。

jar 包 :
只包含一些class 文件,方便管理。在有 main_class 的情况下 可以用java 命令运行。

接下来是打zip包的形式,我们的需求是除了将项目必须的文件打包进去后还要将一些说明文档打包进去

数据库连接参数

1.-Ddruid.mysql.usePingMethod=false

如果此参数为true;数据库连接容易出现超时或者连接失效。原因是应用线程池还hold着数据库已经断掉的连接,druid配置的validationQuery就不会执行。 解决方式:使用应用级别的心跳检测,不使用系统级别的 ping ,使用“select X” 做法:在jvm参数配置Ddruid.mysql.usePingMethod=false,禁止mysql connector自带的ping机制。

  1. testOnBorrow=false由于不检测池里连接的可用性,于是假如连接池中的连接被数据库关闭了,应用通过连接池getConnection时,都可能获取到这些不可用的连接,且这些连接如果不被其他线程回收的话,它们不会被连接池废除,也不会重新被创建, 占用了连接池的名额。但是属性为true会消耗一定的性

  2. testWhileIdle = true设置从连接池获取连接时是否检查连接有效性,true时,如果连接空闲时间超过minEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查。当要求不高时,可以testOnBorrow=false时配合使用,提升一定性能。

    4.keepAlive=true打开后,增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,每次检查强制验证连接有效性

    5.validationQuery检验连接是否有效的查询语句。如果数据库Driver支持ping()方法,则优先使用ping()方法进行检查,否则使用validationQuery查询进行检查。

MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。

一次开发过程中,修改了主键,xml中sql语句有一处未作更新。一个表一边插入,一边更新,导致mysql锁表,产生锁超时,JDBC连接不足等问题。
在RR(Repeatable Read)隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行

top和free

free 显示的是整个系统的内存使用情况。如果你想查看进程的内存使用情况,可以用 top 或者 ps 等工具。

druid连接池参数及配置

和其它连接池一样DRUID的DataSource类为:com.alibaba.druid.pool.DruidDataSource,基本配置参数如下:

配置缺省值说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this)
jdbcUrl连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName根据url自动识别这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1) Destroy线程会检测连接的间隔时间2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

例如:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=" http://www.springframework.org/schema/beans" xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance" xmlns:batch=" http://www.springframework.org/schema/batch"
	xsi:schemaLocation=" http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
 
	<bean id="propertyConfigure" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>./conf/application.properties</value>
			</list>
		</property>
	</bean>
 
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		
		<!-- 配置初始化大小、最小、最大 -->
		<property name="initialSize" value="1" />
		<property name="minIdle" value="1" />
		<property name="maxActive" value="10" />
		<!-- 配置获取连接等待超时的时间 -->
		<property name="maxWait" value="10000" />
		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="300000" />
		<property name="testWhileIdle" value="true" />
		<!-- 这里建议配置为TRUE,防止取到的连接不可用 -->
		<property name="testOnBorrow" value="true" />
		<property name="testOnReturn" value="false" />
		<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
		<property name="poolPreparedStatements" value="true" />
		<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
		<!-- 这里配置提交方式,默认就是TRUE,可以不用配置 -->
		<property name="defaultAutoCommit" value="true" />
		<!-- 验证连接有效与否的SQL,不同的数据配置不同 -->
		<property name="validationQuery" value="select 1 " />
		<property name="filters" value="stat" />
		<property name="proxyFilters">
			<list>
				<ref bean="logFilter" />
			</list>
		</property>
	</bean>
 
	<bean id="logFilter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter">
		<property name="statementExecutableSqlLogEnable" value="false" />
	</bean>
</beans>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值