以下论述,一点儿个人理解。 欢迎各路大佬支持!
java项目开发历经了,从
jsp(servlet)+实体+数据库的蛮荒时代
到
MVC三层架构
到
spring诞生后的 ssm = spring + springMVC +mybatis
到
springboot 自动装配
这时候 单个服务的开发已经变得极为迅速高效,而java强大的生态,导致项目的访问者会逐渐膨胀。
而单机服务器,再强大,也无法承载极高的并发数据量访问。
这时候有一些策略就是 服务器集群,等价复制多个服务器。 这必然会有计算机资源的浪费!
一条业务线,可能某些环节的处理非常简单, 而某些环节的处理非常复杂。
基于这样的现状, martin fowler 提出了微服务的概念! 将服务拆分! 按需组装~
引入这些概念去解决 大访问量的 软件承载力问题的时候,不可避免的引入了几个基本问题
springcloud就是微服务分布式架构下的一站式解决方案!
可以认为是领头羊的一家公司 spring-cloud-netflix 他们针对上述问题给出了一套解决方案
1.Api网关, 相关插件 zuul
2.http的通讯方式,同步并阻塞 相关插件 Feign -> HttpClient
3.服务注册与发现 , 相关插件 Eureka
4.熔断机制 相关插件 Hystrix
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>com.zzgroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
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
spring:
application:
name: springcloud-provider-dept
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8
driver-class-name: org.gjt.mm.mysql.Driver
mybatis:
type-aliases-package: com.zz.springcloud.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
# 当前这个服务注册到哪里
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-dept8001
<mapper namespace="com.zz.springcloud.mapper.DeptMapper">
<insert id="addDept" parameterType="dept">
insert into dept (dname, db_source) values (#{dname}, database());
insert>
<select id="queryById" resultType="dept" parameterType="long">
select * from dept where deptno = #{id}
select>
<select id="queryAll" resultType="dept">
select * from dept
select>
mapper>
//对消费者提供服务的controller层
import com.zz.springcloud.pojo.Dept;
import com.zz.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class DeptController {
@Autowired
private DeptService service;
@PostMapping("/add")
public int add(Dept dept) {
return service.addDept(dept);
}
@GetMapping("/get/{id}")
public Dept queryById(@PathVariable("id") int id) {
return service.queryById(id);
}
@GetMapping("/all")
public List<Dept> queryAll() {
return service.queryAll();
}
}
//*****************************下面是mapper接口****************************************
import com.zz.springcloud.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import java.util.List;
@Mapper
@Component
public interface DeptMapper {
int addDept(Dept dept);
Dept queryById(long id);
List<Dept> queryAll();
}
//******************************下面是服务接口*****************************************
import com.zz.springcloud.pojo.Dept;
import java.util.List;
public interface DeptService {
int addDept(Dept dept);
Dept queryById(long id);
List<Dept> queryAll();
}
//*********************************下面是服务是实现类*****************************
import com.zz.springcloud.mapper.DeptMapper;
import com.zz.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper mapper;
public int addDept(Dept dept) {
return mapper.addDept(dept);
}
public Dept queryById(long id) {
return mapper.queryById(id);
}
public List<Dept> queryAll() {
return mapper.queryAll();
}
}
//**********************************下面是启动类*************************************
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class, args);
}
}
eureka注册中心示例
我们先创建一个。步骤更是极为简单,创建一个springboot项目导入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eureka-serverartifactId>
<version>1.4.6.RELEASEversion>
dependency>
dependencies>
后只需要自定义一下配置文件就ok了
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com
client:
register-with-eureka: false #表示是否向eureka注册自己
fetch-registry: false #fetch-registry为false 即表示自己为注册中心
service-url: #监控页面
defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
然后服务提供者中引入了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-dept8001
完成之后,我们先启动eureka注册中心,然后再浏览器 localhost:7001访问下,就能看到他们的自定义首页
然后再启动我们的服务提供,成功后,就可以在eureka首页的服务列表中,看到我们所提供的服务!
服务消费者示例
项目文件结构
依赖文件
<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>springcloudartifactId>
<groupId>com.zzgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>springcloud-consumer-dept-80artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>com.zzgroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
server:
port: 80
#eureka配置
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
register-with-eureka: false #不向eureka注册自己
//自定义的基于ribbon的负载均衡策略
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
/**
* 自定义负载均衡策略
* 总共3个服务每个服务访问5次 就替换为下一个
*/
public class ZzftfRule extends AbstractLoadBalancerRule {
private int count = 0;//当前服务被访问的次数
private int curIndex = 0;// 当前时谁在提供服务, 服务下标
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 (count < 5) {
server = upList.get(curIndex);
count++;
} else {
count = 0;
curIndex++;
if (curIndex > 2) {
curIndex = 0;
}
server = upList.get(curIndex);
}
//=====================我们自己的策略=====================
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig iClientConfig) {}
}
//**************************下面是java式注解配置文件****************************************
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZzRule {
@Bean
public IRule getRule() {
return new ZzftfRule();
}
}
//*************************restful风格所需要的模板bean*******也是java式注解配置文件********
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//等价于 spring配置地狱版本的 那个applicationContext.xml
public class ConfigBean {//消费端 需要远程调用 springcloud提供了RestTemplate模板技术来做这件事情
@LoadBalanced//Ribbon去实现负载均衡
@Bean
public RestTemplate getTemplate() {
return new RestTemplate();
}
}
//***************************对web提供访问的controller层***************************************
import com.zz.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
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
public class DeptConsumerController {
// private final static String REST_URI_PREFIX = "http://localhost:8001";
//负载均衡时,服务不会固定在某个机器的某个端口 所以要通过服务名访问
private final static String REST_URI_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
@Autowired
private RestTemplate template;
@RequestMapping("/dept/con/add")
public int add(Dept dept) {
return template.postForObject(REST_URI_PREFIX + "/add", dept, int.class);
}
@RequestMapping("/dept/con/get/{id}")
public Dept get(@PathVariable("id") long id) {
return template.getForObject(REST_URI_PREFIX + "/get/" + id, Dept.class);
}
@RequestMapping("/dept/con/all")
public List<Dept> queryAll() {
return template.getForObject(REST_URI_PREFIX + "/all", List.class);
}
}
//*****************************启动类******************************
import com.zz.myRule.ZzRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = ZzRule.class)
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
消费端需要到注册中心访问服务,所以
然后 ribbon 是在服务端加入 策略的负载均衡插件,我们可以在 模板bean 上直接加注解@LoadBalance,Ribbon就会默认使用轮询的负载均衡策略
而我们需要自定义负载均衡策略的话 也非常简单,在启动类的注解中 指定 实现策略的类。如以上启动类中的 configuration = ZzRule.class 然后在 ZzRule类中 注入实体bean,这个bean的具体实现,就是我们写自定义负载均衡策略的地方了,如上ZzftfRule这个类,它需要继承ribbon提供的抽象类AbstractLoadBalancerRule。
feign接口式远程访问服务示例
在(实体)API中增加接口
import com.zz.springcloud.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptService {
//这个类里面方法上注解中的路径值,必须和上面服务提供者的controller中的注解路径值保持一致!!!
@PostMapping("/add")
int addDept(Dept dept);
@GetMapping("/get/{id}")
Dept queryById(@PathVariable("id") long id);
@RequestMapping("/all")
List<Dept> queryAll();
}
复刻一个消费者实现,在其中增加feign的依赖,替换掉template的访问方式,通过API中的服务来访问
import com.zz.springcloud.pojo.Dept;
import com.zz.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class DeptConsumerController {
@Autowired
private DeptService service;
@RequestMapping("/dept/con/add")
@ResponseBody
public int add(Dept dept) {
return service.addDept(dept);
}
@RequestMapping("/dept/con/get/{id}")
@ResponseBody
public Dept get(@PathVariable("id") long id) {
return service.queryById(id);
}
@RequestMapping("/dept/con/all")
@ResponseBody
public List<Dept> queryAll() {
return service.queryAll();
}
}
//************************下面的启动类增加了feign客户端允许开关注解***************************
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.zz.springcloud"})
public class FeignConsumer {
public static void main(String[] args) {
SpringApplication.run(FeignConsumer.class, args);
}
}
Hystrix服务熔断示例
增加依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
<version>1.4.6.RELEASEversion>
dependency>
增加对应的熔断处理方法 一一对应的关系
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.zz.springcloud.pojo.Dept;
import com.zz.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class DeptController {
@Autowired
private DeptService service;
@GetMapping("/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept get(@PathVariable("id") int id) {
Dept dept = service.queryById(id);
if (dept == null) {
throw new RuntimeException("id=>" + id + ",不存在该用户,或者用户信息找不到!");
}
return dept;
}
//备选方法
public Dept hystrixGet(@PathVariable("id") int id) {
return new Dept()
.setDeptno(id)
.setDname("id=>" + id + ",没有对应信息, null--@Hystrix")
.setDb_source("No this database in MySQL");
}
}
主启动类增加开启的注解 @EnableCircuitBreaker
Hystrix服务降级示例
在接口中心中增加,降级工厂实现并指定
import com.zz.springcloud.pojo.Dept;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class DeptFallbackFactory implements FallbackFactory {
public DeptService create(Throwable throwable) {
return new DeptService() {
public int addDept(Dept dept) {
return 0;
}
public Dept queryById(long id) {
return new Dept()
.setDeptno(id)
.setDname("id==>" + id + "本服务暂时关闭, 降级操作")
.setDb_source("没有数据~~~");
}
public List<Dept> queryAll() {
return null;
}
};
}
}
//***************在服务接口中心指定降级工厂*******************************************
import com.zz.springcloud.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT", fallbackFactory = DeptFallbackFactory.class)
public interface DeptService {
@PostMapping("/add")
int addDept(Dept dept);
@GetMapping("/get/{id}")
Dept queryById(@PathVariable("id") long id);
@RequestMapping("/all")
List<Dept> queryAll();
}
在配置了feign的消费端 增加 降级功能开启配置
#开启降级允许配置
feign:
hystrix:
enabled: true
Hystrix:dashboard流监控示例
创建一个监控器,引入依赖 直接创建maven项目即可
<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>
增加启动注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboard {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard.class, args);
}
}
指定下端口,避免冲突
server:
port: 9001
在支持hystrix的服务提供端的启动类中增加 一个servlet(和之前web.xml中的意义一样,但这里使用的是java代码 bean注入版的)
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class HystrixProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(HystrixProvider_8001.class, args);
}
//增加一个servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
bean.addUrlMappings("/actuator/hystrix.stream");
return bean;
}
}
然后启动 注册中心7001,启动支持监控的hystrix服务8001,再启动监控中心9001,最后启动消费端80
zuul:API网关使用示例
引入必要的依赖
<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-hystrixartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrix-dashboardartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>com.zzgroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
增加一些初始化的配置 (zuul可以也被理解为一个服务,会注册到eureka注册中心,但他会去发现其它服务)
server:
port: 9527
spring:
application:
name: springcloud-zuul
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
instance:
instance-id: zuul9527.com
prefer-ip-address: true #隐藏真实IP
info:
app.name: zz-springcloud
company.name: blog.zz.com
zuul:
routes:
zzdept.serviceId: springcloud-provider-dept
zzdept.path: /zzdept/**
#ignored-services: springcloud-provider-dept #配置不再能够通过指定微服务名访问
ignored-services: "*" #屏蔽所有微服务名称
prefix: /wocao #必须带有前缀才能访问到服务 !!!!!这个前缀不能使用 zuul 这个特定单词,用了就无法访问!!!!!!
主启动类上增加开启zuul功能的注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
public class ZuulGateway {
public static void main(String[] args) {
SpringApplication.run(ZuulGateway.class, args);
}
}
有了zuul这一层在用户之前处理后,用户面对的就是一个固定的访问地址,服务器集群的 各个微服务名称将不再暴露在访问uri中了!
两大核心功能