学习总结(2022.07.04-2022.07.20)
Nginx
Nginx是一个高性能的支持HTTP协议和反向代理的web服务器,其特点是占有内存少,并发能力强。
正向代理:代理服务器代理的是用户的客户端,访问的是服务端的服务器。
反向代理:代理服务器代理的是服务端的服务器,客户端访问的是被代理的服务器。
在微服务架构下,Nginx主要用于静态代理和负载均衡。
Nginx优点:
- 高并发,高性能
- 扩展性好
- 异步非阻塞的事件驱动模型
- 高可靠性
微服务
单体架构的好处有:
- 应用开发简单
- 应用修改简单
- 测试相对简单
- 部署简单
- 横向扩容性好
弊端有:
- 代码过度复杂且严重耦合,导致难以维护
- 从代码提交到实际部署的周期很长
- 扩展性受限
- 开发慢,启动慢,严重影响开发效率
- 难以交付可靠的单体应用
为了解决单体架构的问题,提出了微服务架构,即每个服务器按照单一职责原则实现特定的一组功能,每个服务器可以独立部署,独立运行,独立对外提供服务。当一个单体应用程序的规模过大时,就很难作为一个整体开发,也很难让一个人完全理解,所以需要拆分为模块来开发。但是在单体应用中,模块通常由编程语言提供的结构来定义,这样不同模块的代码还是可以互相引用,导致模块中依赖关系的混乱。而在微服务架构中,要访问别的模块,只能通过实现该模块的服务所提供的API调用,这样就保持了模块的独立性。
微服务的优势有:
- 每个服务都相对较小,易维护
- 大型的应用程序可以实现快速的持续交付和持续部署
- 应用扩展灵活
- 更好的容错
弊端有:
- 系统复杂难以管理
- 部署追踪问题难
- 事务难处理
- 服务数量增加,管理复杂性增加
服务注册中心
服务注册中心用于统一管理服务的状态和信息,以方便在微服务架构中服务间的互相调用。
在服务启动时,会将自己的信息注册至服务注册中心,包括ip地址、端口号等;在服务运行的过程中,会实时向服务注册中心报告自己的状态,如果服务注册中心太久没有收到来自服务的消息,会自动将该服务注销,以免影响整个应用程序,这被称为心跳机制;服务启动后,会定时去注册中心拉取其他服务信息,即将服务注册表下载到本地,这样每个服务都知道其他服务的信息。
目前使用的注册中心有Eureka和Nacos。
Eureka
Eureka是SpringCloud中包含的注册中心的组件,采用了CS架构的设计,Server作为服务器端实现了服务注册中心的功能,Client则是作为客户端,服务提供者通过它实现服务的注册,服务消费者通过它完成服务的自动发现。
EurekaServer的配置例子如下:
1 | |
1 | |
EurekaClient的配置例子如下:
1 | |
1 | |
Eureka具有自我保护机制,如果在短时间内丢失大量服务连接,Eureka并不会将所有丢失连接的服务注销,而是会保护这些服务。自我保护机制的意义是避免发生网络连接故障时对服务状态的误判,这会使服务集群更加健壮。
Nacos
Nacos在功能方面与Eureka并无区别,只是Nacos在实现方式上不同相同。它是一个类似tomcat的独立服务器,需要自行开启才可使用。开启方式实在其bin目录下输入
1 | |
即可启动。默认端口号为8848。
在需要注册的服务中添加如下配置:
1 | |
1 | |
Ribbon负载均衡
微服务架构中有一个很重要的问题是服务调用的选择策略问题,解决该问题的方法则是使用Ribbon。Ribbon是一个客户端负载均衡器,可以帮助我们在一个服务集群中,根据一组选择策略,选择一个服务实例,并向该实例发起调用请求。
它支持的负载均衡策略如下。
| 策略 | 实现类 | 描述 |
|---|---|---|
| 随机策略 | RandomRule | 随机选择server |
| 轮训策略 | RoundRobinRule | 轮询选择 |
| 重试策略 | RetryRule | 对选定的负载均衡策略(轮训)之上重试机制,在一个配置时间段内当选择服务不成功,则一直尝试使用该策略选择一个可用的服务; |
| 最低并发策略 | BestAvailableRule | 逐个考察服务,如果服务断路器打开,则忽略,再选择其中并发连接最低的服务 |
| 可用过滤策略 | AvailabilityFilteringRule | 过滤掉因一直失败并被标记为circuit tripped的服务,过滤掉那些高并发链接的服务(active connections超过配置的阈值) |
| 响应时间加权重策略 | WeightedResponseTimeRule | 根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间 |
| 区域权重策略 | ZoneAvoidanceRule | 综合判断服务所在区域的性能,和服务的,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server |
可以通过配置文件指定负载均衡策略。
1 | |
OpenFeign
在引入OpenFeign之前,我们调用服务的方式都是由RestTemplate实现的,但是需要自己构建url、请求参数等,这导致我们的代码和RESTful风格的Http请求紧密耦合。OpenFeign则可以实现它们的解耦,帮助我们以统一的方式,将接口翻译为RESTful风格的请求。
因为OpenFeign是面向Java接口而非HTTP的API,所以需要我们自己建立一个工程用于存放一些公共接口的请求参数和响应类。
使用OpenFeign需要定义Feign客户端。
1 | |
1 | |
如果需要查看日志,可以在FeignClient的配置文件中打开日志配置。
1 | |
为了防止在非正常情况下服务在调用过程中长时间阻塞等待,可以在配置文件中设置超时时间。
1 | |
配置中心
若每个服务都拥有自己的配置,那么如果有一天一些公共的配置出现了更改的话,所有服务都需要一起更改,且每个服务都需要重启,这明显是不合理的。要解决这个问题,就需要引入配置中心。配置中心类似于注册中心,可以以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置,消除了配置变更时重新部署应用和服务的需求,让配置管理变得更加高效。
目前Nacos除了作为服务注册中心,还具有配置中心的功能。Nacos配置中心定义了如下概念。
- 配置项:一个具体的可配置的参数与其值域,通常以 param-key=param-value 的形式存在。例如我们常配置系统的日志输出级别(logLevel=INFO|WARN|ERROR) 就是一个配置项。
- 配置集:一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置,每一个配置集都对应一个唯一的DataId,DataId必须由我们自己定义。
- 配置分组: Nacos 中的一组配置集,是组织配置的维度之一,每一个分组都有一个唯一的组名,如果我们未定义,则默认使用DEFAULT-GROUP分组
- 命名空间: 用于进行用户粒度的配置隔离,每一个命名空间都有一个唯一的Id值,如果我们未定义,则默认使用public命名空间
有以上的概念,即可自己定义配置项了。
配置项的key值以及分组组名都由自己定义。
命名空间的id值由Nacos生成。
配置集的DataId定义如下。
1
${prefix}-${spring.profiles.active}.${file-extension}prefix默认为spring配置文件中spring.application.name的值;spring.profiles.active对应spring配置文件中名字相同的值;`file-exetension为配置内容的数据格式,可以通过spring.cloud.nacos.config.file-extension配置,只支持properties和yaml。
在spring项目中加入配置中心的配置,
1 | |
此外Nacos的配置也可以通过数据库实现持久化,Nacos的配置会保存到Nacos自带的一个数据库derby中,因此即使重启Nacos也可以看到之前的配置信息。由于derby是嵌入式数据库,其中的数据无法可视化,所以Nacos也支持将数据库更换为mysql,具体方式如下。
在数据库中,创建名为nacos的数据库
在nacos数据库中,执行数据库初始化文件:nacos-mysql.sql(改文件在conf目录下已经提供)
.修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。
1
2
3
4
5
6
7spring.datasource.platform=mysql
db.num=1
# 这里的url要改成你自己的mysql数据库地址,并在你的mysql中创建名为nacos的数据库
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
# 这里要改成你自己登录mysql的用户名和密码
db.user.0=nacos_devtest
db.password.0=youdontknow
TK-Mybatis
TK-Mybatis是Mybatis的增强工具,为简化开发,提高效率而生。
TK-Mybatis的示例如下。
1 | |
1 | |
1 | |
1 | |
Map-Struct
Map-Struct主要用途是简化对象间的映射。在实际开发中,DAO层的实体类(即PO)与数据传输类(即BO或VO)不一定完全相同,而通过Map-Struct可以使它们间的转换变得简单。
使用示例
1 | |
SPI机制
SPI是JDK内置的一种服务提供发现机制,用于做接口实现类的扩展发现,简单来说,它就是一种动态替换发现的机制。
SPI要实现其机制,需要遵循以下约定。
- 当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录中同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当程序运行时,使用这个接口的实现类的时,就能通过该jar包META-INF/Services/的配置文件找到具体的实现类名。并加载该实现类(可能有多个)并实例化。
基于这样的一个约定就能很好的找到服务接口的实现类,而不需要在代码里指定。
示例
定义接口
1 | |
定义接口实现类
1 | |
定义配置文件
在src/META-INF/services目录下新增和接口全类名一样的一个文件,文件里面配置需要接口动态 加载的实现类的全类名,这样Java在调用接口的时候就能动态的去找到接口的实现,而对原来的代 码没有丝毫的侵入性。
Gateway
网关是一个服务器,是系统的唯一入口。所有的客户端和消费端都通过统一的网关接入微服务,在网关处理所有的非业务功能,如请求认证,请求记录,请求限流,黑白名单判断等。
Spring Cloud Gateway是一个基于Spring生态的API网关,基于WebFlux框架实现。它旨在以简单高效的方式实现,请求路由,以及一些其他的边缘功能,比如,安全,监控等功能。
通用的网关框架除了Gateway之外,还有Zuul,Zuul2等框架。其中,Zuul是由Netflix公司开发出的最早的通用网关框架,功能丰富,但是基于同步阻塞式Servlet API实现,性能不佳。Zuul2可以理解为Zuul的升级版,它基于异步非阻塞模式实现,但是由于zuul2在开发过程中一直延期,所以Spring Coud官方并未采用Zuul2最为自己的通用网关,而是自己推出了自己的基于异步非阻塞实现的第二代服务网关Gateway。
Gateway的一些核心概念如下。
- Predicate:路由规则的匹配条件。
- Filter:过滤器,在请求处理前实现对请求的拦截处理,在请求处理之后实现对响应的拦截处理。
- Route:定义请求和路由目标之间的映射,它由一个唯一ID(自定义),一个目标地址URI,表示路由条件Predicate集合,以及一个Filter集合组成。对于一个请求而言,如果它满足一个路由的全部路由条件(Predicate),那么该请求就会按照该路由(Route)规则,向目标地址URI转发。
Gateway的工作流程如下。
- 客户端向Gateway发起请求
- Gateway的Handler Mapping组件,会对请求做路由匹配,如果请求和某个路由规则匹配,则把该请求交给Web Handler处理
- 在将请求转发给目标之前,Web Handler会将请求,交给满足请求过滤条件的一系列过滤器,即一个过滤器链对该请求进行过滤处理
- 过滤器既在转发请求前拦截请求,也在请求处理之后对响应进行拦截处理。
Gateway可以通过整合Nacos或Eureka、Ribbon从而实现动态路由(即服务的自动发现和调用、服务的负载均衡)
配置文件示例如下。
1 | |
JWT
JWT是一个开发标准,它定义了一种紧凑的,自包含的方式,用于JSON对象在各方之间安全的传输信息,该信息可以被验证和信任,因为它是数字签名的。
下列场景中使用JWT是很有用的:
- Information Exchange(信息交换):对于安全的在各方之间传输信息而言,Json·Web·Token无疑是一种很好的方式。因为JWT可以被签名,例如,用公钥/私钥配对,你可以确定发送人就是他们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容有没有被篡改。
- 正因为数据可以基于JWT进行安全的传输,所以基于JWT,我们可以实现单点登录功能。(单点登录,即在多应用系统中,用户只需登录一次就能访问所有相互信任的应用系统)
使用JWT可以完成基于Token的身份验证,流程如下:
- 客户端使用用户名、密码请求登录
- 服务器收到请求去验证用户名和密码
- 验证成功之后服务端会签发一个token,再把这个token发送给客户端
- 客户端收到token以后可以把它存储起来,存到客户端内存或者其他地方
- 客户端每次向服务器请求资源的时候需要带着服务器签发的token
- 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求的数据
对比使用Session的身份验证:
- 每次用户认证通过以后,服务器需要创建一条记录来保存用户信息,通常是在内存中。那么随着认证通过的用户越来越多,服务器在这里的开销就会越来越大。
- 由于session是在内存中的,这就带来一些扩展性的问题。
- 用户很容易受到CSRF(跨站请求伪造)的攻击
使用Token的身份验证:
- JWT是把用户信息保存在客户端,基于token的方式将用户状态分散到了各个客户端中,可以减轻服务端的内存压力。
- token的状态是存储在客户端,这就使得服务器端并未存储用户登录状态,是无状态的,因为是无状态的,所以便于集群的扩容。
- Token不是cookie。每次请求的时候token都会被发送,可以作为请求参数发送,可以放在请求头里面发送,也可以放在cookie里面被发送。即使在你的实现中将token存储到客户端的cookie中,这个cookie也只是一种存储机制,而非身份认证机制。没有基于会话的信息可以操作,因为我们没有会话。
JWT由三部分组成,中间以·连接,三部分分别是:
- Header
- Payload
- Signature
Header由两部分信息组成:
- type:声明类型
- alg:声明加密的算法,通常使用HMAC SHA256
Payload由8部分信息组成:
- iss:签发者
- sub:所面向的用户
- aud:接收方
- exp:过期时间
- nbf:可用时间,在该时间前JWT不可用
- iat:签发时间
- jti:唯一身份标识,主要用于作为一次性token
- claim:存放信息的地方
Signature即签名信息。
在java中,JWT的使用方式如下。
1 | |
RocketMQ
消息中间件(消息队列):利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
RocketMQ是阿里巴巴开源的一个消息中间件,是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式特点。目前已贡献给apache。
对比一些其他的消息中间件:
- ActiveMQ:Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,是使用Java语言编写的。由于年代久远且社区不活跃,目前已极少人使用。
- RabbitMQ:AMQP协议的领导实现,支持多种场景。淘宝的MySQL集群内部有使用它进行通讯,OpenStack开源云平台的通信组件,最先在金融行业得到运用,使用Erlang语言编写的。
- Kafka:最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop的批处理系统、低延迟的实时系统、storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等,用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源 项目。
RocketMQ的主要功能有以下两种:
- 异步化。将一些可以异步化的操作通过发送消息来进行异步化,从而提高效率。
- 限流削峰。在高并发场景下把请求存入消息队列,利用排队思想降低系统瞬间峰值。
RocketMQ的一些相关概念如下。
Producer:消息生产者,负责消息的产生。
Consumer:消息消费者,负责消息消费。
Topic:消息的逻辑管理单位,每个消息都具有的一个属性。
Broker:消息的中转角色,负责存储消息,转发消息,一般也称为server,可以理解为一个存放消息的服务,里面可以有多个Topic。
MessageQueue: 消息的物理管理单位,一个Topic下有多个Queue,默认一个Topic创建时会创建四个MessageQueue。
ConsumerGroup: 具有同样消费逻辑消费同样消息的Consumer,可以归并为一个group。
ProducerGroup: 具有同样属性的一些Producer可以归并为同一个Group。
同样属性是指:发送同样Topic类型的消息
Nameserver 注册中心。
作用:
- 每个Broker启动的时候会向namesrv注册
- Producer发送消息的时候根据Topic获取路由到Broker里面Broker的信息
- Consumer根据Topic到Namesrv 获取topic的路由到Broker的信息
RocketMQ部署步骤如下:
- 注册中心Nameserver启动
- 消息中转服务Broker启动。启动时会创建Topic及其对应的MessageQueue,然后去注册中心注册,把自己的地址以及负责的Topic告诉注册中心。Broker和Nameserver之间通过心跳机制来检测对方是否存活。(Broker每隔30秒向所有Nameserver发送心跳,Nameserver则每隔10秒钟扫描所有存活的Broker,若2分钟内未收到心跳,则断开连接)
- 消息生产者Producer启动。启动后与一个Nameserver和所有与该生产者关联的Broker保持长连接。每30秒从Nameserver获取所有topic的情况,发送消息时,根据Nameserver、Topic和Broker建立连接。每30秒向所有Broker发送心跳。
- 消息消费者Consumer启动。启动后操作与Producer相同。
同步刷盘和异步刷盘
RocketMQ的消息可以存储到磁盘,保证断电后可以恢复,且存储的消息量不会超出内存限制。为了提高性能,RocketMQ会尽可能保证磁盘的顺序写,此时写入磁盘会有两种方式:
- 异步刷盘,返回