模块化 功能化
用户 支付 签到 娱乐 …
人越来越多 一台服务器解决不了 在增加服务器 横向
假如 a 服务器 占用 98% 资源 b 服务器 只占用 10% 负载均衡
将原来的整体项目 分成模块化 用户就是一个单独的项目 签到也是一个单独的项目 项目和项目之间需要通信 怎么样通信
用户十分多 而签到少 给用户多一个服务 给签到一点服务
微服务架构问题:
四个核心问题:
Spring Cloud :是一套生态 就是用来解决分布式架构的四个问题
Spring Cloud 是基于 spring Boot 的
Api 网关 zuul 组件
Feign – 》 httpClient — 》 http 通信方式 同步并且阻塞
服务注册于发现 Eureka
熔断机制 Hystrix
2108 年 netFlix 宣布无限期停止维护 生态不在维护 就会脱节
Api : 没有 要么使用第三方组件 要么自己实现
Dubbo 是一个高性能的基于java实现的 RPC 通信框架
服务注册与发现 zookeeper :(动物管理者) (hadoop hive)
熔断机制: 没有 借助了 Hystrix
spring Cloud Alibaba 一站式解决
服务网格 Server Mesh
万变不离其宗 一通百通!
一切的问题 都是由于 网咯不可靠
微服务条目 | 技术 |
---|---|
服务开发 | SpringBoot Spring SpringMVC |
服务配置与管理 | Netflix 公司的 Archaius , 阿里的 Diamond |
服务注册与发现 | Eureka, Consul, Zookeeper |
服务调用 | Rest , RPC , gRPC |
服务熔断器 | Hystrix,Envoy |
负载均衡 | Ribbon, Nginx |
服务接口调用(客户端调用服务的简单工具) | Feign |
消息队列 | Kafka, RabbitMQ, ActiveMQ |
服务配置中心管理 | SpringCloudConfig, Chef |
服务路由(API 网关) | Zuul |
服务监控 | Zabbix , Nagios, Metrics, pecator |
全链路追踪 | Zipkin, Brave , apper |
服务部署 | Docker,OpenStack, Kubernetes |
数据流操作开发包 | Spring Cloud Stram(封装 Redis Rabbit kafka 等发送接收消息) |
事件消息总线 | SpringCloud Bus |
以下都是创建的 maven 项目
pom.xml
<packaging>pompackaging>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<junit.version>4.13.2junit.version>
<lombok.version>1.18.22lombok.version>
<log4j.service>1.2.17log4j.service>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR12version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.3.12.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.27version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.6version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
<version>1.2.6version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.service}version>
dependency>
dependencies>
dependencyManagement>
pom.xml
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
数据库: db01
#数据库存在则删除
drop database if exists db01;
#创建数据库
create database db01;
#设置编码
alter database db01 character set utf8;
use db01;
DROP TABLE IF EXISTS dept;
CREATE TABLE dept
(
deptno int primary key auto_increment,
dname VARCHAR(50) not null,
db_source VARCHAR(50) not NULL
);
INSERT into dept (dname,db_source) VALUES('开发部',DATABASE());
INSERT into dept (dname,db_source) VALUES('人事部',DATABASE());
INSERT into dept (dname,db_source) VALUES('财务部',DATABASE());
INSERT into dept (dname,db_source) VALUES('市场部',DATABASE());
INSERT into dept (dname,db_source) VALUES('运维部',DATABASE());
SELECT * from dept;
Dept
package com.zhang.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@NoArgsConstructor
@Accessors(chain = true) // 链式写法
public class Dept implements Serializable {
private int deptno;
private String dname;
private String db_source; //指定当前属于哪个数据库
public Dept(String dname) {
this.dname = dname;
}
/**
* 链式写法
* Dept dept = new Dept();
* dept.setDeptno(1).setDname("xiaohuihui").setDb_source("001");
*/
}
pom.xml
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>SpringCloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-testartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
dependencies>
application.yml
server:
port: 8001
# mybatis 配置
mybatis:
type-aliases-package: com.zhang.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
# Spring 配置
spring:
application:
name: SpringCloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
mybatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
configuration>
DeptMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhang.mapper.DeptMapper">
<insert id="addDept" parameterType="Dept">
insert into dept (dname,db_source)
values (#{dname},DATABASE());
insert>
<select id="queryDeptById" resultType="Dept" parameterType="int">
select *
from dept where deptno = #{deptno};
select>
<select id="queryAll" resultType="Dept">
select * from dept;
select>
mapper>
DeptMapper
@Mapper
@Repository
public interface DeptMapper {
public boolean addDept(Dept dept);
public Dept queryDeptById(int id);
public List<Dept> queryAll();
}
DeptController
// 提供 restFul 服务
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@PostMapping("/dept/add")
public boolean addDept(@RequestBody Dept dept){
return deptService.addDept(dept);
}
@GetMapping("/dept/selById/{id}")
public Dept queryDeptById(@PathVariable("id") int id){
return deptService.queryDeptById(id);
}
@GetMapping("/dept/query")
public List<Dept> queryAll(){
return deptService.queryAll();
}
}
DeptProvider_8001
// 主启动类
@SpringBootApplication
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
pom.xml
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>SpringCloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
dependencies>
application.yml
server:
port: 80
ConfigBean
@Configuration
public class ConfigBean {
/**
* 将 RestTemplate 注册到 bean 中
* @return
*/
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
DeptConsumerController
@RestController
public class DeptConsumerController {
// 消费者 不应该有 service 层
// 可以通过 RestFul 调用远程服务 (通过 url)
// RestTemplate 模板供我们直接调用 注册到spring 中
@Autowired
private RestTemplate restTemplate; // 提供多种便捷访问远程 http 服务的方法 简单的restFul 服务模板
// 远程请求的地址 前缀
private static final String REST_URL_PREFIX = "http://localhost:8001/";
/**
* 通过 ID 查询部门
* @param id
* @return
*/
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") int id){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/selById"+id,Dept.class);
}
/**
* 添加一个部门
* @param dept
* @return
*/
@RequestMapping("/consumer/dept/add")
public Boolean add(Dept dept){
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
}
/**
* 查询所有部门
* @return
*/
@RequestMapping("/consumer/dept/list")
public List<Dept> list(){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/query",List.class);
}
}
DeptConsumer_80
// 主启动类
@SpringBootApplication
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class,args);
}
}
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eureka-serverartifactId>
<version>1.4.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
dependencies>
application.yml
server:
port: 7001
# eureka 配置
eureka:
instance:
hostname: localhost # eureka 服务端的实例名称
client:
register-with-eureka: false # 表示是否向 eureka 注册中心注册自己
fetch-registry: false # false : 表示 自己是注册中心
service-url: # 监控中心的注册地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
Eureka_7001
// 主启动类
@SpringBootApplication
@EnableEurekaServer // EnableEurekaServer 服务端的启动类 可以接收别人注册进来
public class Eureka_7001 {
public static void main(String[] args) {
SpringApplication.run(Eureka_7001.class,args);
}
}
启动 访问 :http://localhost:7001/
pom 依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
application.yml
# eureka 配置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: SpringCloud-provider-dept-8001 # 修改 eureka 上默认描述信息
# info
info:
app.name: xiaotao-springcloud
company.name: www.xiaotao.cloud
在主启动类 添加
@EnableEurekaClient // 在服务启动后自动注册到 Eureka 中
先启动 7001 在启动 8001
点击 SpringCloud-provider-dept-8001 可以看到当前的一些配置信息
当 8001 因为某种原因突然停止 eureka 会通过保护机制 监控 当30秒还没有监控到 8001 的心跳 就会报如下 红
但是 8001 服务依然存在 等8001 启动成功 eureka 恢复正常
可以避免一些信息的丢失
多人开发时 有很多服务 方便开发 与管理
在 8001 获取
DeptController 添加
// 获取一些配置信息 得到具体的 微服务
@Autowired
private DiscoveryClient client;
/**
* 获取注册进来的微服务的一些信息
* @return
*/
@GetMapping("/dept/discovery")
public Object discovery(){
// 获取 微服务 清单
List<String> services = client.getServices();
System.out.println("discovery=>services:"+services);
// 得到具体的微服务信息 通过具体的微服务 id:applicationName
List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println(
instance.getHost()+"\t"+
instance.getServiceId()+"\t"+
instance.getPort()+"\t"+
instance.getUri()
);
}
return this.client;
}
在主启动类添加
@EnableDiscoveryClient // 服务发现
启动访问:
http://localhost:8001/dept/discovery
在控制台 可以看到输出的配置信息
在搭建两个注册中心 形成 集群
7002 7003 注册中心 跟 7001 一样 只需要修改 对应端口号即可
启动访问
说明搭建成功
现在三个注册中心 是单独的 我们需要使他们成为一个整体
域名映射 这样 我们访问 eureka7001.com eureka7002.com eureka7003.com 都代表 127.0.0.1
这里 只是为了让我们能够方便识别 假装在 三个服务器上 方便理解
修改上面三个配置文件 yml
server:
port: 7001
# eureka 配置
eureka:
instance:
hostname: eureka7001.com # eureka 服务端的实例名称
client:
register-with-eureka: false # 表示是否向 eureka 注册中心注册自己
fetch-registry: false # false : 表示 自己是注册中心
service-url: # 监控中心的注册地址
# 单机
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群 (关联)
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
server:
port: 7002
# eureka 配置
eureka:
instance:
hostname: eureka7002.com # eureka 服务端的实例名称
client:
register-with-eureka: false # 表示是否向 eureka 注册中心注册自己
fetch-registry: false # false : 表示 自己是注册中心
service-url: # 监控中心的注册地址
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
其他 同理 只要修改 defaultZone 即可
将 8001 发布在集群上 之前是在 单机上
修改 8001 application.yml
# eureka 配置
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: SpringCloud-provider-dept-8001 # 修改 eureka 上默认描述信息
启动
7001 7002 7003 8001
访问 7003
现在假如 7002 挂了
但是 7001 7003 仍然在运行 不会对 数据产生影响
RDBMS (Mysql Oracle sqlServer) ===》 ACID
NoSQL (redis mongDB) ===》 CAP
ACID:
CAP:
CAP 的三进二: CA AP CP
CPA 理论核心
著名的 ACP 理论指出,一个分布式系统不能同时满足C(一致性) A(可用性) P(分区容错性)
由于分区容错性 p 在分布式系统中是必须要保证的 因此只能在A和C之间进行权衡
zookeeper 保证的是 CP;
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟前的注册信息 但不能接受服务直接down掉不可用。也就是说 服务注册功能对可用性的要求高于一致性 但是zookeeper 会出现这样一种情况 当master节点因为网络故障与其他节点失去联系时 剩余节点会重新进行 leader 选举 问题在于 leader 选举 的时间过长 30~120s 且选举期间整个zookeeper 集群都是不可用的 这就导致在选举期间注册服务瘫痪 在云部署的环境下 因为网络问题使得zookeeper 集群失去maoter 节点是较大概率会发生的事件 虽然服务最终会恢复 但在漫长的选举时间导致注册长期不可用是不能容忍的
eureka 保证的是AP;
eureka 在设计时优先保证可用性 eureka 的每个节点都是平等的 几个节点挂掉都不会影响正常节点的工作 剩余的节点依旧可以提供注册和查询服务 eureka 只要有一个节点还在 就能保证注册服务的可用性 只是查询到的数据可能不是最新的
除此之外 eureka 还有一种自我保护机制 如果在 15分钟内超过85%的节点都没有正常工作 那么 Eureka 就认为客户端与注册中心出现了网络故障 可能会出现:
因此 eureka 可以很好的应对网络故障导致部分节点失去联系的情况 而不会像 zookeeper 那样使整个注册服务瘫痪
什么是 ribbon?
ribbon 能干嘛?
db02 db03
复制 SpringCloud-provider-dept-8001
application.yml
server:
port: 8002
# mybatis 配置
mybatis:
type-aliases-package: com.zhang.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
# Spring 配置
spring:
application:
name: SpringCloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
# eureka 配置
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: SpringCloud-provider-dept-8002 # 修改 eureka 上默认描述信息
# info
info:
app.name: xiaotao-springcloud
company.name: www.xiaotao.cloud
@SpringBootApplication
@EnableEurekaClient // 在服务启动后自动注册到 Eureka 中
@EnableDiscoveryClient // 服务发现
public class DeptProvider_8002 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8002.class,args);
}
}
同理
SpringCloud-consumer-dept-80
导入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
<version>1.4.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.7.RELEASEversion>
dependency>
报错 : 原因 service 写错
No instances available for SPRINGCLOUD-PROVIDER-DEPT
每次查询都会从不同的服务器查询 采用轮循的方式
查询方式:
IRule : 负载均衡的接口
AvailabilityFilteringRule:会先过滤掉 跳闸 访问故障的服务 对剩下的进行轮循
RoundRobinRule:轮循 默认
RandomRule: 随机
RetryRule:会先按照轮循获取服务 如果服务获取失败 则会在指定的时间内进行重试
在 80 ConfigBean 添加
/**
* 使用随机算法进行获取
* @return
*/
@Bean
public IRule myIRule(){
return new RandomRule();
}
不要和主启动类在同一级下
MyRule
public class MyRule extends AbstractLoadBalancerRule {
// 每个服务 访问5次 换下一个服务 总共3个服务
private int total = 0; //被调用的次数
private int currentIndex = 0; // 当前是谁在提供服务
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers(); // 获取活着的服务
List<Server> allList = lb.getAllServers(); // 获取所有的服务
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
/*int index = chooseRandomInt(serverCount); // 生成区间随机数
server = upList.get(index); // 从活着的服务中 随机获取一个*/
// ============================================================
if(total<5){
server = upList.get(currentIndex); // 从活着的服务中获取指定的服务
total ++;
}else {
total = 0;
currentIndex ++;
if(currentIndex > upList.size()){ // 如果当前服务大于活着的服务
currentIndex = 0;
}
server = upList.get(currentIndex);
}
// ============================================================
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
TaoRule
@Configuration
public class TaoRule {
@Bean
public IRule myIRule(){
return new MyRule(); //使用自定义的策略
}
}
主启动类
// 在微服务启动时就能加载我们自定义的ribbon类
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = MyRule.class)
feign 是声明的wer service 客户端 它让微服务之间的调用变的更简单 类似于controller 调用 service SpringCloud 集成了Ribbon 和Eureka 可在使用 Feign 时提供负载均衡的http 客户端
只要创建一个接口添加注解即可
利用Ribbon维护了 MicroServiceCloud-Dept 的服务列表信息 并且通过轮循实现了客户端的负载均衡 而与 Ribbon 不同的是 通过 Feign 只需要定义定义服务绑定接口且以声明式的方式 实现了服务调用
导入pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
<version>1.4.7.RELEASEversion>
dependency>
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") int id);
@GetMapping("/dept/list")
public List<Dept> queryAll();
@PostMapping("/dept/add")
public Boolean addDept(Dept dept);
}
复制:
SpringCloud-consumer-dept-80
DeptConsumerController
@RestController
public class DeptConsumerController {
@Autowired
public DeptClientService deptClientService = null;
/**
* 通过 ID 查询部门
* @param id
* @return
*/
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") int id){
return deptClientService.queryById(id);
}
/**
* 添加一个部门
* @param dept
* @return
*/
@RequestMapping("/consumer/dept/add")
public Boolean add(Dept dept){
return deptClientService.addDept(dept);
}
/**
* 查询所有部门
* @return
*/
@RequestMapping("/consumer/dept/list")
public List<Dept> list(){
return deptClientService.queryAll();
}
}
FeginDeptConsumer_80
// ribbon 和 Eureka 整合后 客户端可以直接调用 不用关心IP地址及端口号
// 主启动类
@SpringBootApplication
@EnableEurekaClient // 在服务启动后自动注册到 Eureka 中
@EnableFeignClients(basePackages = "com.zhang")
public class FeginDeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(FeginDeptConsumer_80.class,args);
}
}