微服务环境搭建
电商系统:搭建用户微服务,商品微服务,订单微服务
技术选型
maven:3.5+
数据库:mysql 5.7
持久层:MybatisPlus
其他:springboot→springcloud Alibaba
下单业务实现
调用多个微服务,把服务提供者的网络地址(ip,端口)等硬编码到了代码中
弊端
- 一旦服务提供者地址变化,就需要手工修改代码
- 一旦服务提供者为多个,无法实现负载均衡功能
- 一旦服务变的越来越多,人工维护调用关系困难
那么应该则么解决呢?这时候就需要通过注册中心动态的实现服务治理。
服务治理
服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册和发现。
- 服务注册: 在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要以心跳的方式去监测清单中的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。
- 服务发现: 服务调用方向服务注册中心咨询服务,并获取所有服务的服务清单,实现对具体服务实例的访问。
服务注册中心
除了微服务,还有一个组件是服务注册中心, 他是微服务架构中非常重要的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:
- 服务发现
- 服务注册: 保存服务提供者和服务调用者的信息
- 服务订阅: 服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息
- 服务配置
- 配置订阅:服务提供者和服务调用者订阅微服务相关的配置
- 配置下发:主动将配置推送给服务提供者和服务调用者
- 服务健康检测
- 检测服务提供者的健康情况,如果发现异常,执行服务剔除
常见的注册中心
Zookeeper**
zookeeper是一个分布式服务框架,是Apache Hadoop的一个子项目,它主要用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务,状态同步服务,集群管理,分布式应用配置项的管理等
Eureka
Eureka是Spring Cloud Netflix中的重要组件,主要作用就是做服务注册和发现。
Consul
Consul是基于Go语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册,服务发现和配置管理等功能。Consul的功能都很实用,其中包括:服务注册/发现,健康检查,Key/Value存储,多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件,所以安装和部署都非常简单,只需要从官网下载后,执行对应的脚本即可。
https://www.consul.io/
Nacos
Nacos 致力于帮助您发现、配置和管理微服务。它是Spring Cloud Alibaba的组件之一。
Nacos简介
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。
什么是 Nacos?
服务(Service)是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理:
Nacos 的关键特性包括:
-
服务发现和服务健康监测
Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDK、OpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODO 或HTTP&API查找和发现服务。
Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。
-
动态配置服务
动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。
配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
Nacos 提供了一个简洁易用的UI (控制台样例 Demo) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。
-
动态 DNS 服务
动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能让您更容易地实现以 DNS 协议为基础的服务发现,以帮助您消除耦合到厂商私有服务发现 API 上的风险。
Nacos 提供了一些简单的 DNS APIs TODO 帮助您管理服务的关联域名和可用的 IP:PORT 列表.
-
服务及其元数据管理
Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。
下载安装Nacos
Nacos 依赖 Java 环境来运行。如果您是从代码开始构建并运行Nacos,还需要为此配置 Maven环境,请确保是在以下版本环境中安装使用:
- 64 bit OS,支持 Linux/Unix/Mac/Windows,推荐选用 Linux/Unix/Mac。
- 64 bit JDK 1.8+;下载 & 配置。
- Maven 3.2.x+;下载 & 配置。
您可以从 最新稳定版本 下载 nacos-server-$version.zip
包。
我们下载:https://github.com/alibaba/nacos/releases/download/2.0.2/nacos-server-2.0.2.zip
命令行启动访问
# 解压nacos-server-2.0.2.zip
# 进入解压目录,进入bin目录
cd nacos/bin
#命令启动
startup.cmd -m standalone
打开浏览器,输入http://localhost:8848/nacos 即可访问服务,默认密码 nacos/nacos
注册微服务到Nacos
添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
使用dependencyManagement结合properties可以统一管理项目的版本号,确保应用的各个项目的依赖和版本一致,不用每个模块项目都弄一个版本号,不利于管理
添加@EnableDiscoveryClient
启动类上添加
package com.mszlu.shop.goods;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GoodsApp {
public static void main(String[] args) {
SpringApplication.run(GoodsApp.class);
}
}
配置中添加nacos的地址
application.yml
注意缩进
spring:
application:
name: shop-goods
datasource:
password: root
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
server:
port: 8002
mybatis-plus:
global-config:
db-config:
table-prefix: shop_
启动服务
出现 ErrCode:503, ErrMsg:server is DOWN now, please try again later!
failed to req API:/nacos/v1/ns/instance after all servers([127.0.0.1:8848]) tried: ErrCode:503, ErrM
可能原因:
1:ip地址等发生改变
删除nacos/data/目录下的protocol文件夹,这个文件夹放置一些缓存的ip历史记录,删除后重启nacos就行了
基于Feign实现服务调用(下单业务)
Feign是Spring Cloud提供的一个声明式的伪Http客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。
Nacos很好的集成了Feign,Feign默认集成了Ribbon,所以在Nacos下使用Feign默认就实现了负载均衡的效果。
Feign的使用
依赖注解
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@EnableFeignClients
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
创建各个微服务的feign接口,实现feign的微服务调用
package com.mszlu.shop.app.feign;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.common.vo.user.UserBO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("shop-user")
public interface UserFeign {
//调用路径同http访问路径
@GetMapping("/user/findUser/{id}")
public Result<UserBO> findUser(@PathVariable("id") Long id);
}
更改BuyService的实现,使用feign来完成调用
package com.mszlu.shop.app.service.impl;
import com.mszlu.shop.app.feign.GoodsFeign;
import com.mszlu.shop.app.feign.OrderFeign;
import com.mszlu.shop.app.feign.UserFeign;
import com.mszlu.shop.app.model.params.BuyParams;
import com.mszlu.shop.app.service.BuyService;
import com.mszlu.shop.common.vo.OrderParams.OrderParams;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.common.vo.goods.GoodsBO;
import com.mszlu.shop.common.vo.user.UserBO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class BuyServiceImpl implements BuyService {
@Autowired
private UserFeign userFeign;
@Autowired
private GoodsFeign goodsFeign;
@Autowired
private OrderFeign orderFeign;
@Override
public Result submitOrder(BuyParams buyParams) {
Result<UserBO> userBOResult = userFeign.findUser(buyParams.getUserId());
if (userBOResult == null || !userBOResult.isSuccess() || userBOResult.getData() == null){
return Result.fail(10001,"用户不存在");
}
UserBO userBO = userBOResult.getData();
Result<GoodsBO> goodsBOResult = goodsFeign.findGoods(buyParams.getGoodsId());
if (goodsBOResult == null || !goodsBOResult.isSuccess() || goodsBOResult.getData() == null){
return Result.fail(10002,"商品不存在");
}
GoodsBO goodsBO = goodsBOResult.getData();
Integer goodsStock = goodsBO.getGoodsStock();
if (goodsStock < 0){
return Result.fail(10003,"商品库存不足");
}
BigDecimal goodsPrice = goodsBO.getGoodsPrice();
BigDecimal account = userBO.getAccount();
if (account.compareTo(goodsPrice) < 0){
return Result.fail(10004,"余额不足");
}
OrderParams orderParams = new OrderParams();
orderParams.setUserId(userBO.getId());
orderParams.setGoodsId(goodsBO.getId());
orderParams.setGoodsPrice(goodsBO.getGoodsPrice());
Result<String> orderResultString = orderFeign.createOrder(orderParams);
if (orderResultString == null || !orderResultString.isSuccess()){
return Result.fail(10005,"下单失败");
}
String orderId = orderResultString.getData();
return Result.success(orderId);
}
}
负载均衡
通俗的讲,负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
根据负载均衡发生位置的不同,一般分为服务端负载均衡和客户端负载均衡。
服务端负载均衡指的是发生在服务提供者一方,比如常见的nginx负载均衡。
而客户端负载均衡指的是发生在服务请求一方,也就是在发送请求之前已经选好了由哪个实例处理请求。
在微服务调用关系中,一般会选择客户端负载均衡,也就是由服务调用一方来决定由哪个服务来提供执行。
基于Ribbon实现负载均衡
Ribbon是Spring Cloud的一个组件,它可以让我们使用一个注解@LoadBalanced 就能轻松的搞定负载均衡。
负载均衡策略
Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule,具体的负载策略如下所示:
策略名 | 策略描述 | 实现说明 |
---|---|---|
BestAvailableRule | 选择一个最小的并发请求的server | 逐个考察Server,如果Server被Tripped了,则忽略,再选择其中ActiveRequestCount最小的Server |
AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的后端server(activeconnections超过配置的阀值) | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里记录的各个server的运行状态 |
WeightedResponseTimeRule | 根据相应的时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低 | 一个后台线程定期的从status中读取响应时间,为每个server计算一个weight。weight的计算也比较简单,responsetime减去每个server自己的平均的响应时间,就是server的权重。当开始运行,没有形成status时,使用roundbin策略选择server |
RetryRule | 对选定的负载均衡策略机添加重试机制 | 在配置时间内选择的server不成功,则一直尝试使用subRule的方式选择一个可用的server |
RoundRobinRule | 轮询方式选择server | 轮询index,选择index位置对应的server |
RandomRule | 随机选择一个server | 在index上随机,选择index位置对应的server |
ZoneAvoidanceRule | 复合判断server所在的区域的性能和server的可用性选择server | 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone内的运行性能是否可用,剔除不可用的zone(区域内的所有server),AvailabilityPredicate用于过滤掉连接次数过多的server |
我们可以通过配置来调整Ribbon的负载均衡策略:
加在app端
shop-goods:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule