一、微服务优点:
1.完全耦合:关联紧密,不能分开,下一步以上一步为基础
2. 松耦合:关联度不高
3.完全解耦:没有任何关联
微服务可以放在容器里跑,容器需要给微服务提供环境,容器里跑的就是微服务,容器跟微服务没有关系。
二、主要组件
1.Eureka、Nacos(注册发现): Nacos和Eureka都是注册中心,都具有各自的负载均衡策略。Nacos有自己的配置中心,Eureka需要配合config实现配置中心,且不提供管理界面,nacos是动态刷新的,它采用Netty保持长连接实时推送,eureka需要配合MQ实现配置动态刷新。相比较来说Nacos性能更好,因此现在用的最多的也是Nacos。
2.Feign、Openfeign(远程调用):微服务之间通过rest接口通讯,springcloud提供fegin框架来支持rest的调用,Feign本身不支持Spring MVC的注解,使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务,openfeign是feign的升级版,OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
3.Ribbon(负载均衡):Ribbon实现客户端的负载均衡,负载均衡器提供很多对http和tcp的行为控制。Spring cloud Feign已经集成Ribbon,所以注解@FeignClient的类,默认实现了ribbon的功能。
4.Hystrix(熔断器):Hystrix被称为熔断器,它是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多服务之间通过远程调用实现信息交互,调用时不可避免会出现调用失败,比如超时、异常等原因导致调用失败,Hystrix能够保证在一个服务出问题的情况下,不会导致整体服务失败,避免级联故障(服务雪崩),以提高分布式系统的弹性。
5.Gateway(网关):为微服务框架提供一种简单而有效的统一的 API 路由管理方式,统一访问接口,目前已经替代传统的zuul网关了。
三.代码实现
3.1 首先创建两个模块,实现基本查询功能(基础,不做演示),分为用户服务和订单服务
3.2 Nacos服务注册发现
windows中去github先下载Nacos:输入网址 nacos.io,前往github下载,点击release,再点击tag选择需要下载的版本。
下载好后去到nacos\bin目录下双击startup,开启nacos
或者通过cmd去到bin目录下执行startup.cmd-m standalone命令也可以
成功后出现如下:
这边需要注意,我的这个默认是单点模式stand alone mode, 有些可能默认是cluster mode集群模式,因为我这边测试就是用的单点模式,如有是集群模式需要改一下,还是在bin目录下的startup文件中改就可以。默认端口号是8848
然后输入localhost:8848/nacos进入nacos页面,用户名密码都是nacos
随后添加相关依赖:
父工程添加springcloud-alibaba依赖
<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>
然后在userservice和orderservice中分别添加nacos依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
然后在application.yml中配置相关信息
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
application:
name: userservice
cloud:
nacos:
server-addr: 127.0.0.1:8848
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
然后启动项目:随便查一个id数据,可以看到已成功
然后我们去nacos界面查看,可以看到userservice微服务已经成功开启
同理,再去启动orderservice
至此服务都起来了。
3.3 远程调用
首先传统的我们可以通过restTemplate来进行,首先我们需要创建一个配置文件config,用来创建restTemplate对象。(加入从orderservice中调用userservice)
package cn.itcast.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
然后在orderservice中注入restTemplate, 调用相应的api即可
package cn.itcast.order.service;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//利用resttemplate发起http请求,查询用户信息,这个路径就是userservice的访问路径
String url = "http://localhost:8081/user/"+order.getUserId();
User user = restTemplate.getForObject(url,User.class);
order.setUser(user);
// 4.返回
return order;
}
}
随后访问http://localhost:8080/order/101
可以看到,user信息可以查出来,远程调用成功
3.4 负载均衡
在上述远程调用基础上,我们通过在restTemplateConfig中添加注解@LoadBalance来实现负载均衡。
package cn.itcast.order.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
orderservice:这时候访问路径就可以直接通过服务名称来查询
package cn.itcast.order.service;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//利用resttemplate发起http请求,查询用户信息,这个路径就是userservice的访问路径
// String url = "http://localhost:8081/user/"+order.getUserId();
String url = "http://userservice/user/"+order.getUserId();
User user = restTemplate.getForObject(url,User.class);
order.setUser(user);
// 4.返回
return order;
}
}
3.5 远程调用feign: 传统resttemplate还是有些麻烦复杂,如果有很多个服务需要远程调用,不可能做到每一个都输入url地址,会很麻烦,所以我们通过feign来实现远程调用替代restTemplate
首先创建一个新的module feign,引入feign依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
创建Userclient远程调用接口,并且将user类写入到feign微服务中
package cn.itcast.feign.clients;
import cn.itcast.feign.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("userservice") //服务名
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
然后在userservice和orderservice中引入feign依赖
<!-- openfeign远程调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<!-- 引入feign -->
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign</artifactId>
<version>1.0</version>
</dependency>
然后在启动类上需要加上@EnableFeignClients(clients=UserClient.class)注解,clients=UserClient.class指定具体远程调用的是哪个服务
package cn.itcast.order;
import cn.itcast.feign.clients.UserClient;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(clients= UserClient.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
修改orderservice,userclient替代restTemplate
package cn.itcast.order.service;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//利用resttemplate发起http请求,查询用户信息,这个路径就是userservice的访问路径
// String url = "http://localhost:8081/user/"+order.getUserId();
// String url = "http://userservice/user/"+order.getUserId();
// User user = restTemplate.getForObject(url,User.class);
User user = userClient.findById(order.getUserId());
order.setUser(user);
// 4.返回
return order;
}
}
重新运行可以看到结果成功
3.6 Gateway网关
添加相应依赖
<?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">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>cn.itcast.demo</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
网关主要就是一些相应的配置:在application.yml配置如下信息
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
# filters:
# - AddRequestHeader=sign, xn2001.com is eternal # 添加请求头
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求 allowedOrigins: “*” 允许所有网站
- "*"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
这里涉及到一些断言规则以及拦截规则,还有跨域请求问题。
还可以设置拦截功能,在访问的时候需要权限才可以访问,创建filter文件
package cn.itcast.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Order(-1)
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> queryParams = request.getQueryParams();
//2.获取参数中的authorization参数
String auth = queryParams.getFirst("authorization");
//判断参数值是否等于admin
if("admin".equals(auth)){
//是,放行
return chain.filter(exchange);
}
//否,拦截
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
最后启动运行:localhost:10010/order/101 出现如下页面,这是因为我们做了拦截,输入localhost:10010/order/101?authorization=admin才行
输入localhost:10010/order/101?authorization=admin
再去查user信息也成功
可以看到网关统一了访问端口都为10010,并且可以设置权限访问。