目录
(3)程序计数器(Program Counter Register)
(5)本地方法栈(Native Method Stacks)
7.Spring MVC 框架(Model模型、View视图、Control控制层)
4)日志处理 日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。架构简化如下
5)消息通讯消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等点对点通讯:
一、Redis
1.Redis五大数据类型
(1)String(字符串): 表面上它是字符串,但其实他可以灵活的表示字符串、整数、浮点数3种值。Redis会自动的识别这3种值。
(2) List(列表):
- 实际上是一个链表,before Node after , left,right 都可以插入值
- 如果key 不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在!
- 在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点~
- 消息排队!消息队列 (Lpush Rpop), 栈( Lpush Lpop)!
(3)Set(集合):元素唯一不重复
(4)Hash(哈希):比String更适合存储对象
(5)zSet(有序集合):成绩排序、年龄排序等需求可以用zSet实现
2.Redis集群之主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主。
主要作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
3.Redis集群之哨兵模式(单机集群)
哨兵模式是一种特殊的模式(一主二从是最基本的),首先Redis提供了哨兵的命令,哨兵是一个独立的 进程 ,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
(1)优点
①哨兵集群,基于主从复制模式 ,所有的主从配置优点,它全有
②主从可以切换,故障可以转移 ,系统的 可用性 就会更好
③哨兵模式就是主从模式的升级,手动到自动,更加健壮!
(2)缺点
①Redis 不好在线扩容 的,集群容量一旦到达上限,在线扩容就十分麻烦!
②实现哨兵模式的配置其实是很 麻烦 的,里面有很多选择!
4.Redis持久化之RDB和AOF
Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能 !RDB方式为redis默认模式。
(1)RDB(Redis DataBase)
优点:
① 适合大规模的数据恢复!
② 对数据的完整性要求不高!
缺点:
①需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了!
②fork进程的时候,会占用一定的内容空间!
总结:Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。
这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!
(2)AOF(Append Only File)
优点:
①每一次修改都同步,文件的完整性会更加好!
②每秒同步一次,最多会丢失一秒的数据!
③从不同步,效率最高的!
缺点:
①相对于数据文件来说,aof远远大于 rdb,修复的速度也比 rdb慢!
②Aof 运行效率也要比 rdb 慢,所以我们redis默认的配置就是rdb持久化!
总结:
1)RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2)AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
3)只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
5.Redis发布和订阅
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
6.Redis缓存雪崩、穿透和击穿
6.1 缓存雪崩:指在某一个时间段,缓存集中过期失效。当这些缓存的过期时间一样,并且Redis巧合将这部分的数据都删光了。这就会导致这段时间内,全部数请求到数据库中。
解决方式:
1)过期时间使用随机值。
2)多级缓存(nginx缓存、reids缓存,jvm缓存ehcache)
3)对redis cluster资源访问进行隔离(hytrix 避免所有资源都hang在redis上),对访问失败的时候,进行熔断、降级。
4)数据预热,把可能被大量访问的数据提前加载到缓存中。
5)搭建高可用集群
6.2 缓存穿透:请求的数据在redis缓存大量不命中(例如请求不存在的数据),导致请求走数据库。
解决方式:
1)由于每次的参数是不合法的,可以提前过滤掉。
2)即使从数据库中查不到,也将这个空对象放入缓存,并设置较短的过期时间。
3)布隆过滤器(借助于bitmap实现,多个hash算法,算出索引下标记,将二进制向量对应的元素改成1。所有的都匹配时,表示可能存在,存在一个不匹配,则绝对不存在。)。
6.3缓存击穿:是指一个非常热点的key,在不停的扛着大并发,当这个key失效时,一瞬间大量的请求冲到持久层的数据库中,就像在一堵墙上某个点凿开了一个洞!
解决方式:
1)设置热点数据不过期。
2)多级缓存,时间各不相同(本地缓存、分布式缓存)
3)加互斥锁。从缓存读取,如果不存在(存在则直接读取缓存返回)。
获取锁,如果获取成功,从数据库中读取数据放到缓存中,释放锁。
获取锁失败,线程sleep(100),调用当前方法再次获取。
7.Redis实现session共享
在项目中引入redis依赖和spring-session配置依赖(自动将 session 存储到 redis 中):
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>2.6.3</version>
</dependency>
在application.yml文件中配置连接redis和session相关配置:
spring: # session配置 session: timeout: 86400 # 设置session失效时间 store-type: redis # 修改spring-session存储配置,默认存储到服务器内存中,现在设置存到redis中(关键) # redis配置 redis: port: 8081 # redis的端口号(这里是我的redis容器在docker中对应的端口号) host: xx.xxx.xxx.xxx # 我的云服务器ip database: 0 # 设置存入redis的哪一个库(默认是0)
8 .Redis分布式锁方案
完整的方案地址:
https://www.cnblogs.com/wangyingshuo/p/14510524.html
方案一Redisson框架:
可能存在「锁过期释放,业务没执行完」的问题。有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。
Redisson底层原理图吧:
只要线程一加锁成功,就会启动一个watch dog
看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用Redisson解决了「锁过期释放,业务没执行完」问题。
方案二多机实现的分布式锁Redlock+Redisson
Redis一般都是集群部署的:
如果线程一在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。线程二就可以获取同个key的锁啦,但线程一也已经拿到锁了,锁的安全性就没了。
为了解决这个问题,Redis作者 antirez提出一种高级的分布式锁算法:Redlock。Redlock核心思想是这样的:
搞多个Redis master部署,以保证它们不会同时宕掉。并且这些master节点是完全相互独立的,相互之间不存在数据同步。同时,需要确保在这多个master实例上,是与在Redis单实例,使用相同方法来获取和释放锁。
我们假设当前有5个Redis master节点,在5台服务器上面运行这些Redis实例。
- 1.获取当前时间,以毫秒为单位。
- 2.按顺序向5个master节点请求加锁。客户端设置网络连接和响应超时时间,并且超时时间要小于锁的失效时间。(假设锁自动失效时间为10秒,则超时时间一般在5-50毫秒之间,我们就假设超时时间是50ms吧)。如果超时,跳过该master节点,尽快去尝试下一个master节点。
- 3.客户端使用当前时间减去开始获取锁时间(即步骤1记录的时间),得到获取锁使用的时间。当且仅当超过一半(N/2+1,这里是5/2+1=3个节点)的Redis master节点都获得锁,并且使用的时间小于锁失效时间时,锁才算获取成功。(如上图,10s> 30ms+40ms+50ms+4m0s+50ms)
- 如果取到了锁,key的真正有效时间就变啦,需要减去获取锁所使用的时间。
- 如果获取锁失败(没有在至少N/2+1个master实例取到锁,有或者获取锁时间已经超过了有效时间),客户端要在所有的master节点上解锁(即便有些master节点根本就没有加锁成功,也需要解锁,以防止有些漏网之鱼)。
简化下步骤就是:
- 按顺序向5个master节点请求加锁
- 根据设置的超时时间来判断,是不是要跳过该master节点。
- 如果大于等于3个节点加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦。
- 如果获取锁失败,解锁!
二、Nginx
1.简介:
-
Nginx 是高性能的 HTTP 和反向代理的web服务器,处理高并发能力是十分强大的,能经受高负 载的考验,有报告表明能支持高达 50,000 个并发连接数。
2.Nginx的作用
(1)正向代理:如果把局域网外的 Internet 想象成一个巨大的资源库,则局域网中的客户端要访 问 Internet,则需要通过代理服务器来访问,这种代理服务就称为正向代理。
- 简述:通过代理服务器来访问服务器的过程 就叫 正向代理。
- 需要在客户端配置代理服务器进行指定网站访问
(2)反向代理:是指以代理服务器来接收浏览器的请求,然后再将请求转发到内网的服务器,然后再将从服务器上得到的结果返回给客户端。
- 客户端对代理是无感知的,因为客户端不需要任何配置就可以访问。
- 暴露的是代理服务器的ip地址,隐藏了真实服务器的ip地址。
(3)负载均衡:将原先请求集中到单个服务器上的情况改为将请求分发到多个服务器上,将负载分发到不同的服务器,也就是我们所说的负载均衡。
(4)动静分离:为了加快网站的解析速度,可以把动态页面和静态页面由不同的服务器来解析,加快解析速 度。降低原来单个服务器的压力。
-
一种是纯粹把静态文件独立成单独的域名,放在独立的服务器上,也是目前主流推崇的方案;
-
另外一种方法就是动态跟静态文件混合在一起发布,通过 nginx 来分开。
3.Nginx配置文件
(1)配置文件位置:/usr/local/nginx/conf/nginx.conf
(2)Nginx组成部分:
精简的配置内容如下:
worker_processes 2;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
- 第一部分全局块
主要会设置一些影响nginx 服务器整体运行的配置指令,主要包括配 置运行 Nginx 服务器的用户(组)、允许生成的 worker process 数,进程 PID 存放路径、日志存放路径和类型以 及配置文件的引入等,如下:
worker_processes 2;
这是 Nginx 服务器并发处理服务的关键配置,worker_processes 值越大,可以支持的并发处理量也越多,但是 会受到硬件、软件等设备的制约。
- 第二部分:events块
events {
worker_connections 1024;
}
events 块涉及的指令**主要影响 Nginx 服务器与用户的网络连接,常用的设置包括是否开启对多 work process 下的网络连接进行序列化,是否 允许同时接收多个网络连接,选取哪种事件驱动模型来处理连接请求,每个 word process 可以同时支持的最大连接数等。**
上述例子就表示每个 work process 支持的最大连接数为 1024*2.
- 第三部分
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
这算是 Nginx 服务器配置中最频繁的部分,代理、缓存和日志定义等绝大多数功能和第三方模块的配置都在这里。
需要注意的是:http 块也可以包括 http全局块、server 块。
1)http全局块
http全局块配置的指令包括文件引入、MIME-TYPE 定义、日志自定义、连接超时时间、单链接请求数上限等。
2)server 块
这块和虚拟主机有密切关系,虚拟主机从用户角度看,和一台独立的硬件主机是完全一样的,该技术的产生是为了 节省互联网服务器硬件成本。
每个 http 块可以包括多个 server 块,而每个 server 块就相当于一个虚拟主机。
而每个 server 块也分为全局 server 块,以及可以同时包含多个 locaton 块。
全局 server 块
最常见的配置是本虚拟机主机的监听配置和本虚拟主机的名称或IP配置。
location 块
一个 server 块可以配置多个 location 块。
这块的主要作用是基于 Nginx 服务器接收到的请求字符串(例如 server_name/uri-string),对虚拟主机名称 (也可以是IP 别名)之外的字符串(例如 前面的 /uri-string)进行匹配,对特定的请求进行处理。 地址定向、数据缓 存和应答控制等功能,还有许多第三方模块的配置也在这里进行。
4. nginx 分配服务器策略
-
轮询(默认):
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器 down 掉,能自动剔除。
upstream myserver {
server 10.0.0.110:8099;
server 10.0.0.110:8098;
}
server {
listen 8096;
server_name www.baidu.com;
location / {
proxy_pass http://myserver;
}
}
请求会自动轮流访问8099和8098两台服务器上
-
weight(权重)
weight 代表权重, 默认为 1,权重越高被分配的客户端越多
upstream myserver {
server 208.208.128.122:8081 weight=1; # 在这儿
server 208.208.128.122:8082 weight=2;
}
server {
listen 80;
server_name 208.208.128.122;
location / {
root html;
proxy_pass http://myserver;
index index.html index.htm;
}
理论上,访问8082服务器的请求会是8081服务器的2倍左右。
-
ip_hash
ip_hash 每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器
upstream myserver {
ip_hash; // 在这儿
server 208.208.128.122:8081 ;
server 208.208.128.122:8082 ;
}
server {
listen 80;
server_name 208.208.128.122;
location / {
root html;
proxy_pass http://myserver;
index index.html index.htm;
}
因为每个请求ip的hash值是固定的,只要ip地址不变,就会访问固定的服务器。在没有做session共享的项目里,可以避免访问不同服务器获取不到session时,需要频繁登录。
-
fair(第三方)
fair(第三方),按后端服务器的响应时间来分配请求,响应时间短的优先分配。
upstream myserver {
server 208.208.128.122:8081 ;
server 208.208.128.122:8082 ;
fair; # 在这儿
}
server {
listen 80;
server_name 208.208.128.122;
location / {
root html;
proxy_pass http://myserver;
index index.html index.htm;
}
5.使用keepalived实现Nginx高可用
keepalived的工作原理
keepalived是基于VRRP协议实现的保证集群高可用的一个服务软件,主要功能是实现真机的故障隔离和负载均衡器间的失败切换,防止单点故障。VRRP协议保证当主机的下一路由器出现故障时,由另外一台路由器来代替出现故障的路由器进行工作,从而保持网络通信的连续性和可靠性。
三、JVM
1.jvm的结构
(1)Java堆(Heap)
存放Java对象实例,存放字符串常量。这里是GC的主要区域。
(2)方法区(Method Area)JVM规范的叫法
在HotSpot虚拟机也被称为永久代,也是各个线程共享的内存区域,它用于存储已被虚拟加载的类信息、常量、静态变量(jdk7移动到了堆)、即时编译器编译后的代码等数据。无法满足内存分配需求时,将抛出OutOfMemoryError异常。很少进行垃圾回收.
方法区和“PermGen space”(永久代)又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。
(3)程序计数器(Program Counter Register)
记录正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)
是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
(4)JVM栈
当前线程执行的方法栈。每次出入一个方法都会对应一次栈操作。主要结构如下:
- 局部变量表
- 操作栈
- 动态链接
- 方法返回地址
(5)本地方法栈(Native Method Stacks)
(6)元空间
类元信息(klass)、字段、静态属性、方法、常量,还有运行时常量池等。
2.Java虚拟机是如何加载Java类的?
(1)加载
加载是指查找字节流,并且据此创建类的过程。加载需要借助类加载器,在 Java 虚拟机中,类加载器使用了双亲委派模型,即接收到加载请求时,会先将请求转发给父类加载器。
(2)链接
链接是指将创建成的类合并至 Java 虚拟机中,使之能够执行的过程。分为下面三个阶段:
- 验证:确保被加载类能够满足 Java 虚拟机的约束条件。这就好比需要将设计好的房型提交给市政部门审核。只有当审核通过,才能继续下面的建造工作。通常而言,Java 编译器生成的类文件必然满足 Java 虚拟机的约束条件。
- 准备:是为被加载类的静态字段分配内存,编译器会生成一个包含目标方法所在类的名字、目标方法的名字、接收参数类型以及返回值类型的符号引用,来指代所要调用的方法。
- 解析:正是将这些符号引用解析成为实际引用。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。)
(3)初始化
在 Java 代码中,如果要初始化一个静态字段,可以在声明时直接赋值,也可以在静态代码块中对其赋值。
总结:
Java 虚拟机将字节流转化为 Java 类的过程。这个过程可分为加载、链接以及初始化三大步骤。
加载是指查找字节流,并且据此创建类的过程。加载需要借助类加载器,在 Java 虚拟机中,类加载器使用了双亲委派模型,即接收到加载请求时,会先将请求转发给父类加载器。
链接,是指将创建成的类合并至 Java 虚拟机中,使之能够执行的过程。链接还分验证、准备和解析三个阶段。其中,解析阶段为非必须的。
初始化,是为标记为常量值的字段赋值,以及执行 < clinit > 方法的过程。类的初始化仅会被执行一次,这个特性被用来实现单例的延迟初始化。
四、Java垃圾回收机制
垃圾回收算法
1.标记算法
(1) 引用计数算法
该算法的实现是给对象添加一个引用计数器,每当有一个地方引用它时,计数器数值就加1;当引用失效时,计数器值就减1;当计数器数值为0时表示该对象不可再被使用,则可以对其进行回收。
优点: 实现简单,判定效率高。
缺点: 很难解决对象循环引用问题。
(2) 可达性分析算法(根搜索算法)
该算法是从离散数学中的图论引入的,程序将所有引用关系看成是一张图,通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来讲,就是从GC Roots到这个对象不可达)时,则说明该对象是不可用的。
Java中可以作为GC Roots的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Native方法)引用的对象。
2.回收算法
(1)标记-清除算法
标记-清除算法分为标记和清除两个阶段,首先从根集合进行扫描,对存货的对象进行标记,然后对堆内存从头到尾进行线性遍历,回收不可达对象的内存。
缺点:
- 效率问题,标记和清除两个过程的效率都不高。
- 空间问题,内存碎片化严重。
(2)复制算法
复制算法的原理是将可用内存按容量划分为大小相等的两块(对象面和空闲面),每次只使用其中的一块,当这一块的内存用完了,就将还存活的对象复制到另一块上,然后再把已使用过的内存空间一次清理掉。
优点:
- 解决了内存碎片化问题。
- 按顺序分配内存,简单高效。
- 适用于对象存活率低的场景(如:新生代)。
缺点:
- 将内存缩小为原来的一半。
- 在对象存活率较高的场景下需要进行较多的复制操作,效率会变低。
(3) 标记-整理算法
标记-整理算法的标记过程与标记-清除算法一致,先从根集合进行扫描,对存活的对象进行标记,但是后续步骤不是直接对可回收的对象进行清理,而是移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。
优点: 解决了内存碎片化问题,不用设置两块内存互换。
缺点: 比标记-清除算法增加了对象移动的过程,所以实现成本更高。适用于对象存活率高的场景(如:老年代)。
(4)分代收集算法
分代收集算法是根据对象的存活周期的不同,将内存划分为不同的区域,然后针对不同的区域采用不同的垃圾回收算法。一般是将Java堆分为新生代和老年代,新生代的对象存活率低,每次垃圾回收都会有大量对象需要被回收,所以该区域采用复制算法。而老年代的对象存活率高,该区域采用标记-清除算法或标记-整理算法。这样做的好处是可以提高JVM的回收效率,避免了只采用某一种算法的缺陷。当前商业虚拟机都是采用该种算法进行垃圾回收。
五、Spring
1.spring Core(核心容器)
核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
1)IOC(控制反转,将对象交给容器管理)
以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 。Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
作用:
- 由IOC容器帮对象找相应的依赖思想并注入,并不是由对象主动去找
- 资源集中管理,实现资源的可配置和易管理
- 降低了使用资源双方的依赖程度,松耦合
2)DI(依赖注入)
所谓的IOC称之为控制反转,简单来说就是将对象 的创建的权力及对象的生命周期的管理过程交由Spring框架来处理,从此在开发过程中不在需要关注对象的创建和生命周期的管理,而是在需要的时候由Spring框架提供,这个由Spring框架管理对象创建和生命周期的机制称之为控制反转。而在创建对象的过程中Spring可以依据配置对象的属性进行设置,这个过程称之为依赖注入,也即DI
2.Spring Context (Spring上下文)
一个核心配置文件,为Spring框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
3.Spring AOP(面向切面编程)
AOP 是面向切面编程,是通过代理的方式为程序添加统一功能,集中解决一些公共问题。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
集成了面向切面的编程功能(AOP把一个业务流程分成几部分,例如权限检查、业务处理、日志记录, 每个部分单独处理,然后把它们组装成完整的业务流程。每个部分被称为切面),
可以将声明性事物管理集成到应用程序中。
文章解释:
Spring AOP的使用场景_Java炼金术的博客-CSDN博客_springaop应用场景
4.Spring DAO
Spring操作数据库的模块。它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
5.Spring ORM(对象关系映射)
ORM的全称是Object Relational Mapping,即对象关系映射。它的实现思想就是将关系数据库中表的数据映射成为对象,以对象的形式展现,这样开发人员就可以把对数据库的操作转化为对这些对象的操作。因此它的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。
6.Spring Web 模块
集成各种优秀的web层框架的模块。Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
7.Spring MVC 框架(Model模型、View视图、Control控制层)
在MVC设计思想中要求一个符合MVC设计思想的软件应该保证上面这三部分相互独立,互不干扰,每一个部分只负责自己擅长的部分。如果某一个模块发生变化,应该尽量做到不影响其他两个模块。提高代码的可读性,实现程序间的松耦合、提高代码复用性。
工作原理:
客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Moder)->将得到视图对象返回给用户.
六、消息队列
概述:消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构
1、消息中间件的组成
Broker:消息服务器,作为server提供消息核心服务
Producer:消息生产者,业务的发起方,负责生产消息传输给broker,
Consumer:消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理
Topic:主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的广播
Queue:队列,PTP(点对点)模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收
Message:消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输
2.消息中间件的模式分类
1)点对点(PTP)模式
使用queue作为通信载体。
一对一,消费者主动拉取数据,消息收到后消息清除。
消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息。
消息被消费以后,queue中不再存储,所以消息消费者不可能消费到已经被消费的消息。
Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
缺陷:不能复用,消息如果要给多个消费者用(要有多个队列),就会很麻烦。
2)发布订阅(Pub/Sub)模式
Pub/Sub发布订阅(广播):使用topic作为通信载体。
一对多,消费者消费数据之后不会清除消息。
消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。
消息队列会保存消息,但是有时间限制(可配置的),不能一直保存的。
优点:消息可以传给多个消费者使用。
该种模式下有几个关键点:生产者的速度,队列的推送速度,消费者的消费速度,如果这三者的速度相差很大,是很容易出问题的,所以发布订阅模式下又有两种模式:
消费者主动拉取消息,这个模式下,消费者要维护一个长轮询不断地去询问队列中是否有消息,不管里面有没有消息,所有可能会造成资源浪费。(kafka采用的模式)
队列主动推送数据,这个模式下要注意,如果各个消费者的消费速率不一样,比较小的会出现系统崩溃的情况,速率比较大的会造成资源浪费。(公众号采用的模式)
3、应用场景
1)异步处理
场景说明:用户注册后,需要发注册邮件和注册短信。传统的做法有两种 1.串行的方式;2.并行方式
a、串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。
b、并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
假设三个业务节点每个使用50毫秒钟,不考虑网络等其他开销,则串行方式的时间是150毫秒,并行的时间可能是100毫秒。
因为CPU在单位时间内处理的请求数是一定的,假设CPU1秒内吞吐量是100次。则串行方式1秒内CPU可处理的请求量是7次(1000/150)。并行方式处理的请求量是10次(1000/100)
小结:如以上案例描述,传统的方式系统的性能(并发量,吞吐量,响应时间)会有瓶颈。如何解决这个问题呢?
引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构如下:
按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍
2)应用解耦
场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图:
传统模式的缺点:假如库存系统无法访问,则订单减库存将失败,从而导致订单失败,订单系统与库存系统耦合
如何解决以上问题呢?引入应用消息队列后的方案,如下图:
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功
库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作
假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦
3)流量削锋
流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。
应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。
a、可以控制活动的人数
b、可以缓解短时间内高流量压垮应用
用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
秒杀业务根据消息队列中的请求信息,再做后续处理
4)日志处理
日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。架构简化如下
日志采集客户端,负责日志数据采集,定时写受写入Kafka队列
Kafka消息队列,负责日志数据的接收,存储和转发
日志处理应用:订阅并消费kafka队列中的日志数据
5)消息通讯
消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等
点对点通讯:
客户端A和客户端B使用同一队列,进行消息通讯。
聊天室通讯:
客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。
以上实际是消息队列的两种消息模式,点对点或发布订阅模式。模型为示意图,供参考。
4.常见的MQ产品
1)RocketMQ
2)kafka
5.优缺点
七、Nacos
阿里的一个开源产品,是针对微服务架构中的服务发现、配置管理、服务治理的综合型解决方案。(用来实现配置中心和服务注册中心)
1.四大功能
1)服务发现和服务健康监测(使服务更容易注册,并通过DNS或HTTP接口发现其他服务,还提供服务的实时健康检查,以防 止向不健康的主机或服务实例发送请求。 )
- 支持基于DNS和基于RPC的服务发现。服务提供者使用原生SDK、OpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODO 或HTTP&API查找和发现服务。
- Nacos提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。
2)动态配置服务
- 以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
- 消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。
- 配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
- 提供了一个简洁易用的UI (控制台样例 Demo) 帮助管理所有的服务和应用的配置。
- Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,能更安全地在生产环境中管理配置变更和降低配置变更带来的风险。
3)动态 DNS 服务
- 动态 DNS 服务支持权重路由,更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能更容易地实现以 DNS 协议为基础的服务发现,消除耦合到厂商私有服务发现 API 上的风险。
- Nacos 提供了一些简单的 DNS APIs TODO ,管理服务的关联域名和可用的 IP:PORT 列表
4)服务及其元数据管理
- 从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。
2、服务发现
1)存在的问题
在微服务架构中,整个系统会按职责能力划分为多个服务,通过服务之间协作来实现业务目标。服务的消费方要调用服务的生产方,为了完成一次请求,消费方需要知道服务生产方的网络位置(IP地址和端口号)。
微服务可能是部署在云环境的,服务实例的网络位置或许是动态分配的。
每个服务一般会有多个实例来做负载均衡,由于宕机或升级,服务实例网络地址会经常动态改变。
每个服务也可能应对临时访问压力增加新的服务节点。
2)服务发现概念
服务发现就是服务消费方通过服务发现中心智能发现服务提供方,从而进行远程调用的过程。
3)服务发现流程
服务实例本身并不记录服务生产方的网络地址,所有服务实例内部都会包含服务发现客户端。
1、 在每个服务启动时会向服务发现中心上报自己的网络位置。在服务发现中心内部会形成一个服务注册表,服务注册表是服务发现的核心部分,是包含所有服务实例的网络地址的数据库。
2、服务发现客户端会定期从服务发现中心同步服务注册表 ,并缓存在客户端。
3、当需要对某服务进行请求时,服务实例通过该注册表,定位目标服务网络地址。若目标服务存在多个网络地址,则使用负载均衡算法从多个服务实例中选择出一个,然后发出请求。
总结,在微服务环境中,由于服务运行实例的网络地址是不断动态变化的,服务实例数量的动态变化 ,因此无法使用固定的配置文件来记录服务提供方的网络地址,必须使用动态的服务发现机制用于实现微服务间的相互感知。 各服务实例会上报自己的网络地址,这样服务中心就形成了一个完整的服务注册表,各服务实例会通过服务发现中心来获取访问目标服务的网络地址,从而实现服务发现的机制。