一、菜鸟版
目录结构
1、新建一个Distribute父工程
2、新建一个eureka子项目
①pom.xml主要依赖
<!--springboot版本-->
<version>2.2.6.RELEASE</version>
<!--springclooud版本-->
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<!--eureka服务端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--springcloud版本控制-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
②主要代码
启动类上标注@EnableEurekaServer注解,启用eureka服务端
@SpringBootApplication
@EnableEurekaServer //启用eureka服务端
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
③application.yml配置文件
server:
port: 10086 #启动端口
spring:
application:
name: huan-eureka #服务名称
eureka:
client:
service-url:
defaultZone: http://localhost:${server.port}/eureka #注册的服务
④访问http://localhost:10086 搭建成功
3、新建service-provider(服务的提供方)
目录结构
①pom.xml主要依赖
<!--springboot版本-->
<version>2.2.6.RELEASE</version>
<!--springclooud版本-->
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--springcloud版本控制-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
②主要代码
a、User实体类
@Data //提供get和set方法
@AllArgsConstructor //添加一个构造函数,该构造函数含有所有已声明字段属性参数
@NoArgsConstructor //创建一个无参构造函数
public class User {
private int id;
private String username;
private String password;
}
b、UserMapper接口
@Mapper
@Repository
public interface UserMapper {
@Select("select * from t_user")
public List<User> listUser();
@Select("select * from t_user where id = #{id}")
public User GetUserById(@Param("id") int id);
}
c、UserService业务类
@Service
public class UserService {
@Autowired
public UserMapper userMapper;
public List<User> listUser(){
return userMapper.listUser();
}
public User GetUserById(int id) {
return userMapper.GetUserById(id);
}
}
d、UserController控制类
@Controller
public class UserController {
@Autowired
UserService userService;
@GetMapping("user")
@ResponseBody
public List<User> listUser(){
return userService.listUser();
}
@GetMapping({"user/{id}"})
@ResponseBody
public User GetUserById(@PathVariable int id){
return userService.GetUserById(id);
}
}
e、启动类中配置包扫描以及服务发现
@SpringBootApplication
@MapperScan("com.huan.service.mapper")//mapper接口的包扫描
@EnableDiscoveryClient //启动服务发现
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}
③application.yml配置文件
server:
port: 8080 #启动端口
spring:
datasource: #MySQL数据库
username: root
password: root
url: jdbc:mysql://123.60.12.197:3306/swagger
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: service-provider #服务名称
mybatis:
type-aliases-package: com.huan.service.pojo #mybatis实体类
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka #把服务注册到eureka
④接口测试(成功)
4、新建service-consumer(服务的消费方)
①pom.xml主要依赖
<!--springboot版本-->
<version>2.2.6.RELEASE</version>
<!--springclooud版本-->
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--springcloud版本控制-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
②主要代码
a、启动类
@SpringBootApplication
@MapperScan("com.huan.service.mapper")//mapper接口的包扫描
@EnableDiscoveryClient //启动服务发现
public class ServiceConsumerApplication {
/**
* HTTP 请求工具,它提供了常见的REST请求方案的模版,
* 例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}
b、Controller控制器
@Controller
public class UserController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; //包含了拉取的所有服务信息
@Autowired
UserService userService;
@GetMapping("user")
@ResponseBody
public List<User> listUser(){
return userService.listUser();
}
@GetMapping({"user/{id}"})
@ResponseBody
public User GetUserById(@PathVariable int id){
//获取服务
List<ServiceInstance> instances = discoveryClient.getInstances("service-provider");
//获得服务实例
ServiceInstance instance = instances.get(0);
//instance.getHost()获取主机号,instance.getPort()获取端口
return this.restTemplate.getForObject("http://"+instance.getHost()+":"+instance.getPort()+"/user/"+id,User.class);
}
}
③application.yml配置文件
server:
port: 8081
spring:
datasource:
username: root
password: root
url: jdbc:mysql://123.60.12.197:3306/swagger
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: service-consumer #服务名称
mybatis:
type-aliases-package: com.huan.service.pojo
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka #注册服务到eureka注册中心
5、最后测试
①启动三个模块
②访问http://localhost:10086,所有服务已成功注册
③访问消费方服务,成功调用
二、菜鸟plus版
①ribbon负载均衡
多启动一个加个服务提供方,启动类上加个@LoadBalanced就搞定了
@SpringBootApplication
@MapperScan("com.huan.service.mapper")//mapper接口的包扫描
@EnableDiscoveryClient //启动服务发现
public class ServiceConsumerApplication {
/**
* HTTP 请求工具,它提供了常见的REST请求方案的模版,
* 例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute
*/
@Bean
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}
地址改为service-provider服务提供方的id
@Controller
public class UserController {
@GetMapping({"user/{id}"})
@ResponseBody
public User GetUserById(@PathVariable int id){
return this.restTemplate.getForObject("http://service-provider/user/"+id,User.class);
}
}
三、hystrix
分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务。对于同步调用,当库存服务不可用时,商品服务请求线程被阻塞,当有大批量请求调用库存服务时,最终可能导致整个商品服务资源耗尽,无法继续对外提供服务。并且这种不可用可能沿请求调用链向上传递,这种现象被称为雪崩效应。
①添加依赖
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
②添加@EnableCircuitBreaker 注解开启熔断器
@SpringBootApplication
@MapperScan("com.huan.service.mapper")//mapper接口的包扫描
@EnableDiscoveryClient //启动服务发现
@EnableCircuitBreaker //开启熔断器
public class ServiceConsumerApplication {
/**
* HTTP 请求工具,它提供了常见的REST请求方案的模版,
* 例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute
*/
@Bean
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}
③添加一个GetUserByIdHystrix(int id)方法,通过@HystrixCommand(fallbackMethod = “GetUserByIdHystrix”)关联,返回类型改为String
@Controller
public class UserController {
@GetMapping({"user/{id}"})
@ResponseBody
@HystrixCommand(fallbackMethod = "GetUserByIdHystrix")
public String GetUserById(@PathVariable int id){
return this.restTemplate.getForObject("http://service-provider/user/"+id,String.class);
}
public String GetUserByIdHystrix(int id){
return "服务器繁忙,请稍后重试";
}
}
④全局的熔断方法
@DefaultProperties(defaultFallback = “allHystrix”) //定义全局的熔断方法
@HystrixCommand //不写参数代表使用全局的熔断方法
@Controller
@DefaultProperties(defaultFallback = "allHystrix") //定义全局的熔断方法
public class UserController {
@Autowired
private RestTemplate restTemplate;
@Autowired
UserService userService;
@GetMapping("user")
@ResponseBody
@HystrixCommand //不写参数代表使用全局的熔断方法
public String listUser(){
return this.restTemplate.getForObject("http://service-provider/user/",String.class);
}
@GetMapping({"user/{id}"})
@ResponseBody
@HystrixCommand(fallbackMethod = "GetUserByIdHystrix") //指定熔断时执行的方法
public String GetUserById(@PathVariable int id){
return this.restTemplate.getForObject("http://"+instance.getHost()+":"+instance.getPort()+"/user/"+id,User.class);*/
return this.restTemplate.getForObject("http://service-provider/user/"+id,String.class);
}
//指定的熔断方法(返回值要一致,参数列表必须跟熔断的方法一样)
public String GetUserByIdHystrix(int id){
return "服务器繁忙,请稍后重试";
}
//全局的熔断方法(返回值要一致,不用带参数)
public String allHystrix(){
return "全局-服务器繁忙,请稍后重试";
}
}
⑤配置hystrix熔断器超时时间
hystrix:
command:
default: #default全局有效
execution:
timeout:
#是否开启超时熔断
enabled: true
isolation:
thread:
timeoutInMilliseconds: 6000 #断路器超时时间,默认1000ms
@SpringCloudApplication //组合注解包含了@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker
⑥hystrix的三种状态
关闭(closed)
正常情况下hystrix为关闭状态,所有请求都可以正常访问
半打开(half open)
在进入该状态后会放入部分请求;判断请求是否成功,不成功,进入open状态,重新计时,进入halfopen状态;成功,进入closed状态
打开(open)
统计请求的失败比例,达到阀值时,打开熔断器,请求被降级处理;延时一段时候后(默认休眠时间是5S)会进入halfopen状态;默认失败比例阀值是50%,请求次数最少不低于20次;
四、feign
①引入依赖
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
②启动类上加上@EnableFeignClients注解
@EnableFeignClients //启用feign组件
然后这些代码就可以注释掉了
/*@Bean
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}*/
/*@Autowired
private RestTemplate restTemplate;*/
//return this.restTemplate.getForObject("http://service-provider/user/",String.class);
//return this.restTemplate.getForObject("http://service-provider/user/"+id,String.class);
③创建一个调用远程服务的接口UserClient(方法与Controller中的方法一样)
@FeignClient("service-provider")//调用服务的id
public interface UserClient {
@GetMapping("users/user")//全局的请求路径在这里加上
public List<User> listUser();
@GetMapping("users/user/{id}")//全局的请求路径在这里加上
public User GetUserById(@PathVariable int id);
}
④在Controller中调用方法
@Controller
@RequestMapping("users")
@DefaultProperties(defaultFallback = "allHystrix") //定义全局的熔断方法
public class UserController {
@Autowired
UserClient userClient;
@GetMapping("user")
@ResponseBody
@HystrixCommand //不写参数代表使用全局的熔断方法
public String listUser(){
return this.userClient.listUser().toString();
}
@GetMapping({"user/{id}"})
@ResponseBody
@HystrixCommand(fallbackMethod = "GetUserByIdHystrix") //指定熔断时执行的方法
public String GetUserById(@PathVariable int id){
return this.userClient.GetUserById(id).toString();
}
//指定的熔断方法(返回值要一致,参数列表必须跟熔断的方法一样)
public String GetUserByIdHystrix(int id){
return "服务器繁忙,请稍后重试";
}
//全局的熔断方法(返回值要一致,不用带参数)
public String allHystrix(){
return "全局-服务器繁忙,请稍后重试";
}
}
⑤开启feign的熔断功能
a、yml配置
feign:
hystrix:
enabled: true #开启feign的熔断功能
b、编写远程调用接口的实现类UserClientFallback
@Component//注入容器
public class UserClientFallback implements UserClient {
@Override
public List<User> listUser() {
List<User> userList = null;
User user=new User();
user.setUsername("服务器忙,请稍后再试!(feign)");
userList.add(user);
return userList;
}
@Override
public User GetUserById(int id) {
User user=new User();
user.setUsername("服务器忙,请稍后再试!(feign)");
return user;
}
}
c、上面的接口注解那里声明一下熔断实现类
@FeignClient(value = "service-provider",fallback = UserClientFallback.class)//调用服务的以及熔断的实现类
public interface UserClient {
@GetMapping("users/user")//全局的请求路径在这里加上
public List<User> listUser();
@GetMapping("users/user/{id}")//全局的请求路径在这里加上
public User GetUserById(@PathVariable int id);
}
貌似public List<User> listUser();这个方法熔断失败,后续再研究一下
五、zuul
1、菜鸟版
①新建一个子模块zuul
②引入zuul依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
③启动类上加上@EnableZuulProxy注解
@SpringBootApplication
@EnableZuulProxy //启用zuul组件
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
④配置文件
server:
port: 10010
spring:
application:
name: zuul
zuul:
routes:
service-provider: #路由名称,可以随便写,习惯上路由名称
path: /service-provider/** #你想把带有这个service-provider的请求路径路由到下面的地址
url: http://localhost:8082
2、菜鸟puls版
①引入eureka
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
②把zuul网关注册到eureka
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka #注册服务到eureka注册中心
③在启动类中启动服务发现@EnableDiscoveryClient
@SpringBootApplication
@EnableZuulProxy //启用zuul组件
@EnableDiscoveryClient //启动服务发现
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
④配置文件升级一下
zuul:
routes:
service-provider: #路由名称,可以随便写,习惯上路由名称
path: /service-provider/** #你想把带有这个service-provider的请求路径路由到下面的地址
#url: http://localhost:8082
serviceId: service-provider #把zuul注册到eureka,这样就可以直接路由到这个服务(因为这个相同功能的服务可能分布式部署到了多台服务器上面)
prefix: /api #前缀
⑤配置文件大多数这样写
简简单单,真香
http://localhost:10010/api/service-provider/users/user/1
zuul:
routes:
service-provider: /service-provider/** #可以看作前面的是服务id,后面的是接口中的请求路径(把带有后面的请求路径的请求路由到前面的服务上面去)
3、zull过滤器
①编写Filter类
- http://localhost:10010/api/service-provider/users/user/?name=huan
- http://localhost:10010/api/service-consumer/users/user/?name=huan
package com.huan.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author :huan
* @date :2021/4/23 14:47
* @description:TODO
**/
@Component//放进spring容器
public class LoginFilter extends ZuulFilter {
/**
* @author: huan
* @description:
* 过滤器的类型:pre route post error
* pre:在请求被路由(转发)之前调用
* route:在路由(请求)转发时被调用
* error:服务网关发生异常时被调用
* post:在路由(转发)请求后调用
*/
@Override
public String filterType() {
//登录应该在之前就过滤一下
return "pre";
}
/**
* @author: huan
* @description:
* 执行顺序,返回值越小,优先级越高
*/
@Override
public int filterOrder() {
//弄大一点,这样以后前面如果还要加过滤器的话,就还有扩展的机会
return 10;
}
/**
* @author: huan
* @description:
* 是否执行下面的run方法
* true:代表执行
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* @author: huan
* @description:
* 这里编写过滤器的业务逻辑
*/
@Override
public Object run() throws ZuulException {
//初始化zuul的context上下文对象 import com.netflix.zuul.context.RequestContext;
RequestContext context = RequestContext.getCurrentContext();
//获取request对象
HttpServletRequest request = context.getRequest();
//获取请求参数
String name = request.getParameter("name");
if (StringUtils.isBlank(name)){//判断是否为空
//拦截,不转发请求
context.setSendZuulResponse(false);
//响应状态码401
context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
//设置响应的提示
context.setResponseBody("Didn't login!");
}
//返回null代表该过滤器啥都不做
return null;
}
}
六、最终版
e…就是把上面组件集成后的产品了