1)新建一个Spring Boot项目,取名为EurekaServer,代码见码云:https://gitee.com/wudiyong/EurekaServer.git,然后在pom.xml文件中加入依赖:
org.springframework.boot
spring-boot-starter-parent
1.4.5.RELEASE
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-eureka-server
org.springframework.cloud
spring-cloud-dependencies
Camden.SR6
pom
import
2)在入口类处加上@EnableEurekaServer注解,用于开启服务注册中心,如下:
@EnableEurekaServer
@SpringBootApplication
public class ErukaServerApplication {
public static void main(String[] args) {
SpringApplication.run(ErukaServerApplication.class, args);
}
}
3)配置application.properties
server.port=8761
eureka.instance.hostname=localhost#当前实例的主机名称
eureka.client.register-with-eureka=false#false代表不向注册中心注册自己(因为本身就是注册中心),默认是true,如果不设为false,启动会报找不到注册中心的错误
eureka.client.fetch-registry=false#注册中心用于维护服务实例,无需检索服务,故设为false
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
eureka.client.serviceUrl.defaultZone与eureka.client.service-url.defaultZone有什么区别?
“eureka.client.service-url.”指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。
如果服务注册中心加入了安全验证,这里配置的地址格式为: http://
至此,注册中心搭建完成,访问http://localhost:8761/可看到注册中心信息,如系统状态、已注册的服务列表等。
可以看到页面中的environment的值是test,可以通过eureka.environment=dev来改变该值。
新建一个项目,取名为userInfoService,代码见码云:https://gitee.com/wudiyong/userInfoService.git
1)pom.xml文件与注册中心类似
2)入口类加上注解@EnableEurekaClient,开启eureka客户端,可以注册服务及发现调用服务,与注册中心的@EnableEurekaServer刚好相反。
@EnableDiscoveryClient也能起到该作用,@EnableDiscoveryClient与@EnableEurekaClient的关系如下:
SpringCloud中的“Discovery Service”有多种实现,比如:eureka, consul, zookeeper,可见eruka只是其中的一种。
@EnableDiscoveryClient
注解是基于spring-cloud-commons
依赖,并且在classpath中实现(根据导入的jar包有关);@EnableEurekaClient
注解是基于spring-cloud-netflix
依赖,只能为eureka作用;
如果你的classpath中添加了eureka,则它们的作用是一样的。
3)编写controller类,对外提供服务,如:
@RestController
public class UserInfoController {
//@RequestBody告诉从body里取值,请求方的Content-Type要设置为application/json,否则报415错误
//@RequestBody好像不能用多个,如@RequestBody String name,@RequestBody int age
@RequestMapping(value="/addUserInfo",method=RequestMethod.POST)
public String addUserInfo(@RequestBody UserInfo userInfo){
return userInfo.getName() + ":" + userInfo.getAge();
}
//@RequestParam告诉从url里取值
@RequestMapping(value="/userInfo",method=RequestMethod.GET)
public String userInfo(@RequestParam String name,@RequestParam String age){
return name + ":" + age;
}
}
4)配置application.properties
spring.application.name=userInfo-service
server.port=1001
#指向注册中心,http://${eureka.instance.hostname}:${server.port}/eureka/
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
5)测试
启动服务注册中心和服务提供者程序,再访问http://localhost:8761/,可以看到如下信息,说明服务已经注册到注册中心了:
Application | AMIs | Availability Zones | Status |
---|---|---|---|
USERINFO-SERVICE | n/a (1) | (1) | UP (1) - 80245675-OAPC.itc.cmbchina.cn:userInfo-service:1001 |
可以在浏览器输入http://www.localhost:1001/userInfo?name=吴帝永&age=12来访问提供的服务,但这种方式并不涉及到eureka,仅仅是普通的url访问,并不是通过eureka客户端进行服务发现和调用。
可以修改端口号,启动多个userInfo-service服务,这时可以看到
Application | AMIs | Availability Zones | Status |
---|---|---|---|
USERINFO-SERVICE | n/a (2) | (2) | UP (2) - 192.168.1.106:userInfo-service :1002 , 192.168.1.106:userInfo-service :1001 |
服务的调用者若想调用某个服务,首先向注册中心发起咨询服务请求,获取服务实例清单,可以有多个服务名相同的服务提供者,即有多个服务实例,当调用者发起调用时,会以某种轮询策略,选择其中一个实例进行调用,这涉及到客户端(Eureka Client)负载均衡。
首先新建一个项目,取名为userInfoService,代码见码云:https://gitee.com/wudiyong/ribbonConsumer.git
1)配置pom.xml,如下:
4.0.0
com
ribbonConsumer
0.0.1-SNAPSHOT
jar
ribbonConsumer
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.8.RELEASE
UTF-8
UTF-8
1.8
Camden.SR6
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-ribbon
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
2)入口类加上注解@EnableEurekaClient,同时,在该类中创建RestTemplate实例,并通过@LoadBalanced注解开启客户端负载均衡
@EnableEurekaClient
@SpringBootApplication
public class RibbonConsumerApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
}
3)配置application.properties
spring.application.name=ribbon-consumer
server.port=9000
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
4)编写调用服务的类
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@RequestMapping(value="/userInfo", method = RequestMethod.GET)
public String userInfo(@RequestParam String name, @RequestParam String age) {
//如果是post请求,可以用postForEntity
Map params = new HashMap();
params.put("name", name);
params.put("age", age);
return restTemplate.getForEntity("http://USERINFO-SERVICE/userInfo?name={name}&age={age}",
String.class,params).getBody();
}
}
分别启动EurekaServer、userInfoService和userInfoService,其中userInfoService可以修改端口号来启动多个,打开http://localhost:8761/,可看到:
Application | AMIs | Availability Zones | Status |
---|---|---|---|
RIBBON-CONSUMER | n/a (1) | (1) | UP (1) - 192.168.1.106:ribbon-consumer:9000 |
USERINFO-SERVICE | n/a (2) | (2) | UP (2) - 192.168.1.106:userInfo-service :1001 , 192.168.1.106:userInfo-service :1002 |
4)测试,访问:http://www.localhost:9000/userInfo?name=杀杀杀&age=326
如果有多个USERINFO-SERVICE服务,则请求会轮询被分配到某一个服务。
服务提供者本身也可以作为服务调用者,可以调用其它的服务,也就是说,提供者与调用者是相对的,一个系统可以既是服务提供者又是服务调用者,这一点很重要,因为服务提供者之间会互相调用。
前面我们只使用一个注册中心,当该注册中心不可用时,所有的服务都不可用,解决方案是,采用多个注册中心,注册中心之间互相注册,前面的注册中心我们通过设置下面两个参数让注册中心不要注册自己:
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
现在需要把这两行删掉。
由于除了application.properties之外,其它代码都一样,所以采用spring.profiles.active来控制加载不同的配置文件。
假设有两个注册中心,则分别创建两个配置文件:
application-peer1.properties:
server.port=1111
spring.application.name=eureka-server
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://localhost:1112/eureka/
application-peer2.properties:
server.port=1112
spring.application.name=eureka-server
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
上面的例子中,都是通过eureka.instance.hostname设置主机名来定义实例地址的,这里的localhost只是一个特殊的情况,系统会自动转换为127.0.0.1,我们也可以设置为
eureka.instance.hostname=peer1
eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/
和
eureka.instance.hostname=peer2
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
然后在C:\Windows\System32\drivers\etc\hosts或/etc/hosts中加入如下信息:
127.0.0.1 peer1
127.0.0.1 peer2
这样系统就会自动把peer1、peer2转换成ip地址。除了以上方式,还可以直接指定ip,eureka.instance.prefer-ip-address默认是false,需要把其设为true来开启,如果采用ip方式,则eureka.instance.hostname可以去掉,系统会自动为实例设置ip地址,如果要手动设置,可以通过eureka.instance.ip-address来设置。
注意:如果不显式地把eureka.instance.prefer-ip-address设为true,则服务会以域名的方式注册到eureka,其它服务会通过该服务的域名来调用,如果客户端无法将域名转换成ip,则会调不通。
实例主机名或实例ip地址不仅仅是用在注册中心,也能用于服务的提供者和调用者,因为三者本质上都是一个实例。
application.properties文件只要写spring.profiles.active=peer1或spring.profiles.active=peer2
分别启动这两个注册中心,打开http://localhost:1112/或http://localhost:1111/,都能看到如下信息:
Application | AMIs | Availability Zones | Status |
---|---|---|---|
EUREKA-SERVER | n/a (2) | (2) | UP (2) - 80245675-OAPC.itc.cmbchina.cn:eureka-server:1111 , 80245675-OAPC.itc.cmbchina.cn:eureka-server:1112 |
至此,两个注册中心已互相注册成功。
服务提供者、服务调用者都需修改application.properties文件,使其都注册到这两个注册中心中,修改成如下:
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/,http://localhost:1112/eureka/
服务续约
eureka.instance.lease-renewal-interval-in-seconds=30
该配置用于定义服务续约任务的调用间隔时间,默认为30秒
eureka.instance.lease-expiration-duration-in-seconds=90
该配置用于定义服务失效时间,默认为90秒
获取服务
eureka.client.registry-fetch-interval-seconds=30
获取服务列表的时间间隔,默认为30秒,
服务下线
在系统运行过程中,总会有停止或重启服务的时候,这时我们不希望别的服务调用到这个已经停掉的服务,所以在服务正常关闭关闭时,它会出发一个请求发给eureka server,告诉eureka server自己下线了,eureka server会把这个状态传播出去(似乎不是立刻的,可能是别的服务过来拉取的)。
失效剔除
失效剔除和服务下线不一样失效剔除是服务自己非正常下线,例如内存溢出导致应用crash,这时该服务没有继续往eureka server发续约心跳,eureka server在90秒后(默认90秒)还没有收到心跳,就认为该服务失效,然后把该服务从服务列表中删除,但eureka server不是每时每刻都在判断有没有达到90秒的阈值的,而是默认60秒会刷新一次,所以,极端情况下,服务失效后,有可能150秒才会被从服务列表中删除。
自我保护
看自我保护的文章,这里不详细说
实例启动后,正常的状态是UP,Eureka Client从Eureka Server中拉取状态为UP的实例列表,服务间调用的请求也只会发给UP状态的实例。当某个实例不希望被别的实例调用时,可以把自己的状态设为DOWN或OUT_OF_SERVICE,比如,要更新某个实例的jar包时,如果直接更新,则由于该实例还在实例列表中保留一段时间,默认时120秒,这段时间如果有请求发给该实例的话,则会报错,因为实例实际上已经停掉了,只是别的实例不知道。一种解决方法是:
在更新实例之前,先把该实例设为OUT_OF_SERVICE,此时别的实例会在下一次刷新实例列表时,会把该实例删掉或至为不可用,等到所有实例都刷新了实例列表之后,再更新或停掉该实例。修改实例状态的方法如下:
curl -l -H "Content-type: application/json" -X POST -d 'OUT_OF_SERVICE' http://实例IP:PORT/service-registry/instance-status
可以用ssh等工具,向要修改状态的实例发送该请求(请求命令里面已经包含了该实例的地址了)