spring cloud多项组件宣布闭源或者停止维护了!!!
比如spring cloud2.x 注册中心Eureka 2.0停止维护了
spring cloud已近不再完整了
Spring Cloud Alibaba优于spring cloud
1.2.1.微服务治理
微服务治理:进行服务自动化管理,其核心是服务的注册与发现。
服务注册:服务实例将自身服务信息注册到注册中心。
服务发现:服务实例通过注册中心,将获取注册到启动的服务实例的信息,通过这些信息去请求他们提供服务。
服务剔除:服务注册中心将出问题的服务自动剔除到可用列表外,使其不能被调用到。
1.2.2.服务调用
spring cloud alibaba:致力于提供微服务开发的一站式解决方案
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.0.RELEASEversion>
parent>
<properties>
<java.version>1.8java.version>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<spring-cloud.version>Greenwich.RELEASEspring-cloud.version>
<spring-cloud-alibaba.version>2.1.0.RELEASEspring-cloud-alibaba.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
注意版本对应问题 2.1.0—Greenwich.RELEASE
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.73version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.21version>
dependency>
dependencies>
实体类创建
User
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.io.Serializable;
@Entity(name = "user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //数据库自增主键
private Integer id; //编号
private String username; //账号
private String password; //密码
private String phone; //手机号码
private String status; //账号状态
}
Role
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
@Entity(name = "role")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //主键自增
private Integer id; //编号
private String roleName; //编号名称
private String roleDescribe; //编号描述
}
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.cartergroupId>
<artifactId>shop-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);//启动配置
}
}
server:
port: 8071
spring:
application:
name: service-user
datasource:
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&userSSL=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 自动创建数据库
jpa:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.cartergroupId>
<artifactId>shop-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
@SpringBootApplication
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class,args);
}
}
server:
port: 8081
spring:
application:
name: service-product
datasource:
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&userSSL=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 自动创建数据库
jpa:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
Controller层
@RestController
@Slf4j
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/product/{pid}") //商品信息查询
public Product getOneProduct(@PathVariable("pid") Integer id){
log.info("接下来进行{}号商品信息的查询",id);
Product product = productService.findById(id);
log.info("商品信息查询成功,内容为{}", JSONObject.toJSONString(product));
return product;
}
Service层
public interface ProductService {
Product findById(Integer id);
}
ServiceImpl接口实现层
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductDao productDao;
@Override
public Product findById(Integer id) {
return productDao.findById(id).get();//jpa提供的方法
}
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate(); //远程调用
}
@RestController
@Slf4j
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderService orderService;
@RequestMapping("/order/product/{pid}") //下单
public Order order(@PathVariable("pid") Integer pid){
log.info("收到{}号商品下单请求,接下来调用微服务查询此商品信息",pid);
Product product = restTemplate.getForObject("http://localhost:8081/product/"+pid,Product.class);
log.info("查询到{}号商品的信息,内容是{}",pid, JSONObject.toJSONString(product));
//创建订单
Order order = new Order();
order.setUid(1); //订单编号
order.setUsername("测试用户"); //下订单的用户名称
order.setPid(pid); //商品的编号
order.setPname(product.getName()); //商品的名称
order.setPprice(product.getPrice()); //商品的单价
order.setNumber(1); //每次一个
orderService.createOrder(order);
log.info("创建订单成功,订单信息是{}",JSONObject.toJSONString(order));
return order;
}
}
JPA 工具包不能创建数据表名称是order的直接报错
order是mysql关键字,不能作为字段名/表名。
@Entity(name = "shop_order")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; //订单编号
private Integer uid; //客户编号
private String uname; //客户姓名
private Integer pid; //产品编号
private String pname; //产品名称
private Double pprice; //产品价格
}
public interface OrderService {
void createOrder(Order order); //创建订单
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Override
public void createOrder(Order order) {
orderDao.save(order); //JPA中的存储方法
}
}
@Repository
public interface OrderDao extends JpaRepository<Order,Long> {
}
服务地址写的太死了,一旦发生改变怎么处理?
这时候我们就需要Nacos组件
Nacos官网下载:https://github.com/alibaba/nacos/releases
账户/密码:nacos
下载 nacos-server-1.2.1.zip版本,最新版本的有配置问题
访问:http://192.168.43.170:8848/nacos/index.html
在商品shop-product/shop-order项目的pom.xml导入
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.cartergroupId>
<artifactId>shop-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
dependencies>
@SpringBootApplication
@EnableDiscoveryClient //开启nacos
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class,args);
}
}
#服务端口
server:
port: 8081
#spring的配置
spring:
application:
name: service-product
#数据库配置
datasource:
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&userSSL=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 自动创建数据库
jpa:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
#nacos服务地址配置
cloud:
nacos:
discovery:
server-addr: localhost:8848
其他服务同上
import com.alibaba.fastjson.JSONObject;
import com.carter.pojo.Order;
import com.carter.pojo.Product;
import com.carter.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@Slf4j
public class OrderController {
@Autowired
private RestTemplate restTemplate;//远程调用
@Autowired
private OrderService orderService;//业务层
@Autowired
private DiscoveryClient discoveryClient;//nacos的服务发现
@GetMapping("/order/product/{pid}") //下单
public Order order(@PathVariable("pid") Integer pid){
log.info("收到{}号商品下单请求,接下来调用微服务查询此商品信息",pid); //打印pid信息
List<ServiceInstance> instanceList = discoveryClient.getInstances("service-product"); //得到service-product服务集群
ServiceInstance instance = instanceList.get(0); //取到第一个实例
String host = instance.getHost(); //取地址
int port = instance.getPort(); //取到端口
Product product = restTemplate.getForObject("http://"+host+":"+port+"/product/"+pid,Product.class); //进行远程调用
log.info("查询到{}号商品的信息,内容是{}",pid, JSONObject.toJSONString(product)); //打印查询到的商品信息
Order order = new Order();//创建订单对象
order.setUid(2); //下单客户编号
order.setUname("测试用户"); //下单用户名称
order.setPid(pid);//商品编号
order.setPname(product.getName());//商品名称
order.setPprice(product.getPrice());//商品单价
orderService.createOrder(order);//创建订单
log.info("创建订单成功,订单信息是{}",JSONObject.toJSONString(order));//打印输出订单信息
return order;//返回到前端
}
}
问题:如果做了集群ServiceInstance instance = instanceList.get(0); //只能取到第一个?
解决方案:负载均衡
多次请求----第一次请求8080端口、第二次8081端口…
服务端的负载均衡:shop-product提供端,访问方法时进行分配 nginx
客户端的负载均衡:shop-order消费端,请求发送之前就定义好【一般使用】
增加好提供者后
import com.alibaba.fastjson.JSONObject;
import com.carter.pojo.Order;
import com.carter.pojo.Product;
import com.carter.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
@Slf4j
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderService orderService;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/order/product/{pid}") //下单---自定义负载均衡
public Order order(@PathVariable("pid") Integer pid){
log.info("收到{}号商品下单请求,接下来调用微服务查询此商品信息",pid); //打印pid信息
List<ServiceInstance> instanceList = discoveryClient.getInstances("service-product"); //得到service-product服务集群
//随机负载均衡实现
int index = new Random().nextInt(instanceList.size());//nacos中service-product的数量【随机数0-size】
ServiceInstance instance = instanceList.get(index); //取到一个随机的服务
String host = instance.getHost(); //取地址
int port = instance.getPort(); //取到端口
Product product = restTemplate.getForObject("http://"+host+":"+port+"/product/"+pid,Product.class); //进行远程调用
log.info("查询到{}号商品的信息,内容是{}",pid, JSONObject.toJSONString(product)); //打印查询到的商品信息
Order order = new Order();//创建订单对象
order.setUid(2); //下单客户编号
order.setUname("测试用户"); //下单用户名称
order.setPid(pid);//商品编号
order.setPname(product.getName());//商品名称
order.setPprice(product.getPrice());//商品单价
orderService.createOrder(order);//创建订单
log.info("创建订单成功,订单信息是{}",JSONObject.toJSONString(order));//打印输出订单信息
return order;//返回到前端
}
}
@SpringBootApplication
@EnableDiscoveryClient
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
@Bean
@LoadBalanced //Ribbon负载均衡实现的注解
public RestTemplate restTemplate(){
//远程连接
return new RestTemplate();
}
}
import com.alibaba.fastjson.JSONObject;
import com.carter.pojo.Order;
import com.carter.pojo.Product;
import com.carter.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@Slf4j
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderService orderService;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/order/product/{pid}") //下单---Ribbon负载均衡
public Order order(@PathVariable("pid") Integer pid){
log.info("收到{}号商品下单请求,接下来调用微服务查询此商品信息",pid); //打印pid信息
//Ribbon负载均衡实现
Product product = restTemplate.getForObject("http://service-product/product/"+pid,Product.class); //进行远程调用
log.info("查询到{}号商品的信息,内容是{}",pid, JSONObject.toJSONString(product)); //打印查询到的商品信息
Order order = new Order();//创建订单对象
order.setUid(2); //下单客户编号
order.setUname("测试用户"); //下单用户名称
order.setPid(pid);//商品编号
order.setPname(product.getName());//商品名称
order.setPprice(product.getPrice());//商品单价
orderService.createOrder(order);//创建订单
log.info("创建订单成功,订单信息是{}",JSONObject.toJSONString(order));//打印输出订单信息
return order;//返回到前端
}
}
Ribbon负载均衡—>默认是循环调用
策略类 | 命名 | 说明 |
---|---|---|
RandomRule | 随机策略 | 随机选择 Server |
RoundRobinRule | 轮训策略 | 按顺序循环选择 Server(默认) |
RetryRule | 重试策略 | 在一个配置时问段内当选择 Server 不成功,则一直尝试选择一个可用的 Server |
BestAvailableRule | 最低并发策略 | 逐个考察 Server,如果 Server 断路器打开,则忽略,再选择其中并发连接最低的 Server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直连接失败并被标记为 circuit tripped 的 Server,过滤掉那些高并发连接的 Server(active connections 超过配置的网值) |
ResponseTimeWeightedRule | 响应时间加权策略 | 根据 Server 的响应时间分配权重。响应时间越长,权重越低,被选择到的概率就越低;响应时间越短,权重越高,被选择到的概率就越高。这个策略很贴切,综合了各种因素,如:网络、磁盘、IO等,这些因素直接影响着响应时间 |
ZoneAvoidanceRule | 区域权衡策略 | 综合判断 Server 所在区域的性能和 Server 的可用性轮询选择 Server,并且判定一个 AWS Zone 的运行性能是否可用,剔除不可用的 Zone 中的所有 Server |
Ribbon负载均衡调用方式配置
server:
port: 8091
spring:
application:
name: service-order
datasource:
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&userSSL=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 自动创建数据库
jpa:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
cloud:
nacos:
discovery:
server-addr: localhost:8848
### 针对单个服务的 Ribbon 配置
service-product:
ribbon:
# 基于配置文件形式的 针对单个服务的 Ribbon 负载均衡策略
# RoundRobinRule 轮训策略 RandomRule 随机策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule //RetryRule
Product product = restTemplate.getForObject("http://service-product/product/"+pid,Product.class);
解决方案:Fegin组件
shop-order添加Fegin依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.cartergroupId>
<artifactId>shop-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
dependencies>
@SpringBootApplication //开启springboot微服务功能
@EnableDiscoveryClient //开启nacos服务注册功能
@EnableFeignClients //开启Fegin服务调用功能
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
创建一个ProductService借口
import com.carter.pojo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "service-product")//指定调用nacos下的维服务
public interface ProductService {
@GetMapping("/product/{pid}")//指定请求的url部分
Product findById(@PathVariable("pid") Integer id);
@PostMapping("/product/addProduct")//传对象+@RequestBody+@PostMapping
Boolean addProduct(@RequestBody final Product product, @RequestParam("roleId") Integer roleId);
}
远程Server
@PostMapping("/product/addProduct")//对象+@RequestBody
public Boolean addProduct(@RequestBody Product product, Integer roleId){
return productService.addProduct(product,roleId);
}
@RestController
@Slf4j
public class OrderController {
@Autowired
private RestTemplate restTemplate;//远程调用
@Autowired
private OrderService orderService;//订单生成业务
@Autowired
private DiscoveryClient discoveryClient;//nacos中的服务调用
@Autowired
private ProductService productService;//Fegin使用的本地Service接口
@GetMapping("/order/product/{pid}") //下单---Ribbon负载均衡
public Order order(@PathVariable("pid") Integer pid){
log.info("收到{}号商品下单请求,接下来调用微服务查询此商品信息",pid); //打印pid信息
Product product = productService.findById(pid);
log.info("查询到{}号商品的信息,内容是{}",pid, JSONObject.toJSONString(product)); //打印查询到的商品信息
Order order = new Order();//创建订单对象
order.setUid(2); //下单客户编号
order.setUname("测试用户"); //下单用户名称
order.setPid(pid);//商品编号
order.setPname(product.getName());//商品名称
order.setPprice(product.getPrice());//商品单价
orderService.createOrder(order);//创建订单
log.info("创建订单成功,订单信息是{}",JSONObject.toJSONString(order));//打印输出订单信息
return order;//返回到前端
}
}
#第一次调用超时处理
feign:
client:
config:
default:
connectTimeout: 10000 #毫秒
readTimeout: 10000 #毫秒【只需要这一个】
解决方案:服务容错Sentinel
整个微服务瘫痪
黑马-高并发模拟-视频教学
由于order囤积了大量的请求,这导致其它访问很慢—>服务雪崩问题雏形
服务出现问题—>请求阻塞—>服务崩溃
常见的容错思路:
Sentinel是阿里巴巴一款开源的断路器实现,非常稳定!!!
上游微服务容错order中导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.cartergroupId>
<artifactId>shop-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
dependencies>
OrderController测试类
@RestController
@Slf4j
public class OrderController1 {
@GetMapping("/order/message1")
public String message1(){
return "message1";
}
@GetMapping("/order/message2")
public String message2(){
return "message2";
}
}
Sentinel控制台下载地址
https://github.com/alibaba/Sentinel/releases
控制台启动命令
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
登录 http://localhost:8080/ 密码:sentinel
server:
port: 8091
spring:
application:
name: service-order
datasource:
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&userSSL=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 自动创建数据库
jpa:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
cloud:
nacos:
discovery:
server-addr: localhost:8848
#sentinel服务容错配置
sentinel:
transport:
port: 8719 #随便一个端口
dashboard: localhost:8080 #指定控制台服务地址
### 针对单个服务的 Ribbon 配置
service-product:
ribbon:
# 基于配置文件形式的 针对单个服务的 Ribbon 负载均衡策略
# RoundRobinRule 轮训策略 RandomRule 随机策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
请求不规则问题【第1s:1个,第2s:10个,…】
漏斗:每次最多只能3个
·需要升级springcloud Alibaba的版本 2.1.1.RELEASE
filter.enable=false
注意:Sentinel 1.6.3版本以后,Sentinel Web过滤器默认收敛所有URL的入口上下文,因此互连限流不生效。1.7.0和1.7.1版本可以使用配置类的方式关闭URL PATH聚合,示例如下:
热点限流 —根据参数限流
授权规则 A访问都放行 B访问都不放行
授权规则的配置(测试:所有带有serviceName的才能进行访问)
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
feign:
sentinel:
enabled: true
sentinel:
transport:
port: 9999
dashboard: localhost:8080 #Sentinel控制台启动的端口
问题:不知道发生了什么异常 fallback可以将异常信息也告诉我吗?
使用即可
1:客户端需要维护服务端的各个地址 代码困难
2:认证 鉴权复杂
3:跨域问题
解决方案:网关
API网关:系统的统一入口,为客户端提供统一服务:认证、鉴权、监控、路由转发等等.
从nacos中发现,网关也是一个微服务
springboot必须2.0以上
第1步:创建一个api-gateway的模块,导入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
dependencies>
第2步:创建主类
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class,args);
}
}
第3步:添加配置文件
aplication.yaml
#设置网关
server:
port: 7000
#服务网关的名称
spring:
application:
name: api-gateway
#配置路由数组
cloud:
gateway:
routes: #路由数组 【满足条件--转发到微服务】
- id: user_route #当前路由的标识,要求唯一,默认是UUID产生的
uri: http://localhost:8078/ #请求最终要被转发的地址,uri加/表示8078端口有其它方法
order: 1 #路由的优先级 数字小,优先级高
predicates: #断言(条件判断,返回的是boolean 转发请求要满足的条件)
- Path=/user-serv/** #当请求路径满足Path指定的规则时,此路由信息才会正常转发
filters: #过滤器请求地址(在请求传递过程中,对请求做一些手脚)
- StripPrefix=1 #在请求转发之前,去掉一层路径
第4步:启动项目
问题:uri: http://localhost:8078 【不能写死】
1:uri写死
2:负载均衡未设置
第1步:网关中加入nacos依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
dependencies>
第2步:网关中加入注解
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class,args);
}
}
第3步:修改配置文件
#设置网关
server:
port: 7000
#服务网关的名称
spring:
application:
name: api-gateway
cloud:
#将gateway注册到nacos中
nacos:
discovery:
server-addr: localhost:8848
#配置路由数组
gateway:
discovery:
locator:
enabled: true #让gateway在nacos中获取服务中心 不然404错误
routes: #路由数组 【满足条件--转发到微服务】
- id: user_route #当前路由的标识,要求唯一,默认是UUID产生的
uri: lb://user-service/ #lb:负载均衡,后面跟的是具体微服务的标识,加/表示后面还有方法
order: 1 #路由的优先级 数字小,优先级高
predicates: #断言(条件判断,返回的是boolean 转发请求要满足的条件)
- Path=/user-serv/**,/static/** #当请求路径满足Path指定的规则时,此路由信息才会正常转发+放行静态资源
filters: #过滤器请求地址(在请求传递过程中,对请求做一些手脚)
- StripPrefix=1 #在请求转发之前,去掉一层路径
uri后面必须加上/,表示该负载均衡的下的服务还有许多接口方法
注意:uri后面最好加 “/ 斜杠”
第4步:测试
问题:点击另外的连接需要重新输入网址
统一鉴权案例
package com.carter.filters;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
//自定义的全局过滤器(作用是:统一鉴权)
@Slf4j
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
//过滤器逻辑
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//统一鉴权逻辑编写 网址参数必须有token=admin 否则401没有权限
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (!StringUtils.equals("admin",token)){
//认证失败
log.info("认证失败了...");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
//标识当前过滤器的优先级
@Override
public int getOrder() {
return 0;
}
}
java代码1
package com.carter.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
//跨域问题
@Configuration
public class GatewayConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
1、RedirectTo GatewayFilter Factory
该过滤器有一个 status 和一个 url参数。status是300类重定向HTTP代码,如301。该URL应为有效的URL,这将是 Location header的值。
2、RewritePath GatewayFilter Factory
包含一个 regexp
正则表达式参数和一个 replacement
参数. 通过使用Java正则表达式灵活地重写请求路径。
#设置网关
server:
port: 7000
#服务网关的名称
spring:
application:
name: api-gateway
cloud:
#将gateway注册到nacos中
nacos:
discovery:
server-addr: localhost:8848
#配置路由数组
gateway:
discovery:
locator:
enabled: true #让gateway在nacos中获取服务中心
routes: #路由数组 【满足条件--转发到微服务】
- id: starter_route #当前路由的标识,要求唯一,默认是UUID产生的
order: 1 #路由的优先级 数字小,优先级高
uri: lb://starter-service/ #后加个/表示负载均衡的starter-service后面还有其他的方法
filters:
- PreserveHostHeader #发送网关原始主机头---解决重定向问题
predicates: #断言(条件判断,返回的是boolean 转发请求要满足的条件)
- Path=/** #放行所有路径
#开启日志打印
logging:
pattern:
#控制台输出格式
console: "%d 【%p】 %msg%n"
#日志文件输出格式
file: "%d 【%p】 %msg%n"
#日志输出的路径
path: C:\Users\海角天涯S\Desktop\springcloud-shop
#日志的名字
file: C:\Users\海角天涯S\Desktop\springcloud-shop\gateway.log
【全局过滤器配置】
#设置网关
server:
port: 7000
#服务网关的名称
spring:
application:
name: api-gateway
cloud:
#将gateway注册到nacos中
nacos:
discovery:
server-addr: localhost:8848
#配置路由数组
gateway:
default-filters:
- PreserveHostHeader #发送网关原始主机头---解决重定向问题 【全局配置】
discovery:
locator:
enabled: true #让gateway在nacos中获取服务中心
routes: #路由数组 【满足条件--转发到微服务】
- id: starter_route #当前路由的标识,要求唯一,默认是UUID产生的
order: 1 #路由的优先级 数字小,优先级高
uri: lb://starter-service/ #后加个/表示负载均衡的starter-service后面还有其他的方法
predicates: #断言(条件判断,返回的是boolean 转发请求要满足的条件)
- Path=/** #放行所有路径
#开启日志打印
logging:
pattern:
#控制台输出格式
console: "%d 【%p】 %msg%n"
#日志文件输出格式
file: "%d 【%p】 %msg%n"
#日志输出的路径
path: C:\Users\海角天涯S\Desktop\springcloud-shop
#日志的名字
file: C:\Users\海角天涯S\Desktop\springcloud-shop\gateway.log
1:配置文件相对分散
作用:用于进行统一的鉴权
配置文件相对分散
配置文件无法区分环境
配置文件无法实时更新
1:搭建nacos环境
启动nacos-starter.cmd,打开网页版—研究配置管理中的功能
2:在微服务中引用nacos配置中心的依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
3:在配置中心设置和微服务bootstrap.yaml的使用
spring:
application:
name: user-service
cloud:
nacos:
config:
file-extension: yaml
server-addr: localhost:8848
profiles:
active: dev
注意:不能如果用mysql,please 屏蔽mybatis-spring-boot-starter的pom依赖,,否则会报错url
将nacos放置到数据库中
修改nacos中的application.properties
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=root
全局事务基于DTP模型实现 的。DTP是由X/Open组织提出的
一种分布式事务模型X/Open Distributed Transaction Processing Reference Model。他规定实现分布式事务,需要三种角色:
第一步:预提交–都OK
第二步:执行 回滚
降低分布式事务出现的概率
优点
缺点
使用 消息中间件【MQ】保证上、下游数据的一致性
try confirm Canncel—>后者认为前者一定成功,失败人工解决
2019年1月阿里巴巴的开源项目Fescar---->seata简单+高效
Seata主要由3个重要组件组成:
Seata实现2PC与传统的2PC的差别:
安装Seata
下载地址 :https://github.com/seata/seata/releases
修改配置
seata.config
启动
seata-server.bat -p 9000 -m file
添加表undo_log
微服务
<dependence>
<groupId>com.alibaba.cloud<groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependence>
代理数据源DataSourceProxyConfig
添加一系列配置
如何定位耗时久的微服务
分布式链路追踪技术(Distributed Tracing),就是将一个分布式请求还原成调用链路,进行日志记录,性能监控
并将一次分布式请求的调用情况集中展示,比如各个服务节点上的耗时 具体请求到哪 每个服务节点请求的状态等等。
链路追踪技术
spring alibaba 链路追踪—>Sleuth链路+zipkin可视化工具
spring cloud Sleuth主要提供链路追踪解决方案
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-sleuthartifactId>
dependency>
启动
【微服务名 TraceId SpanId 是否将链路追踪的结果输出到第三方平台】
Zipkin是Twitter的一个开源项目,基于Google Dapper实现
下载地址
https://search.maven.org/search?q=g:org.apache.zipkin
http://localhost:9941/
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
spring:
zipkin:
base-url: http://localhost:9941/ #Zikpin的服务端地址
discovery-client-enabled: false #让nacos把它当成一个url,而不是服务名
sleuth:
sampler:
probability: 1.0 #采样的百分比 0.0~0.1就行
OAuth2.0支持不注册----微信、QQ登录
客户端认证+用户本身认证
Spring Security Oauth2是对OAuth2的一种实现,与spring security相辅相成
AuthorizationEndpoint 服务于认证请求,默认URL: /oauth/authorize 【验证用户】
TokenEndpoint服务于访问令牌的请求,默认URL: /oauth/token【是否有权限】
OAuth2AuthenticationProcessingFilter:校验token[令牌]的合法性
org.springframework.cloud
spring-cloud-starter-security
org.springframework.cloud
spring-cloud-starter-oauth2
server:
port: ${
PORT:40400}
servlet:
context-path: /auth
spring:
application:
name: xc-service-ucenter-auth
redis:
host: ${
REDIS_HOST:127.0.0.1}
port: ${
REDIS_PORT:6379}
timeout: 5000 #连接超时 毫秒
jedis:
pool:
maxActive: 3
maxIdle: 3
minIdle: 1
maxWait: -1 #连接池最大等行时间 -1没有限制
datasource:
druid:
url: ${
MYSQL_URL:jdbc:mysql://localhost:3306/xc_user?characterEncoding=utf-8}
username: root
password: mysql
driverClassName: com.mysql.jdbc.Driver
initialSize: 5 #初始建立连接数量
minIdle: 5 #最小连接数量
maxActive: 20 #最大连接数量
maxWait: 10000 #获取连接最大等待时间,毫秒
testOnBorrow: true #申请连接时检测连接是否有效
testOnReturn: false #归还连接时检测连接是否有效
timeBetweenEvictionRunsMillis: 60000 #配置间隔检测连接是否有效的时间(单位是毫秒)
minEvictableIdleTimeMillis: 300000 #连接在连接池的最小生存时间(毫秒)
auth:
tokenValiditySeconds: 1200 #token存储到redis的过期时间
clientId: XcWebApp
clientSecret: XcWebApp
cookieDomain: java.itcast.cn
cookieMaxAge: -1
encrypt:
key-store:
location: classpath:/xc.keystore
secret: xuechengkeystore
alias: xckey
password: xuecheng
eureka:
client:
registerWithEureka: true #服务注册开关
fetchRegistry: true #服务发现开关
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔
defaultZone: ${
EUREKA_SERVER:http://localhost:50101/eureka/,http://localhost:50102/eureka/}
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: ${
IP_ADDRESS:127.0.0.1}
instance-id: ${
spring.application.name}:${
server.port} #指定实例id
ribbon:
MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试,如果eureka中找不到服务则直接走断路器
MaxAutoRetriesNextServer: 3 #切换实例的重试次数
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 6000 #请求处理的超时时间
然后启动测试
用于第三方接口接入
将publicKey.txt放入微服务的resources下
添加oauth2依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
创建ResourceServerConfig类
package com.xuecheng.manage_course.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
/**
* @author Administrator
* @version 1.0
**/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY = "publickey.txt";
//定义JwtTokenStore,使用jwt令牌
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
//定义JJwtAccessTokenConverter,使用jwt令牌
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密公钥 Key
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
//Http安全配置,对每个到达系统的http请求链接进行校验
@Override
public void configure(HttpSecurity http) throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
"/swagger-resources","/swagger-resources/configuration/security",
"/swagger-ui.html","/webjars/**").permitAll()
.anyRequest().authenticated();
}
}
用于用户登录
令牌刷新---->用于后台自动刷新令牌(提高用户体验)
传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性,并根
据令牌获取用户的相关信息,性能低下。
解决:使用JWT
JWT令牌的优点:
1、jwt基于json,非常方便解析。
2、可以在令牌中自定义丰富的内容,易扩展。
3、通过非对称加密算法及数字签名技术,JWT防止篡改
4、资源服务使用JWT可不依赖认证服务即可完成授
缺点:
ue, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY = "publickey.txt";
//定义JwtTokenStore,使用jwt令牌
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
//定义JJwtAccessTokenConverter,使用jwt令牌
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密公钥 Key
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
//Http安全配置,对每个到达系统的http请求链接进行校验
@Override
public void configure(HttpSecurity http) throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
"/swagger-resources","/swagger-resources/configuration/security",
"/swagger-ui.html","/webjars/**").permitAll()
.anyRequest().authenticated();
}
}
### 3、密码模式授权
- 用于用户登录
- 令牌刷新---->用于后台自动刷新令牌(提高用户体验)
传统授权方法的问题是用户**每次**请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的**合法性**,并根
据令牌获取用户的相关信息,**性能低下**。
> 解决:使用JWT
### 4、JWT令牌介绍
JWT令牌的优点:
1、jwt基于json,非常方便解析。
2、可以在令牌中自定义丰富的内容,易扩展。
3、通过非对称加密算法及数字签名技术,JWT防止篡改
4、资源服务使用JWT可不依赖认证服务即可完成授
缺点:
1、JWT令牌较长,占存储空间比较大。