JVM进程内存大小大致为:
- 非heap(非heap=元空间+栈内存+…)+heap+JVM进程运行所需内存+其他数据等。
(1)处于解释执行字节码的状态中,解释器在通过字节码派发表(dispatch table)获取到下一条字节码的时候会主动检查安全点的状态;
(2)处于执行native代码的状态,也就是执行JNI,此时,VMThread不会等待线程进入安全点,执行JNI退出后线程需要主动检查安全点状态,如果此时安全点位置被标记了,那么就不能继续执行,需要等待安全点位置被清除后才能继续执行;
(3)处于编译代码执行中,那么编译器会在合适的位置(比如循环、方法调用等)插入读取全局Safepoint Polling内存页的指令,如果此时安全点位置被标记了,那么Safepoint Polling内存页会变成不可读,此时线程会因为读取了不可读的内存也而陷入内核,事先注册好的信号处理程序就会处理这个信号并让线程进入安全点。
(4)线程本身处于blocked状态,比如线程在等待锁,那么线程的阻塞状态将不会结束直到安全点标志被清除掉;
(5)当线程处于(1)/(2)/(3)三种状态的切换中,那么切换前会先检查安全点的状态,如果此时要求进入安全点,那么切换将不被允许,需要等待直到安全点状态清除;
1 |
|
1 |
|
1 |
|
使用 v2ray 来实现代理访问,v2ray 功能很强大我这里只使用 VMess 传输代理
https://guide.v2fly.org/basics/vmess.html
以上部署方式为 Docker
拉取镜像:sudo docker pull v2fly/v2fly-core
提前准备日志文件 access.log、error.log 。例如:touch /opt/v2test/logs/access.log
创建配置文件,配置文件目录自定义(要跟 docker run 目录挂载目录保持一致):vim /opt/v2test/config.json
1 |
|
“id” 的生成策略是uuid,可以使用linux自带程序生成:cat /proc/sys/kernel/random/uuid
这里的id相当于访问密码,客户端连接配置要输入该值
这里用三个关键点:
- –env v2ray.vmess.aead.forced=false 关闭该策略,”alterId”: 0 这里很关键,被坑惨了
- -p 8080:8080 暴露的端口号 要和配置文件中一致
- -v /opt/v2test:/etc/v2ray 挂载配置文件
tail -f /opt/v2test/error.log
客户端配置,这里介绍 ios 系统操作方式(其他系统参考:https://www.v2ray.com/awesome/tools.html)
苹果手机安装软件 oneClick
添加配置,选择 VMESS,IP 端口 ID alertId 要和上述配置文件保持一致,流设定 tcp 默认。
心跳超时会引发Rebalance,可以通过参数调整、提高消费速度等方法解决。
最多一次其实非常容易保证的,UDP 这种传输层的协议其实保证的就是最多一次消息投递,消息的发送者只会尝试发送该消息一次,并不会关心该消息是否得到了远程节点的响应。
无论该请求是否发送给了接受者,发送者都不会重新发送这条消息;这其实就是最最基本的消息投递语义,然而消息可能由于网络或者节点的故障出现丢失。
这本质上是一种尽力而为的方法。
为了解决最多一次时的消息丢失问题,消息的发送者需要在网络出现超时重新发送相同的消息,也就是引入超时重试的机制,在发送者发出消息会监听消息的响应,如果超过了一定时间也没有得到响应就会重新发送该消息,直到得到确定的响应结果。
对于最少一次的投递语义,我们不仅需要引入超时重试机制,还需要关心每一次请求的响应,只有这样才能确保消息不会丢失,但是却可能会造成消息的重复,这就是最少一次在解决消息丢失后引入的新问题。
虽然最少一次解决了最多一次的消息丢失问题,但是由于重试却带来了另一个问题 - 消息重复,也就是接受者可能会多次收到同一条消息;从理论上来说,在分布式系统中想要解决消息重复的问题是不可能的,很多消息服务提供了正好一次的 QoS 其实是在接收端进行了去重。
消息去重需要生产者生产消息时加入去重的 key
,消费者可以通过唯一的 key
来判断当前消息是否是重复消息,从消息发送者的角度来看,实现正好一次的投递是不可能的,但是从整体来看,我们可以通过唯一 key
或者重入幂等的方式对消息进行『去重』。
消息的重复是不可能避免的,除非我们允许消息的丢失,然而相比于丢失消息,重复发送消息其实是一种更能让人接受的处理方式,因为一旦消息丢失就无法找回,但是消息重复却可以通过其他方法来避免副作用。
提高消费速度有以下两个办法:
增加Consumer实例个数。
可以在进程内直接增加(需要保证每个实例对应一个线程,否则没有太大意义),也可以部署多个消费实例进程;需要注意的是,实例个数超过分区数量后就不再能提高速度,将会有消费实例不工作。
增加消费线程。
增加Consumer实例本质上也是增加线程的方式来提升速度,因此更加重要的性能提升方式是增加消费线程,最基本的步骤如下:
定义一个线程池。
Poll数据。
把数据提交到线程池进行并发处理。
等并发结果返回成功后,再次poll数据执行。
消息队列Kafka版自身没有消息过滤的语义。实践中可以采取以下两个办法:
如果过滤的种类不多,可以采取多个Topic的方式达到过滤的目的。
如果过滤的种类多,则最好在客户端业务层面自行过滤。
实践中请根据业务具体情况进行选择,也可以综合运用上面两种办法。
batch.size=16384
和linger.ms=1000
。使用 MySQL 提供的内置函数 replace
1 |
|
nginx配置
1 |
|
ApplicationContextInitializer
,( “apollo.bootstrap.enabled” : true )在Spring容器刷新前读取远程配置参数,刷新Environment配置信@EnableApolloConfig -> @Import(ApolloConfigRegistrar.class) -> 导入 PropertySourcesProcessor implements BeanFactoryPostProcessor
-> postProcessBeanFactory() 读取远程配置参数,刷新Environment配置信息
ApolloAutoConfiguration -> “apollo.bootstrap.enabled” : true -> 导入 ConfigPropertySourcesProcessor implements BeanFactoryPostProcessor
导入 ApolloProcessor
利用BeanPostProcessor
( postProcessBeforeInitialization)
在 Bean 初始化前收集当前 Bean 中 (SpringValueProcessor)@Value 打了标记的字段、方法 封装成 SpringValue
(Bean、Field、Method)到 SpringValueRegistry 作为缓存。
实现 Apollo 配置更新监听器:AutoUpdateConfigChangeListener implements ConfigChangeListener ,读取 ChangeEvent 具体修改的字段,使用 步骤1 中产生的动态配置字段/方法的缓存获取对应的SpringValue,通过反射修改字段、方法参数值
在 Redis 主从架构下,Master 节点负责写数据,异步复制给从节点,Slave 只负责读请求。这样就会存在一个问题,主节点挂了,需要运维人员手动修改配置,将从节点晋升为主节点,同时应用连接 Redis 的数据源信息也要修改。这样无法达到高可用,而哨兵模式解决这个问题,达到真正的高可用。
哨兵模式是Redis的高可用方式,是特殊的Redis服务,不提供数据读写服务,主要用来监控redis节点。Redis 客户端连接哨兵集群,哨兵集群中维护了Redis的节点信息,将主节点地址返回给客户端,后面的Redis的请求不再经过哨兵,直连Redis服务。当Redis主节点挂掉以后,Sentinel 在从节点选出新的主节点,同时通知客户端连接新的主节点,从而达到高可用。后续Redis服务的节点信息有变化,哨兵集群会将信息同步给客户端,客户端与哨兵建立一个订阅机制,订阅 Sentinel 发布的节点变动消息。
监控 Redis 节点信息
通知各个 Redis 节点状态信息
自动故障转移
Setinel 根据配置的 Redis 主节点信息,从 主节点 上获取所有从节点的信息,随后定时向所有 Redis 节点发送info命令,获取 Redis 节点信息以及拓扑结构。
Sentinel 与 Sentinel 间也会建立沟通机制,主 Sentinel 利用 Redis 发布/订阅功能,向相应频道发送 Redis 节点信息, 其他 Sentinel 都会订阅这个频道拿到节点信息
每个 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 秒一次改为每秒一次。作用:发现最新的集群拓扑结构
- 判定 Master 下线的 Sentinel1 ,在将 Master 标记为客观下线之后,会其他Sentinel 发送消息,推举自己成为领头。
- 投票规则,先到先得(网络)。Sentinel2 收到 Sentinel1 的消息后回复同意消息,后续假设收到 Sentinel3 的消息,将会拒绝。
- 当 Sentinel 发现选自己的节点 超过了majority 的个数,那么自己将会成为领头节点
- 假设没有一个sentinel 超过 majority 个数,然后会休眠一段时间,再次选举
在选出领头 sentinel后,就要进行 故障转移,这里涉及到如何在 多个 slave 节点选出 master 节点。
首先过滤一批不可用的 slave 节点,过滤规则:
- 剔除判断为下线的slaver 节点
- 剔除有5s没有回复sentinel的info命令的slaver
- 剔除与已经下线的主服务连接断开时间超过 down-after-milliseconds * 10 + master宕机时长 的slaver
- 选择优先级最高的节点, redis.conf 中 replical-prority 值越小代表优先级越高
- 优先级相同选择,选择 offset 大的,offset 代表了 主节点向从节点同步数据的偏移量,值越大同步的数据约新
- 若 sffset 也相同,则选择 runid较 小的
- sentinel 会向 选出来的从节点的执行 slave no one 命令,让其成为主节点
- 向 其余从节点发送 slave 命令,让他们知道谁是 master
- 如果之前的master重新恢复上线,领头sentinel 也会向其发送 slave 命令
- 领头Sentinel 在完成 故障转移之后,会向 pubsub 发送一条消息,客户端会订阅这条消息拿到最新的master节点信息
- 客户端主动也会主动去拉取redis节点信息
- 哨兵也提供了钩子逻辑,可以在配置文件写一段脚本,在故障转移完成之后会调用脚本,达到通知客户端的效果