一、微服务
1.1 什么是微服务
- 微服务化的核心是将传统的一站式应用,根据业务拆分成一个一个服务,彻底的祛耦合,每一个服务提供单个业务功能的服务,一个服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程的概念,能够自行单独启动或者销毁,拥有自己独立的数据库
1.2 SpringCloud
- SpringCloud,基于SpringBoot提供的一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,出了基于NetFix的开源组件做高度抽象封装外,还有一些选型中立的开源组件。
- SpirngCloud利用SpringBoot的开发便利性,巧妙简化了分布式系统基础设施开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等,他们都可以用SpringBoot的开发风格做到一键启动和部署。
- SpringBoot并没有重复造轮子,它只是将目前各家公司开发的比较成熟,经得起实际考研的服务框架组合起来,通过SpringBoot风格进行再封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
- SpringCloud是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶。
1.3 分布式+服务治理Dubbo
二、SpringCloud项目
2.1 新建maven工程
- 删除src文件
- 配置pom
<!-- 打包方式pom -->
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<!-- spingcloud依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 打包插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
- 在当前项目下创建maven scloud-api module
- 创建数据库表
CREATE TABLE `dept` (
`deptno` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`dname` VARCHAR(60) ,
`db_source` VARCHAR(60)
)
COLLATE='utf8_general_ci'
;
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());
- 创建实体类
@Data
@NoArgsConstructor
@Accessors(chain = true) //链式写法
public class Dept implements Serializable{
private Long deptno;
private String dname;
private String dbSource;
public Dept(String dname) {
this.dname = dname;
}
/*
* 链式写法:
* Dept dept = new Dept();
* dept.setDeptno(11).setDname('sss').setDbSource('001');
*/
}
- 在当前项目下创建scloud-provider-dept-8001 module
- 添加依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- 需要拿到实体类,配置api module -->
<dependency>
<groupId>com.company</groupId>
<artifactId>scloud-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
- 配置application.yml
server:
port: 8001
#mybatis配置
mybatis:
type-aliases-package: com.scloud.api.pojo
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
#spring配置
spring:
application:
name: scloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/cloud01?serverTimezone=UTC&characterEncoding=utf-8
username: root
password: root
- 添加对dept表的操作
- 控制器相关
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptService deptService;
@PostMapping("/add")
public boolean addDept(Dept dept) {
return deptService.addDept(dept);
}
@GetMapping("/get/{id}")
public Dept getDeptById(@PathVariable("id")Long deptno) {
return deptService.queryById(deptno);
}
@GetMapping("/list")
public List<Dept> queryAll(){
return deptService.queryAll();
}
}
2.2 Rest环境搭建
- 创建消费者scloud-consumer-dept-80 module
- 添加依赖
<dependencies>
<!-- 实体类+web -->
<dependency>
<groupId>com.company</groupId>
<artifactId>scloud-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
- 配置端口8080
- 添加config
@Configuration
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- 添加controller
@RestController
@RequestMapping("/consumer")
public class DeptConsumerController {
//理解:消费者不应该有service层
//RestTemplate....供我们直接调用
//(url,实体:Map,Class<T> responseType)
@Autowired
private RestTemplate restTemplate;
private static final String REST_URL_PREFIX = "http://localhost:8001";
@RequestMapping("/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add", dept, Boolean.class);
}
@RequestMapping("/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
//return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
ResponseEntity<Dept> response=restTemplate.exchange(
REST_URL_PREFIX+"/dept/get/{id}",
HttpMethod.GET,
null,
Dept.class,
id
);
if(!response.getStatusCode().is2xxSuccessful()) {
return null;
}
return response.getBody();
}
@RequestMapping("/dept/list")
public List<Dept> list() {
//return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list/",List.class);
ResponseEntity<List<Dept>> response = restTemplate.exchange(
REST_URL_PREFIX+"/dept/list/",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Dept>>() {
});
if(!response.getStatusCode().is2xxSuccessful()) {
return null;
}
return response.getBody();
}
}
- 先运行provider,再运行consumer,然后测试嫩否通过consumer访问provider
2.3 Eureka服务注册与发现
2.3.1 创建Eureka Server
- 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
- 配置application
server:
port: 7001
#port: 8761 默认端口
#Eureka配置
eureka:
instance:
#Eureka服务端实例名称
hostname: localhost
client:
#表示是否向eureka注册中心注册自己
register-with-eureka: false
#false表示自己为注册中心
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args)
}
}
2.3.2 注册服务
- 在服务提供者下添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 完善监控信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 配置application
#Eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: sc-provider-dept8001
#info配置
info:
app.name: company-springcloud
company.name: supercokou
- 启动类添加@EnableEurekaClient注解
- 添加服务信息查找方法
@Autowired
private DiscoveryClient discoveryClient;
//注册进来的微服务获取信息
@GetMapping("/discovery")
public Object discovery() {
//获取微服务列表清单
List<String> services=discoveryClient.getServices();
System.out.println("discovery=>services:"+services);
List<ServiceInstance> instances = discoveryClient.getInstances("SCLOUD-PROVIDER-DEPT");
for(ServiceInstance instance:instances) {
System.out.println(
instance.getHost()+"\t"+
instance.getPort()+"\t"+
instance.getUri())+"\t"+
instance.getServiceId()
);
}
return this.discoveryClient;
}
- 集群就是在defaultZone多注册几个地址就行了
CAP原则:
C:Consistency 强一致性
A:Availability 可用性
P:Partition tolerance 分区容错性
2.4 负载均衡
2.4.1 ribbon
-
负载均衡简单分类:
- 集中式LB :即在服务的消费方和提供方之间使用独立的LB设施,如Nginx,由该设施负责把访问请求通过某种策略转发至服务的
- 进程式LB :将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
-
使用Ribbon实现负载均衡
- 在consumer模块添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 配置application
#配置Eureka
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka
- 在启动类添加@EnableEurekaClient
- 在config添加
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced //配置负载均衡
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- 在deptcontroller修改
//Ribbon:地址是一个变量,通过服务名称来访问
private static final String REST_URL_PREFIX = "http://SCLOUD-PROVIDER-DEPT";
- 测试访问 http://localhost:8080/consumer/dept/list
2.5 服务熔断
- 服务熔断简单介绍
- 服务熔断:当调用链路中某个服务不可用(断路)时,快速返回错误信息,不再继续调用后续的服务。
- 服务降级:当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此保证核心服务可用,以及部分非核心服务可以降级使用。
- 什么是Hystrix
- Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
- ”断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
2.5.1 服务熔断实现
- 复制一个服务提供者改名为scloud-provider-dept-hystrix-8001
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 添加备选方法
@HystrixCommand(fallbackMethod = "getHystrixDeptById")
@GetMapping("/get/{id}")
public Dept getDeptById(@PathVariable("id")Long deptno) {
Dept dept=deptService.queryById(deptno);
if(dept==null) {
throw new RuntimeException("id=>"+deptno+",不存在该用户,或者信息无法找到");
}
return dept;
}
//备选方案
public Dept getHystrixDeptById(@PathVariable("id")Long deptno,Throwable throwable) {
return new Dept()
.setDeptno(deptno)
.setDname(throwable.getMessage())
.setDbSource("no this database in MYSQL");
}
- 配置启动
@SpringBootApplication
@MapperScan("com.scloud.provider.mapper")
@EnableEurekaClient
@EnableCircuitBreaker //添加对熔断的支持
public class DeptProvider_hystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_hystrix_8001.class, args);
}
}
- 测试 http://localhost:8080/consumer/dept/get/6
2.6 服务降级
参考这篇文章
https://www.kancloud.cn/qingshou/aaa1/2667188
2.7 Nacos服务注册中心
2.7.1 nacos服务创建
- 建立nacos环境配置文件
PREFER_HOST_MODE=hostname
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=192.168.150.128
MYSQL_SERVICE_DB_NAME=nacos
MYSQL_SERVICE_PORT=3306
MYSQL_SERVICE_USER=root
MYSQL_SERVICE_PASSWORD=123
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
- 创建nacos数据库
-
- 配置文件复制到相应目录下
- docker 配置nacos运行环境
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim
- 使用docker logs -f nacos 查看启动日志
2.7.2 注册nacos服务
- 添加依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 注册服务
spring:
application:
name: scloud-provider-dept
cloud:
nacos:
server-addr: 192.168.150.128:8848
- 把之前用过Eureka相关的注释掉,才能正常启动
2.7.3 Nacos服务发现
步骤和上面一样
- 负载均衡实现
/**
* 通过DiscoveryClient 实现负债均衡
* @return
*/
public Object getUriString() {
List<ServiceInstance> instances=discoveryClient.getInstances("scloud-provider-dept");
ServiceInstance instance=instances.get(new Random().nextInt(instances.size()));
return instance.getUri();
}
2.8 OpenFeign
2.8.1 初步使用
- 添加依赖
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
- 通过@EnableFeignClients注解开启Feign功能
- 创建DeptService
@FeignClient("scloud-provider-dept")
@Service
public interface DeptService {
@PostMapping("/dept/add")
public boolean addDept(Dept dept);
@GetMapping("/dept/get/{id}")
public Dept getDeptById(@PathVariable("id")Long deptno);
@GetMapping("/dept/list")
public List<Dept> queryAll();
}
- DeptController
@RestController
@RequestMapping("/consumer")
public class DeptConsumerController2 {
@Autowired DeptService deptService;
@RequestMapping("/dept/add")
public boolean add(Dept dept) {
return deptService.addDept(dept);
}
@RequestMapping("/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return deptService.getDeptById(id);
}
@RequestMapping("/dept/list")
public List<Dept> list() {
return deptService.queryAll();
}
}
2.8.2 连接池
- 引入依赖
<!--ok-html-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
- 开启连接池
fegin:
okhttp:
enabled: true