近况
一转眼12天就过去了,又忙实习的事情,又看网课,还有学校方面的事情,看完这部分的网课终于可以着手
写一篇博客了,说是博客,我更加觉得我只是在记笔记,记笔记是必要的,不然在学习上不能给我成就感
没有成就感,就难有动力继续走下去,但凡能让我有成就感的事情,我都想去完成,这种成就感鼓励我继续
写博客记笔记,形成正循环,现在这个博客还不完美,等以后,我可能想去做一个自己的博客,现在实力不够
继续好好学习,OK,咱们废话不多说,先来看看本篇博客主要讲写什么:
微服务,服务注册中心的概念和理解
微服务的概念
从前我们的后端项目只是单体应用,所有东西写在一个项目中,大不了搞多个实例做负载均衡
而微服务指的是将大型单体应用按功能划分,将不同的功能拆分出来,形成许多小的应用,
我们可以使用maven中的模块功能来实现上面功能,从而实现一个个模块,例如拆分成:订单模块,用户模块等等。这一个个模块我们称之为微服务,而被拆分出来的微服务同时也面临着很多问题,自然而然产生了许多解决
微服务问题的框架,技术,例如我们的SpringCloud,SpringCloud解决的是这些问题中的服务治理问题
由这些解决问题的框架加上我们一个个业务模块,以此形成一整套架构,这被称为分布式架构或者微服务架构分布式架构的主要特点是高可用,什么是高可用,例如订单模块访问用户模块,如果这些模块只有一台实例
那么当用户模块挂了,订单模块岂不是也会被影响到,所以这肯定不是高可用,高可用的前提是集群
即一个模块有多个实例,模块也被称为服务,所以可以说订单服务下有多个订单实例,用户服务下有多个用户实例,
当某个订单实例访问某个用户实例时,这个用户实例挂了,还有其他用户实例可以访问,不会影响到自己,
这就是所谓的高可用,分布式架构需要的就是高可用,可以随时配置服务实例,之后你会看到,分布式架构中,
不管什么东西都会是集群配置。最后,或许你会看到分布式架构,微服务架构,在我初学者看来,这两者都是一样的概念。我们在微服务中需要学习哪些技术
如何快速学习一项技术,需要看这个技术是什么,有什么作用,在整体环境下解决什么问题,
先从大的角度了解这项技术,再去学习这个技术下的每一个部分,下面来看看微服务中我们需要学习哪些技术
图一:图二:
图3:接着看下,黑马和尚硅谷微服务课程中教授的框架: 黑马:
尚硅谷:
最后来看看,微服务架构整体设计:
服务注册中心
黑马尚硅谷的网课我都看,目前学完nacos为止,个人感觉尚硅谷网课非常不错,我们看上面尚硅谷技术图中
服务注册中心下需要学习的框架是:eureka,ZooKeeper,consul,nacos,当然在学习这些框架时候
我还学习了部分Ribbon的部分知识,本篇博客就围绕简单介绍这几个框架的应用吧,当然在介绍框架之前
让我们来了解下,什么是服务注册中心上面提到SpringCloud是用来解决服务治理问题的,而SpringCloud正是借助eureka模块来实现服务治理的,
在传统的RPC远程调用框架中,管理每个服务与服务之间的依赖关系比较复杂,管理非常复杂,所以需要使用
服务治理,管理服务与服务之间的依赖关系,可以实现服务调用,负载均衡,容错等,实现服务发现与注册
上述的服务就是我们说的模块,即服务=模块服务注册中心概念:你可以想象,有一个服务器,我们所有微服务将自身的信息,IP,端口号等等
信息保存在服务器的程序中,当有一个微服务/服务实例(服务或模块内的其中一个实例)想访问另一个微服务
时,它会先从保存微服务信息的服务器上获取其他微服务的信息,再发送请求到指定的微服务获取数据。
这个服务器上运行的程序,能保存微服务的自身信息,能把数据交给需要请求的微服务,
这个程序或者说服务/模块,被称为服务注册中心。我们微服务将自身数据保存在服务注册中心这个过程叫做服务注册发现
我们微服务拿到其他微服务的信息,向其中一个微服务发送请求这个过程叫做服务调用,
我们微服务想调用其他服务时,先从服务注册中心获取这个服务的全部实例数据到本地内存中,然后挑选其中
一个服务实例,向它发送真实请求,这个挑选一个服务实例的过程被叫做软件负载均衡,
与硬件负载均衡nginx不同的是,软件负载均衡实现微服务内部调用的负载均衡,而nginx则是内外系统的负载均衡。
对于发起请求的微服务,我们叫它服务消费者,对于响应请求的微服务,我们叫它服务提供者。
最后,实现服务注册中心功能有很多种框架或模块,例如:eureka,ZooKeeper,consul,nacos
下面将会讲这些框架的使用,因为没正式学过ZooKeeper,只在网课中看老师在linux中使用过,所以没有去了解。
另外顺便介绍下ribbon的使用,这是一个实现客户端软件负载均衡的工具。
项目基本介绍
在讲各种服务注册中心框架之前,我先大概介绍一下他们在什么环境下测试的
项目结构图
MyAPI_Commons放着所有模块都需要的代码,都是些实体类 MyConsumer_Order80是eureka测试中服务消费者,端口80,表示订单服务 MyConsumerXXX_Order80则是使用不同技术作为服务消费者 MyEureka_Server7001,7002是eureka服务注册中心 MyProvider_Payment8001,8002是eureka测试中服务提供者,表示支付服务 MyProviderXXX_Payment则是使用不同技术作为服务提供者
项目POM文件
下面是主项目MySpringCloud的pom文件<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>world.keyi</groupId> <artifactId>MySpringCloud</artifactId> <version>1.0-SNAPSHOT</version> <!--父模块下所有子模块,父模块打包方式务必是POM方式--> <modules> <module>MyAPI_Commons</module> <module>MyEureka_Server7001</module> <module>MyEureka_Server7002</module> <module>MyProvider_Payment8001</module> <module>MyProvider_Payment8002</module> <module>MyConsumer_Order80</module> <module>MyProviderConsul_Payment8006</module> <module>MyConsumerConsul_Order80</module> <module>MyProviderNacos_Payment8840</module> <module>MyConsumerNacos_Order80</module> </modules> <packaging>pom</packaging> <!--统一jar包和版本号的管理--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>2.13.3</log4j.version> <lombok>1.16.18</lombok> <mysql.version>8.0.22</mysql.version> <druid.version>1.1.17</druid.version> <mybatis.spring.boot.version>2.1.0</mybatis.spring.boot.version> </properties> <!-- 子模块继承之后,提供作用:锁定版本+子模块(module)不用写groupId和version dependencyManagement中的依赖,子模块如果使用了其中的依赖,依赖的版本号都是继承这里 XXX-dependencies中包含很多个依赖,所有只需要引入其整体就行。 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--springCloud依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <!--springCloudAlibaba依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--mysql依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!--druid依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!--mybatis依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok}</version> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> </dependencies> </dependencyManagement> <build> <pluginManagement> <plugins> </plugins> </pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.1.3.RELEASE</version> <configuration> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> </build> </project>
SpringBoot,SpringCloud,SpringCloudAlibaba三者之间版本的选择
开发项目流程
这个与本篇博客的服务注册中心没关系,主要是想记录一下,下面配置名称都是中文名,
因为我使用了idea中文插件,如果你也想配置某个功能,请按照英文名找相应配置项选择maven中的
maven-archetype-site
创建项目
(可以选择其他的demo,反正最后src都删掉,只留pom.xml文件)设置文件编码,打开项目设置,在设置 > 编辑器 > File Encodings 之中
将Global Encoding,Project Encoding,及下面的属性文件的默认编码都改为UTF-8
并且将[属性文件的默认编码]旁边打上勾√开启项目注解,打开项目设置,在设置 > 构建,执行,部署 > 编译器 > Annotation Processors中
将Default和maven default annotation processors profile中的启用注释处理打上勾√设置项目版本,打开项目设置,在设置 > 构建,执行,部署 > 编译器 > java编译器中
将目标字节码版本改成8,然后打开项目结构,在模块中点击相应的模块,在右边选择语言级别为8将项目中的
*.idea
目录或*.iml
文件隐藏,打开设置 > 编辑器 > 文件类型(File types)
在忽略文件和文件夹输入框中加上:*.idea;*.iml
即可,推荐!以上就是父项目的创建,父项目创建完毕后,就是子项目/模块的创建
大体上就是:创建模块–>改POM文件–>写启动类–>写yaml文件–>写业务类
业务类流:建表–>写实体类–>controller–>service–>dao
eureka的基本使用
eureka框架是实现服务注册中心的其中一种方式,服务注册中心基于CS架构,分为客户端和服务端,
所有微服务都是客户端,eureka作为服务端,下面是eureka注册中心架构图,其他注册中心基本如此:
让我们再次捋一捋:服务消费者和服务提供者作为eureka客户端注册到eureka服务中心,当服务消费者发起
服务调用时,会被ribbon拦截到请求,ribbon会向eureka注册中心获取服务提供者的服务列表,因为该服务下
可能包含多个服务实例,然后使用负载均衡算法选择一个服务实例,向该实例发起请求。
eureka服务端和客户端的配置
eureka服务端配置
如上面项目结构图中一样,创建好MyEureka_Server7001子模块,然后引入eureka服务端依赖
//该依赖中包含ribbon的依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
eureka服务模块的application.xml
server: port: 7001 eureka: instance: hostname: eureka7001 #eureka服务端的实例名字 client: register-with-eureka: false #表示不向注册中心注册自己 fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务 service-url: #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址 #就是指定eureka服务注册中心的地址,下面配置的是其他eureka服务端,达到集群效果。 defaultZone: http://eureka7002:7002/eureka/ server: enable-self-preservation: false #关闭eureka自我保护模式 # eviction-interval-timer-in-ms: 2000 #eureka内部定时任务调用频率,用于剔除过期的实例
eureka服务模块的启动类
@SpringBootApplication @EnableEurekaServer //开启eureka服务端注解,表示该模块作为eureka服务注册中心 public class MyEurekaServer7001 { public static void main(String[] args) { SpringApplication.run(MyEurekaServer7001.class,args); } }
eureka客户端配置
我们要模拟订单模块的一个实例向支付模块的一个实例发起请求,首先需要配置订单实例和支付实例
两个微服务作为我们的eureka客户端,下面是配置信息订单实例,MyConsumer_Order80
//eureka客户端依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
//订单实例的application.yaml server: port: 80 spring: application: name: MyConsumerOrder80 eureka: client: register-with-eureka: true #是否注册进服务注册中心 # 是否从服务注册中心抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: # 服务注册中心的地址 defaultZone: http://localhost:7001/eureka # 单机版 # defaultZone: http://eureka7001:7001/eureka,http://eureka7002:7002/eureka # 集群版
//MyConsumer_Order80启动类 @SpringBootApplication @EnableEurekaClient public class MyConsumerOrder80 { public static void main(String[] args) { SpringApplication.run(MyConsumerOrder80.class,args); } }
支付实例,MyProvider_Payment8001
eureka客户端依赖同上//MyProvider_Payment8001的application.xml server: port: 8001 spring: application: name: MyProviderPayment datasource: url: jdbc:mysql://localhost:3306/mycloud?serverTimezone=GMT&useSSL=false&characterEncoding=utf-8 username: root password: 123765 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml # type-aliases-package: world.keyi.entities # 所有Entity别名类所在包,别名在MyAPI_Commons模块中 eureka: client: register-with-eureka: true #是否注册进服务注册中心 # 是否从服务注册中心抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: # 服务注册中心的地址 defaultZone: http://localhost:7001/eureka # 单机版 # defaultZone: http://eureka7001:7001/eureka,http://eureka7002:7002/eureka # 集群版 instance: instance-id: payment8001 #配置主机名,在eureka界面上status下就会显示该名称,而不是主机名称+服务名称+端口号 #在eureka界面上,鼠标放到status下的链接上,左下角可以显示该服务的ip地址端口号等信息,表示点击后会跳转的地址 prefer-ip-address: true # lease-renewal-interval-in-seconds: 1 #每次间隔多长时间发送心跳,默认30秒 #过了多长时间内不发送心跳,如果没有开启eureka保护模式,则会被剔除,默认90秒 # lease-expiration-duration-in-seconds: 2
//MyProvider_Payment8001的启动类 @SpringBootApplication @EnableEurekaClient public class MyProviderPayment8001 { public static void main(String[] args) { SpringApplication.run(MyProviderPayment8001.class,args); } }
本次实验eureka服务端和客户端的配置已经完成,当我们启动eureka服务模块后,再启动订单模块和支付模块后
输入eureka的网址:http://localhost:7001
,我们就可以发现,订单服务和支付服务都注册到了eureka中了
让我们来看看订单服务如何调用支付服务呢?
在没学习服务调用框架之前,我们可以使用restTemplate库来调用其他微服务,下面是restTemplate的配置
以及订单服务的controller,支付服务的controller
restTemplate的配置,当然是配置在订单模块中,因为是它发起请求嘛
@Configuration public class ApplicationContextConfig { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
订单模块的controller
@Slf4j @RestController @RequestMapping("/order") public class OrderController { public static final String PAYMENT_URL="http://localhost:8001/payment/eureka/"; @Resource private RestTemplate restTemplate; @GetMapping("/{id}") public CommonResult getPayment(@PathVariable("id")Long id){ log.info("正在获取订单数据"); return restTemplate.getForObject(PAYMENT_URL+id,CommonResult.class); } @PostMapping("/") public CommonResult insert(@RequestBody Payment payment){ log.info("正在存入订单数据"); return restTemplate.postForObject(PAYMENT_URL,payment,CommonResult.class); } }
支付模块的controller
package world.keyi.controller; @RestController @Slf4j @RequestMapping("/payment/eureka") public class PaymentController { @Value("${server.port}") private String port; @Resource private PaymentService paymentService; @GetMapping("/{id}") public CommonResult<Payment> queryById(@PathVariable("id")Long id){ Payment payment = paymentService.queryById(id); log.info("查询结果是:"+payment); if (Objects.nonNull(payment)){ return new CommonResult<>(200,"查询成功,访问的port:"+port,payment); } return new CommonResult<>(404,"查询失败",null); } @PostMapping("/") public CommonResult<Integer> insert(@RequestBody Payment payment){ int result = paymentService.insert(payment); log.info("插入结果是:"+result); if (result>0){ return new CommonResult<>(200,"插入成功,访问的port:"+port,result); } return new CommonResult<>(500,"插入失败",null); } }
以上是最普通不过的服务之间的调用,虽然我们可以在eureka网址上查看的到订单模块和支付模块的信息,
表明这两个模块已经注册到了eureka服务端上,但是上面的调用,完全没有使用到eureka提供的信息,
并且,因为请求路径写死,表明订单模块只能访问到支付模块的固定一个支付实例,完全没有高可用,也没有所谓的负载均衡
那么,订单模块如何获取eureka上的支付模块数据呢?当支付模块下有多个支付实例时,又是如何实现负载均衡的呢?配置步骤如下:
将订单模块的请求地址改成支付模块的服务名称
public static final String PAYMENT_URL="http://MYPROVIDERPAYMENT/payment/eureka/";
在restTemplate的Bean上添加@LoadBalanced注解
@Configuration public class ApplicationContextConfig { @Bean //使得请求可以被负载均衡,配置后请求的接口应该是服务端的项目名称,例如:MyProviderPayment,这就是Ribbon的负载均衡的功能 @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
OK,通过配置我们就可以实现,订单模块调用支付模块的数据了,你可能好奇,为什么请求地址要写成支付模块
的服务名称,这里的服务名称就是支付模块中配置的spring.application.name
,底层是如何解析这个服务名称
负载均衡又是如何实现的呢?别急,在ribbon中我们会讲到。
上面实现的是单个订单实例访问单个支付实例,没有负载均衡的效果,因为只有一台实例嘛,并且eureka也不是
集群的配置,只有一台eureka服务,那么如何配置多个eureka,达到集群的效果的?
实际上我们只需要在eureka的application.yaml上配置其他eureka服务地址即可,所有eureka实例互相配置其他
eureka实例的地址,而客户端则配置所有的eureka服务实例
//MyEureka_Server7001,eureka服务端
server:
port: 7001
eureka:
instance:
hostname: eureka7001 #eureka服务端的实例名字
client:
register-with-eureka: false #表示不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7002:7002/eureka/ #配置eureka集群中其他的地址
//MyEureka_Server7002,eureka服务端
server:
port: 7002
eureka:
instance:
hostname: eureka7002 #eureka服务端的实例名字
client:
register-with-eureka: false #表示不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001:7001/eureka/ #同上
//订单服务
server:
port: 80
spring:
application:
name: MyConsumerOrder80 #服务名称,服务名称可以相同,表示一个服务名称下有多个服务实例
eureka:
client:
register-with-eureka: true #是否注册进服务注册中心
fetchRegistry: true
service-url: # 服务注册中心的地址
# defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://eureka7001:7001/eureka,http://eureka7002:7002/eureka # 集群版
//支付模块同样如此配置,只需要配置eureka.client.service-url.defaultZone即可
讲完eureka服务端和客户端的配置,讲完如何配置使得可以通过服务名称达到请求支付实例,
并且还具有负载均衡的效果,讲完如何配置eureka集群,接下来我们讲讲eureka的保护机制
eureka有一个保护机制,即当微服务注册到eureka上后,每30秒发送一次心跳给eureka服务注册中心,
如果90秒后,eureka发现微服务还没有发送心跳,eureka本来是要剔除该微服务的,但是如果eureka在短时间内
发现过多的微服务超过90秒没有发送心跳,就会认为当前网络不是很好,便会开启保护模式,不会剔除这些微服务
eureka的保护机制是默认开启的,那么如何关闭保护机制呢?通过修改eureka的application.yaml中的eureka.server.enable-self-preservation=false
来关闭保护机制
eureka的保护机制强调的是可用性,即当网络不好的时候,首先保证用户的可用性,即属于CAP理论中的AP分支
CAP即数据一致性,数据可用性和数据容错分区,分为AP和CP分支,下面是一张CAP阵营的图片
以上就是关于eureka的介绍及基本使用
ribbon的基本使用
还记得刚刚上面说的嘛?为什么我们订单模块中使用服务名称就可以请求到支付模块呢?
为什么加上@LoadBalanced注解,就可以实现请求负载均衡?全部都是ribbon的功劳
当我们发出请求后,ribbon会拦截我们的请求,拿到服务名称,根据服务名称获取eureka注册中心上相应的服务
这个服务可能有多个服务实例,ribbon会根据不同的负载均衡算法去选择一个服务实例,拿到该服务实例的URI
再去请求这个地址,这就是ribbon在请求时的作用。这里来一张ribbon的执行流程图片
下面是对上图的解释:
1)拦截我们的RestTemplate请求http://userservice/user/1
2)RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-service
3)DynamicServerListLoadBalancer根据user-service到eureka拉取服务列表
4)eureka返回列表,localhost:8081、localhost:8082
5)IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
6)RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求
其实呢,我们自己也可以根据服务名称,获取到该服务名称下所有的服务实例,这叫做服务发现,步骤如下:
程序主入口中加上@EnableDiscoveryClient注解
自动注入DiscoveryClient接口
@Resource private DiscoveryClient discoveryClient;
使用DiscoveryClient可以获取eureka上所有的服务名称,或者根据服务名称,拿到注册中心的所有服务实例
@GetMapping("discovery") public void getDiscovery(){ /*查看eureka上注册的所有服务名称*/ List<String> services = discoveryClient.getServices(); for (String service : services) { System.out.println(service); } /*根据服务名称,查看该服务名称下的所有服务实例*/ List<ServiceInstance> instances = discoveryClient.getInstances("MYPROVIDERPAYMENT"); for (ServiceInstance instance : instances) { System.out.println(instance.getServiceId()+"---"+instance.getHost()+"---"+instance.getPort()+"---"+instance.getUri()); } }
我们知道,一旦开启了@LoadBalanced注解就能实现请求负载均衡,默认情况下,ribbon负载均衡算法是轮询模式
来看看ribbon中有多少种负载均衡算法吧那么怎么更改默认的负载均衡算法呢?更改默认的负载均衡算法有两种方式:
全局生效,即更改后,不管是向那个服务名称发送请求,都是使用该模式
//直接在配置类上增加Bean @Bean public IRule rule(){ return new RandomRule(); }
局部生效,向指定的服务名称发送请求使用更改的负载均衡算法,向其他服务名称还是使用轮询算法
//在模块的application.yaml中配置 MyProviderNacosPayment: #指定某个服务名称,实现更改单个请求该服务时选择指定的负载均衡规则 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机算法
通过上面的配置,我们的默认负载均衡算法就可以更改了,那么,可不可以定义自己的负载均衡算法呢?
当然也是可以的,下面我们来实现一下ribbon的默认负载均衡算法,轮询算法
首先创建一个LoadBalancer接口,并创建该接口的实现类
//LoadBalancer接口,该接口用于根据实现类的不同实现,找到合适的服务实例 public interface LoadBalancer { ServiceInstance selectInstance(List<ServiceInstance> instances); } //如果想实现轮询算法,则实现上面的接口,重写该方法即可,下面是MyLoadBalancer,重写轮询算法 @Component public class MyLoadBalancer implements LoadBalancer { /* 自定义负载均衡算法,实现轮询算法,轮询算法根据:rest请求次数%可用服务集合总数等于index下标 根据该index下标,从而获取可用服务集合中某个微服务。 该算法要做两件事,一是rest请求次数需要在多线程的环境下每次都自增,二是通过取余拿到下标值,返回指定的服务实例 主要问题是rest请求需要在多线程环境下保持自增,我们通过CAS+自旋锁的方式使得rest请求数自增+1 当然使用AtomicInteger.getAndIncrement()就行,我们这是相当于实现这个方法。 */ AtomicInteger atomicInteger = new AtomicInteger(0); public final int getAndIncrement(){ int current,next; do{ //通过do-while不断自旋 current = atomicInteger.get(); //根据内存地址拿到主内存中共同的值到本地内存中 next = current >=2147483647 ? 0: current+1; }while (!atomicInteger.compareAndSet(current,next)); //如果current值不变,则将current对应的地址中的值改成next,有乐观锁感觉了 return next; } @Override public ServiceInstance selectInstance(List<ServiceInstance> instances) { return instances.get(getAndIncrement()%instances.size()); } }
在controller中使用我们自定义的负载均衡算法吧
@Resource MyLoadBalancer myLoadBalancer; @Resource DiscoveryClient discoveryClient; /* 测试自定义负载均衡算法 */ @GetMapping("/lb") public String myLoadBalancer(){ log.info("正在使用自己的负载均衡算法"); //获取该服务名称下所有的服务实例 List<ServiceInstance> myproviderpayment = discoveryClient.getInstances("MYPROVIDERPAYMENT"); if (myproviderpayment==null||myproviderpayment.size()<=0){ return null; } //根据自定义负载均衡算法找到合适的服务实例 ServiceInstance serviceInstance = myLoadBalancer.selectInstance(myproviderpayment); URI uri = serviceInstance.getUri(); //拿到服务实例信息,获取URI发送请求 return restTemplate.getForObject(uri+"/payment/eureka/lb",String.class); }
OK,最后我们了解一下ribbon的饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalancedClient,请求时间会很长
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: MYPROVIDERPAYMENT #指定对该服务饥饿加载
以上是ribbon的全部介绍和使用
consul的基本使用
consul和eureka一样,也可以作为服务注册中心,我们可以去它官网下载consul应用,运行起来后consul服务端就开启了
接下来我们只需要在我们的微服务中配置consul客户端就行,下面是配置步骤
导入consul依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
在入口类中加上@EnableDiscoveryClient注解用于consul找到consul客户端服务
@EnableDiscoveryClient和@EnableEurekaClient异同:
- 共同点就是:都是能够让注册中心能够发现,扫描到该服务。
- 不同点:@EnableEurekaClient只适用于Eureka作为注册中心,@EnableDiscoveryClient可以是其他注册中心。
在模块的application.yaml中增加consul的配置
server: port: 8006 spring: application: name: MyProviderConsulPayment # 本模块,服务的名称 cloud: # 注册中心的配置 consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name} # 向外暴露本服务,即注册到consul中
与eureka不同的是:consul,ZooKeeper都属于CAP中的CP分支,而eureka服务注册中心则是AP分支
CP分支强调数据的一致性,一旦发现微服务心跳不正常,立马将该服务信息剔除出注册中心。
nacos中关于注册中心的使用
nacos是SpringCloudAlibaba中的组件,既能做服务注册中心,也能做服务配置中心,关于服务配置中心,
我会在之后的博客中有更细节的介绍使用,这里主要讲讲nacos作为注册中心的使用,下面是nacos关于注册中心的内容
nacos客户端的配置
和consul一样,在官网下好nacos运行后,就启动了nacos服务端,端口号是8848,钛金手机~
下面是客户端的配置,直接三板斧:加依赖,加注解,加配置导入nacos依赖
//父工程中引入 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.6.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> //子模块中引入 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> </dependency>
增加@EnableDiscoveryClient注解在入口类上
增加nacos配置
server: port: 8840 spring: application: name: MyProviderNacosPayment #本模块的服务名称 cloud: nacos: discovery: server-addr: localhost:8848 #nacos注册中心地址
配置完成后,访问nacos的地址就可以看到注册中心上已经有模块的信息了
nacos服务分级存储模型
一个服务可能有多个服务实例,这些实例并不一定是在同一个地方,可能有的被部署在上海
有的服务实例部署在杭州,北京等等,部署在上海的就是上海机房,部署某地一般就叫某地机房
nacos将同一个地方的所有实例划分为一个集群,例如上海集群有2个订单实例,2个支付实例
杭州集群有3个订单实例,2个支付实例,当某个微服务访问的时候,首先会访问本集群中的实例,如果
本集群中没有这个实例,就会跨集群访问,这种容灾备份的方式非常不错,下面是这种服务分级存储模型要实现集群的效果,我们需要加上相应的配置:
spring.cloud.nacos.discovery.cluster-name=XXX
,表示该实例属于XXX集群
配置好集群后,通过请求,我们发现还是没有达到我们想要的效果:当本集群没有想要的服务实例时才去访问其他集群的实例
本集群有就只访问本集群中的服务实例。要达到本集群优先的负载均衡,我们需要增加一些配置:userservice: #指定某个服务名称,访问该服务时,先访问本地集群的服务实例 ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
nacos权重配置
现实生活中,一定有这种情况:有些服务器性能好,我想让他处理更多请求,有些服务器性能差我想让他
少处理点请求,这种情况下,nacos的权重就非常有用了,服务实例的权重在nacos的服务注册中可以更改
不需要更改我们的代码,默认情况下,所有服务实例的权重都是1,即同集群下随机挑选实例。nacos的环境隔离
环境隔离指的是将服务实例区分开来,例如开发环境,测试环境,生产环境等,不同的环境的实例不能相互访问
在地点上,我们可以把服务实例分为不同的集群,在项目上,我们把服务实例分为不同的环境
那么如何配置服务实例的环境呢?我们的nacos客户端默认的是public环境,如果想更改成其他环境,
首先需要在nacos网站上增加一个namespace命名空间,会生成一个命名空间ID,复制这个ID,
然后在我们的服务实例上配置命令空间,如下:spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: HZ namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID
配置好后,启动该服务实例,就可以看到该服务出现在新建的命名空间之下了。
以上就是学习服务注册中心的全部内容,今后碰到新的内容,还会不断的更新,加油加油!