微服务学习入口 - (一)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,当看到下面页面,就说明注册中心已经搭建完毕。

微服务学习入口 - (一)Eureka注册中心_第1张图片

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. 启动

项目启动,测试接口,这里记得先启动注册中心。启动成功后,再访问注册中心。
微服务学习入口 - (一)Eureka注册中心_第2张图片

REST风格http://127.0.0.1:9000/user,测试接口
微服务学习入口 - (一)Eureka注册中心_第3张图片

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注册中心_第4张图片
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注册中心_第5张图片

快速移除已经失效的服务

在实际的开发过程中,我们可能会不停的重启服务,由于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 启动");
    }

}

微服务学习入口 - (一)Eureka注册中心_第6张图片

微服务学习入口 - (一)Eureka注册中心_第7张图片

源码参考

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

你可能感兴趣的:(Spring,Cloud微服务)