Java内存模型
Java内存模型概述JMM即 Java Memory Model,它定义了主存、工作内存抽象概念。
底层对应着复杂的优化:
CPU 增加了缓存,以均衡与内存的速度差异;
操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;
编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。
引发的问题体现在以下几个方面:
原子性:保证指令不会受到线程上下文切换的影响
可见性:保证指令不会受cpu缓存的影响
有序性:保证指令不会受cpu指令并行优化的影响
原子性原子性是指一个操作或者多次操作是不可中断的。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
例如:两个线程对同一个变量进行修改。
12345678910111213141516171819202122232425262728293031public class 原子性 { static int t = 100; public static void main(String[] args) { // 减少t n ...
Reactor模式
Reactor 模式前言
针对传统阻塞 I/O 服务模型的 2 个缺点:
当并发数很大,就会创建大量的线程,占用很大系统资源
连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在 Handler对象中的read 操作,导致上面的处理线程资源浪费
Reactor 模式的解决方案:本质就是I/O多路复用
多个连接共用一个阻塞对象ServiceHandler,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。
当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。
概述Reactor 模式也叫 Dispatcher 模式,即 I/O 多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个线程。
核心组件:
Reactor(也就是那个ServiceHandler):
Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理线程来对 IO 事件做出反应。
Handlers(处理线程EventHandler):
处理线程执行 I/O 事件要完成的实际事件。
分类:
单 Reactor 单线程
...
JWT认证
JWT认证概念JWT(JSON Web Token)是一种用于认证和授权的开放标准;是一个经过加密的,包含用户信息的且具有时效性的固定格式字符串,通常用于在不同的系统之间安全地传递声明(claims)。
JWT 由三部分组成:
头部(Header):头部通常包含了两部分信息,令牌的类型(JWT)和所使用的签名算法(例如 HMAC SHA256 或 RSA)。
载荷(Payload):载荷包含了一系列声明(claims)。声明是关于实体(通常是用户)和其他数据的声明。有三种类型的声明:
注册声明(Registered Claims):这些是一组预定义的声明,包括 iss(签发者)、sub(主题)、aud(受众)、exp(到期时间)、nbf(生效时间)和iat(签发时间)等。
私有声明(Private Claims):这些声明是应用程序特定的,通常用于在双方之间共享信息。
公共声明(Public Claims):这些声明是可选的,可以按需要添加到令牌中。
签名(Signature):签名用于验证令牌的真实性和完整性。它是通过将头部和载荷进行签名生成的,使用密钥来生成签名。验证方可 ...
零拷贝
零拷贝概述所谓的【零拷贝】,并不是真正无拷贝,而是在不会拷贝重复数据到 jvm 内存中。
零拷贝的优点有:
更少的用户态与内核态的切换
不利用 cpu 计算,减少 cpu 缓存伪共享
零拷贝适合小文件传输
前言传统的IO操作,例如一个文件的传输:
12345RandomAccessFile file = new RandomAccessFile(new File("data.txt"), "r");byte[] buf = new byte[1024*1024];file.read(buf);// 以流的形式进行网络传输socket.getOutputStream().write(buf);
数据拷贝流程:
java 本身并不具备 IO 读写能力,需要利用操作系统来完成,因此 read 方法调用后,要从 java 程序的用户态切换至内核态,去调用操作系统(Kernel)的读能力,将数据读入内核缓冲区。这期间用户线程阻塞,操作系统使用 DMA(Direct Memory Access)来实现文件读,其间也不会使用 cpu。
DMA 也可以 ...
Java基础编程优化
Java基础编程调优前言JDK 是 Java 语言的基础库,熟悉 JDK 中各个包中的工具类,可以帮助你编写出高性能代码。但是一些基本类不正确使用会有个别的性能问题,需要去注意去调优。
字符串的性能调优String 对象是我们使用最频繁的一个对象类型,但它的性能问题却是最容易被忽略的。
结果变化:
每次更新就都是为了能够节约内存,提高性能
不可变性:
String 类被 final 关键字修饰了,而且变量 char 数组也被 final 修饰了。而 char[] 被 final+private 修饰,代表了 String 对象不可被更改。
这样的好处是为了能够将创建过的字符串缓存在字符串常量池中,以提供给后面的相同字符串引用。
代码优化
多使用StringBuilder和StringBuffer:
例如:String str= "ab" + "cd" + "ef";
理论上首先会生成 ab 对象,再生成 abcd 对象,最后生成 abcdef 对象,但是编译器底层做了优化:
使用StringBuilder来进行拼接,但 ...
DDD设计流程
设计流程战略设计战略设计是根据用户旅程分析,找出领域对象和聚合根,对实体和值对象进行聚类组成聚合,划分限界上下文,建立领域模型的过程。
战略设计采用的方法是事件风暴,包括:产品愿景、场景分析、领域建模和微服务拆分等几个主要过程。
产品愿景事件风暴时,所有参与者针对每一个要点,在贴纸上写出自己的意见,贴到白板上。事件风暴主持者会对每个贴纸,讨论并对发散的意见进行收敛和统一,形成产品愿景图。
产品愿景分析对于初创系统明确系统建设重点,统一团队建设目标和建立通用语言是很有价值的。但如果你的系统目标和需求非常清晰,这一步可以忽略。
场景分析场景分析是从用户视角出发,探索业务领域中的典型场景,产出领域中需要支撑的场景分类、用例操作以及不同子域之间的依赖关系,用以支撑领域建模。
方法:
项目团队成员一起用事件风暴分析,根据不同角色的旅程和场景分析,尽可能全面地梳理从前端操作到后端业务逻辑发生的所有操作、命令、领域事件以及外部依赖关系等信息。
例如:
领域建模领域建模是通过对业务和问题域进行分析,建立领域模型。向上通过限界上下文指导微服务边界设计,向下通过聚合指导实体对象设计。
步骤:
找 ...
DDD领域模型基础
领域驱动设计(DDD)概述DDD 核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。
按照 DDD 方法设计出的微服务的业务和应用边界都非常合理,可以很好地实现微服务内部和外部的“高内聚、低耦合”。
为什么 DDD 适合微服务?
DDD 不是架构,而是一种架构设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现架构演进。
相关名词
领域和子域:
在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。
DDD 的领域就是这个边界内要解决的业务问题域,而子域就是领域再细分,对应一个更小的问题域或更小的业务范围。
每一个细分的领域都会有一个知识体系,也就是 DDD 的领域模型。
核心域、通用域和支撑域:
子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。
核心域:决定产品和公司核心竞争力的子域是核心域,它是 ...
Redis6.0新特性
Redis6.0新特性从单线程处理网络请求到多线程处理在之前Redis 一直被大家熟知的就是它的单线程架构,虽然有些命令操作可以用后台线程或子进程执行(比如数据删除、快照生成、AOF 重写),但是,从网络 IO 处理到实际的读写命令处理,都是由单个线程完成的。
但是后来硬件的发展,单个主线程处理网络请求的速度跟不上底层网络硬件的速度。
Redis6.0为了解决这个问题,采用了多个 IO 线程来处理网络请求,提高网络请求处理的并行度的方案。但是,Redis 的多 IO 线程只是用来处理网络请求的,对于读写命令,Redis 仍然使用单线程来处理。
具体多个IO线程的使用是在解析请求以及写会响应的阶段(利用并行提高速度):
相关设置:
开启多线程:io-threads-do-reads yes
设置多线程数量:io-threads 6(一般来说,线程个数要小于 Redis 实例所在机器的 CPU 核个数,例如,对于一个 8 核的机器来说,Redis 官方建议配置 6 个 IO 线程。)
实现服务端协助的客户端缓存和之前的版本相比,Redis 6.0 新增了一个重要的特性,就是实 ...
Redis数据倾斜问题
数据倾斜数据倾斜有两类。
数据量倾斜:在某些情况下,实例上的数据分布不均衡,某个实例上的数据特别多。
数据访问倾斜:虽然每个集群实例上的数据量相差不大,但是某个实例上的数据是热点数据,被访问得非常频繁。
数据量倾斜
bigkey 导致倾斜
bigkey 的 value 值很大(String 类型),或者是 bigkey 保存了大量集合元素(集合类型),会导致这个实例的数据量增加,内存资源消耗也相应增加。
解决方法:
我们在业务层生成数据时,要尽量避免把过多的数据保存在同一个键值对中。
如果 bigkey 正好是集合类型,我们还有一个方法,就是把 bigkey 拆分成很多个小的集合类型数据,分散保存在不同的实例上。
Slot 分配不均衡导致倾斜
如果没有均衡地分配 Slot,就会有大量的数据被分配到同一个 Slot 中,而同一个 Slot 只会在一个实例上分布,这就会导致,大量数据被集中到一个实例上,造成数据倾斜。
解决方法:
在分配之前,我们就要避免把过多的 Slot 分配到同一个实例。
如果已经分配,可以对不合理的slot进行迁移
Hash Tag导致倾斜
Hash ...
秒杀场景模拟
秒杀场景秒杀场景的负载特征对支撑系统的要求
特征是瞬时并发访问量非常高。
一般数据库每秒只能支撑千级别的并发请求,而 Redis 的并发处理能力(每秒处理请求数)能达到万级别,甚至更高。所以,当有大量并发请求涌入秒杀系统时,我们就需要使用 Redis 先拦截大部分请求,避免大量请求直接发送给数据库,把数据库压垮。
特征是读多写少,而且读操作是简单的查询操作。
库存查验操作是典型的键值对查询,而 Redis 对键值对查询的高效支持,正好和这个操作的要求相匹配。
秒杀的三个阶段
秒杀活动前
在这个阶段,用户会不断刷新商品详情页,这会导致详情页的瞬时请求量剧增。这个阶段的应对方案,一般是尽量把商品详情页的页面元素静态化,然后使用 CDN 或是浏览器把这些静态化的元素缓存起来。
秒杀活动开始
这个阶段的操作就是三个:库存查验、库存扣减和订单处理。(并发主要是在库存查验操作上)
库存查验:
为了支撑大量高并发的库存查验请求,我们需要在这个环节使用 Redis 保存库存量,这样一来,请求可以直接从 Redis 中读取库存并进行查验。
库存扣减:
需要使用Redis来执行;
因为放在数据 ...