微服務學習入口 - (一)Eureka註冊中心

Eureka註冊中心在微服務架構中是必不可少的一部分,主要用來實現服務治理功能,總之,很重要。
如果需要看懂後面的文檔,需要有一定的Spring Boot和maven的基礎。

使用Eureka編寫註冊中心服務

這裏使用Maven多模塊來管理項目,maven多模塊這裏就不多說了。Spring cloud版本爲:2.2.0.RELEASE,Spring boot版本爲:2.2.1.RELEASE,Spring版本爲:5.2.1.RELEASE。
父模塊:demo、三個子模塊:demo-eureka(註冊中心)、demo-provider(服務提供者)、demo-client(服務調用者)。

demo-eureka

  1. 添加依賴
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>
  1. 啓動類EurekaApplication
package com.demo.eureka;

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

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }

}

@EnableEurekaServer:表示這個應用作爲註冊中心

  1. 配置文件application.yml
server:
  port: 8761
spring:
  application:
    name: eureka-server
eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
  client:
    register-with-eureka: false
    fetch-registry: false

本應用爲註冊中心,所以設置爲false,表示不向註冊中心註冊自己
eureka.client.register-with-eureka = false
註冊中心的職責就是維護服務實例,它並不需要去檢索服務
eureka.client.fetch-registry = false

  1. 啓動

啓動類右鍵 >> debug,啓動完成後,瀏覽器訪問http://127.0.0.1:8761,當看到下面頁面,就說明註冊中心已經搭建完畢。

demo-provider

這裏使用JPA實現對錶user_info的增刪改查,只是一個簡單的demo

  1. 添加依賴
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

Lombok:省略getter、setter等,簡化實體類,網上自己找
druid:alibaba數據源
驅動:mysql 和 postgresql都加進來了

  1. 啓動類ProviderApplication
package com.demo.provider;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }

}

@EnableDiscoveryClient:當前服務是一個Eureka的客戶端

  1. 配置文件application.yml
server:
  port: 9000
spring:
  application:
    name: demo-provider
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:postgresql://127.0.0.1:5432/postgres
    driver-class-name: org.postgresql.Driver
    username: postgres
    password: 123456
  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        temp:
          use_jdbc_metadata_defaults: false


eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/

數據源配置不同多說
spring.datasource.*
簡單理解爲,不用手動建表,表會自動創建或更新
spring.jpa.hibernate.ddl-auto = update
這個地址指向註冊中心,多個用“,”隔開
eureka.client.service-url.defaultZone = http://127.0.0.1:8761/eureka/

  1. 新建實體類User
package com.demo.provider.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Data
@Entity
@Table(name = "user_info")
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
public class User implements Serializable {

    @Id
    @Column(name = "id", length = 32)
    @GenericGenerator(name = "uuidGenerator", strategy = "uuid")
    @GeneratedValue(generator = "uuidGenerator")
    private String userId;

    @Column(name = "name", length = 50)
    private String name;

    @Column(name = "sex")
    private Integer sex;

    @Column(name = "age")
    private Integer age;

    @Column(name = "birthday")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private Date birthday;

}

這裏使用了Lombok,所有沒有getter、setter,使用@Data就行
@JsonIgnoreProperties(value = { “hibernateLazyInitializer”}):直接將這個對象返回到客戶端段會有問題,所以需要加上這個註解
@JsonFormat:將這個對象返回到客戶端時,這個時間會格式化字符串。
@DateTimeFormat:客戶端傳值,用這個對象接受的話,傳的是一個格式化後的時間字符串

  1. Repository
package com.demo.provider.repository;

import com.demo.provider.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, String> {

}
  1. Controller,一個簡單的增刪改查
import com.demo.provider.entity.User;
import com.demo.provider.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping
    public Page<User> findUser(@RequestParam(required = false, defaultValue = "1") Integer pageNo
            , @RequestParam(required = false, defaultValue = "10") Integer size) {
        return userRepository.findAll(PageRequest.of(pageNo, size));
    }

    @RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT })
    public User save(@RequestBody User user) {
        userRepository.save(user);
        return user;
    }

    @GetMapping("/{id}")
    public User findById(@PathVariable String id) {
        return userRepository.getOne(id);
    }

    @DeleteMapping("/{id}")
    public String deleteById(@PathVariable String id) {
        userRepository.deleteById(id);
        return id;
    }

}
  1. 啓動

項目啓動,測試接口,這裏記得先啓動註冊中心。啓動成功後,再訪問註冊中心。

REST風格http://127.0.0.1:9000/user,測試接口

demo-client

  1. 添加依賴
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
  1. 新建實體類User,爲什麼需要新家,就是服務提供者的User需要用到JPA
package com.demo.client.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;

@Data
public class User implements Serializable {

    private String userId;
    private String name;
    private Integer sex;
    private Integer age;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private Date birthday;

}
  1. 請求模板
package com.demo.client.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class BeanConfiguration {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

RestTemplate對象可以很方便的使用Java代碼發送http請求,所以創建一個RestTemplate,使用它來調用服務提供者提供的接口。

  1. UserController
package com.demo.client.controller;

import com.demo.client.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    public Object findUser(@RequestParam(required = false, defaultValue = "1") Integer pageNo
            , @RequestParam(required = false, defaultValue = "10") Integer size) {
        Map<String, Object> map = new HashMap<>();
        map.put("pageNo", pageNo);
        map.put("size", size);
        return restTemplate.getForObject("http://127.0.0.1:9000/user?pageNo={pageNo}&size={size}", Object.class, map);
    }

    @RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT })
    public User save(@RequestBody User user) {
        return restTemplate.postForObject("http://127.0.0.1:9000/user", user, User.class);
    }

    @GetMapping("/{id}")
    public User findById(@PathVariable String id) {
        return restTemplate.getForObject("http://127.0.0.1:9000/user/{id}", User.class, id);
    }

    @DeleteMapping("/{id}")
    public String deleteById(@PathVariable String id) {
        restTemplate.delete("http://127.0.0.1:9000/user/{id}", id);
        return id;
    }

}

注入一個RestTemplate對象,然後使用其發送請求。
本來到這一步已經基本上算完成了,但是細心的同學還是會發現,使用RestTemplate直接去調用服務的提供者提供的接口好像並沒有用到註冊中心,並且請求地址直接用IP+端口的方式顯然不科學。所以這裏需要稍微調整一下。

RestTemplate

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class BeanConfiguration {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

構造RestTemplate 對象時,加上@LoadBalanced註解,然後調用接口的時候就可以使用應用名稱${spring.applicaiton.name},而不用直接使用IP+端口了

UserController調整後代碼如下:

import com.demo.client.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    public Object findUser(@RequestParam(required = false, defaultValue = "1") Integer pageNo
            , @RequestParam(required = false, defaultValue = "10") Integer size) {
        Map<String, Object> map = new HashMap<>();
        map.put("pageNo", pageNo);
        map.put("size", size);
        return restTemplate.getForObject("http://demo-provider/user?pageNo={pageNo}&size={size}", Object.class, map);
    }

    @RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT })
    public User save(@RequestBody User user) {
        return restTemplate.postForObject("http://demo-provider/user", user, User.class);
    }

    @GetMapping("/{id}")
    public User findById(@PathVariable String id) {
        return restTemplate.getForObject("http://demo-provider/user/{id}", User.class, id);
    }

    @DeleteMapping("/{id}")
    public String deleteById(@PathVariable String id) {
        restTemplate.delete("http://demo-provider/user/{id}", id);
        return id;
    }

}

開啓Eureka認證

註冊中心(demo-eureka)中添加spring-security的依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

application設置用戶名密碼

spring:
  security:
    user:
      name: admin
      password: 123456

此時在訪問註冊中心就需要使用剛纔的用戶名密碼登錄了


Eureka註冊的時候,地址需要稍微修改一下

eureka:
  client:
    service-url:
      defaultZone: http://admin:123456@127.0.0.1:8761/eureka/

格式爲:http://{用戶名}:{密碼}:@127.0.0.1:8761/eureka/

這裏需要注意的是,需要關閉CSRF。 這裏爲了簡化,直接修改啓動類。

package com.demo.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@SpringBootApplication
@EnableEurekaServer
@EnableWebSecurity
public class EurekaApplication extends WebSecurityConfigurerAdapter {

    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        super.configure(http);
    }
}

Eureka高可用搭建

理解Eureka高可用很簡單,如:有三臺機器master、worker1、worker2,讓Eureka分別運行在三臺機器上,同時,自己向其他兩臺機器上的註冊中心註冊自己,好比:
master向worker1和worker2註冊
worker1向master和worker2註冊
worker2向master和worker1註冊

application-test.yml

server:
  port: 8761
spring:
  application:
    name: eureka-server
  security:
    user:
      name: admin
      password: 123456
eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
  client:
    register-with-eureka: false
    fetch-registry: false

啓動命令

# master
java -jar demo-eureka.jar \
	--spring.profiles.active=test \
	--eureka.client.service-url.defaultZone=http://admin:123456@worker1:8761/eureka/,http://admin:123456@worker2:8761/eureka/

# worker1
java -jar demo-eureka.jar \
	--spring.profiles.active=test \
	--eureka.client.service-url.defaultZone=http://admin:123456@master:8761/eureka/,http://admin:123456@worker2:8761/eureka/

# worker2
java -jar demo-eureka.jar \
	--spring.profiles.active=test \
	--eureka.client.service-url.defaultZone=http://admin:123456@master:8761/eureka/,http://admin:123456@worker1:8761/eureka/

結果:

快速移除已經失效的服務

在實際的開發過程中,我們可能會不停的重啓服務,由於Eureka有自己的保護機制,故節點下線後,服務信息還是一直存在Eureka。我們可以通過增加一些配置讓移除的速度更快一點,當然只是在開發環境下使用,生產環境不推薦。

eureka:
  instance:
    lease-renewal-interval-in-seconds: 5 # 默認30s
    lease-expiration-duration-in-seconds: 5 # 默認60s
  client:
    healthcheck:
      enabled: true
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 5000 #默認60000毫秒
  • 關閉自我保護
    eureka.server.enable-self-preservation=false
  • 清理間隔
    eureka.server.eviction-interval-tirner-in-ms=5000
  • 開啓健康檢查
    eureka.client.healthcheck.enabled=true
  • Eureka Client發送心跳給server的頻率
    eureka.instance.lease-renewal-interval-in-seconds=5
  • Eureka Server至上一次收到client的心跳之後,等待一下次心跳的超時時間,在這個時間內沒有收到下一次心跳,則移除該Instance
    eureka.instance.lease-expiration-duration-in-seconds=5

服務的上下線監控

註冊中心demo-eureka,新增代碼

package com.demo.eureka.conf;

import org.springframework.cloud.netflix.eureka.server.event.*;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class EurekaStateChangeListener {

    @EventListener
    public void listener(EurekaInstanceCanceledEvent event) {
        System.out.println("服務下線:" + event.getServerId() + event.getAppName());
    }

    @EventListener
    public void listener(EurekaInstanceRegisteredEvent event) {
        System.out.println("服務註冊:" + event.getInstanceInfo().getAppName());
    }

    @EventListener
    public void listener(EurekaInstanceRenewedEvent event) {
        System.out.println("服務續約:" + event.getServerId() + event.getAppName());
    }

    @EventListener
    public void listener(EurekaRegistryAvailableEvent event) {
        System.out.println("註冊中心啓動");
    }

    @EventListener
    public void listener(EurekaServerStartedEvent event) {
        System.out.println("Eureka server 啓動");
    }

}

源碼參考

https://github.com/phone15082037343/demo.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章