Eureka注册中心在微服务架构中是必不可少的一部分,主要用来实现服务治理功能,总之,很重要。
如果需要看懂后面的文档,需要有一定的Spring Boot和maven的基础。
这里使用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(服务调用者)。
<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>
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:表示这个应用作为注册中心
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
启动类右键 >> debug,启动完成后,浏览器访问http://127.0.0.1:8761,当看到下面页面,就说明注册中心已经搭建完毕。
这里使用JPA实现对表user_info的增删改查,只是一个简单的demo
<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都加进来了
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的客户端
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/
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:客户端传值,用这个对象接受的话,传的是一个格式化后的时间字符串
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> {
}
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;
}
}
项目启动,测试接口,这里记得先启动注册中心。启动成功后,再访问注册中心。
REST风格http://127.0.0.1:9000/user,测试接口
<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>
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;
}
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,使用它来调用服务提供者提供的接口。
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;
}
}
注册中心(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:
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高可用很简单,如:有三台机器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毫秒
注册中心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