狂神springCloud
注意依赖版本的兼容问题微服务架构4个核心问题?
- 服务很多, 客户端该怎么访问?
- 这么多服务? 服务之间如何通信?
- 这么多服务? 如何治理?
- 服务挂了怎么办?
https://springcloud.cc/spring-cloud-netflix.html
中文API文档: https://springcloud.cc/spring-cloud-dalston.html
SpringCloud中国社区: http://springcloud.cn/
SpringCloud中文网: https://www.springcloud.cc/
<packaging>pompackaging>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<junit.version>4.13.1junit.version>
<log4j.version>1.2.17log4j.version>
<lombok.version>1.18.20lombok.version>
<logback-core.version>1.2.3logback-core.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR9version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.3.7.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.23version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
<version>${logback-core.version}version>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
@Data
@NoArgsConstructor
@Accessors(chain = true) //链式编程
public class dept implements Serializable {
//序列化,对象传输
private int did;
private String deptName;
private String dbName;
public void dept(String deptName){
this.dbName=deptName;
}
}
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
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>
server:
port: 8001
#mybatis配置
mybatis:
type-aliases-package: com.lzk.springcloud.pojo
mapper-locations: classpath: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/springclouddemo?useUnicode=true&characterEncoding=utf-8
username: root
password: xxxx
<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>
server:
port: 80
RestTemplate提供访问api并封装结果的spring对象
@Configuration
public class RestTemplateConf {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
消费者中访问的是生产者提供的接口
消费者暴露的接口才是面向客户端的
@RestController
public class deptController {
// 理解:消费者,不应该有service层。
// RestTemplate .... 供我们直接调用就可以了!
// 注册到Spring中
// (urL, 实体:Map , CLass responseType)
@Autowired
private RestTemplate restTemplate;//提供多种便捷访问远程http服务的方法,简单的Restful服务模板
private static final String REST_URL_PREFIX = "http://localhost:8001/";
@RequestMapping("/dept/getAll")
public List<dept>getAllDept(){
//getForObject就是get请求,postForObject是post请求
return restTemplate.getForObject(REST_URL_PREFIX+"getAll",List.class);
}
}
接下来的使用都是几个基本步骤
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eureka-serverartifactId>
<version>1.4.7.RELEASEversion>
dependency>
dependencies>
server:
port: 7001
spring:
application:
name: xxx # 微服务名
#Eureka配置
eureka:
instance:
hostname: localhost #Eureka服务端的实例名称
client:
register-with-eureka: false # 表示是否向注册中心注册自己
fetch-registry: false #fetch-registry如果为false,则表示自己为注册中心
service-url: # 监控页面
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
@EnableEurekaServer // 开启服务
@SpringBootApplication
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
#spring的配置
spring:
application:
name: springcloud-provider-dept # 微服务服务名
# eureka
eureka:
client:
# provider 配置将服务注册到哪个注册中心
service-url:
defaultZone: http://localhost:7001/eureka/
# 注册到eureka中心时的id,若不配置则默认描述
instance:
instance-id: springcloud-provider-dept-8001
# info配置,完善提供服务的信息(向注册中心提供该模块的一些信息,如模块名、编写的人)
info:
moudle.name: provider-dept
author.name: lzk
@SpringBootApplication
@EnableEurekaClient //开启eureka客户端
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
左侧的【application是在左侧的spring.application.name配置,status下的是instance-id,点击可获得配置的info配置的详细信息
查看注册中心的微服务的信息
//获取注册中心的服务的信息
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/discovery")
public Object discovery(){
//获取微服务列表清单
List<String> services = discoveryClient.getServices();
System.out.println("微服务:"+services);
//获取一个具体的微服务信息,通过服务名applicationName
List<ServiceInstance> instances=discoveryClient.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println(
instance.getHost()+'\t'+
instance.getPort()+'\t'+
instance.getUri()+'\t'
);
}
return discoveryClient;
}
@SpringBootApplication
@EnableEurekaClient //开启eureka客户端
@EnableDiscoveryClient //开启服务发现
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
基本和设置eureka注册中心无异=
将server_url 注册到其他集群上(不注册到自己的端口号下)
如端口7001
eureka1.com、eureka2.com、eureka3.com在本地的host文件中已经有映射到
localhost
上
即是eureka1.com=eureka2.com=eureka3.com=localhost
server:
port: 7001
#Eureka配置
eureka:
instance:
hostname: eureka1.com #Eureka服务端的实例名称
client:
register-with-eureka: false # 表示是否向注册中心注册自己
fetch-registry: false #fetch-registry如果为false,则表示自己为注册中心
service-url: # 监控页面,并向其他注册中心注册自己
defaultZone: http://eureka2.com:7002/eureka/,http://eureka3.com:7003/eureka/
提供者
注册到所有集群中
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
2. 策略
这里说的都是消费者层的代码,因为负载均衡是在访问时进行的
//eureka
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
//ribbon
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
<version>1.4.6.RELEASEversion>
dependency>
server:
port: 80
eureka:
client:
register-with-eureka: false # 不注册自己
service-url:
defaultZone: http://eureka1.com:7001/eureka/,http://eureka2.com:7002/eureka/,http://eureka3.com:7003/eureka/
@Configuration
public class RestTemplateConf {
@Bean
@LoadBalanced //ribbon控制客户端
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
至此无需关心ip和端口号
因为此时不再向http://ip:端口号
访问,而是通过服务名SPRINGCLOUD-PROVIDER-DEPT
(服务向注册中心注册的服务名)来访问
@RestController
public class deptController {
@Autowired
private RestTemplate restTemplate;
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT/";
@RequestMapping("/dept/getAll")
public List<dept>getAllDept(){
return restTemplate.getForObject(REST_URL_PREFIX+"getAll",List.class);
}
}
搭建如下项目环境,一个微服务(三个实例,不粘贴代码),根据负载均衡算法看访问哪个
负载均衡默认为轮询算法(使用同一个服务有多个实例的分配)
import com.netflix.loadbalancer.IRule;
上面IRule获得微服务的状态,它的实现类控制负载均衡的算法实现
RoundRobinRuLe 轮询
RandomRule 随机
AvaiLabiLityFiLteringRuLe : 会先过滤掉,跳闸,访问故障的服务,对剩下的进行轮询
RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
创建一个文件夹封装我们自己的均衡算法,该文件夹不能和启动类同一级
编写自己的均衡算法
public class IRule {
//使用自己定义的算法 myRandomRule:负载均衡实现类
@Bean
public myRandomRule getIRule(){
return new myRandomRule();
}
}
实现类
public class myRandomRule extends AbstractLoadBalancerRule {
public myRandomRule() {
}
/**
改造成:随机选取一个服务,执行5次之后再转换下一个服务
*/
//当前执行的服务次数
private int runNum=0;
//当前执行的服务
private int currNum=0;
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
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;
}
if (runNum<5){
runNum++;
server = upList.get(currNum);
}else{
runNum=0;
//随机选取下一个服务索引
currNum = this.chooseRandomInt(serverCount);
server = upList.get(currNum);
}
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
@SpringBootApplication
//在微服务启动的时候就能去加载我们自定义Ribbon类
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = IRule.class)
public class deptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(deptConsumer_80.class,args);
}
}
feign替代restTemplate的功能,更加简洁,但是性能变低了,多了一层
对象:消费者
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
<version>1.4.6.RELEASEversion>
dependency>
// 和@Mapper相似
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEP")//微服务名applicationName
public interface deptService {
@RequestMapping("/getAll")
public List<dept> getAll();
}
@RestController
public class deptController {
@Autowired
private deptService service=null;//引入写好的类
@RequestMapping("/dept/getAll")
public List<dept>getAllDept(){
return service.getAll();
}
}
server:
port: 80
eureka:
client:
register-with-eureka: false # 不注册自己
service-url:
defaultZone: http://eureka1.com:7001/eureka/,http://eureka2.com:7002/eureka/,http://eureka3.com:7003/eureka/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.lzk.springcloud"})
public class feignConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(feignConsumer_80.class,args);
}
}
feign整合了ribbon的负载均衡,所以还是可以将请求根据均衡算法分配
对象:提供者
复制一份springcloud-provider-dept-8001
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
<version>1.4.6.RELEASEversion>
dependency>
@RestController
public class deptController {
@Autowired
private deptService deptService;
@RequestMapping("/getAll")
@HystrixCommand(fallbackMethod = "demo")
public List<dept> getAll(){
if (true){
throw new RuntimeException("人为抛出错误。。");
}
return deptService.getAll();
}
//熔断后的备用方案
public List<dept> demo(){
dept d=new dept();
d.setDid(0).setDbName("未知").setDeptName("Hystrix测试");
List<dept> list=new ArrayList<>();
list.add(d);
return list;
}
}
@SpringBootApplication
@EnableEurekaClient //开启eureka客户端
@EnableCircuitBreaker //添加熔断支持
public class DeptProvider_hystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_hystrix_8001.class,args);
}
}
对象客户端
很多人访问一个微服务,将其他服务的请求不处理,返回提示,将资源给急需的服务使用
//服务降级 回调工厂
public class DeptClientServiceFallbackFactory implements FallbackFactory {
@Override
public deptService create(Throwable throwable) {
return new deptService() {
@Override
public List<dept> getAll() {
List<dept>list=new ArrayList<>();
dept d=new dept();
d.setDid(0).setDbName("未知").setDeptName("服务降级");
list.add(d);
return list;
}
};
}
}
@Component
//设置降级调用的类
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface deptService {
@RequestMapping("/getAll")
public List<dept> getAll();
}
# 开启降级
feign:
hystrix:
enabled: true
关掉提供者,查询消费者springcloud-consumer-feign-80
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrix-dashboardartifactId>
<version>1.4.6.RELEASEversion>
dependency>
server:
port: 9001
hystrix:
dashboard:
proxy-stream-allow-list: "*"
@SpringBootApplication
@EnableHystrixDashboard //开启监控页面
public class HystrixDashboard_9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard_9001.class,args);
}
}
此时监控页面可以启动服务
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
<version>1.4.6.RELEASEversion>
dependency>
@SpringBootApplication
@EnableEurekaClient //开启eureka客户端
@EnableDiscoveryClient //开启服务发现
@EnableCircuitBreaker //添加熔断支持
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
//增加一个Servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
ServletRegistrationBean registrationBean=new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
@HystrixCommand
@RequestMapping("/getAll")
public List<dept> getAll(){
return deptService.getAll();
}
监控方式:
Single Hystrix App: https://hystrix-app:port/actuator/hystrix.stream
eg: http://localhost:8001/actuator/hystrix.stream
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zuulartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
dependencies>
server:
port: 9527
#spring的配置
spring:
application:
name: springcloud-zuul
eureka:
client:
service-url:
defaultZone: http://eureka1.com:7001/eureka/,http://eureka2.com:7002/eureka/,http://eureka3.com:7003/eureka/
# 注册到eureka中心时的id,若不配置则默认描述
instance:
instance-id: springcloud-zuul
zuul:
routes:
dept.serviceId: springcloud-provider-dept # 微服务名
dept.path: /mydept/**
ignored-services: "*" # 不能使用这个路径访问, * :不能使用所有微服务名访问
prefix: /lzk # 所有服务的公共前缀
@SpringBootApplication
@EnableZuulProxy //开启路由代理
public class Zuul_9527 {
public static void main(String[] args) {
SpringApplication.run(Zuul_9527.class,args);
}
}