唯's Blog

笔者是一个热爱编程的 Java 程序员。

0%

JVM进程内存大小大致为:

  • 非heap(非heap=元空间+栈内存+…)+heap+JVM进程运行所需内存+其他数据等。

进入安全点时java线程可能存在不同的状态,这里需要处理所有的可能情况:

  • (1)处于解释执行字节码的状态中,解释器在通过字节码派发表(dispatch table)获取到下一条字节码的时候会主动检查安全点的状态;

  • (2)处于执行native代码的状态,也就是执行JNI,此时,VMThread不会等待线程进入安全点,执行JNI退出后线程需要主动检查安全点状态,如果此时安全点位置被标记了,那么就不能继续执行,需要等待安全点位置被清除后才能继续执行;

  • (3)处于编译代码执行中,那么编译器会在合适的位置(比如循环、方法调用等)插入读取全局Safepoint Polling内存页的指令,如果此时安全点位置被标记了,那么Safepoint Polling内存页会变成不可读,此时线程会因为读取了不可读的内存也而陷入内核,事先注册好的信号处理程序就会处理这个信号并让线程进入安全点。

  • (4)线程本身处于blocked状态,比如线程在等待锁,那么线程的阻塞状态将不会结束直到安全点标志被清除掉;

  • (5)当线程处于(1)/(2)/(3)三种状态的切换中,那么切换前会先检查安全点的状态,如果此时要求进入安全点,那么切换将不被允许,需要等待直到安全点状态清除;

强引用 默认

  • A a = new A();

软引用

  • 在垃圾回收器 GC 时,内存不够用的情况下 会回收此类对象

弱引用

  • 当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。

虚引用

  • 最弱的引用,JDK 堆外内存使用了此类引用 DirectByteBuffer -> Cleane对象

  • 
    DirectByteBuffer 
    
        Cleaner extends PhantomReference
    
            使用 虚引用 ReferenceQueue 机制来清除 堆外内存
    

绝对路径

1
2
3

File file = new File("D://test.txt")

相对路径 - 项目根目录

1
2
3

File file = new File("test.txt")

读取 Java 工程中 Resource 资源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15



// 方法1

URL fileURL=this.getClass().getResource("/resource/res.txt");

System.out.println(fileURL.getFile());

// 方法2

//返回读取指定资源的输入流

InputStream is=this.getClass().getResourceAsStream("/resource/res.txt");

使用 v2ray 来实现代理访问,v2ray 功能很强大我这里只使用 VMess 传输代理

https://guide.v2fly.org/basics/vmess.html

以上部署方式为 Docker

服务端部署

  1. 拉取镜像:sudo docker pull v2fly/v2fly-core

  2. 提前准备日志文件 access.log、error.log 。例如:touch /opt/v2test/logs/access.log

  3. 创建配置文件,配置文件目录自定义(要跟 docker run 目录挂载目录保持一致):vim /opt/v2test/config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

> {

> "log": {

> "loglevel": "debug",

> "access": "/etc/v2ray/logs/access.log",

> "error": "/etc/v2ray/logs/error.log"

> },

> "inbounds":[

> {

> "port": 8080,

> "protocol": "vmess",

> "settings":{

> "clients":[

> {

> "id": "023017b3-101d-453e-b92d-fc67b21969d5",

> "alterId": 0

> }

> ]

> }

>

> }

> ],

> "outbounds": [

> {

> "protocol": "freedom",

> "settings": {}

> }

> ]

>

> }

“id” 的生成策略是uuid,可以使用linux自带程序生成:cat /proc/sys/kernel/random/uuid

这里的id相当于访问密码,客户端连接配置要输入该值

  1. 启动 v2fly 容器: docker run –env v2ray.vmess.aead.forced=false -d –name v2ray -e TZ=Asia/Shanghai -v /opt/v2test:/etc/v2ray -p 8080:8080 –restart always v2fly/v2fly-core run -c /etc/v2ray/config.json

这里用三个关键点:

  1. –env v2ray.vmess.aead.forced=false 关闭该策略,”alterId”: 0 这里很关键,被坑惨了
  1. -p 8080:8080 暴露的端口号 要和配置文件中一致
  1. -v /opt/v2test:/etc/v2ray 挂载配置文件
  1. 按照上述步骤,正常情况是没问题的。如果有异常,进入配置文件中配置的log路径,查看日志。例如:tail -f /opt/v2test/error.log

客户端配置

客户端配置,这里介绍 ios 系统操作方式(其他系统参考:https://www.v2ray.com/awesome/tools.html)

  1. 苹果手机安装软件 oneClick

  2. 添加配置,选择 VMESS,IP 端口 ID alertId 要和上述配置文件保持一致,流设定 tcp 默认。

消费客户端(Consumer)频繁出现Rebalance

心跳超时会引发Rebalance,可以通过参数调整、提高消费速度等方法解决。

.最多一次(At-most-once)

最多一次其实非常容易保证的,UDP 这种传输层的协议其实保证的就是最多一次消息投递,消息的发送者只会尝试发送该消息一次,并不会关心该消息是否得到了远程节点的响应。

无论该请求是否发送给了接受者,发送者都不会重新发送这条消息;这其实就是最最基本的消息投递语义,然而消息可能由于网络或者节点的故障出现丢失。

这本质上是一种尽力而为的方法。

至少一次(At-least-once)

为了解决最多一次时的消息丢失问题,消息的发送者需要在网络出现超时重新发送相同的消息,也就是引入超时重试的机制,在发送者发出消息会监听消息的响应,如果超过了一定时间也没有得到响应就会重新发送该消息,直到得到确定的响应结果。

对于最少一次的投递语义,我们不仅需要引入超时重试机制,还需要关心每一次请求的响应,只有这样才能确保消息不会丢失,但是却可能会造成消息的重复,这就是最少一次在解决消息丢失后引入的新问题。

精确一次(Exactly-once)

虽然最少一次解决了最多一次的消息丢失问题,但是由于重试却带来了另一个问题 - 消息重复,也就是接受者可能会多次收到同一条消息;从理论上来说,在分布式系统中想要解决消息重复的问题是不可能的,很多消息服务提供了正好一次的 QoS 其实是在接收端进行了去重。

消息去重需要生产者生产消息时加入去重的 key,消费者可以通过唯一的 key 来判断当前消息是否是重复消息,从消息发送者的角度来看,实现正好一次的投递是不可能的,但是从整体来看,我们可以通过唯一 key 或者重入幂等的方式对消息进行『去重』。

消息的重复是不可能避免的,除非我们允许消息的丢失,然而相比于丢失消息,重复发送消息其实是一种更能让人接受的处理方式,因为一旦消息丢失就无法找回,但是消息重复却可以通过其他方法来避免副作用。

提高消费速度

提高消费速度有以下两个办法:

  • 增加Consumer实例个数。

    可以在进程内直接增加(需要保证每个实例对应一个线程,否则没有太大意义),也可以部署多个消费实例进程;需要注意的是,实例个数超过分区数量后就不再能提高速度,将会有消费实例不工作。

  • 增加消费线程。

    增加Consumer实例本质上也是增加线程的方式来提升速度,因此更加重要的性能提升方式是增加消费线程,最基本的步骤如下:

    1. 定义一个线程池。

    2. Poll数据。

    3. 把数据提交到线程池进行并发处理。

    4. 等并发结果返回成功后,再次poll数据执行。

消息过滤

消息队列Kafka版自身没有消息过滤的语义。实践中可以采取以下两个办法:

  • 如果过滤的种类不多,可以采取多个Topic的方式达到过滤的目的。

  • 如果过滤的种类多,则最好在客户端业务层面自行过滤。

实践中请根据业务具体情况进行选择,也可以综合运用上面两种办法。

提升发送性能

  • 建议您设置batch.size=16384linger.ms=1000

nginx配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

server {

listen 80;

server_name igoogle.com;

resolver 8.8.8.8;

location / {

root html;

index index.html index.htm;

proxy_pass https://www.google.com;

proxy_connect_timeout 120;

proxy_read_timeout 600;

proxy_send_timeout 600;

send_timeout 600;

proxy_redirect off;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

}



}

Spring 容器初始化时,怎么实现读取远程配置参数

SpringBoot

  1. 利用ApplicationContextInitializer,( “apollo.bootstrap.enabled” : true )在Spring容器刷新前读取远程配置参数,刷新Environment配置信

Spring

  1. @EnableApolloConfig -> @Import(ApolloConfigRegistrar.class) -> 导入 PropertySourcesProcessor implements BeanFactoryPostProcessor -> postProcessBeanFactory() 读取远程配置参数,刷新Environment配置信息

  2. ApolloAutoConfiguration -> “apollo.bootstrap.enabled” : true -> 导入 ConfigPropertySourcesProcessor implements BeanFactoryPostProcessor

    导入 ApolloProcessor

怎么实现动态配置刷新

利用BeanPostProcessor( postProcessBeforeInitialization)

  1. 在 Bean 初始化前收集当前 Bean 中 (SpringValueProcessor)@Value 打了标记的字段、方法 封装成 SpringValue (Bean、Field、Method)到 SpringValueRegistry 作为缓存。

  2. 实现 Apollo 配置更新监听器:AutoUpdateConfigChangeListener implements ConfigChangeListener ,读取 ChangeEvent 具体修改的字段,使用 步骤1 中产生的动态配置字段/方法的缓存获取对应的SpringValue,通过反射修改字段、方法参数值

什么是哨兵模式?

  • 在 Redis 主从架构下,Master 节点负责写数据,异步复制给从节点,Slave 只负责读请求。这样就会存在一个问题,主节点挂了,需要运维人员手动修改配置,将从节点晋升为主节点,同时应用连接 Redis 的数据源信息也要修改。这样无法达到高可用,而哨兵模式解决这个问题,达到真正的高可用。

  • 哨兵模式是Redis的高可用方式,是特殊的Redis服务,不提供数据读写服务,主要用来监控redis节点。Redis 客户端连接哨兵集群,哨兵集群中维护了Redis的节点信息,将主节点地址返回给客户端,后面的Redis的请求不再经过哨兵,直连Redis服务。当Redis主节点挂掉以后,Sentinel 在从节点选出新的主节点,同时通知客户端连接新的主节点,从而达到高可用。后续Redis服务的节点信息有变化,哨兵集群会将信息同步给客户端,客户端与哨兵建立一个订阅机制,订阅 Sentinel 发布的节点变动消息。

哨兵模式的功能

  • 监控 Redis 节点信息

  • 通知各个 Redis 节点状态信息

  • 自动故障转移

哨兵模式工作原理

  • 哨兵模式是一个分布式系统,使用流言协议(gossip protocols)来传播 Master 下线消息、投票协议(agreement protocols)来实现主节点的选举。

心跳机制

  • Setinel 根据配置的 Redis 主节点信息,从 主节点 上获取所有从节点的信息,随后定时向所有 Redis 节点发送info命令,获取 Redis 节点信息以及拓扑结构。

  • Sentinel 与 Sentinel 间也会建立沟通机制,主 Sentinel 利用 Redis 发布/订阅功能,向相应频道发送 Redis 节点信息, 其他 Sentinel 都会订阅这个频道拿到节点信息

感知 Master 节点下线
  • 每个 Setinel 每隔1s 会向所有 Redis 节点以及其他的 Sentinel 发送 Ping 消息,用于心跳检测

  • 如果 Master 回复 Ping 命令超过设置的阈值(默认为30s),Sentinel 将会认为将 master 标记为主观下线

  • 判断 Master 下线的 Sentinel 将会向其他 Sentinel 发送 entinel is-master-down-by-addr 消息,询问是否同意 master 下线

  • Sentinel 收到该消息,会根据发送过来 IP Port 自己判断 Master 是否存活,回复是否同意 master 下线

  • 如果收到的同意master下线消息,大于 quorum(配置)的值,则将 master 标记为客观下线

  • 至此 Master 已经被判定为下线,在一般情况下,每个 Sentinel 每隔 10s 向所有的Master,Slave发送 INFO 命令。当Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次。作用:发现最新的集群拓扑结构

利用raft算法选举领头sentinel

  • 在判定 Master 下线之后,需要进行故障转移选举出新的Master,需要选举出哪个 Sentinel 来执行该操作,选举过程:
  1. 判定 Master 下线的 Sentinel1 ,在将 Master 标记为客观下线之后,会其他Sentinel 发送消息,推举自己成为领头。
  1. 投票规则,先到先得(网络)。Sentinel2 收到 Sentinel1 的消息后回复同意消息,后续假设收到 Sentinel3 的消息,将会拒绝。
  1. 当 Sentinel 发现选自己的节点 超过了majority 的个数,那么自己将会成为领头节点
  1. 假设没有一个sentinel 超过 majority 个数,然后会休眠一段时间,再次选举

故障转移

  • 在选出领头 sentinel后,就要进行 故障转移,这里涉及到如何在 多个 slave 节点选出 master 节点。

  • 首先过滤一批不可用的 slave 节点,过滤规则:

  1. 剔除判断为下线的slaver 节点
  1. 剔除有5s没有回复sentinel的info命令的slaver
  1. 剔除与已经下线的主服务连接断开时间超过 down-after-milliseconds * 10 + master宕机时长 的slaver
  • 选主过程
  1. 选择优先级最高的节点, redis.conf 中 replical-prority 值越小代表优先级越高
  1. 优先级相同选择,选择 offset 大的,offset 代表了 主节点向从节点同步数据的偏移量,值越大同步的数据约新
  1. 若 sffset 也相同,则选择 runid较 小的

修改配置(激活主节点)

  • 主节点选择出来后,将会修改配置,让主节点生效
  1. sentinel 会向 选出来的从节点的执行 slave no one 命令,让其成为主节点
  1. 向 其余从节点发送 slave 命令,让他们知道谁是 master
  1. 如果之前的master重新恢复上线,领头sentinel 也会向其发送 slave 命令

客户端感知主节点

  • 在新的 master 重新上线之后,客户端如何感知主节点?
  1. 领头Sentinel 在完成 故障转移之后,会向 pubsub 发送一条消息,客户端会订阅这条消息拿到最新的master节点信息
  1. 客户端主动也会主动去拉取redis节点信息
  1. 哨兵也提供了钩子逻辑,可以在配置文件写一段脚本,在故障转移完成之后会调用脚本,达到通知客户端的效果

总结

  • 哨兵的选举过程还是很复杂的,其中包含了分布式共识、分布式协商的机制都是用来保证故障恢复的准确性。