isWulongbo

isWulongbo 查看完整档案

深圳编辑Sapporo City University  |  计算机科学与技术 编辑把把智能  |  JAVA工程师 编辑 www.wulongbo.com 编辑
编辑

在人生的头三十年,你培养习惯,后三十年,习惯铸就你
一起交流学习请加微信:1191935532

个人动态

isWulongbo 发布了文章 · 1月21日

SpringCloud手写Ribbon实现负载均衡

前言

前面我们学习了 SpringCloud整合Consul,在此基础上我们手写本地客户端实现类似Ribbon负载均衡的效果。
注:order 模块调用者 记得关闭 @LoadBalanced注解。
我们这里只演示 注册中心 consul,至于 zookeeper 也是一模一样。

生产者

member模块

member 服务需要集群,所以我们copy application-consul.yml 文件命名为 application-consul2.yml
image.png
image.png
服务别名一致,只需要修改端口号即可。
application-consul2.yml 配置文件:

##服务端口号
server:
  port: 8503
spring:
  application:
    ##服务别名--服务注册到consul名称
 name: consul-member
  ##注册中心consul地址
 cloud:
    consul:
      host: localhost
      port: 8500
 discovery:
        ## consul ip地址
 hostname: 192.168.3.91

启动 member 集群服务:
idea 运行 AppMember.java 启动 8501 端口
再打开 jar 包路径
image.png
shift + 右键 启动 PowerShell 窗口,运行命令:

java -jar E:\ideaworkspaceback\springcloud-zookeeper\springcloud-zookeeper-member\target\springcloud-zookeeper-member-1.0-SNAPSHOT.jar --spring.profiles.active=consul2

启动 8503 端口
image.png

打开 http://localhost:8500/ui/dc1/services 可以发现上面 注册了 member 集群服务:
image.png

消费者

order模块

OrderApiController.java控制页面使用原子类来 AtomicInteger 保证操作的原子性。

package com.baba.wlb.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:32
 * @Version 1.0
 */@RestController
public class OrderApiController {
    @Autowired
 private RestTemplate restTemplate;
 @Autowired
 private DiscoveryClient discoveryClient;
 //使用原子类AtomicInteger
 private AtomicInteger atomicInteger = new AtomicInteger(1);
 public int add() {
        return atomicInteger.getAndIncrement(); //先获取再自增,对应a++
 //若使用++a 则对应方法是a.incrementAndGet(); 先自增再获取 , //多说一句 a-- 就是 a.getAndDecrement(); //若a = a + 10;————对应API  a.getAndAdd(10);
 }
    /**
 * springCloud中,两种方式调用(rest/feign)
 *
 * @return
 */
 // 订单服务调用会员服务
 @RequestMapping("/getOrder")
    public String getOrder() {
        // 有两种调用方式,一种是采用服务别名方式调用,另一种是使用别名去注册中心上获取对应服务调用地址
 // 第一种方式
 String url = "http://dy-202006281547:8000/getMember";
 // 第二种方式
 url = "http://zk-member/getMember";
 String result = restTemplate.getForObject(url, String.class);
 return "订单服务调用会员服务:" + result;
 }
    /**
 * springCloud中,两种方式调用(rest/feign)
 *
 * @return
 */
 // 订单服务调用会员服务
 @RequestMapping("/getRibbonOrder")
    public String getRibbonOrder() {
        // 第一种方式
 String url = getUri() + "/getMember";
 String result = restTemplate.getForObject(url, String.class);
 return "纯手写Ribbon本地负载均衡:" + result;
 }
    public String getUri() {
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("consul-member");
 if (serviceInstances == null || serviceInstances.isEmpty()) {
            return null;
 }
        int serverSize = serviceInstances.size();
 int count = add();
 int indexServer = count % serverSize;
 return serviceInstances.get(indexServer).getUri().toString();
 }
}

AppOrder.java 启动类,关闭 @LoadBalanced 注解即可:
image.png

由于我们是在 springcloud集成zookeeper 项目上改造的,所以order 模块 yml 配置也做相应的调整,注册中心修改为consul,并且在该模块中引入 consul 的 maven依赖

<!--springcloud 整合consul-->
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

image.png

启动 AppOrder.java,并刷新 http://localhost:8500/ui/dc1/services 可以发现上面 注册了 order 服务:
image.png

这里有一个巨坑,导致 order 模块调用不到 member 模块,我们需要在yml 配置文件中注释掉:
参考博客: https://blog.csdn.net/apdkapskdad/article/details/93927860

## consul ip地址
##hostname: 192.168.3.91

消费者调用生产者:访问http://localhost:8508/getRibbonOrder
image.png
image.png
OK!我们便达到了负载均衡!

查看原文

赞 0 收藏 0 评论 0

isWulongbo 发布了文章 · 1月21日

SpringCloud获取注册中心上服务列表

@DiscoveryClient注解

使用 @DiscoveryClient 获取服务列表

基于 SpringCloud整合Consul 我们修改一下 MemberApiController.java:

package com.baba.wlb.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:20
 * @Version 1.0
 */@RestController
public class MemberApiController {
    @Value("${server.port}")
    private String serverPort;
 @Autowired
 private DiscoveryClient discoveryClient;
 @RequestMapping("/getMember")
    public String getMember() {
        return "我是会员服务!端口号:" + serverPort;
 }
    @RequestMapping("/discoveryServer")
    public String discoveryServer() {
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("consul-member");
 return serviceInstances.get(0).getHost() + serviceInstances.get(0).getPort();
 }
}

启动项目

启动 AppMember.java

浏览器访问 http://localhost:8501/discoveryServer

image.png

查看原文

赞 0 收藏 0 评论 0

isWulongbo 发布了文章 · 1月21日

SpringCloud整合Consul

下载安装Consul

访问Consul 官网下载 Consul 的最新版本,我这里是 consul_1.9.1。

这里以 Windows 为例,下载下来是一个 consul_1.9.1_windows_amd64.zip 的压缩包,解压是是一个 consul.exe 的执行文件。

image.png

启动Consul

cd 到对应的目录下,使用 cmd 启动 Consul

cd E:\迅雷下载\consul_1.9.1_windows_amd64
#cmd启动:
consul agent -dev        # -dev表示开发模式运行,另外还有-server表示服务模式运行

为了方便期间,可以在同级目录下创建一个 run.bat 脚本来启动,脚本内容如下:

consul agent -dev
pause

image.png

启动run.bat 脚本成功之后访问:http://localhost:8500,可以看到 Consul 的管理界面
image.png

springcloud整合Consul

由于比较简单在这里我们就不再新建项目了,我们基于之前的项目SpringCloud整合Zookeeper 来做修改。

修改pom依赖

我们只需要把原先的 zookeeper依赖替换为 consul 依赖即可

<!--springcloud 整合zookeeper客户端-->
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<!--springcloud 整合consul-->
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

替换后如下:

<?xml version="1.0" encoding="UTF-8"?>
<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>springcloud-zookeeper</artifactId>
 <groupId>com.baba.wlb</groupId>
 <version>1.0-SNAPSHOT</version>
 </parent> <modelVersion>4.0.0</modelVersion>
 <artifactId>springcloud-zookeeper-member</artifactId>
 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Finchley.M7</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency> </dependencies> </dependencyManagement>
 <dependencies>
 <!--springboot 整合web组件-->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <!--springcloud 整合zookeeper客户端-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>-->
<!--        </dependency>-->
 <!--springcloud 整合consul-->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-consul-discovery</artifactId>
 </dependency>
 </dependencies>
 <!--注意:这里必须添加,否则各种依赖有问题-->
 <repositories>
 <repository> <id>spring-milestones</id>
 <name>Spring Milestones</name>
 <url>https://repo.spring.io/libs-milestone</url>
 <snapshots> <enabled>false</enabled>
 </snapshots> </repository> </repositories></project>

修改yml配置文件

为了方便切换 zookeeperconsul 我们调整一下yml 配置即可:

application.yml文件:

spring:
  profiles:
    active: consul

application-zk.yml文件:

##服务器端口号
server:
  port: 7001
##dubbo 注册到注册中心的名称
spring:
  application:
    name: zk-member
  cloud:
    zookeeper:
      connect-string: 39.102.56.91:2181

application-consul.yml文件:

##服务端口号
server:
  port: 8501
spring:
  application:
    ##服务别名--服务注册到consul名称
 name: consul-member
  ##注册中心consul地址
 cloud:
    consul:
      host: localhost
      port: 8500
 discovery:
        ## consul ip地址
 hostname: 192.168.3.91

启动项目

启动 AppMember.java

启动类的注解都为 @EnableDiscoveryClient
image.png

访问 http://localhost:8500/ui/dc1/services
image.png

可以看到 member 服务注册到 sonsul 上来。

查看原文

赞 0 收藏 0 评论 0

isWulongbo 发布了文章 · 1月21日

SpringCloud整合Zookeeper

创建项目

使用 Spring Initializr 新建一个项目命名为: springcloud-zookeeper,删除多余的文件,并新建两个子模块分别为:springcloud-zookeeper-memberspringcloud-zookeeper-order
image.png

父工程 pom.xml配置文件spring-boot 版本这里选用 2.0.1.RELEASE

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <packaging>pom</packaging>
 <modules> <module>springcloud-zookeeper-member</module>
 <module>springcloud-zookeeper-order</module>
 </modules> <parent> <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.0.1.RELEASE</version>
 <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <groupId>com.baba.wlb</groupId>
 <artifactId>springcloud-zookeeper</artifactId>
 <version>1.0-SNAPSHOT</version>
 <name>springcloud-zookeeper</name>
 <description>Demo project for Spring Boot</description>
 <properties> <java.version>1.8</java.version>
 </properties> <dependencies> <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> <dependency> <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <optional>true</optional>
 </dependency> <dependency> <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency> </dependencies>
 <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>
</project>

子模块

springcloud-zookeeper-member模块:

image.png

pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<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>springcloud-zookeeper</artifactId>
 <groupId>com.baba.wlb</groupId>
 <version>1.0-SNAPSHOT</version>
 </parent> <modelVersion>4.0.0</modelVersion>
 <artifactId>springcloud-zookeeper-member</artifactId>
 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Finchley.M7</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency> </dependencies> </dependencyManagement>
 <dependencies>
 <!--springboot 整合web组件-->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <!--springcloud 整合zookeeper客户端-->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
 </dependency> </dependencies>
 <!--注意:这里必须添加,否则各种依赖有问题-->
 <repositories>
 <repository> <id>spring-milestones</id>
 <name>Spring Milestones</name>
 <url>https://repo.spring.io/libs-milestone</url>
 <snapshots> <enabled>false</enabled>
 </snapshots> </repository> </repositories></project>

application.yml配置文件:

##服务器端口号
server:
  port: 7001
##dubbo 注册到注册中心的名称
spring:
  application:
    name: zk-member
  cloud:
    zookeeper:
      connect-string: 39.102.56.91:2181

MemberApiController 控制页面:

package com.baba.wlb.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:20
 * @Version 1.0
 */@RestController
public class MemberApiController {
    @Value("${server.port}")
    private String serverPort;
 @RequestMapping("/getMember")
    public String getMember() {
        return "我是会员服务!端口号:"+serverPort;
 }
}

AppMember 启动类:

package com.baba.wlb.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:22
 * @Version 1.0
 */@EnableDiscoveryClient
@SpringBootApplication
public class AppMember {
    // @EnableDiscoveryClient作用是 如果服务使用zookeeper或者connsul,使用@EnableDiscoveryClient向注册中心注册服务
 public static void main(String[] args) {
        SpringApplication.run(AppMember.class,args);
 }
}

springcloud-zookeeper-order模块:

image.png

pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<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>springcloud-zookeeper</artifactId>
 <groupId>com.baba.wlb</groupId>
 <version>1.0-SNAPSHOT</version>
 </parent> <modelVersion>4.0.0</modelVersion>
 <artifactId>springcloud-zookeeper-order</artifactId>
 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Finchley.M7</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency> </dependencies> </dependencyManagement>
 <dependencies>
 <!--springboot 整合web组件-->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <!--springcloud 整合zookeeper客户端-->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
 </dependency> </dependencies>
 <!--注意:这里必须添加,否则各种依赖有问题-->
 <repositories>
 <repository> <id>spring-milestones</id>
 <name>Spring Milestones</name>
 <url>https://repo.spring.io/libs-milestone</url>
 <snapshots> <enabled>false</enabled>
 </snapshots> </repository> </repositories></project>

application.yml配置文件:

##服务器端口号
server:
  port: 7002
##dubbo 注册到注册中心的名称
spring:
  application:
    name: zk-order
  cloud:
    zookeeper:
      connect-string: 39.102.56.91:2181

OrderApiController 控制页面:

package com.baba.wlb.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:32
 * @Version 1.0
 */@RestController
public class OrderApiController {
    @Autowired
 private RestTemplate restTemplate;
 /**
 * springCloud中,两种方式调用(rest/feign)
 *
 * @return
 */
 // 订单服务调用会员服务
 @RequestMapping("/getOrder")
    public String getOrder() {
        // 有两种调用方式,一种是采用服务别名方式调用,另一种是使用别名去注册中心上获取对应服务调用地址
 // 第一种方式
 String url = "http://dy-202006281547:8000/getMember";
 // 第二种方式
 url = "http://zk-member/getMember";
 String result = restTemplate.getForObject(url, String.class);
 return "订单服务调用会员服务:" + result;
 }
}

AppOrder.java 启动类:

package com.baba.wlb.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:39
 * @Version 1.0
 */@SpringBootApplication
@EnableDiscoveryClient
public class AppOrder {
    public static void main(String[] args) {
        SpringApplication.run(AppOrder.class,args);
 // 第二种方式 如果使用rest方式以别名方式进行调用依赖ribbon负载均衡器
 // 第二种方式 @LoadBalanced能让restTemplate 模板在请求时拥有客户端负载均衡的能力
 }
    // 解决RestTemplate 找不到原因, 把RestTemplate注册到Springboot容器中 @Bean
 // 第一种方式
//    @Bean
//    RestTemplate restTemplate(){
//        return new RestTemplate();
//    }
 // 第二种方式 @LoadBalanced能让restTemplate 模板在请求时拥有客户端负载均衡的能力
 @Bean
 @LoadBalanced RestTemplate restTemplate(){
        return new RestTemplate();
 }
}

启动项目

启动 AppMember.javaAppOrder.java

image.png

image.png

查看zookeeper上新注册了两个临时节点
image.png

浏览器访问 http://localhost:7002/getOrderorder服务可以调用到member服务。
image.png
读者也可以给member服务做集群,启动多个member服务,实现负载均衡。

查看原文

赞 0 收藏 0 评论 0

isWulongbo 发布了文章 · 1月21日

SpringCloud整合之Eureka自我保护机制

为什么有自我保护机制

防止由于网络波动情况下误剔client,进行90s的保活。

什么情况下开启(关闭)保护机制

一般本地测试环境关闭保护机制,生成环境开启保护机制

关闭保护机制

  • Server端 application.yml 配置
##服务端口号
server:
  port: 8100
spring:
  application:
    ##Eureka集群使用,名称必须一致
 name: baba-eureka
eureka:
  instance:
    ##服务注册中心ip地址
 hostname: 127.0.0.1
  client:
    serviceUrl:
      ##注册地址
#      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
 defaultZone: http://${eureka.instance.hostname}:9100/eureka/
    ##因为自己是注册中心,是否需要自己将自己注册到注册中心(集群的时候为true)
 register-with-eureka: true
 ##因为自己是注册中心,不需要去检索服务信息
 fetch-registry: true
 server:
    ##测试时关闭自我保护机制,保证不可用服务及时剔除
 enable-self-preservation: false
 eviction-interval-timer-in-ms: 2000
  • Clinet端 application.yml 配置
##服务端口号
server:
  port: 8200
spring:
  application:
    ##服务别名--服务注册到Eureka名称
 name: app-order
eureka:
  client:
    service-url:
      ##当前服务注册到Eureka服务地址
 defaultZone: http://localhost:8100/eureka,http://localhost:9100/eureka
    register-with-eureka: true
 ## 需要检索服务信息
 fetch-registry: true
 ##心跳检测与续约时间
 ##测试的时候将值设置小一些,保证服务关闭后注册中心能及时剔除服务
 instance:
    ##Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端会告诉服务端会按照该规则)
 lease-renewal-interval-in-seconds: 1
    ##Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端会告诉服务端会按照该规则)
 lease-expiration-duration-in-seconds: 2
查看原文

赞 0 收藏 0 评论 0

isWulongbo 发布了文章 · 1月19日

SpringCloud整合之Eureka高可用集群

前言

上篇博客 SpringCloud整合之Eureka集群,我们了解了Eureka server的集群,下面我们看看客户端如何连接。

Eureka-Client

客户端配置

我们只需要修改客户端 application.yml 配置文件中的 defaultZoneEureka 集群地址即可

application-dev.yml配置文件:

##服务端口号
server:
  port: 8000
spring:
  application:
    ##服务别名--服务注册到Eureka名称
 name: app-member
eureka:
  client:
    service-url:
      ##当前服务注册到Eureka服务地址
 defaultZone: http://localhost:8100/eureka,http://localhost:9100/eureka
    register-with-eureka: true
 ## 需要检索服务信息
 fetch-registry: true

application-prod.yml配置文件:

##服务端口号
server:
  port: 8010
spring:
  application:
    ##服务别名--服务注册到Eureka名称
 name: app-member
eureka:
  client:
    service-url:
      ##当前服务注册到Eureka服务地址
 defaultZone: http://localhost:8100/eureka,http://localhost:9100/eureka
    register-with-eureka: true
 ## 需要检索服务信息
 fetch-registry: true

同理我们修改order模块 application.yml

##服务端口号
server:
  port: 8200
spring:
  application:
    ##服务别名--服务注册到Eureka名称
 name: app-order
eureka:
  client:
    service-url:
      ##当前服务注册到Eureka服务地址
 defaultZone: http://localhost:8100/eureka,http://localhost:9100/eureka
    register-with-eureka: true
 ## 需要检索服务信息
 fetch-registry: true

image.png

启动服务

  • 启动 EurekaServerApplication.java 以及 ColonyApplication.java 两个 eureka server服务
  • 启动 AppMember.java 以及 AppOrder.java两个 eureka client服务
  • 发现client服务只注册到了 8100 并没有注册到 9100 上来image.pngimage.png

注:因为在注册过程当中只会保证只有一台注册中心服务有对应服务信息数据

  • 8100注册中心 宕机后,启动转移同步数据到 9100注册中心上去

我们停掉 8100注册中心,等待30s时间,查看 9100注册中心
image.png
image.png
发现服务已经转移到 9100注册中心

查看原文

赞 0 收藏 0 评论 0

isWulongbo 发布了文章 · 1月18日

SpringCloud整合之Eureka集群

前言

该博客基于SpringCloud整合之Eureka

原理

相互注册,你中有我,我中有你。

配置

Eureka server 的集群版,需要修改 register-with-eureka:truefetch-registry 为true来支持集群。并且相互注册的服务名称必须一致。
springcloud-eureka-server服务中修改配置文件 application.yml

##服务端口号
server:
  port: 8100
spring:
  application:
    ##Eureka集群使用,名称必须一致
 name: baba-eureka
eureka:
  instance:
    ##服务注册中心ip地址
 hostname: 127.0.0.1
  client:
    serviceUrl:
      ##注册地址
#      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
 defaultZone: http://${eureka.instance.hostname}:9100/eureka/
    ##因为自己是注册中心,是否需要自己将自己注册到注册中心(集群的时候为true)
 register-with-eureka: true
 ##因为自己是注册中心,不需要去检索服务信息
 fetch-registry: true

image.png

创建Eureka集群

新建一个项目 eureka-colony
并 copy eureka-serverpom 配置如下
image.png

pom.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent> <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.0.1.RELEASE</version>
 <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <groupId>com.baba.wlb</groupId>
 <artifactId>colony</artifactId>
 <version>1.0-SNAPSHOT</version>
 <name>colony</name>
 <description>Demo project for Spring Boot</description>
 <properties> <java.version>1.8</java.version>
 </properties>
 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Finchley.M7</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency> </dependencies> </dependencyManagement>
 <dependencies> <!--SpringCloud Eureka Server-->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
 </dependency> </dependencies>
 <!--注意:这里必须添加,否则各种依赖有问题-->
 <repositories>
 <repository> <id>spring-milestones</id>
 <name>Spring Milestones</name>
 <url>https://repo.spring.io/libs-milestone</url>
 <snapshots> <enabled>false</enabled>
 </snapshots> </repository> </repositories>
 <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>
</project>

application.yml配置文件:

##服务端口号
server:
  port: 9100
spring:
  application:
    ##Eureka集群使用,名称必须一致
 name: baba-eureka
eureka:
  instance:
    ##服务注册中心ip地址
 hostname: 127.0.0.1
  client:
    serviceUrl:
      ##注册地址
 defaultZone: http://${eureka.instance.hostname}:8100/eureka/
    ##因为自己是注册中心,是否需要自己将自己注册到注册中心(集群的时候为true)
 register-with-eureka: true
 ##因为自己是注册中心,不需要去检索服务信息
 fetch-registry: true

启动项目

分别启动 eureka-server81009100
image.png
image.png
并访问http://localhost:8100/
http://localhost:9100/
image.png
image.png
至此eureka集群就部署完毕!如果需要加入多台只需要修改 application.yml 默认节点即可:

defaultZone: http://${eureka.instance.hostname}:7100/eureka/,http://${eureka.instance.hostname}:8100/eureka/
查看原文

赞 0 收藏 0 评论 0

isWulongbo 发布了文章 · 1月18日

SpringCloud整合之Eureka

前言

我们前面的博客已经学习了 springboot 如何整合 dubbo 框架
springboot整合dubbo

在整合 SpringCloud 前,大家有没有想过他和 dubbo 有什么差异:

  1. 来源(背景):Dubbo,是阿里巴巴服务化治理的核心框架,并被广泛应用于阿里巴巴集团的各成员站点。Spring Cloud,从命名我们就可以知道,它是Spring Source的产物,Spring社区的强大背书可以说是Java企业界最有影响力的组织了,除了Spring Source之外,还有Pivotal和Netfix是其强大的后盾与技术输出。其中Netflix开源的整套微服务架构套件是Spring Cloud的核心。简而言之dubbo是阿里写的属于国产,springcloud 属于国际版应用更广。

2.注册中心:dubbo支持的注册中心为:Zookeeper(官方推荐)MulticastRedisSimple,我们常用为zk。springcloud 支持的注册中心有EurekaZookeeperConsulNacos,各个注册中心的区别请查阅springcloud四个注册中心的比较这里我使用的是已经闭源的EurekaZooKeeper是个CP强一致性和分区容错性,而EurekaAP原则,舍弃了强一致性,保证了高可用。

  1. 传输:Dubbo由于是二进制的传输,占用带宽会更少;Spring Cloud是http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大。但是在国内95%的公司内,网络消耗不是什么太大问题,如果真的成了问题,通过压缩、二进制、高速缓存、分段降级等方法,很容易解。
  2. 开发难度:Dubbo的开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决;Spring Cloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级
  3. 后续改进:Dubbo通过dubbofilter,很多东西没有,需要自己继承,如监控,如日志,如限流,如追踪
  4. 配置中心: dubbo:如果我们使用配置中心、分布式跟踪这些内容都需要自己去集成,无形中增加了使用难度。
  5. 核心部件的比较:

Dubbo:

  • Provider:暴露服务的提供方,可以通过 jar 或者容器的方式启动服务。
  • Consumer:调用远程服务的服务消费方。
  • Registry:服务注册中心和发现中心。
  • Monitor:统计服务和调用次数,调用时间监控中心。(Dubbo 的控制台页面中可以显示,目前只有一个简单版本。)
  • Container:服务运行的容器。

Spring Cloud:

  • Service Provider: 暴露服务的提供方。
  • Service Consumer:调用远程服务的服务消费方。
  • EureKa Server: 服务注册中心和服务发现中心。

Spring Cloud:提供了微服务的一整套解决方案:服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等

  1. 架构的完整度:Dubbo只是实现了服务治理;Spring Cloud下面有17个子项目(可能还会新增)分别覆盖了微服务架构下的方方面面,服务治理只是其中的一个方面;一定程度来说,Dubbo只是Spring Cloud Netflix中的一个子集。
  2. 服务依赖方式: Dubbo:服务提供方与消费方通过接口的方式依赖,服务调用设计如下:
  • Interface 层:服务接口层,定义了服务对外提供的所有接口。
  • Molel 层:服务的 DTO 对象层。
  • Business层:业务实现层,实现 Interface 接口并且和 DB 交互。

因此需要为每个微服务定义各自的 Interface 接口,并通过持续集成发布到私有仓库中。调用方应用对微服务提供的抽象接口存在强依赖关系,开发、测试、集成环境都需要严格的管理版本依赖。

Spring Cloud:
服务提供方和服务消费方通过 Json 方式交互,因此只需要定义好相关 Json 字段即可,消费方和提供方无接口依赖。通过注解方式来实现服务配置,对于程序有一定入侵。
通过 Json 交互,省略了版本管理的问题,但是具体字段含义需要统一管理,自身 Rest API 方式交互,为跨平台调用奠定了基础。

总体:

Dubbo:使用Dubbo构建的微服务架构就像组装电脑,各环节我们的选择自由度很高,但是最终结果很有可能因为一条内存质量不行就点不亮了,总是让人不怎么放心,但是如果你是一名高手,那这些都不是问题;

Spring Cloud就像品牌机,在Spring Source的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础有足够的了解。

优缺点(综上得到):

Dubbo

优点:

1.支持各种通信协议,而且消费方和服务方使用长链接方式交互,通信速度上略胜 ;

2.采用rpc方式,性能上比Spring Cloud的rpc更好;

3.dubbo的网络消耗小于springcloud

缺点:

1.如果我们使用配置中心、分布式跟踪这些内容都需要自己去集成;

2.开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决;

3.

Spring Cloud:

优点:

1、产出于Spring大家族,Spring在企业级开发框架中来头很大,可以保证后续的更新、完善。

2、spring cloud社区活跃,教程丰富,遇到问题很容易找到解决方案;

3、spring cloud功能比dubbo更加完善;

5、spring cloud采用rest访问方式,rest的技术无关性使用效果更棒;

6、spring cloud轻轻松松几行代码就完成了熔断、均衡负责、服务中心的各种平台功能;

7、从公司招聘工程师方面,spring cloud更有优势,因为其技术更新更炫;

8、提供了微服务的一整套解决方案:服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等;作为一个微服务治理的大家伙,考虑的很全面,几乎服务治理的方方面面都考虑到了,方便开发开箱即用;

缺点:

1.如果对于系统的响应时间有严格要求,长链接更合适。

2.接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级


参考自:

https://blog.csdn.net/u010664947/article/details/80007767

https://blog.csdn.net/zuiyingong6567/article/details/80229310

创建项目

使用 Spring Initializr 新建一个项目命名为: springcloud-parent,删除多余的文件,并新建三个子模块分别为:springcloud-eureka-serverspringcloud-memberspringcloud-order
image.png

父工程 pom.xml配置文件spring-boot 版本这里选用 2.0.1.RELEASE

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <packaging>pom</packaging>
 <modules> <module>springcloud-eureka-server</module>
 <module>springcloud-member</module>
 <module>springcloud-order</module>
 </modules> <parent> <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.0.1.RELEASE</version>
 <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <groupId>com.baba.wlb</groupId>
 <artifactId>springcloud-parent</artifactId>
 <version>1.0-SNAPSHOT</version>
 <name>springcloud-parent</name>
 <description>Demo project for Spring Boot</description>
 <properties> <java.version>1.8</java.version>
 </properties>
 <dependencies> <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> <dependency> <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <optional>true</optional>
 </dependency> <dependency> <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>
 </dependencies>
 <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>
</project>

子模块

springcloud-eureka-server模块:

image.png

pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<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>springcloud-parent</artifactId>
 <groupId>com.baba.wlb</groupId>
 <version>1.0-SNAPSHOT</version>
 </parent> <modelVersion>4.0.0</modelVersion>
 <artifactId>springcloud-eureka-server</artifactId>
 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Finchley.M7</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency> </dependencies> </dependencyManagement>
 <dependencies> <!--SpringCloud Eureka Server-->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
 </dependency> </dependencies>
 <!--注意:这里必须添加,否则各种依赖有问题-->
 <repositories>
 <repository> <id>spring-milestones</id>
 <name>Spring Milestones</name>
 <url>https://repo.spring.io/libs-milestone</url>
 <snapshots> <enabled>false</enabled>
 </snapshots> </repository> </repositories></project>

application.yml配置文件:

##服务端口号
server:
  port: 8100
eureka:
  instance:
    ##服务注册中心ip地址
 hostname: 127.0.0.1
  client:
    serviceUrl:
      ##注册地址
 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    ##因为自己是注册中心,是否需要自己将自己注册到注册中心(集群的时候为true)
 register-with-eureka: false
 ##因为自己是注册中心,不需要去检索服务信息
 fetch-registry: false

EurekaServerApplication 启动类:

package com.baba.wlb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    //@EnableEurekaServer 表示开启Eureka服务 开启注册中心
 public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
 }
}

springcloud-member模块:

image.png

pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<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>springcloud-parent</artifactId>
 <groupId>com.baba.wlb</groupId>
 <version>1.0-SNAPSHOT</version>
 </parent> <modelVersion>4.0.0</modelVersion>
 <artifactId>springcloud-member</artifactId>
 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Finchley.M7</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency> </dependencies> </dependencyManagement>
 <dependencies> <!--Springboot 整合web组件-->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <!--SpringBoot 整合eureka客户端-->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 </dependency>
 </dependencies>
 <!--注意:这里必须添加,否则各种依赖有问题-->
 <repositories>
 <repository> <id>spring-milestones</id>
 <name>Spring Milestones</name>
 <url>https://repo.spring.io/libs-milestone</url>
 <snapshots> <enabled>false</enabled>
 </snapshots> </repository> </repositories></project>

application.yml配置文件:

spring:
  profiles:
    active: dev

application-dev.yml配置文件:

##服务端口号
server:
  port: 8000
spring:
  application:
    ##服务别名--服务注册到Eureka名称
 name: app-member
eureka:
  client:
    service-url:
      ##当前服务注册到Eureka服务地址
 defaultZone: http://localhost:8100/eureka
    register-with-eureka: true
 ## 需要检索服务信息
 fetch-registry: true

application-prod.yml配置文件:

##服务端口号
server:
  port: 8010
spring:
  application:
    ##服务别名--服务注册到Eureka名称
 name: app-member
eureka:
  client:
    service-url:
      ##当前服务注册到Eureka服务地址
 defaultZone: http://localhost:8100/eureka
    register-with-eureka: true
    ## 需要检索服务信息
 fetch-registry: true

MemberApiController 控制页面:

package com.baba.wlb.api.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:20
 * @Version 1.0
 */@RestController
public class MemberApiController {
    @Value("${server.port}")
    private String serverPort;
 @RequestMapping("/getMember")
    public String getMember() {
        return "我是会员服务!端口号:"+serverPort;
 }
}

AppMember.java 启动类:

package com.baba.wlb.api.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:22
 * @Version 1.0
 */
@EnableEurekaClient
@SpringBootApplication
public class AppMember {
    public static void main(String[] args) {
        SpringApplication.run(AppMember.class,args);
 }
}

springcloud-order模块:

image.png

pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<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>springcloud-parent</artifactId>
 <groupId>com.baba.wlb</groupId>
 <version>1.0-SNAPSHOT</version>
 </parent> <modelVersion>4.0.0</modelVersion>
 <artifactId>springcloud-order</artifactId>
 <!--管理依赖-->
 <dependencyManagement>
 <dependencies> <dependency> <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Finchley.M7</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency> </dependencies> </dependencyManagement>
 <dependencies> <!--Springboot 整合web组件-->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <!--SpringBoot 整合eureka客户端-->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 </dependency>
 </dependencies>
 <!--注意:这里必须添加,否则各种依赖有问题-->
 <repositories>
 <repository> <id>spring-milestones</id>
 <name>Spring Milestones</name>
 <url>https://repo.spring.io/libs-milestone</url>
 <snapshots> <enabled>false</enabled>
 </snapshots> </repository> </repositories></project>

application.yml 配置文件:

##服务端口号
server:
  port: 8200
spring:
  application:
    ##服务别名--服务注册到Eureka名称
 name: app-order
eureka:
  client:
    service-url:
      ##当前服务注册到Eureka服务地址
 defaultZone: http://localhost:8100/eureka
    register-with-eureka: true
 ## 需要检索服务信息
 fetch-registry: true

OrderApiController 控制页面:
springCloud中,两种方式调用(rest/feign)

package com.baba.wlb.api.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:32
 * @Version 1.0
 */@RestController
public class OrderApiController {
    @Autowired
 private RestTemplate restTemplate;
 /**
 * springCloud中,两种方式调用(rest/feign)
 *
 * @return
 */
 // 订单服务调用会员服务
 @RequestMapping("/getOrder")
    public String getOrder() {
        // 有两种调用方式,一种是采用服务别名方式调用,另一种是使用别名去注册中心上获取对应服务调用地址
 // 第一种方式
 String url = "http://dy-202006281547:8000/getMember";
 // 第二种方式
 url = "http://app-member/getMember";
 String result = restTemplate.getForObject(url, String.class);
 return "订单服务调用会员服务:" + result;
 }
}

AppOrder.java 启动类:
在启动类中注入RestTemplate bean对象。

package com.baba.wlb.api.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
 * @Author wulongbo
 * @Date 2021/1/9 15:39
 * @Version 1.0
 */@SpringBootApplication
@EnableEurekaClient
public class AppOrder {
    public static void main(String[] args) {
        SpringApplication.run(AppOrder.class,args);
 // 第二种方式 如果使用rest方式以别名方式进行调用依赖ribbon负载均衡器
 // 第二种方式 @LoadBalanced能让restTemplate 模板在请求时拥有客户端负载均衡的能力
 }
    // 解决RestTemplate 找不到原因, 把RestTemplate注册到Springboot容器中 @Bean
 // 第一种方式
//    @Bean
//    RestTemplate restTemplate(){
//        return new RestTemplate();
//    }
 // 第二种方式 @LoadBalanced能让restTemplate 模板在请求时拥有客户端负载均衡的能力
 @Bean
 @LoadBalanced RestTemplate restTemplate(){
        return new RestTemplate();
 }
}

启动项目

启动 EurekaServerApplication Eureka服务 开启注册中心

image.png

启动 member服务:

  • 启动AppMember.java,加载的是dev开发环境

image.png
并访问http://localhost:8100/,可以看到注册了别名为 APP-MEMBER的服务
image.png

  • 再启动生产环境,这里我们使用直接启动jar包的方式
  1. 找到jar包位置image.png

为:E:\ideaworkspaceback\springcloud-parent\springcloud-eureka-server\target
2.打开 Terminal
image.png
3.执行命令:java -jar E:\ideaworkspaceback\springcloud-parent\springcloud-member\target\springcloud-member-1.0-SNAPSHOT.jar --spring.profiles.active=prod 启动生产环境
image.png
4.访问http://localhost:8100/,可以看到注册了别名为 APP-MEMBER的服务有两个
image.png
我们点击上图服务地址,并修改http://dy-202006281547:8000/infohttp://dy-202006281547:8000/getMember即可访问member服务
image.png

启动 order服务:

启动AppOrder.java
image.png
并访问http://localhost:8100/,可以看到注册了别名为 APP-ORDER的服务
image.png

测试

Postman 访问localhost:8084/orderToMember
image.png
多次提交可发现做了负载均衡。

总结

springcloud集成Eureka还是很简单,使用注解就完事了!

查看原文

赞 0 收藏 0 评论 0

isWulongbo 发布了文章 · 1月15日

推荐一款 IDEA 神器 ,人工智能帮你写代码,再也不用加班了!!

以下文章来源于Java技术栈 ,作者栈长

简介

Codota 是一款优秀的 AI 代码自动完成工具,可以帮助我们极大的提高开发效率。

官网:https://www.codota.com/

支持主流语言:

Java, Javascript, TypeScript, Python, PHP, Go, Ruby, C, C++, Rust, C# ……

支持主流开发工具:
image

代码私有:

Codota 不会将代码发送到 Codota 服务器,只会从当前编辑的文件中发送最小的上下文信息,以便帮助 Codota 在当前本机范围内做出预测。

使用

下面以 IntelliJ IDEA 为例演示 Codota 到底有多强大。

先安装 Codota 插件:
image.png

1、代码自动完成行

Codota 基于数百万个开源的 Java 程序代码自动完成代码行,根据自动提示的上下文,可以帮助我们更快地编写代码,错误更少。

输出一个类的字母,就提示常用的类列表:
image.png

输完变量名或者 = 号之后,还知道你可能需要干什么:
image.png

输完对象后,展示一个类常用的方法列表:
image.png

2、代码示例

Java API 不会用?还要到处找使用示例?Codota 可以帮你找出最佳开源代码相关示例。

如:你对 stream 不熟悉,只需要点中该方法,然后选择获取相关示例菜单,最佳使用示例都帮你呈现出来了。
image.png
image.png

太棒了!
如果需要 API 的详细的解释说明,可以跳转到网页查看。

也可以手动在 codota 代码索引库中查询对应 API 的最佳使用示例:

https://www.codota.com/code/java/class-index

总结

Codota 基于数百万的开源代码帮助我们自动提示完成行、代码示例功能真的太强大了,大大提升了开发效率,这个足已帮到许多人了。

Codota 能分析你的代码习惯,人工智能协助你写代码,自动走在你的前面,以后再也不用加班咯。。

查看原文

赞 3 收藏 1 评论 1

isWulongbo 发布了文章 · 1月8日

定时任务的实现原理,看完就能手撸一个!

以下文章来源于Java极客技术 ,作者鸭血粉丝

一、摘要

在很多业务的系统中,我们常常需要定时的执行一些任务,例如定时发短信、定时变更数据、定时发起促销活动等等。

在上篇文章中,我们简单的介绍了定时任务的使用方式,不同的架构对应的解决方案也有所不同,总结起来主要分单机分布式两大类,本文会重点分析下单机的定时任务实现原理以及优缺点,分布式框架的实现原理会在后续文章中进行分析。

从单机角度,定时任务实现主要有以下 3 种方案:

  • while + sleep 组合
  • 最小堆实现
  • 时间轮实现

二、while+sleep组合

while+sleep 方案,简单的说,就是定义一个线程,然后 while 循环,通过 sleep 延迟时间来达到周期性调度任务。

简单示例如下:

public static void main(String[] args) {
    final long timeInterval = 5000;
    new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                System.out.println(Thread.currentThread().getName() + "每隔5秒执行一次");
                try {
                    Thread.sleep(timeInterval);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();
}

实现上非常简单,如果我们想在创建一个每隔3秒钟执行一次任务,怎么办呢?

同样的,也可以在创建一个线程,然后间隔性的调度方法;但是如果创建了大量这种类型的线程,这个时候会发现大量的定时任务线程在调度切换时性能消耗会非常大,而且整体效率低!

面对这种在情况,大佬们也想到了,于是想出了用一个线程将所有的定时任务存起来,事先排好序,按照一定的规则来调度,这样不就可以极大的减少每个线程的切换消耗吗?

正因此,JDK 中的 Timer 定时器由此诞生了!

三、最小堆实现

所谓最小堆方案,正如我们上面所说的,每当有新任务加入的时候,会把需要即将要执行的任务排到前面,同时会有一个线程不断的轮询判断,如果当前某个任务已经到达执行时间点,就会立即执行,具体实现代表就是 JDK 中的 Timer 定时器!

3.1、Timer

首先我们来一个简单的 Timer 定时器例子

public static void main(String[] args) {
    Timer timer = new Timer();
    //每隔1秒调用一次
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("test1");
        }
    }, 1000, 1000);
    //每隔3秒调用一次
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("test2");
        }
    }, 3000, 3000);
}

实现上,好像跟我们上面介绍的 while+sleep 方案差不多,同样也是起一个TimerTask线程任务,只不过共用一个Timer调度器。

下面我们一起来打开源码看看里面到底有些啥!

  • 进入Timer.schedule()方法
从方法上可以看出,这里主要做参数验证,其中TimerTask是一个线程任务,delay表示延迟多久执行(单位毫秒),period表示多久执行一次(单位毫秒)
public void schedule(TimerTask task, long delay, long period) {
    if (delay < 0)
        throw new IllegalArgumentException("Negative delay.");
    if (period <= 0)
        throw new IllegalArgumentException("Non-positive period.");
    sched(task, System.currentTimeMillis()+delay, -period);
}
  • 接着看sched()方法
这步操作中,可以很清晰的看到,在同步代码块里,会将task对象加入到queue
private void sched(TimerTask task, long time, long period) {
    if (time < 0)
        throw new IllegalArgumentException("Illegal execution time.");
    // Constrain value of period sufficiently to prevent numeric
    // overflow while still being effectively infinitely large.
    if (Math.abs(period) > (Long.MAX_VALUE >> 1))
        period >>= 1;
    synchronized(queue) {
        if (!thread.newTasksMayBeScheduled)
            throw new IllegalStateException("Timer already cancelled.");
        synchronized(task.lock) {
            if (task.state != TimerTask.VIRGIN)
                throw new IllegalStateException(
                    "Task already scheduled or cancelled");
            task.nextExecutionTime = time;
            task.period = period;
            task.state = TimerTask.SCHEDULED;
        }
        queue.add(task);
        if (queue.getMin() == task)
            queue.notify();
    }
}
  • 我们继续来看queue对象
任务会将入到TaskQueue队列中,同时在Timer初始化阶段会将TaskQueue作为参数传入到TimerThread线程中,并且起到线程
public class Timer {
    
    private final TaskQueue queue = new TaskQueue();
    private final TimerThread thread = new TimerThread(queue);
    public Timer() {
        this("Timer-" + serialNumber());
    }
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }
    //...
}
  • TaskQueue其实是一个最小堆的数据实体类,源码如下
每当有新元素加入的时候,会对原来的数组进行重排,会将即将要执行的任务排在数组的前面
class TaskQueue {
    
    private TimerTask[] queue = new TimerTask[128];
    private int size = 0;
    void add(TimerTask task) {
        // Grow backing store if necessary
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);
        queue[++size] = task;
        fixUp(size);
    }
    private void fixUp(int k) {
        while (k > 1) {
            int j = k >> 1;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];
   queue[j] = queue[k];
   queue[k] = tmp;
            k = j;
        }
    }
 
 //....
}
  • 最后我们来看看TimerThread
TimerThread其实就是一个任务调度线程,首先从TaskQueue里面获取排在最前面的任务,然后判断它是否到达任务执行时间点,如果已到达,就会立刻执行任务
class TimerThread extends Thread {
    boolean newTasksMayBeScheduled = true;
    private TaskQueue queue;
    TimerThread(TaskQueue queue) {
        this.queue = queue;
    }
    public void run() {
        try {
            mainLoop();
        } finally {
            // Someone killed this Thread, behave as if Timer cancelled
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }
    /**
     * The main timer loop.  (See class comment.)
     */
    private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die
                    // Queue nonempty; look at first evt and do the right thing
                    long currentTime, executionTime;
                    task = queue.getMin();
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) { // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }
}

总结这个利用最小堆实现的方案,相比 while + sleep 方案,多了一个线程来管理所有的任务,优点就是减少了线程之间的性能开销,提升了执行效率;但是同样也带来的了一些缺点,整体的新加任务写入效率变成了 O(log(n))。

同时,细心的发现,这个方案还有以下几个缺点:

  • 串行阻塞:调度线程只有一个,长任务会阻塞短任务的执行,例如,A任务跑了一分钟,B任务至少需要等1分钟才能跑
  • 容错能力差:没有异常处理能力,一旦一个任务执行故障,后续任务都无法执行

3.2、ScheduledThreadPoolExecutor

鉴于 Timer 的上述缺陷,从 Java 5 开始,推出了基于线程池设计的 ScheduledThreadPoolExecutor 。

图片

其设计思想是,每一个被调度的任务都会由线程池来管理执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduledThreadPoolExecutor 才会真正启动一个线程,其余时间 ScheduledThreadPoolExecutor 都是在轮询任务的状态。

简单的使用示例:

public static void main(String[] args) {
    ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(3);
    //启动1秒之后,每隔1秒执行一次
    executor.scheduleAtFixedRate((new Runnable() {
        @Override
        public void run() {
            System.out.println("test3");
        }
    }),1,1, TimeUnit.SECONDS);
    //启动1秒之后,每隔3秒执行一次
    executor.scheduleAtFixedRate((new Runnable() {
        @Override
        public void run() {
            System.out.println("test4");
        }
    }),1,3, TimeUnit.SECONDS);
}

同样的,我们首先打开源码,看看里面到底做了啥

  • 进入scheduleAtFixedRate()方法
首先是校验基本参数,然后将任务作为封装到ScheduledFutureTask线程中,ScheduledFutureTask继承自RunnableScheduledFuture,并作为参数调用delayedExecute()方法进行预处理
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (period <= 0)
        throw new IllegalArgumentException();
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(period));
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    delayedExecute(t);
    return t;
}
  • 继续看delayedExecute()方法
可以很清晰的看到,当线程池没有关闭的时候,会通过super.getQueue().add(task)操作,将任务加入到队列,同时调用ensurePrestart()方法做预处理
private void delayedExecute(RunnableScheduledFuture<?> task) {
    if (isShutdown())
        reject(task);
    else {
        super.getQueue().add(task);
        if (isShutdown() &&
            !canRunInCurrentRunState(task.isPeriodic()) &&
            remove(task))
            task.cancel(false);
        else
   //预处理
            ensurePrestart();
    }
}
其中super.getQueue()得到的是一个自定义的new DelayedWorkQueue()阻塞队列,数据存储方面也是一个最小堆结构的队列,这一点在初始化new ScheduledThreadPoolExecutor()的时候,可以看出!
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
打开源码可以看到,DelayedWorkQueue其实是ScheduledThreadPoolExecutor中的一个静态内部类,在添加的时候,会将任务加入到RunnableScheduledFuture数组中,同时线程池中的Woker线程会通过调用任务队列中的take()方法获取对应的ScheduledFutureTask线程任务,接着执行对应的任务线程
static class DelayedWorkQueue extends AbstractQueue<Runnable>
        implements BlockingQueue<Runnable> {
    private static final int INITIAL_CAPACITY = 16;
    private RunnableScheduledFuture<?>[] queue =
        new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
    private final ReentrantLock lock = new ReentrantLock();
    private int size = 0;   
    //....
    public boolean add(Runnable e) {
        return offer(e);
    }
    public boolean offer(Runnable x) {
        if (x == null)
            throw new NullPointerException();
        RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            int i = size;
            if (i >= queue.length)
                grow();
            size = i + 1;
            if (i == 0) {
                queue[0] = e;
                setIndex(e, 0);
            } else {
                siftUp(i, e);
            }
            if (queue[0] == e) {
                leader = null;
                available.signal();
            }
        } finally {
            lock.unlock();
        }
        return true;
    }
    public RunnableScheduledFuture<?> take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                RunnableScheduledFuture<?> first = queue[0];
                if (first == null)
                    available.await();
                else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        return finishPoll(first);
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && queue[0] != null)
                available.signal();
            lock.unlock();
        }
    }
}
  • 回到我们最开始说到的ScheduledFutureTask任务线程类,最终执行任务的其实就是它
ScheduledFutureTask任务线程,才是真正执行任务的线程类,只是绕了一圈,做了很多包装,run()方法就是真正执行定时任务的方法。
private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> {
    /** Sequence number to break ties FIFO */
    private final long sequenceNumber;
    /** The time the task is enabled to execute in nanoTime units */
    private long time;
    /**
     * Period in nanoseconds for repeating tasks.  A positive
     * value indicates fixed-rate execution.  A negative value
     * indicates fixed-delay execution.  A value of 0 indicates a
     * non-repeating task.
     */
    private final long period;
    /** The actual task to be re-enqueued by reExecutePeriodic */
    RunnableScheduledFuture<V> outerTask = this;
    /**
     * Overrides FutureTask version so as to reset/requeue if periodic.
     */
    public void run() {
        boolean periodic = isPeriodic();
        if (!canRunInCurrentRunState(periodic))
            cancel(false);
        else if (!periodic)
            ScheduledFutureTask.super.run();
        else if (ScheduledFutureTask.super.runAndReset()) {
            setNextRunTime();
            reExecutePeriodic(outerTask);
        }
    }
 
 //...
}

3.3、小结

ScheduledExecutorService 相比 Timer 定时器,完美的解决上面说到的 Timer 存在的两个缺点!

在单体应用里面,使用 ScheduledExecutorService 可以解决大部分需要使用定时任务的业务需求!

但是这是否意味着它是最佳的解决方案呢?

我们发现线程池中 ScheduledExecutorService 的排序容器跟 Timer 一样,都是采用最小堆的存储结构,新任务加入排序效率是O(log(n)),执行取任务是O(1)

这里的写入排序效率其实是有空间可提升的,有可能优化到O(1)的时间复杂度,也就是我们下面要介绍的时间轮实现

四、时间轮实现

所谓时间轮(RingBuffer)实现,从数据结构上看,简单的说就是循环队列,从名称上看可能感觉很抽象。

它其实就是一个环形的数组,如图所示,假设我们创建了一个长度为 8 的时间轮。

图片

插入、取值流程:

  • 1.当我们需要新建一个 1s 延时任务的时候,则只需要将它放到下标为 1 的那个槽中,2、3、...、7也同样如此。
  • 2.而如果是新建一个 10s 的延时任务,则需要将它放到下标为 2 的槽中,但同时需要记录它所对应的圈数,也就是 1 圈,不然就和 2 秒的延时消息重复了
  • 3.当创建一个 21s 的延时任务时,它所在的位置就在下标为 5 的槽中,同样的需要为他加上圈数为 2,依次类推...

因此,总结起来有两个核心的变量:

  • 数组下标:表示某个任务延迟时间,从数据操作上对执行时间点进行取余
  • 圈数:表示需要循环圈数

通过这张图可以更直观的理解!

图片

当我们需要取出延时任务时,只需要每秒往下移动这个指针,然后取出该位置的所有任务即可,取任务的时间消耗为O(1)

当我们需要插入任务式,也只需要计算出对应的下表和圈数,即可将任务插入到对应的数组位置中,插入任务的时间消耗为O(1)

如果时间轮的槽比较少,会导致某一个槽上的任务非常多,那么效率也比较低,这就和 HashMap 的 hash 冲突是一样的,因此在设计槽的时候不能太大也不能太小。

4.1、代码实现

  • 首先创建一个RingBufferWheel时间轮定时任务管理器
public class RingBufferWheel {
    private Logger logger = LoggerFactory.getLogger(RingBufferWheel.class);
    /**
     * default ring buffer size
     */
    private static final int STATIC_RING_SIZE = 64;
    private Object[] ringBuffer;
    private int bufferSize;
    /**
     * business thread pool
     */
    private ExecutorService executorService;
    private volatile int size = 0;
    /***
     * task stop sign
     */
    private volatile boolean stop = false;
    /**
     * task start sign
     */
    private volatile AtomicBoolean start = new AtomicBoolean(false);
    /**
     * total tick times
     */
    private AtomicInteger tick = new AtomicInteger();
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private AtomicInteger taskId = new AtomicInteger();
    private Map<Integer, Task> taskMap = new ConcurrentHashMap<>(16);
    /**
     * Create a new delay task ring buffer by default size
     *
     * @param executorService the business thread pool
     */
    public RingBufferWheel(ExecutorService executorService) {
        this.executorService = executorService;
        this.bufferSize = STATIC_RING_SIZE;
        this.ringBuffer = new Object[bufferSize];
    }
    /**
     * Create a new delay task ring buffer by custom buffer size
     *
     * @param executorService the business thread pool
     * @param bufferSize      custom buffer size
     */
    public RingBufferWheel(ExecutorService executorService, int bufferSize) {
        this(executorService);
        if (!powerOf2(bufferSize)) {
            throw new RuntimeException("bufferSize=[" + bufferSize + "] must be a power of 2");
        }
        this.bufferSize = bufferSize;
        this.ringBuffer = new Object[bufferSize];
    }
    /**
     * Add a task into the ring buffer(thread safe)
     *
     * @param task business task extends {@link Task}
     */
    public int addTask(Task task) {
        int key = task.getKey();
        int id;
        try {
            lock.lock();
            int index = mod(key, bufferSize);
            task.setIndex(index);
            Set<Task> tasks = get(index);
            int cycleNum = cycleNum(key, bufferSize);
            if (tasks != null) {
                task.setCycleNum(cycleNum);
                tasks.add(task);
            } else {
                task.setIndex(index);
                task.setCycleNum(cycleNum);
                Set<Task> sets = new HashSet<>();
                sets.add(task);
                put(key, sets);
            }
            id = taskId.incrementAndGet();
            task.setTaskId(id);
            taskMap.put(id, task);
            size++;
        } finally {
            lock.unlock();
        }
        start();
        return id;
    }
    /**
     * Cancel task by taskId
     * @param id unique id through {@link #addTask(Task)}
     * @return
     */
    public boolean cancel(int id) {
        boolean flag = false;
        Set<Task> tempTask = new HashSet<>();
        try {
            lock.lock();
            Task task = taskMap.get(id);
            if (task == null) {
                return false;
            }
            Set<Task> tasks = get(task.getIndex());
            for (Task tk : tasks) {
                if (tk.getKey() == task.getKey() && tk.getCycleNum() == task.getCycleNum()) {
                    size--;
                    flag = true;
                    taskMap.remove(id);
                } else {
                    tempTask.add(tk);
                }
            }
            //update origin data
            ringBuffer[task.getIndex()] = tempTask;
        } finally {
            lock.unlock();
        }
        return flag;
    }
    /**
     * Thread safe
     *
     * @return the size of ring buffer
     */
    public int taskSize() {
        return size;
    }
    /**
     * Same with method {@link #taskSize}
     * @return
     */
    public int taskMapSize(){
        return taskMap.size();
    }
    /**
     * Start background thread to consumer wheel timer, it will always run until you call method {@link #stop}
     */
    public void start() {
        if (!start.get()) {
            if (start.compareAndSet(start.get(), true)) {
                logger.info("Delay task is starting");
                Thread job = new Thread(new TriggerJob());
                job.setName("consumer RingBuffer thread");
                job.start();
                start.set(true);
            }
        }
    }
    /**
     * Stop consumer ring buffer thread
     *
     * @param force True will force close consumer thread and discard all pending tasks
     *              otherwise the consumer thread waits for all tasks to completes before closing.
     */
    public void stop(boolean force) {
        if (force) {
            logger.info("Delay task is forced stop");
            stop = true;
            executorService.shutdownNow();
        } else {
            logger.info("Delay task is stopping");
            if (taskSize() > 0) {
                try {
                    lock.lock();
                    condition.await();
                    stop = true;
                } catch (InterruptedException e) {
                    logger.error("InterruptedException", e);
                } finally {
                    lock.unlock();
                }
            }
            executorService.shutdown();
        }
    }
    private Set<Task> get(int index) {
        return (Set<Task>) ringBuffer[index];
    }
    private void put(int key, Set<Task> tasks) {
        int index = mod(key, bufferSize);
        ringBuffer[index] = tasks;
    }
    /**
     * Remove and get task list.
     * @param key
     * @return task list
     */
    private Set<Task> remove(int key) {
        Set<Task> tempTask = new HashSet<>();
        Set<Task> result = new HashSet<>();
        Set<Task> tasks = (Set<Task>) ringBuffer[key];
        if (tasks == null) {
            return result;
        }
        for (Task task : tasks) {
            if (task.getCycleNum() == 0) {
                result.add(task);
                size2Notify();
            } else {
                // decrement 1 cycle number and update origin data
                task.setCycleNum(task.getCycleNum() - 1);
                tempTask.add(task);
            }
            // remove task, and free the memory.
            taskMap.remove(task.getTaskId());
        }
        //update origin data
        ringBuffer[key] = tempTask;
        return result;
    }
    private void size2Notify() {
        try {
            lock.lock();
            size--;
            if (size == 0) {
                condition.signal();
            }
        } finally {
            lock.unlock();
        }
    }
    private boolean powerOf2(int target) {
        if (target < 0) {
            return false;
        }
        int value = target & (target - 1);
        if (value != 0) {
            return false;
        }
        return true;
    }
    private int mod(int target, int mod) {
        // equals target % mod
        target = target + tick.get();
        return target & (mod - 1);
    }
    private int cycleNum(int target, int mod) {
        //equals target/mod
        return target >> Integer.bitCount(mod - 1);
    }
    /**
     * An abstract class used to implement business.
     */
    public abstract static class Task extends Thread {
        private int index;
        private int cycleNum;
        private int key;
        /**
         * The unique ID of the task
         */
        private int taskId ;
        @Override
        public void run() {
        }
        public int getKey() {
            return key;
        }
        /**
         *
         * @param key Delay time(seconds)
         */
        public void setKey(int key) {
            this.key = key;
        }
        public int getCycleNum() {
            return cycleNum;
        }
        private void setCycleNum(int cycleNum) {
            this.cycleNum = cycleNum;
        }
        public int getIndex() {
            return index;
        }
        private void setIndex(int index) {
            this.index = index;
        }
        public int getTaskId() {
            return taskId;
        }
        public void setTaskId(int taskId) {
            this.taskId = taskId;
        }
    }
    private class TriggerJob implements Runnable {
        @Override
        public void run() {
            int index = 0;
            while (!stop) {
                try {
                    Set<Task> tasks = remove(index);
                    for (Task task : tasks) {
                        executorService.submit(task);
                    }
                    if (++index > bufferSize - 1) {
                        index = 0;
                    }
                    //Total tick number of records
                    tick.incrementAndGet();
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    logger.error("Exception", e);
                }
            }
            logger.info("Delay task has stopped");
        }
    }
}
  • 接着,编写一个客户端,测试客户端
public static void main(String[] args) {
    RingBufferWheel ringBufferWheel = new RingBufferWheel( Executors.newFixedThreadPool(2));
    for (int i = 0; i < 3; i++) {
        RingBufferWheel.Task job = new Job();
        job.setKey(i);
        ringBufferWheel.addTask(job);
    }
}
public static class Job extends RingBufferWheel.Task{
    @Override
    public void run() {
        System.out.println("test5");
    }
}

运行结果:

test5
test5
test5

如果要周期性执行任务,可以在任务执行完成之后,再重新加入到时间轮中。

详细源码分析地址:[https://crossoverjie.top/2019...]

4.2、应用

时间轮的应用还是非常广的,例如在 Disruptor 项目中就运用到了 RingBuffer,还有Netty中的HashedWheelTimer工具原理也差不多等等,有兴趣的同学,可以阅读一下官方对应的源码!

五、小结

本文主要围绕单体应用中的定时任务原理进行分析,可能也有理解不对的地方,欢迎批评指出!

六、参考

1、简书 - 谈谈定时任务解决方案原理
2、crossoverJie's Blog - 延时消息之时间轮

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 66 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2020-07-13
个人主页被 2.7k 人浏览