Spring Cloud Consul入门

1、启动consul

consul agent -dev
#consul agent -dev -config-dir=/etc/consul.d
#consul agent -data-dir=/tmp/consul -node=agent02 -bind=172.16.172.62 -config-dir=/etc/consul.d
#consul agent -server -bootstrap-expect=1 -data-dir=/tmp/consul -node=agent01 -bind=172.16.172.63 -config-dir=/etc/consul.d
#-bootstrap-expect=1	     等待一个节点加入
#-data-dir=/tmp/consu	     缓存路径
#-bind=172.20.20.10	     绑定ip地址
#-config-dir=/etc/consul.d   配置文件路径

==> Starting Consul agent...
==> Starting Consul agent RPC...
==> Consul agent running!
           Version: 'v0.7.5'
           Node ID: 'f82c6b1c-43b5-4423-af1d-343429555bbe'
         Node name: 'hiup03'
        Datacenter: 'dc1'
            Server: true (bootstrap: false)
       Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400)
      Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
    Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false
             Atlas: <disabled>

==> Log data will now stream in as it occurs:

    2017/02/27 10:50:13 [DEBUG] Using unique ID "f82c6b1c-43b5-4423-af1d-343429555bbe" from host as node ID
    2017/02/27 10:50:13 [INFO] raft: Initial configuration (index=1): [{Suffrage:Voter ID:127.0.0.1:8300 Address:127.0.0.1:8300}]
    2017/02/27 10:50:13 [INFO] serf: EventMemberJoin: hiup03 127.0.0.1
    2017/02/27 10:50:13 [INFO] serf: EventMemberJoin: hiup03.dc1 127.0.0.1
    2017/02/27 10:50:13 [INFO] raft: Node at 127.0.0.1:8300 [Follower] entering Follower state (Leader: "")
    2017/02/27 10:50:13 [INFO] consul: Adding LAN server hiup03 (Addr: tcp/127.0.0.1:8300) (DC: dc1)
    2017/02/27 10:50:13 [INFO] consul: Adding WAN server hiup03.dc1 (Addr: tcp/127.0.0.1:8300) (DC: dc1)

    2017/02/27 10:50:19 [WARN] raft: Heartbeat timeout from "" reached, starting election
    2017/02/27 10:50:19 [INFO] raft: Node at 127.0.0.1:8300 [Candidate] entering Candidate state in term 2
    2017/02/27 10:50:19 [DEBUG] raft: Votes needed: 1
    2017/02/27 10:50:19 [DEBUG] raft: Vote granted from 127.0.0.1:8300 in term 2. Tally: 1
    2017/02/27 10:50:19 [INFO] raft: Election won. Tally: 1
    2017/02/27 10:50:19 [INFO] raft: Node at 127.0.0.1:8300 [Leader] entering Leader state
    2017/02/27 10:50:19 [INFO] consul: cluster leadership acquired
    2017/02/27 10:50:19 [DEBUG] consul: reset tombstone GC to index 3
    2017/02/27 10:50:19 [INFO] consul: New leader elected: hiup03
    2017/02/27 10:50:19 [INFO] consul: member 'hiup03' joined, marking health alive
    2017/02/27 10:50:19 [INFO] agent: Synced service 'consul'
    2017/02/27 10:50:19 [DEBUG] agent: Node info in sync
    2017/02/27 10:51:37 [INFO] agent.rpc: Accepted client: 127.0.0.1:39792
    2017/02/27 10:52:07 [DEBUG] agent: Service 'consul' in sync
    2017/02/27 10:52:07 [DEBUG] agent: Node info in sync
    2017/02/27 10:52:47 [DEBUG] http: Request GET /v1/catalog/nodes (218.436μs) from=127.0.0.1:33428

2、带管理界面的启动方式

consul agent -ui  -data-dir /tmp/consul-ui -bind=172.16.172.63
consul agent -ui -client 0.0.0.0 -data-dir=/tmp/consul -node=agent02 -bind=172.16.172.62 -config-dir=/etc/consul.d
#访问http://172.16.172.63:8500/ui/#/dc1/services

3、加入主节点consul

consul join 172.16.172.63
Successfully joined cluster by contacting 1 nodes

4、检查健康状况

curl http://172.16.172.63:8080/health
{"description":"Spring Cloud Consul Discovery Client","status":"UP"}

5、负载均衡相关

curl http://172.16.172.63:8080/choose
http://hiup03:8080

curl http://172.16.172.63:8080/instances
[{"serviceId":"testConsulApp","host":"hiup03","port":8080,"secure":false,"metadata":{},"uri":"http://hiup03:8080"}]

6、服务描述

curl http://172.16.172.63:8080/
{"serviceId":"testConsulApp","server":{"host":"hiup03","port":8080,"id":"hiup03:8080","zone":"UNKNOWN","readyToServe":true,"metaInfo":
{"serverGroup":null,"serviceIdForDiscovery":null,"instanceId":"testConsulApp-8080","appName":"testConsulApp"},"metadata":{},"healthService":{"node":
{"node":"agent01","address":"172.16.172.63"},"service":{"id":"testConsulApp-8080","service":"testConsulApp","tags":
[],"address":"hiup03","port":8080},"checks":[{"node":"agent01","checkId":"serfHealth","name":"Serf Health 
Status","status":"PASSING","notes":"","output":"Agent alive and reachable","serviceId":"","serviceName":""},
{"node":"agent01","checkId":"service:testConsulApp-8080","name":"Service 'testConsulApp' check","status":"PASSING","notes":"","output":"HTTP GET 
http://hiup03:8080/health: 200  Output: {\"description\":\"Spring Cloud Consul Discovery Client\",\"status\":\"UP\"}","serviceId":"testConsulApp-
8080","serviceName":"testConsulApp"}]},"passingChecks":true,"alive":true,"hostPort":"hiup03:8080"},"secure":false,"metadata":
{},"uri":"http://hiup03:8080","host":"hiup03","port":8080}

curl http://172.16.172.63:8080/me
{"serviceId":"testConsulApp-8080","host":"hiup03","port":8080,"secure":false,"metadata":{},"uri":"http://hiup03:8080"}

curl http://172.16.172.63:8080/rest
{"serviceId":"testConsulApp-8080","host":"hiup03","port":8080,"secure":false,"metadata":{},"uri":"http://hiup03:8080"}

curl http://172.16.172.63:8080/myenv
{"timestamp":1488183397275,"status":400,"error":"Bad 
Request","exception":"org.springframework.web.bind.MissingServletRequestParameterException","message":"Required String parameter 'prop' is not present","path":"/myenv"}

curl http://172.16.172.63:8080/prop
default value

curl http://172.16.172.63:8080/feign
http://hiup03:8080

7、查看成员

consul members
Node     Address             Status  Type    Build  Protocol  DC
agent01  172.16.172.63:8301  alive   server  0.7.5  2         dc1
agent02  172.16.172.62:8301  alive   client  0.7.5  2         dc1

8、查看节点、域名及服务

curl http://localhost:8500/v1/catalog/nodes
curl http://localhost:8500/v1/catalog/service/hello
curl 'http://localhost:8500/v1/health/service/hello?passing'
curl http://localhost:8500/v1/health/state/critical
dig @localhost -p 8600 hiup03
dig @localhost -p 8600 hello.service.consul
dig @localhost -p 8600 hello.service.consul SRV
dig @localhost -p 8600 rails.hello.service.consul 
dig @localhost -p 8600 rails.hello.service.consul SRV
dig @127.0.0.1 -p 8600 hello.service.consul

dig @127.0.0.1 -p 8600 agent02.node.consul
; <<>> DiG 9.10.3-P4-Ubuntu <<>> @127.0.0.1 -p 8600 agent02.node.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61862
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;agent02.node.consul.           IN      A

;; ANSWER SECTION:
agent02.node.consul.    0       IN      A       172.16.172.62

;; Query time: 0 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Mon Feb 27 11:44:25 CST 2017
;; MSG SIZE  rcvd: 53

9、重新加载服务

consul reload

10、键值数据库

consul kv put hiup/config/msg "read to go"
consul kv put -flags=15 hiup/config/msg_max_length 128

consul kv get hiup/config/msg
consul kv get -detailed  hiup/config/msg

consul kv get -recurse
consul kv delete hiup/config/msg
consul kv delete -recurse hiup

11、键值数据库并发处理

consul kv get -detailed  hiup/config/msg
CreateIndex      1094
Flags            0
Key              hiup/config/msg
LockIndex        0
ModifyIndex      1094
Session          -
Value            read to go

consul kv put -cas -modify-index=1093 hiup/config/msg "msg1093"
consul kv put -cas -modify-index=1094 hiup/config/msg "msg1094"

Spring Cloud Zookeeper入门

Spring Cloud Zookeeper主要用于服务注册。

1、ZkTestApp

package com.neohope.springcloud.test.zktest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.boot.context.annotation.DeterminableImports;

@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
@RestController
public class ZkTestApp 
{
	  @RequestMapping("/")
	  public String home() {
	    return "Hello World";
	  }

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

2、pom.xml

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

	<groupId>com.neohope.springcloud.test</groupId>
	<artifactId>zktest</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>

	<name>zktest</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-zookeeper-dependencies</artifactId>
				<version>1.0.3.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.3.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>4.3.5.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-autoconfigure</artifactId>
			<version>1.5.1.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-zookeeper-all</artifactId>
		</dependency>
	</dependencies>

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
</project>

Spring Cloud Bus入门

1、BusServer每隔一段时间,就会产生一个TimeInfo的消息

package com.neohope.springcloud.test.busserver;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.boot.context.annotation.DeterminableImports;

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableBinding(Source.class)
public class AppSource {
	private static Logger logger = LoggerFactory.getLogger(AppSource.class);
	
	public static void main(String[] args) {
		SpringApplication.run(AppSource.class, args);
	}
	
	@Bean
	@InboundChannelAdapter(value = Source.OUTPUT, poller = @Poller(fixedDelay = "10000", maxMessagesPerPoll = "1"))
	public MessageSource<TimeInfo> timerMessageSource() {
		return new MessageSource<TimeInfo>() {
			@Override
			public Message<TimeInfo> receive() {
				logger.info("Msg Sent");
				return MessageBuilder.withPayload(new TimeInfo(new Date().getTime()+"","hiup")).build();
			}
		};
	}
 
	public static class TimeInfo{
 
		private String time;
		private String label;
 
		public TimeInfo(String time, String label) {
			super();
			this.time = time;
			this.label = label;
		}
 
		public String getTime() {
			return time;
		}
 
		public String getLabel() {
			return label;
		}
 
	}
}

2、pom.xml

<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.cloud.stream.app</groupId>
		<artifactId>app-starters-build</artifactId>
		<version>1.1.3.M1</version>
		<relativePath />
	</parent>

	<groupId>com.neohope.springcloud.test</groupId>
	<artifactId>busserver</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>

	<name>busserver</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.3.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>4.3.6.RELEASE</version>
		</dependency>
		<!--dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
		</dependency-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-stream-binder-kafka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-stream-kafka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot</artifactId>
			<version>1.5.1.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-autoconfigure</artifactId>
			<version>1.5.1.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
</project>

3、application.properties

server.port=-1
spring.cloud.stream.bindings.output.destination=timerTopic
spring.cloud.stream.bindings.output.content-type=application/json
spring.cloud.stream.kafka.binder.zkNodes=localhost
#spring.cloud.stream.kafka.binder.brokers=localhost

4、BusClient会收到对应的消息并输出日志

package com.neohope.springcloud.test.busclient;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


import org.springframework.boot.SpringApplication;

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableBinding(Sink.class)
public class AppSink 
{
	private static Logger logger = LoggerFactory.getLogger(AppSink.class);
	 
	public static void main(String[] args) {
		SpringApplication.run(AppSink.class, args);
	}

        @StreamListener(Sink.INPUT)
	public void loggerSink(SinkTimeInfo sinkTimeInfo) {
		logger.info("Received: " + sinkTimeInfo.toString());
	}
 
	public static class SinkTimeInfo{
 
		private String time;
		private String label;
 
		public String getTime() {
			return time;
		}
 
		public void setTime(String time) {
			this.time = time;
		}
 
		public void setSinkLabel(String label) {
			this.label = label;
		}
 
		public String getLabel() {
			return label;
		}
 
		@Override
		public String toString() {
			return "SinkTimeInfo [time=" + time + ", label=" + label + "]";
		}
	}
}

5、pom.xml

<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.cloud.stream.app</groupId>
		<artifactId>app-starters-build</artifactId>
		<version>1.1.3.M1</version>
		<relativePath />
	</parent>

	<groupId>com.neohope.springcloud.test</groupId>
	<artifactId>busclient</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>

	<name>busclient</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.3.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>4.3.6.RELEASE</version>
		</dependency>
		<!--dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
		</dependency-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-stream</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-stream-binder-kafka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-stream-kafka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot</artifactId>
			<version>1.5.1.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-autoconfigure</artifactId>
			<version>1.5.1.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
</project>

6、application.properties

server.port=-1
spring.cloud.stream.bindings.input.destination=timerTopic
spring.cloud.stream.bindings.input.content-type=application/json
spring.cloud.stream.bindings.input.group=timerGroup
spring.cloud.stream.binder.kafka.resetOffsets=true
spring.cloud.stream.kafka.binder.zkNodes=localhost
#spring.cloud.stream.kafka.binder.brokers=localhost

Spring Cloud Dataflow入门

1、下载下面的文件

#Kafka 0.10.1.1
http://kafka.apache.org/downloads
#服务端
http://repo.spring.io/release/org/springframework/cloud/spring-cloud-dataflow-server-local/1.1.3.RELEASE/spring-cloud-dataflow-server-local-1.1.3.RELEASE.jar
#客户端
http://repo.spring.io/release/org/springframework/cloud/spring-cloud-dataflow-shell/1.1.3.RELEASE/spring-cloud-dataflow-shell-1.1.3.RELEASE.jar
#Avogadro.properties
http://bit.ly/Avogadro-SR1-stream-applications-kafka-10-maven

2、启动服务

#启动ZK
bin/zookeeper-server-start.sh config/zookeeper.properties  &
#启动kafka
bin/kafka-server-start.sh config/server.properties &
#启动服务端
java -jar spring-cloud-dataflow-server-local-1.1.3.RELEASE.jar &
#启动客户端
java -jar spring-cloud-dataflow-shell-1.1.3.RELEASE.jar
#导入Avogadro.properties
app import --uri file://PATH_TO_FILE/Avogadro.properties

3、打开界面
http://IP:9393/dashboard

4、新建一个简单的流,每15秒,写入一行数据
就不截图了,直接粘进去就好了

per_15s_flow01=per_15s: time --cron="0/15 * * * * ?" --date-format="yyyy-MM-dd HH:mm:ss" | file01: file --directory=PATH_TO_FILE --name=file01.txt

创建,并开启工作流。
查看file01.txt
销毁工作流。

5、新建一个较为复杂的流

per_15s_flow01=per_15s: time --cron="0/15 * * * * ?" --date-format="yyyy-MM-dd HH:mm:ss" | file01: file --directory=PATH_TO_FILE --name=file01.txt
per_15s_subflow01=:per_15s_flow01.per_15s > groovy-transform --script="nscript.groovy" --variables=msg='Time is ' | file02: file --directory=PATH_TO_FILE/ --name=file02.txt

用到了一个脚本nscript.groovy

msg+payload

创建,并开启工作流。
查看file01.txt及file02.txt
销毁工作流。

如果报错找不到文件nscript.groovy的话,可以考虑把nscript.groovy文件加到groovy-transform-processor-kafka-10-1.1.1.RELEASE.jar中。

Spring Cloud Config入门

1、从github上下载最新版本
spring-cloud-config

2、编译并运行spring-cloud-config-server

cd spring-cloud-config-server
../mvnw spring-boot:run

如果运行mvnw遇到下面的错误,用vi处理一下就好了

/bin/sh^M: bad interpreter: No such file or directory

用vi处理一下

:set fileformat=unix

3、默认端口为8888,默认配置仓库为github
config-repo

支持以下访问语法:

#获取配置
/{application}/{profile}[/{label}]
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml

#更新Environment、重新绑定@ConfigurationProperties、重置日志级别
/env
#刷新@RefreshScope
/refresh
#重启Spring上下文,默认是禁用的
/restart` for restarting the Spring context (disabled by default)
#调用ApplicationContext的stop及start等生命周期相关函数
/pause
/resume

我们来测试一下服务端运行情况:(服务端要可以连接到github上哦)

#foo.properties
curl localhost:8888/foo-master.properties
#foo.properties tag=v1.0.0.RC2
curl localhost:8888/v1.0.0.RC2/foo-master.properties
#foo-development.properties
curl localhost:8888/foo-development.properties
#foo-db.properties
curl localhost:8888/foo-db.properties
#foo-development-db.properties
curl localhost:8888/foo-development-db.properties

4、看一下自带的例子
spring-cloud-config-sample/src/main/java/sample/Application.java


package sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class Application {

	@Autowired
	private Environment environment;

	@Value("${spring.application.name}")
	private String appName;
	
	@RequestMapping("/appName")
	public String appName() {
		return appName;
	}

	@RequestMapping("/")
	public String query(@RequestParam("q") String q) {
		return environment.getProperty(q);
	}

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

}

5、测试一下例子

cd spring-cloud-config-sample
mvn spring-boot:run

6、看一下输出信息

Mapping servlet: 'dispatcherServlet' to [/]
Mapping filter: 'metricsFilter' to: [/*]
Mapping filter: 'characterEncodingFilter' to: [/*]
Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
Mapping filter: 'httpPutFormContentFilter' to: [/*]
Mapping filter: 'requestContextFilter' to: [/*]
Mapping filter: 'webRequestLoggingFilter' to: [/*]
Mapping filter: 'applicationContextIdFilter' to: [/*]
Mapping : Mapped "{[/]}" onto public java.lang.String sample.Application.query(java.lang.String)
Mapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
Mapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
Mapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
Mapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
Mapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
Mapping     : Mapped "{[/dump || /dump.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/auditevents || /auditevents.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public org.springframework.http.ResponseEntity<?> org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint.findByPrincipalAndAfterAndType(java.lang.String,java.util.Date,java.lang.String)
Mapping     : Mapped "{[/loggers/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.get(java.lang.String)
Mapping     : Mapped "{[/loggers/{name:.*}],methods=[POST],consumes=[application/vnd.spring-boot.actuator.v1+json || application/json],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)
Mapping     : Mapped "{[/loggers || /loggers.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/env],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.value(java.util.Map<java.lang.String, java.lang.String>)
Mapping     : Mapped "{[/env/reset],methods=[POST]}" onto public java.util.Map<java.lang.String, java.lang.Object> org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.reset()
Mapping     : Mapped "{[/pause || /pause.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
Mapping     : Mapped "{[/resume || /resume.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
Mapping     : Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/configprops || /configprops.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/features || /features.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/env/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
Mapping     : Mapped "{[/env || /env.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/health || /health.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(javax.servlet.http.HttpServletRequest)
Mapping     : Mapped "{[/restart || /restart.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.context.restart.RestartMvcEndpoint.invoke()
Mapping     : Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/heapdump || /heapdump.json],methods=[GET],produces=[application/octet-stream]}" onto public void org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint.invoke(boolean,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException,javax.servlet.ServletException
Mapping     : Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
Mapping     : Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/mappings || /mappings.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/refresh || /refresh.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
Mapping     : Mapped "{[/info || /info.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/trace || /trace.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()

7、测试一下

curl localhost:8080/?q=foo

8、是不是很简单,到自己的项目上试试吧

Spring Cloud各模块作用

首先说明一下,由于工作实在太忙了,这个系列的后面几篇,是后面补的,并没能及时进行总结和整理。
有一些细节的地方,可能并不准确(调整过部分代码和配置文件什么的),遇到问题麻烦调整一下,有问题也可以留言。

1、配置中心(基于git、svn、ftp等方式)
Spring Cloud Config

2、负载均衡
Spring Cloud Cluster

3、消息总线
Spring Cloud Bus

4、服务注册及发现
Spring Cloud Zookeeper
Spring Cloud Consul

5、任务调度
Spring Cloud Data Flow 任务调度
Spring Cloud Stream 工作流
Spring Cloud Stream App Starters 工作流实现
Spring Cloud Task 批处理
Spring Cloud Task App Starters 批处理实现

6、服务监控
Spring Cloud Sleuth

7、安全(OAuth2)
Spring Cloud Security

8、数据库、消息队列等
Spring Cloud Connectors

9、快速开发groovy
Spring Cloud CLI

10、Moke测试
spring-cloud-contract

11、部署(现在有mvn和docker两种方式)
spring-cloud-deployer

12、第三方云厂商集成
Spring Cloud for Amazon Web Services
Spring Cloud for Cloud Foundry
Spring Cloud Cloud Foundry Service Broker

13、Spring Cloud Netflix
archaius 配置管理
ribbon 客户端负载均衡
feign 基于注解的JSON库,自带ribbon功能
eureka 服务注册、LB、故障转移
hystrix 断路器/日志采集
turbine 性能日志聚合
Zuul API网关,数据路由+负载均衡
spectator/servo 客户端性能指标采集
atlas 时间序列数据库

14、其他
sidecar 将非JVM服务,封装为Netflix服务,并提供代理,让非JVM服务调用

15、移动端支持
RxJava 响应式编程支持(observable, operator和susbscriber)

几种常见的微服务编排模式

随着需要管理服务的增多,如何编排服务,成了一个很迫切的问题。本文就介绍几种常见的微服务编排方式:

1、Orchestration
这种方式,和BPM、ESB的思想很相似,实现方案多是同步的。
首先要有一个流程控制服务,该服务接收请求,依照业务逻辑规则,依次调用各个微服务,并最终完成处理逻辑。
这种方法的好处是,流程控制服务时时刻刻都知道每一笔业务究竟进行到了什么地步,监控业务成了相对简单的事情。
这种方法的坏处是,流程控制服务很容易控制了太多的业务逻辑,耦合度过高,变得臃肿,而各个微服务退化为单纯的增删改查,容易失去自身价值。

为了便于理解,您可以把控制服务看作BPM、ESB引擎,微服务为BPM、ESB的各种组件。

2、Choreography
这种方式,可以看作一种消息驱动模式,或者说是订阅发布模式,实现方案多是异步的。
每笔业务到来后,各个监听改事件的服务,会主动获取消息,处理,并可以按需发布自己的消息。
这种方法的好处是,耦合度低,每个服务都可以各司其职。
这种方法的坏处是,业务流程是通过订阅的方式来体现的,很难直接监控每笔业务的处理,因此需要增加相应的监控系统,来保证业务顺畅进行。

为了便于理解,您可以把不同队列看作不同种类的消息,微服务看作消息处理函数。

3、API网关
API网关,可以看作一种简单的接口聚合/拆分的方式。
每笔业务到来后,先到达网关,网关调用各微服务,并最终聚合/拆分需反馈的结果。
这种方法的好处是,对外接口相对稳定,可以利用LAN的带宽,弥补因特网的不足。
这种方法的坏处是,只适合业务逻辑较为简单的场景,业务逻辑过于复杂时,网关接口耦合度及复杂度会急剧升高,变得臃肿。

其实就是一个适配网关,比如对于Web端,可以一个页面同时发起几十个请求,而对于移动端,最好是一个页面就几个请求
。而采用API网关,后面的微服务可以是相同的。

ZooKeeper配置集群

以在同一台机器上的三个节点的集群为例:

1、在每个节点的zoo.cfg增加下面的配置(只给出了变动的部分)

dataDir=D:/Publish/ZooKeeper/node01
clientPort=2181
server.1=localhost:2888:3888
server.2=localhost:2889:3889
server.3=localhost:2888:3888
dataDir=D:/Publish/ZooKeeper/node02
clientPort=2182
server.1=localhost:2888:3888
server.2=localhost:2889:3889
server.3=localhost:2888:3888
dataDir=D:/Publish/ZooKeeper/node03
clientPort=2183
server.1=localhost:2888:3888
server.2=localhost:2889:3889
server.3=localhost:2888:3888

2、在每个dataDir增加一个myid文件,内容分别为1,2,3

3、现在可以启动哦

4、如果是在不同的服务器上,则dataDir、clientPort及2888:3888都不需要变动,localhost换成对应的计算机名称或ip即可。我这里是在一台电脑上运行的,所以要避免路径及端口冲突。

ZooKeeper Queue(Java)

Queue实现了生产者——消费者模式。

1、QueueTest.java

package com.neohope.zookeeper.test;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;

/**
 * Created by Hansen
 */
public class QueueTest implements Watcher {
    static ZooKeeper zk = null;
    static Object mutex;
    private String root;

    /**
     * 构造函数
     * @param hostPort
     * @param name
     */
    QueueTest(String hostPort, String name) {
        this.root = name;

        //创建连接
        if (zk == null) {
            try {
                System.out.println("Starting ZK:");
                zk = new ZooKeeper(hostPort, 30000, this);
                mutex = new Object();
                System.out.println("Finished starting ZK: " + zk);
            } catch (IOException e) {
                System.out.println(e.toString());
                zk = null;
            }

            // 创建root节点
            if (zk != null) {
                try {
                    Stat s = zk.exists(root, false);
                    if (s == null) {
                        zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
                                CreateMode.PERSISTENT);
                    }
                } catch (KeeperException e) {
                    System.out.println("Keeper exception when instantiating queue: "
                                    + e.toString());
                } catch (InterruptedException e) {
                    System.out.println("Interrupted exception");
                }
            }
        }
    }

    /**
     * exists回调函数
     * @param event     发生的事件
     * @see org.apache.zookeeper.Watcher
     */
    synchronized public void process(WatchedEvent event) {
        synchronized (mutex) {
            mutex.notify();
        }
    }

    /**
     * 添加任务队列
     * @param i
     * @return
     */
    boolean produce(int i) throws KeeperException, InterruptedException {
        String s = "element"+i;
        zk.create(root + "/element", s.getBytes(Charset.forName("UTF-8")), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT_SEQUENTIAL);

        return true;
    }


    /**
     * 从任务队列获取任务
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */
    int consume() throws KeeperException, InterruptedException {
        Stat stat = null;

        while (true) {
            synchronized (mutex) {
                List<String> list = zk.getChildren(root, true);
                if (list.size() == 0) {
                    System.out.println("Going to wait");
                    mutex.wait();
                } else {
                    //首先进行排序,找到id最小的任务编号
                    Integer min = Integer.MAX_VALUE;
                    for (String s : list) {
                        Integer tempValue = new Integer(s.substring(7));
                        if (tempValue < min) min = tempValue;
                    }

                    //从节点获取任务,处理,并删除节点
                    System.out.println("Processing task: " + root + "/element" + padLeft(min));
                    byte[] buff = zk.getData(root + "/element" + padLeft(min), false, stat);
                    System.out.println("The value in task is: " + new String(buff));
                    zk.delete(root + "/element" + padLeft(min), -1);

                    return min;
                }
            }
        }
    }

    /**
     * 格式化数字字符串
     * @param num
     */
    public static String padLeft(int num) {
        return String.format("%010d", num);
    }

    /**
     * 入口函数
     * @param args
     */
    public static void main(String args[]) {
        String hostPort = "localhost:2181";
        String root = "/neohope/queue";
        int max = 10;
        QueueTest q = new QueueTest(hostPort, root);

        for (int i = 0; i < max; i++) {
            try {
                q.produce(i);
            } catch (KeeperException e) {

            } catch (InterruptedException e) {
            }
        }

        for (int i = 0; i < max; i++) {
            try {
                int r = q.consume();
                System.out.println("Item: " + r);
            } catch (KeeperException ex) {
                ex.printStackTrace();
                break;
            } catch (InterruptedException ex) {
                ex.printStackTrace();
                break;
            }
        }
    }
}

2、尝试运行一下。

ZooKeeper Barrier(Java)

Barrier主要用于ZooKeeper中的同步。

1、BarrierTest.java

package com.neohope.zookeeper.test;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;

/**
 * Created by Hansen
 */
public class BarrierTest implements Watcher, Runnable {
    static ZooKeeper zk = null;
    static Object mutex;
    String root;
    int size;
    String name;

    /**
     * 构造函数
     *
     * @param hostPort
     * @param root
     * @param name
     * @param size
     */
    BarrierTest(String hostPort, String root, String name, int size) {
        this.root = root;
        this.name = name;
        this.size = size;

        //创建连接
        if (zk == null) {
            try {
                System.out.println("Begin Starting ZK:");
                zk = new ZooKeeper(hostPort, 30000, this);
                mutex = new Object();
                System.out.println("Finished starting ZK: " + zk);
            } catch (IOException e) {
                System.out.println(e.toString());
                zk = null;
            }
        }

        // 创建barrier节点
        if (zk != null) {
            try {
                Stat s = zk.exists(root, false);
                if (s == null) {
                    zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
                            CreateMode.PERSISTENT);
                }
            } catch (KeeperException e) {
                System.out.println("Keeper exception when instantiating queue: "
                                + e.toString());
            } catch (InterruptedException e) {
                System.out.println("Interrupted exception");
            }
        }
    }

    /**
     * exists回调函数
     * @param event     发生的事件
     * @see org.apache.zookeeper.Watcher
     */
    synchronized public void process(WatchedEvent event) {
        synchronized (mutex) {
            mutex.notify();
        }
    }

    /**
     * 新建节点,并等待其他节点被新建
     *
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */
    boolean enter() throws KeeperException, InterruptedException{
        zk.create(root + "/" + name, "Hi".getBytes(Charset.forName("UTF-8")), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL);

        System.out.println("Begin enter barier:" + name);
        while (true) {
            synchronized (mutex) {
                List<String> list = zk.getChildren(root, true);

                if (list.size() < size) {
                    mutex.wait();
                } else {
                    System.out.println("Finished enter barier:" + name);
                    return true;
                }
            }
        }
    }


    /**
     * 新建节点,并等待其他节点被新建
     *
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */
    boolean doSomeThing()
    {
        System.out.println("Begin doSomeThing:" + name);
        //do your job here
        System.out.println("Finished doSomeThing:" + name);
        return true;
    }

    /**
     * 删除自己的节点,并等待其他节点被删除
     *
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */

    boolean leave() throws KeeperException, InterruptedException{
        zk.delete(root + "/" + name, -1);

        System.out.println("Begin leave barier:" + name);
        while (true) {
            synchronized (mutex) {
                List<String> list = zk.getChildren(root, true);
                if (list.size() > 0) {
                    mutex.wait();
                } else {
                    System.out.println("Finished leave barier:" + name);
                    return true;
                }
            }
        }
    }

    /**
     * 线程函数,等待DataMonitor退出
     * @see java.lang.Runnable
     */
    @Override
    public void run() {
        //进入barrier
        try {
            boolean flag = this.enter();
            if (!flag) System.out.println("Error when entering the barrier");
        } catch (KeeperException ex) {
            ex.printStackTrace();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        //处理同步业务
        try {
            doSomeThing();
            Thread.sleep(1000);
        } catch (InterruptedException e) {

        }

        //离开barrier
        try {
            this.leave();
        } catch (KeeperException ex) {
            ex.printStackTrace();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 入口函数
     * @param args
     */
    public static void main(String args[]) throws IOException {
        String hostPort = "localhost:2181";
        String root = "/neohope/barrier";

        try {
            new Thread(new BarrierTest("127.0.0.1:2181", root,"001", 1)).start();
            new Thread(new BarrierTest("127.0.0.1:2181", root,"002", 2)).start();
            new Thread(new BarrierTest("127.0.0.1:2181", root,"003", 3)).start();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.in.read();
    }
}

2、运行结果(由于Finished enter barier时,第一次同步已经结束了,所以是与Begin doSomeThing混在一起的)

Begin enter barier:001
Begin enter barier:003
Begin enter barier:002

Finished enter barier:001
Begin doSomeThing:001
Finished doSomeThing:001
Finished enter barier:002
Begin doSomeThing:002
Finished doSomeThing:002
Finished enter barier:003
Begin doSomeThing:003
Finished doSomeThing:003

Begin leave barier:002
Begin leave barier:001
Begin leave barier:003
Finished leave barier:002
Finished leave barier:003
Finished leave barier:001