ascetic

monk


  • 首页

  • 标签

  • 分类

  • 归档

我们的爱情

发表于 2025-01-10

2020 步入婚姻的殿堂

2021 疫情的里的日子,能相见就是幸福

2022 疫情放开,周末总算可以每周可以见面了

2023 每天都可以见面,开始探索世界喽

2024 我们有了爱情的结晶,但是我最爱你

未完待续。。。

老婆我爱你,一路走来我们走过了太多的坎坷也尽力了很多的磨难,但是我们是幸福的,我也知道我有很多缺点,总是因为我和你掰扯,惹你生气宝宝,对不起,我反思我一直以来的问题,我承认我的错误,2025我们会更好,我爱你宝宝

怎么用DDD

发表于 2022-04-15 | 分类于 DDD

微服务的困境

服务拆分困境产生的根本原因就是不知道业务或者微服务的边界到底在什么地方,换句话说,确定了业务边界和应用边界,这个困境也就迎刃而解了

DDD是什么

DDD 是一种处理高度复杂领域的设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD 不是架构,而是一种架构设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现架构演进。

DDD核心思想

DDD 核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。

DDD怎么用

1.弄清楚DDD与中微服务和中台的关系
中台本质是业务模型,微服务是业务模型的系统落地,DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计,它们之间就是这样的一个铁三角关系。DDD 强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。
2.通过战略设计,建立领域模型,划分微服务边界。
3.通过战术设计,我们会从领域模型转向微服务设计和落地

目的: 建立边界清晰、可持续演进的微服务架构

DDD战略设计和战术设计

战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。

战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

DDD 战略设计会建立领域模型,领域模型可以用于指导微服务的设计和拆分。事件风暴是建立领域模型的主要方法,它是一个从发散到收敛的过程。它通常采用用例分析、场景分析和用户旅程分析,尽可能全面不遗漏地分解业务领域,并梳理领域对象之间的关系,这是一个发散的过程。事件风暴过程会产生很多的实体、命令、事件等领域对象,我们将这些领域对象从不同的维度进行聚类,形成如聚合、限界上下文等边界,建立领域模型,这就是一个收敛的过程。

如何划定领域模型与微服务的边界

第一步:在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。
第二步:根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体。在这个图里,聚合之间的边界是第一层边界,它们在同一个微服务实例中运行,这个边界是逻辑边界,所以用虚线表示。
第三步:根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。在这个图里,限界上下文之间的边界是第二层边界,这一层边界可能就是未来微服务的边界,不同限界上下文内的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界,边界之间用实线来表示。

chapterReader remote阅读问题解决

发表于 2022-04-15 | 分类于 小说

IDEA安装jclasslib

直接应用市场里安装jclasslib,重启即可。

找到IDEA插件位置

1
2
3
MAC:/Users/用户名/Library/Application Support/JetBrains/IntelliJIdea2021.1/plugins
windows版本参考网上教程: https://blog.csdn.net/JOEYHHH/article/details/121975182
注意:用户名和IDEA版本对路径的影响
在该目录下找到chapter_reader/lib目录下的chapter_reader.jar文件,记住该包的地址

任意项目导入该jar

1.file>Project Structure>Modules>Denpendencies
2.选择+,JARS or Directories 选择第二步中的找到的chapter_reader.jar
3.导入成功之后如图所示

修改路径指向

1.选择RemoteSupportFactory,选择view然后选择show bytecode with jclasslib打开
2.选择字段ALLATORIxDEMO下的ConstantValue
3.修改值为https://raw.githubusercontent.com/MonkAscetic/testPost/mater/novel.json
4.点击确定,然后点击保存按钮保存修改,重启IDEA

大工告成

小结

另外如果wind_invade/chapter_reader中未支持您想要看的地址,可以拷贝一份wind_invade/chapter_reader的json文件并增加您要看的地址放到git上,点击raw跳转到raw地址,将请求地址换位raw地址就可以看最新的在线网站了

Atomix安装和使用

发表于 2021-06-22 | 分类于 分布式一致性 , Atomix

Atomix的安装和使用

Atomix 依赖

Atomix 核心依赖

1
2
3
4
5
<dependency>
<groupId>io.atomix</groupId>
<artifactId>atomix</artifactId>
<version>3.1.0-beta2</version>
</dependency>

根据系统的一致性、容错性和持久性要求需要选择不同的依赖

  • atomix-raft - RaftPartitionGroup and MultiRaftProtocol
  • atomix-primary-backup - PrimaryBackupPartitionGroup and `MultiPrimaryProtocol
  • atomix-log - LogPartitionGroup and `DistributedLogProtocol
  • atomix-gossip - AntiEntropyProtocol and CrdtProtocol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>io.atomix</groupId>
<artifactId>atomix</artifactId>
<version>3.1.0-beta2</version>
</dependency>
<dependency>
<groupId>io.atomix</groupId>
<artifactId>atomix-raft</artifactId>
<version>3.1.0-beta2</version>
</dependency>
<dependency>
<groupId>io.atomix</groupId>
<artifactId>atomix-log</artifactId>
<version>3.1.0-beta2</version>
</dependency>
<dependency>
<groupId>io.atomix</groupId>
<artifactId>atomix-primary-backup</artifactId>
<version>3.1.0-beta2</version>
</dependency>

单机部署

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
Atomix atomix = Atomix.builder()
.withMemberId("member1")
.withNodeDiscovery(BootstrapDiscoveryProvider.builder()
.withNodes(
Node.builder()
.withId("member1")
.withAddress("10.192.19.181:5679")
.build(),
Node.builder()
.withId("member2")
.withAddress("10.192.19.182:5679")
.build(),
Node.builder()
.withId("member3")
.withAddress("10.192.19.183:5679")
.build())
.build())
.withManagementGroup(RaftPartitionGroup.builder("system")
.withNumPartitions(1)
.withMembers("member1", "member2", "member3")
.build())
.withPartitionGroups(RaftPartitionGroup.builder("raft")
.withPartitionSize(3)
.withNumPartitions(3)
.withMembers("member1", "member2", "member3")
.build())
.build();
//开始启动集群
atomix.start().join();

Atomix

发表于 2021-06-22 | 分类于 分布式一致性 , Atomix

什么是Atomix

Atomix是一个支持多种方式来解决分布式问题的通用框架。其不关心所解决的问题,而是通过提供多种分布式的原语来解决问题包括的原语有:

  1. 分布式数据结构(Map,Set,Tree,技术器,值等)
  2. 分布式会话(点对点,发布订阅等)
  3. 分布式协调(分布式锁,分布式选举,分布式信号量,分布式barrier(可以看做分布式的CyclicBarrier))
  4. 分组管理
1
2
3
AtomicMap<String, String> map = atomix.<String, String>mapBuilder("my-map")
.withCacheEnabled()
.build();

这些原语中每一个都可以使用不同的分布式协议进行复制,并且根据协议的不同提供不同程度的保证

  1. Multi-Raft 强一致性的分布式算法
  2. Multi-Primary 基于分区leader的同步/异步复制算法
  3. Anti-entropy 高度可扩展的最终一致性协议(Gossip协议 )
  4. CRDT 最终强一致性复制协议(无冲突的数据的一致性数据处理,Gossip风格)
1
2
3
4
5
6
7
8
9
MultiRaftProtocol protocol = MultiRaftProtocol.builder()
.withReadConsistency(ReadConsistency.LINEARIZABLE)
.build()
Map<String, String> map = atomix.<String, String>mapBuilder("my-map")
.withProtocol(protocol)
.withCacheEnabled()
.build();

map.put("foo", "bar");

原语是线程安全的、异步的和反应式的,严重依赖事件通知来检测系统中的状态变化

1
2
3
4
5
6
7
8
LeaderElection<MemberId> election = atomix.getElection("my-election");

election.addListener(event -> {
Leader leader = event.newLeadership().leader();
...
});

election.run(atomix.getMembershipService().getLocalMember().id());

原语支持不同的访问方式:

  1. 异步api
  2. 同步api
  3. REST API

原语支持不同的配置方式:

  1. Java builders
  2. HOCON 配置
  3. JSON配置

HashMap(1.8)

发表于 2020-06-26 | 分类于 java

HashMap

HashMap 由数组+链表+红黑树构成(当当前链表的长度达到8时,并且数组的长度达到64时会将链表转化为红黑树)

HashMap的put过程

  1. 判断当前整个桶是否为空,如果空的话,进行扩容(叫初始化更准确一些,扩容和初始化公用了resize方法)

  2. 如果当前key的hashcode对应的桶的位置为空,直接新建一个node放到该桶上即可

  3. 如果当前桶的第一个node的hashcode和equals都返回true那么将该位置的值修改即可

  4. 如果当前节点是红黑树的node的话,调用红黑树的添加方法

  5. 如果当前节点是链表的话,遍历链表,如果hashcode和equals都返回true的话,修改对应的值,如果找到节点尾部,那么在尾部新建一个节点,并判断是否将链表转化为红黑树

  6. 判断是否需要扩容,如果需要进行扩容

tableSizeFor方法解析

HashMap 在初始化时需要指定initialCapacity(默认16)和loadFactor(默认0.75),initialCapacity在很多规范中建议,为需要初始化数据的1.5倍或者大于等于1.5倍的2的n次方,原因是防止扩容。
但是实际上这是没有必要的,在jdk1.8中HashMap中已经解决了该问题,HashMap的tableSizeFor中通过位运算的方式会将一个不是2的n次方的数转换为大于该数的最小的2的n次方数。如果恰好是2的n次方的话那么只要大于该值就可以了。

1
2
3
4
5
6
7
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

ConcurrentHashMap的put过程

HashMap的基本构造相同,区别是node中value和next被标识为volatile保证数据可见性,CAS + synchronized保证数据可见性。

  1. 如果tab为空初始化数组(通过sizeCtl参数使用CAS方式确保只有一个线程在初始化数组)

  2. 如果当前桶是空的,新建一个node,放到该桶的头部,通过CAS算法保证线程安全

  3. 如果当前是在扩容中通过helpTransfer并发扩容

  4. 如果当前的桶的第一个node等于(hashcode相等并且equals相等)key且不需要更新,则返回该节点

  5. 否则对该桶加锁,遍历该链表,如果找到key相等的就更新该位置位置的数据,否则就把该节点放到链表尾部

  6. 如果链表长度达到8并且桶的个数大于64会将该列表转化为红黑树。通过synchronized实现同步

  7. 计数加1,并且判断是否需要扩容,如果需要扩容,将进行并发扩容

redis数据同步机制

发表于 2019-04-16 | 分类于 多线程

Redis 全量同步

发生时机: slave刚启动初始化时同步

  1. slave向master发送SYNC指令,master收到SYNC指令之后将调用syncCommand()进行同步处理
  2. 在syncCommand()将调用rdbSaveBackground启动一个备份进程进行数据同步,如果已经有一个备份进程在进行了,就不会在重新启动
  3. 备份进程将执行函数rdbSave()完成将redis的全部数据保存到rdb文件
  4. 在redis的时间事件函数serverCron(redis的时间处理函数是指它会定时被redis进行操作的函数)中,将对备份后的数据进行处理,在serverCron函数中将会检查备份进程是否已经执行完毕,如果备份进程已经完成备份,则调用函数backgroundSaveDoneHandler完成后续处理。
  5. 在函数backgroundSaveDoneHandler中,首先更新master的各种状态,例如,备份成功还是失败,备份的时间等等。然后调用函数updateSlavesWaitingBgsave,将备份的rdb数据发送给等待的slave。
  6. 在函数updateSlavesWaitingBgsave中,将遍历所有的等待此次备份的slave,将备份的rdb文件发送给每一个slave。另外,这里并不是立即就把数据发送过去,而是将为每个等待的slave注册写事件,并注册写事件的响应函数sendBulkToSlave,即当slave对应的socket能够发送数据时就调用函数sendBulkToSlave(),实际发送rdb文件的操作都在函数sendBulkToSlave中完成。
  7. sendBulkToSlave函数将把备份的rdb文件发送给slave。

Redis 局部同步

发生时机: redis发生修改时

  1. master接收到一条用户的操作后,将调用函数call函数来执行具体的操作函数,在该函数中首先通过proc执行操作函数,然后将判断操作是否需要扩散到各slave,如果需要则调用函数propagate()来完成此操作
  2. propagate()函数完成将一个操作记录到aof文件中或者扩散到其他slave中;在该函数中通过调用feedAppendOnlyFile将操作记录到aof中,通过调用replicationFeedSlaves()将操作扩散到各slave中。
  3. 函数replicationFeedSlaves主要将操作扩散到每一个slave中;在该函数中将遍历自己下面挂的每一个slave,以此对每个slave进行如下两步的处理:将slave的数据库切换到本操作所对应的数据库(如果slave的数据库id与当前操作的数据id不一致时才进行此操作);将命令和参数按照redis的协议格式写入到slave的缓存中。写入切换数据库的命令时将调用addReply,写入命令和参数时将调用addReplyMultiBulkLen和addReplyBulk,函数addReplyMultiBulkLen和addReplyBulk最终也将调用函数addReply
  4. 在函数addReply中将调用prepareClientToWrite()设置slave的socket写入事件处理函数sendReplyToClient(通过函数aeCreateFileEvent进行设置),这样一旦slave对应的socket发送缓存中有空间写入数据,即调用sendReplyToClient进行处

垃圾收集器

发表于 2019-04-16 | 分类于 JVM

垃圾收集器

按照分代区分为老年代收集器和新生代收集器。年轻代收集器包含:Serial、ParNew 、Parallel Scavenge、G1 ;老年代收集器有:CMS、Serial Old(MSC) 、Parallel Old、G1 。G1 收集器既能收集新生代也能收集老年代。

Serial 收集器

Serial 是一个新生代收集器,单线程工作,使用复制算法。当垃圾收集时其他的所有线程都处于暂停状态(Stop The World),优点:简单高效。

ParNew 收集器

ParNew 是Serial 收集器的多线程版本,使用复制算法。在Serail的基础上增加了多线程垃圾收集。

Parallel Scavenge 收集器

Parallel Scavenge 是一个新生代多线程收集器,使用复制算法。收集器的目标是达到一个可以控制的吞吐量。

Serial Old

Serial 收集器的老年代版本,单线程,使用标记整理算法。

Parallel Old

Parallel Old 收集器是 Serial Old的多线程版本,使用标记整理算法。

CMS 收集器

CMS 是老年代,多线程,使用标记清除算法的收集器,其主要目标是一种以获取最短回收停顿时间为目标的收集器。

其过程分为四个步骤:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

其中,初始标记、重新标记着两个步骤仍然需要”Stop The World”。初始标记仅仅标记一下GC Roots能直接关联到的对象,速度很快。并发标记阶段就是进行GC Roots Tracing 的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始化标记阶段稍长一些。但是远比并发标记的时间短。

CMS 的优点:

  1. 并发收集
  2. 低停顿

CMS 的缺点:

  1. CMS收集器对CPU资源敏感。并发阶段会降低吞吐量。
  2. CMS 收集器无法处理浮动垃圾
  3. 标记清除算法会产生大量空间碎片

G1

  1. 并行与并发:G1 能充分利用CPU、多核环境下的硬件优势、使用多个CPU来缩短 Stop The World 的时间。
  2. 分代收集
  3. 空间整合 从整体上看基于标记整理,局部为复制算法。
  4. 可预测的停顿时间。

垃圾回收算法

发表于 2019-03-20 | 分类于 JVM

对象是否已死检测策略

垃圾回收时需要判断什么样的对象才是已死的呢,主要有两种方法,引用计数法和可达性分析算法。

引用计数算法

给对象添加一个引用计数器,每当一个对象被引用时,计数器加1;当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能再被使用的。客观而言引用计数法实现简单效率也高,在大部分情况下它都是一个不错的算法。但是主流的JVM里没有选用引用计数法来管理内存,其很难解决循环引用的问题。

可达性分析算法

通过一些列GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的引用链,当一个对象到GC Roots没有任何引用链相连时,则对象是不可引用的,则可以判定为可回收对象。

GC Roots的对象包括下面几种:

虚拟机栈中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的native方法)引用的对象。

再谈引用

强引用:类似 “Object obj = new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉呗引用的对象

软引用:用来描述一些还有用但并非必须的对象。对于软引用在系统要发生内存溢出之前,将会对这类对象进行第二次回收,如果回收后仍然内存不够,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。

弱引用:用来描述非必须的对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用。

虚引用:虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不对其生存时间构成影响,也无法通过徐引用来取得一个对象实例。对一个对象设置虚引用的唯一目的就是能在这个对象收集器回收时收到一个系统通知。在JDK1.2之后,提供PhantomReference。

垃圾收集算法

标记清除算法

该算法分为两个阶段:”标记”和”清除”两个过程。其不足主要有两个问题:1)效率问题,标记和清除两个过程的效率都不高。2)标记清除之后会产生大量不连续的内存碎片

复制算法

他将可用的区域按照容量发呢为大小相等的两块,每次只使用其中一块。当这块的内存用完了,就将还存活的对象复制到另外一块上面,然后再把使用过得内存空间清理一次。这样每次都是对半区进行内存回收。内存也不用考虑内存碎片等复杂情况只要已动工堆顶指针按照顺序分配即可。优点:实现简单,运行高效,缺点:空间换时间

标记整理算法

同样分为两个过程:标记和整理 标记过程同标记清楚算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

分代收集算法

新生代朝生夕死就采用复制算法,只需要付出很少存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高,没有额外空间对他进行担保就必须使用标记清除或者标记整理算法来进行空间回收。

java虚拟机运行时数据区

发表于 2019-03-18 | 分类于 JVM

java虚拟机运行时区域

程序计数器

程序计数器是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。该计数器线程切换时相互不影响,所以这类区域为”线程私有“内存。如果线程执行的是一个java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是一个native方法,这个计数器则为空(Undefined)。此区域是唯一一个在java虚拟机规范中没有规定OutOfMemoryError情况的区域。

虚拟机栈

Java虚拟机栈是线程私有的,它的生命周期与线程相同。此虚拟机栈描述的Java方法执行的内存模型:每个方法在执行的同时创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法调用出口等。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出站的过程。当线程请求的栈深度大于虚拟机栈允许的深度,会抛出StackOverflowError。当无法申请到内存时会抛出OutOfMemoryError异常。

本地方法栈

本地方法栈与虚拟栈所发挥的作用是非常相似的,它们之间区别是虚拟机栈为java方法服务,而本地方法栈为native方法服务。

堆

堆是被所有线程共享的区域,在虚拟机启动时创建。对于大多数应用来说,java堆是java虚拟机锁管理的内存中最大的一块。此内存唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配。堆是垃圾收集器管理的主要区域,因此很多时候也称为”GC堆”。堆可以处于不连续的内存空间中,只要逻辑连续即可。主流的虚拟机都可以可扩展的.(通过-Xmx 和-Xms控制)。

方法区(非堆)

方法区也是线程共享的区域,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息,常量、静态常量、即时编译后的代码数据。

运行时常量池

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等还有一想信息是常量池。用于存放编译器生成的字面量和符号引用这部分内容将在类加载后进入方法区的运行时常量池。

12

ascetic

瑶瑶宝宝最可爱

14 日志
8 分类
11 标签
© 2025 ascetic
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4