前言

eureka 在springcloud体系中,主要实现一个注册中心的角色,所有的服务都将注册到eureke中,调用用者将从这里通过名称获取到对应的服务的IP集合列表。 作为一个分布式系统eureka在CAP中,选中了AP,优先保证可用性,放弃了强一致性,这样设计也符合注册中心的需要。 当发生网络分区故障(15分钟内超过85%的节点都没有正常的心跳),eureka会启用注册保护,即维持住当前心跳虽然失败的服务列表,并不进行删除。并能正常的提供查询服务(虽然不是最新)和注册服务(即不会同步到其他eureka节点),当网络恢复时,eureka会正确的同步信息,和恢复删除过期节点信息。

eureka的集群设计方式,采用了对等注册的形式完成。比如我们有三个节点的eureka集群,架构图如下

实现服务

简单的讲了下eureka的概念,下面我们进入编码模式。 当前最新的spring boot版本为2.1.7 我们将他作为我们的parent。主要是方便三方jar的版本管理和maven插件的使用。

1
2
3
4
5
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
</parent>

加入spring cloud的pom依赖,方便spring cloud 提供的相关jar的版本管理。这里需要将此依赖放入dependencyManagement

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<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>

有了这两个方便的pom之后。我们可以添加上eureka server的依赖。这里我将tomcat直接排除了,采用比较好用的undertow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

需要的依赖我们导入完毕,接下来我们需要配置插件,一个插件是spring-boot-maven-plugin,他会方便我们打出fatjar,还有一个插件是docker-maven-plugin方便直接构建出我们需要的镜像并推送到远程仓库。 你可以从我的这片博文如何构建SpringBoot的Docker镜像中了解更多.

那么加上插件之后完成的pom如下

pom.xml

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
<?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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
    </parent>

    <groupId>io.qingmu</groupId>
    <artifactId>eureka</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
    </dependencies>

    <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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <configuration>
                    <imageName>
                        freemanliu/eureka:v1.0.0
                    </imageName>
                    <registryUrl></registryUrl>
                    <workdir>/work</workdir>
                    <rm>true</rm>
                    <env>
                        <TZ>Asia/Shanghai</TZ>
                        <JAVA_OPTS>
                            -XX:+UnlockExperimentalVMOptions \
                            -XX:+UseCGroupMemoryLimitForHeap \
                            -XX:MaxRAMFraction=2 \
                            -XX:CICompilerCount=8 \
                            -XX:ActiveProcessorCount=8 \
                            -XX:+UseG1GC \
                            -XX:+AggressiveOpts \
                            -XX:+UseFastAccessorMethods \
                            -XX:+UseStringDeduplication \
                            -XX:+UseCompressedOops \
                            -XX:+OptimizeStringConcat
                        </JAVA_OPTS>
                    </env>
                    <baseImage>freemanliu/openjre:8.212</baseImage>
                    <cmd>
                        /sbin/tini java ${JAVA_OPTS} -jar ${project.build.finalName}.jar
                    </cmd>
                    <!--是否推送image-->
                    <pushImage>true</pushImage>
                    <resources>
                        <resource>
                            <directory>${project.build.directory}</directory>
                            <include>${project.build.finalName}.jar</include>
                        </resource>
                    </resources>
                    <serverId>docker-hub</serverId>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

EurekaServiceApplication

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package io.qingmu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServiceApplication.class, args);
    }
}

application.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
server:
  port: ${PORT:8761}
spring:
  application:
    name: eureka-service
management:
  endpoints:
    web:
      exposure:
        include: "*"
logging:
  level:
    com:
      netflix:
        eureka:
          registry: error

bootstrap.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
eureka:
  server:
    # 续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)
    eviction-interval-timer-in-ms: 5000
  client:
    # eureka client间隔多久去拉取服务注册信息 默认30s
    registry-fetch-interval-seconds: 30
    fetch-registry: false
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:http://127.0.0.1:${server.port}/eureka/}
  instance:
    # 心跳间隔时间,即发送一次心跳之后,多久在发起下一次(缺省为30s)
    lease-renewal-interval-in-seconds: 10
    # 在收到一次心跳之后,等待下一次心跳的空档时间,大于心跳间隔即可,即服务续约到期时间(缺省为90s)
    lease-expiration-duration-in-seconds: 15
    instance-id: ${EUREKA_INSTANCE_HOSTNAME:${spring.application.name}}:${server.port}@${random.long(1000000,9999999)}
    hostname: ${EUREKA_INSTANCE_HOSTNAME:${spring.application.name}}

完成的项目结构如下:

查看UI

运行application类,在浏览器中输入http://127.0.0.1:8761/ 你将看到单机版本的eureka的UI界面。

打包docker镜像,打开idea的终端,确保你配置好了docker插件中imageNameserverId之后。

1
 $ mvn clean package

如果顺利,你将会看到镜像被成功推送到远程仓库。这我推送的镜像是freemanliu/eureka:v1.0.0

使用Docker部署eureka集群

这里使用三个机器分别是

eureka | host

  • | - eureka-0.eureka | 192.168.0.201 | eureka-1.eureka | 192.168.0.202 | eureka-2.eureka | 192.168.0.203 |

写入映射hosts

1
2
3
4
5
cat > /etc/hosts << EOF
192.168.0.201 eureka-0.eureka
192.168.0.202 eureka-1.eureka
192.168.0.203 eureka-2.eureka
EOF

201-eureka0

1
2
3
4
docker run -it --rm --net host \
-e EUREKA_INSTANCE_HOSTNAME=eureka-0.eureka \
-e EUREKA_SERVER=http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/ \
freemanliu/eureka:v1.0.0

202-eureka1

1
2
3
4
5

docker run -it --rm  --net host \
-e EUREKA_INSTANCE_HOSTNAME=eureka-1.eureka \
-e EUREKA_SERVER=http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/ \
freemanliu/eureka:v1.0.0

203-eureka2

1
2
3
4
docker run -it --rm  --net host \
-e EUREKA_INSTANCE_HOSTNAME=eureka-2.eureka  \
-e EUREKA_SERVER=http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/ \
freemanliu/eureka:v1.0.0

打开浏览器访问 http://192.168.0.201:8761 可以发现我们的集群已经成功搭建好了。

在client使用

1
2
3
4
eureka:
  client:
    serviceUrl:
      defaultZone: http://192.168.0.201:8761/eureka/,http://192.168.0.202:8761/eureka/,http://192.168.0.203:8761/eureka/

部署到kubernetes

这里采用statefulset部署,部署3个副本集的集群,如果你需要更多副本集,则需要改动EUREKA_INSTANCE_HOSTNAME中的URL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# eureka-statefulset.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: eureka
  labels:
    app: eureka
spec:
  ports:
    - port: 8761
      name: eureka
  clusterIP: None
  selector:
    app: eureka
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: eureka
spec:
  serviceName: "eureka"
  replicas: 3
  selector:
    matchLabels:
      app: eureka
  template:
    metadata:
      labels:
        app: eureka
    spec:
      containers:
        - name: eureka
          image: freemanliu/eureka:v1.0.0
          ports:
            - containerPort: 8761
          resources:
            limits:
              # jvm会自动发现该限制
              memory: 2Gi
          env:
            - name: MY_POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: JAVA_OPTS
              value: -XX:+UnlockExperimentalVMOptions
                -XX:+UseCGroupMemoryLimitForHeap
                -XX:MaxRAMFraction=2
                -XX:CICompilerCount=8
                -XX:ActiveProcessorCount=8
                -XX:+UseG1GC
                -XX:+AggressiveOpts
                -XX:+UseFastAccessorMethods
                -XX:+UseStringDeduplication
                -XX:+UseCompressedOops
                -XX:+OptimizeStringConcat
            - name: EUREKA_SERVER
              value: "http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/"
            - name: EUREKA_INSTANCE_HOSTNAME
              value: ${MY_POD_NAME}.eureka
  podManagementPolicy: "Parallel"

部署

1
kubectl apply -f eureka-statefulset.yaml

查看部署状态

1
2
3
4
freemandeMacBook-Pro:qingmu freeman$ kubectl get pods -owide | grep eureka
eureka-0                                   1/1     Running   0          126m   172.224.5.40    node3               <none>           <none>
eureka-1                                   1/1     Running   0          126m   172.224.3.131   node1               <none>           <none>
eureka-2                                   1/1     Running   0          126m   172.224.7.243   node6               <none>           <none>

通过浏览器访问http://podip:8761

在client使用,当都处于default namespace时

1
2
3
4
eureka:
  client:
    serviceUrl:
      defaultZone: http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/

都处于非default namespace 卡的惨不忍睹啊 是输入法的问题妈

1
2
3
4
eureka:
  client:
    serviceUrl:
      defaultZone: http://eureka-0.eureka.default:8761/eureka/,http://eureka-1.eureka.default:8761/eureka/,http://eureka-2.eureka.default:8761/eureka/

Github 地址

完整的代码我上传到了github 你可以从这个https://github.com/qingmuio/eureka 获取到代码。