Java知识总结,梳理自己的知识点
前言
Java知识总结,梳理自己的知识点
框架:
cdn 代理动态请求:

双机房配置:

领域模型:
在获取需求之后,在完成之前,请先避免急切地建立MySQL表。建议采用脑图工具对整体流程进行全面分解,并将各个功能模块细致地划分出来。


- 交易支付场景


交易一致性问题:
重复性支付
在现实系统中出现重复支付问题时:
该系统的重复回调机制需要我们在设计接口时考虑的因素包括:
幂等性,
通过MySQL的悲观锁机制和乐观锁机制来实现防重提交功能。




分布式系统同步问题:
当系统出现超时退出场景时会触发回滚机制必须满足以下条件:
- 确保系统设计兼容以下三个关键点:
- 分布式事务的支持
- 流水号应用的有效应用
- 合理配置重试策略
在分布式系统中需要考虑采用CAP原理来实现系统的可靠性和可扩展性。具体而言:
- 客户端能够预期所有操作会同时执行并成功完成
- 每个操作都必须能够以预期的方式完成
- 系统还应具备分区容错能力
当系统采用强一致性时可能会导致系统出现阻塞现象
为了优化系统的吞吐量通常我们会优先保证系统的高可用性
这可能会带来性能上的妥协
- Base理论旨在保护CAP机制中的属性A,并为此放弃属性C。这将导致系统的软状态得以实现。最终要求我们的系统与业务方在一致性问题上达成共识。
分布式事务方案
分布式事务系统中让人联想到的是二阶段提交方案、异步确保型机制、事务型消息处理方式以及TCC型设计特点;
其中二阶段提交方案确能确保CAP理论下的强一致性与分区容忍性特征实现;然而另外三种类型无法保证强一致性这一关键特性,但其他类别设计则能够保障最终的一致性达成
- 强一致性保证
二阶段提交、读写相等、Raft协议

二阶段提交协议:


- TCC协议





配合二阶段提交才能达到强一致性。
- Raft
要求超过一半以上的节点同步成功才算写成功,外加二阶段提交:

- 异步确保型:
采用异步消息的方式确保事务可以最终一致。

- 事务型消息:

通用的JVM工具:
jps:虚拟机进程状态工具:

jps -v | grep pid
代码解读

jinfo: jvm 参数信息工具
jinfo - flags pid
代码解读

jstat: 查看虚拟机各种运算状态:
jstat -gcutil pid
代码解读


- jstack 线程快照工具
jstack -l pid
代码解读


- jmap : HeapDump 工具
查看堆信息:
jmpa -heap pid
代码解读

jmap -dump:format =b,file=heapDump.hprof pid 导出堆文件并用jhat查看
jhat - port 8899 heapDump.hprof
代码解读
浏览器访问:http:ip:8899 端口号自己指定然后查看信息

- 线上OOM问题排查
? 表示实际设置数据
java -Xms??m -Xmx??m -XX:+HeapDumpOnOutOfMemoryError XX:HeapDumpPath=./heapdump.hprof -jar xxx.jar
代码解读
数据结构

- ArrayList 和 LinkedList 区别:
ArrayList 采用了动态数组作为其核心数据存储机制,而 LinkedList 则遵循链表结构组织数据元素。
在随机访问 get 和 set 操作方面,ArrayList 的表现优于 LinkedList,在此过程中需要处理链表节点之间的链接关系。
然而,在插入(add)和删除(remove)操作方面,在 ArrayList 中仍然具备一定的优势,在这些操作中需要频繁地重新定位数据。
在数据存储方式上存在差异,在Java语言中,ArrayList基于动态数组存储的列表实现,而LinkeLinkedList则基于链表存储的线性列表实现。
在随机访问List(即get和set操作)时,AList在效率上优于LinkedList,这是因为虽然LinkedList采用线性存储方式,但其在查找元素时必须调整指针顺序,从而实现快速定位目标节点。
而对于增删操作(add和remove),由于AList基于动态数组结构,在执行这些操作时会迫使所有后续元素的位置发生偏移,从而降低操作效率。然而实际情况并非如此,对于增删操作,AList和DLL都没有明确指出哪一种方法更为高效。
- HashMap内存结构

HashMap是由数组+链表组成;寻址容易,插入和删除容易。
多线程



编写单例类时,需要使用关键字:


数据库


对单条索引记录进行上加锁操作。通过record lock机制,所锁定的对象始终是索引,而不是具体的记录。即使在表中没有预先定义任何索引的情况下,默认会创建一个隐藏的聚集主键索引。因此,所实现的功能等同于对这个隐藏的聚集主键索引用record lock进行了固定绑定。当一条SQL查询未使用任何优化条件时,系统会在每个聚簇型主键索legg前面附加一种X型锁定机制(X lock)。这种机制类似于传统表锁(table lock)的应用场景和效果,但在实现原理上有本质的区别:传统表lock是在行层面上进行比较和判断以实现并发控制功能;而X型锁定则是在基于行数据的基础上增加了额外的一层逻辑判断步骤来完成同步控制任务。










Redis


持久化

RDB 是 Redis 数据库的缩写, 即内存块快照。由于 Redis 的数据存储于内存中, 当服务器发生故障时, Redis 中的数据将全部丢失。此时需要生成内存快照以恢复 Redis 中的数据。快照就是在特定时间点, 将 Redis 中的全部数据以文件的形式进行保存。由于 Redis 的数据完全存储在内存中, 在确保所有数据可靠性的前提下,默认会执行全量快照。这相当于一次性从内存中捕获并保存了所有当前的数据内容。
这样就会出现一个问题, 当Redis数据库规模越大时, 快照文件生成的时间会相应延长. 那么当Redis进行快照操作时, 在磁盘上完成这一操作是否会占用主线程?这直接影响着Redis的整体性能.
Redis 支持生成完整的 RDB 文件集合,并提供了两个相关的关键操作:一个是 save 命令用于一次性生成所有数据对应的 RDB 文件;另一个是 背景保存操作(BGSAVE),允许在主进程完成其他任务的同时完成持久化存储。
save:在主线程中执行,会导致阻塞;
bgsave:启动一个专门的子进程来负责将RDB文件写入,并以防止主线程被阻塞的方式运行;这同样是Redis实现RDB文件生成时所采用的标准默认配置。
不用想也知道,我们应该使用哪种方式来生成RDB文件了。
我们知道了,RDB文件生成的两种命令,那么接下来说下他的具体实现逻辑:
代码解读

如图所示,在快照启动阶段(Snapshot Start),主线程会通过调用库函数创建一个用于执行快照操作的子进程。这个子进程将接收并复制该数据对应的映射页表副本。通过该缓存表副本间接访问主进程的数据副本,并按照预设的算法生成一份完整的数据快照文件。随后将这份快照文件保存至磁盘驱动器中。考虑到磁盘存储的时间较长,在存在大量用户试图进行写入操作时(When there are numerous write operations queued up at this point),系统需采取相应措施以确保数据的安全性与可用性。
这个时候就要用到 写时复制,即当请求需要对键值C进行操作时,主线程会把新数据或修改后的数据写到一个新的物理内存地址上(键值对C'),并修改主线程自己的页表映射。所以,子进程读到的类似于原始数据的一个副本,而主线程也可以正常进行修改。
这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。
代码解读
增量快照功能 若持续采用全量同步策略 随着时间的发展 快速生成大量快照文件。另一方面 系统可能会因 bgsvae 线程过多而占用过多资源 导致 Redis 的性能压力增大。并且会导致大量的读取和 writes 操作 从而增加 I/O 负担。
这个时候,我们可以采用另一只同步方式:增量快照。所谓增量快照,就是指,做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。在第一次做完全量快照后,T1 和 T2 时刻如果再做快照,我们只需要将被修改的数据写入快照文件就行。但是,这么做的前提是,我们需要记住哪些数据被修改了。你可不要小瞧这个“记住”功能,它需要我们使用额外的元数据信息去记录哪些数据被修改了,这会带来额外的空间开销问题。如下图所示:

如果我们将每一个键值对的修改操作进行记录的话,则会发现当存在1万个需要记录的键值对时就会产生额外的一万份元数据。例如,在某些情况下这些键值对的数据量非常有限像这样的小型键值对每被修改一次就需要新增8字节用于存储相关的元数据信息。这样一来为了确保能够"记住"每一次修改带来的开销我们就会面临较大地增加内存消耗的问题这对于依赖内存资源有限的Redis系统来说显然是得不偿失的做法。
综上所述,在实际应用中,则需要根据具体情况综合考虑两者的优缺点进行权衡。

AOF文件(Append-Only File)的持久化默认设置是被禁用的。要开启该持久化功能,请在redis.conf中将appendonly off设置为appendonly on的方式进行配置。一旦服务器启用了AOF持久化功能,在这种情况下服务器将首先利用AOF文件恢复数据库的状态。除非该持久化功能处于被禁用状态,在那种情况下服务器才会采用RDB文件来进行数据还原操作。

AOF 的持久化机制采用了更为可靠的一种方案。每当Redis接收修改数据集的命令时,会将该指令追加至 AOF 文件中.当Redis重启后,其内部指令会被重新执行一次,并恢复数据状态.AOF持久化如图 1-1-1 所示.








def aof_rewrite(new_aof_file_name):
# 创建新 AOF 文件
f = create_file(new_aof_file_name)
# 遍历数据库
for db in redisServer.db:
# 忽略空数据库
if db.is_empty(): continue
# 写入SELECT命令,指定数据库号码
f.write_command("SELECT" + db.id)
# 遍历数据库中的所有键
for key in db:
# 忽略已过期的键
if key.is_expired(): continue
# 根据键的类型对键进行重写
if key.type == String:
rewrite_string(key)
elif key.type == List:
rewrite_list(key)
elif key.type == Hash:
rewrite_hash(key)
elif key.type == Set:
rewrite_set(key)
elif key.type == SortedSet:
rewrite_sorted_set(key)
# 如果键带有过期时间,那么过期时间也要被重写
if key.have_expire_time():
rewrite_expire_time(key)
# 写入完毕,关闭文件
f.close()
def rewrite_string (key) :
# 使用 GET 命令获取字符串的值
value = GET (key)
# 使用 SET 命令重写字符串键
f.write_command(SET, key, value)
def rewrite_list (key) :
# 使用 LRANG 命令获取列表键包含的所有元素
item1, item2, ..., itemN = LRANG(key, 0, -1)
# 使用 RPUSH 命令重写列表键
f.write_command(RPUSH, key, item1, item2, ..., itemN)
def rewrite_hash (key) :
# 使用 HGETALL 命令获取哈希键包含的所有键值对
field1, value1, field2, value2, ..., fieldN, valueN = HGETALL(key)
# 使用 HMSET 命令重写哈希键
f.write_command(HMSET, key, field1, value1, field2, value2, ..., fieldN, valueN)
def rewrite_set (key) :
# 使用 SMEMBERS 命令获取集合包含的所有元素
elem1, elem2, ..., elemN = SMEMBERS(key)
# 使用 SADD 命令重写集合键
f.wirte_command(SADD, key, elem1, elem2, ..., elemN)
def rewrite_sort_set (key) :
# 使用 ZRANG 命令获取有序集合包含的所有元素
member1, score1, member2, score2, ..., memberN, scoreN = ZRANG(key, 0 , -1, "WITESCORES")
# 使用 ZADD 命令重写有序集合键
f.write_command(ZADD, key, score1, member1, score2, member2, ..., scoreN, memberN)
def rewrite_expire_time (key) :
# 获取毫秒精度的键过期时间戳
timestamp = get_expire_time_in_unixstamp(key)
# 使用 PEXPIREAT 命令重写键的过期时间
f.write_command(PEXPIREAT, kye, timestamp)
代码解读


- 缓存淘汰策略

- Redis 单线程及原子性
setnx


消息队列



- 消息消费确认
网络操作



监听: 最大是1024个用户的socket链接









性能优化

