以前的分布式系统基本上都是基于dubbo,国内现在大多数公司分布式体系这块还都是基于dubbo的。
但最近看到spring cloud 社区 各大论坛比较活跃,很强大的后起之秀,可能一两年后在国内掀起一片热潮。
下面只是针对一些点单独的示例,其实有时间应该整合到一起,做一个完整的示例。
cloud体系支持了应用微服务的集群负载,后续还需要研究下cloud各部分的集群负载。
在真正的分布式系统中,应该尽量避免单点,否则若是cloud的基础部分,如zuul config eureka若是单点,
只有一个出问题,整个分布式系统就完蛋了。
不废话了,进入正题,先看下项目截图
1. Eureka
使用eureka注册和发现服务,主要maven引入如下资源
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
这里分了三块内容:eureka-server,作为注册服务中心;eureka-provider,服务提供最; eureka-consumer 服务调用者
eureka-server
application.properties
spring.application.name=eureka-server
server.port=2100
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:${server.port}/eureka/
启动入口
package com.yonyou;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer //开启eureka服务
@SpringBootApplication //springBoot注解,spring在springBoot基础之上来构建项目
public class EurekaServerApp {
//spirng boot的标准入口
public static void main(String[] args) {
SpringApplication.run(EurekaServerApp.class, args);
}
}
启动后即可通过 http://127.0.0.1:2100/ 访问 看到相关服务信息
eureka-provider
application.properties
spring.application.name=eureka-provider
server.port=2200
logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:2100/eureka/
启动入口
/**
* @author Administrator
*
*/
@EnableDiscoveryClient //通过该注解,实现服务发现,注册
@SpringBootApplication
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class, args);
}
}
服务
package com.yonyou.student.web;
import java.util.ArrayList;
import java.util.List;
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.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.yonyou.student.Student;
@RestController
@RequestMapping("/student")
class StudentController {
@Value("${server.port}")
private String port;
@RequestMapping("list")
public List<Student> list() {
List<Student> list = new ArrayList<Student>();
list.add(new Student(111,"yaoming","156787787823","china shanghai"));
list.add(new Student(222,"chenglong","13898687682","china hongkong"));
list.add(new Student(333,"lilianjie","187923420023","china beijing"));
list.add(new Student(444,port,port,port));
return list;
}
}
eureka-consumer
spring.application.name=eureka-consumer
server.port=2300
logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:2100/eureka/
rest.student=http://eureka-provider
rest.student.list=${rest.student}/student/list
启动入口 和 调用
package com.yonyou;
import java.net.URI;
import java.util.List;
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.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @author Administrator
*
*/
@EnableDiscoveryClient //通过该注解,实现服务发现,注册
@SpringBootApplication
public class ConsumerApp {
/**
* LoadBalanced 注解表明restTemplate使用LoadBalancerClient执行请求
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate template = new RestTemplate();
SimpleClientHttpRequestFactory factory = (SimpleClientHttpRequestFactory) template.getRequestFactory();
factory.setConnectTimeout(3000);
factory.setReadTimeout(3000);
return template;
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class, args);
}
}
@RestController
class ServiceInstanceRestController {
@Autowired
@LoadBalanced
RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/service-instances/{applicationName}")
public List<ServiceInstance> serviceInstancesByApplicationName(
@PathVariable String applicationName) {
return this.discoveryClient.getInstances(applicationName);
}
@RequestMapping("/info")
public String sayhello() {
return "client-teacher";
}
// Restful服务对应的url地址
@Value("${rest.student.list}")
private String restStudentList;
@Value("${server.port}")
private String port;
@RequestMapping("/students")
public String test() {
// ServiceInstance instance = loadBalancer.choose("eureka-client-student");
// URI uri = instance.getUri();
List list = restTemplate.getForObject(restStudentList, List.class);
return port+" : "+list.toString();
}
}
去注册中心可以看到 provider 和 consumer已经起来
单独访问 provider
http://127.0.0.1:2200/student/list
访问consumer
http://127.0.0.1:2300/students
consumer调用provider是通过restTemplate实现的,这里的restStudentList就是provider的服务rest地址。这样写感觉不太友好,后面会将通过feign实现。
2. zuul 网关
maven 主要依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
properties
spring.application.name=zuul
server.port=3100
zuul.prefix=/gw
#ignoredServices: '*'
#这里的配置表示,访问/baidu/** 直接重定向到http://www.baidu.com
zuul.routes.baidu.path=/baidu/**
zuul.routes.baidu.url=http://www.baidu.com
#ribbon.eureka.enabled=false
#反响代理配置
#这里的配置类似nginx的反响代理
#当请求/api/**会直接交给listOfServers配置的服务器处理
#当stripPrefix=true的时候 (http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/user/list)
#当stripPrefix=false的时候(http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/api/user/list)
zuul.routes.api.path=/api/**
zuul.routes.api.stripPrefix=true
#api.ribbon.listOfServers=127.0.0.1:6060,127.0.0.1:6061
zuul.routes.api.serviceId=eureka-consumer
#url重写配置
#这里的配置,相当于访问/index/** 会直接渲染/home的请求内容(和直接请求/home效果一样), url地址不变
zuul.routes.index.path=/index/**
zuul.routes.index.url=forward:/home
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:2100/eureka/
boot 入口
package com.zybros;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class ZuulApp {
public static void main( String[] args ) {
SpringApplication.run(ZuulApp.class, args);
}
}
controller
package com.zybros;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
@RequestMapping("/index")
public Object index() {
return "index";
}
@RequestMapping("/home")
public Object home() {
return "home";
}
}
通过网关 http://127.0.0.1:3100/gw/api/students 可以访问到上面 consumer 的students服务
3. config 配置中心
本示例是基于本地实现的,也可以用 git svn
config-server
主要依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
boot入口
package com.zybros;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.config.server.EnableConfigServer;
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApp {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigServerApp.class).web(true).run(args);
}
}
properties
spring.application.name=config-server
server.port=1100
# git远程配置
#spring.cloud.config.server.git.uri=http://git.oschina.net/didispace/SpringBoot-Learning/
#spring.cloud.config.server.git.searchPaths=Chapter9-1-4/config-repo
#spring.cloud.config.server.git.username=username
#spring.cloud.config.server.git.password=password
# 激活本地配置 Config Server会默认从应用的src/main/resource目录下检索配置文件
# 也可以通过spring.cloud.config.server.native.searchLocations=file:F:/properties/属性来指定配置文件的位置。
spring.profiles.active=native
#URL与配置文件的映射关系如下:
#/{application}/{profile}[/{label}]
#/{application}-{profile}.yml
#/{label}/{application}-{profile}.yml
#/{application}-{profile}.properties
#/{label}/{application}-{profile}.properties
#上面的url会映射{application}-{profile}.properties对应的配置文件,{label}对应git上不同的分支,默认为master。
只有一条数据的配置文件
启动后可以通过 http://127.0.0.1:1100/cloud-test.properties 访问到
config-test远程访问配置文件数据
bootstrap.properties
server.port=1200
#spring.application.name:对应前配置文件中的{application}部分
spring.application.name=cloud
#spring.cloud.config.profile:对应前配置文件中的{profile}部分
spring.cloud.config.profile=dev
#spring.cloud.config.label:对应前配置文件的git分支
spring.cloud.config.label=master
#spring.cloud.config.uri:配置中心的地址
spring.cloud.config.uri=http://127.0.0.1:1100/
boot启动
package com.zybros;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
public class ConfigTestApp {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigTestApp.class).web(true).run(args);
}
}
访问配置文件代码
package com.zybros;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope
@RestController
class TestController {
@Value("${from}")
private String from;
@RequestMapping("/from")
public String from() {
return this.from;
}
public void setFrom(String from) {
this.from = from;
}
public String getFrom() {
return from;
}
}
http://127.0.0.1:1200/from
4. feign 为rest服务编写本地接口,这样是为了调用方便,这样的工作一边应该让开发服务的人员提供对应rest的feign接口
feign-server
properties
spring.application.name=feign-server
server.port=4100
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:2100/eureka/
boot 入口
package com.zybros;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@EnableDiscoveryClient
@SpringBootApplication
public class FeignServerApp {
public static void main(String[] args) {
new SpringApplicationBuilder(FeignServerApp.class).web(true).run(args);
}
}
下面的rest服务,需要在调用端通过feign client 编写接口
package com.zybros;
import java.util.Map;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope
@RestController
class UserController {
@RequestMapping("/login/{name}")
public String login(@PathVariable("name") String name,String pwd) {
return "============ "+name+" "+pwd+" login success ============";
}
@RequestMapping("/login2")
public String login2(String name,String pwd) {
return "============ "+name+" "+pwd+" login success ============";
}
@RequestMapping("/login3")
public String login3(@RequestBody Map map) {
return "============ "+map+" login success ============";
}
@RequestMapping("/login4")
public String login4(@RequestBody User user) {
return "============ "+user.getName()+" "+user.getPwd()+" login success ============";
}
}
feign-client
spring.application.name=feign-client
server.port=4200
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:2100/eureka/
针对feign-server暴露的rest服务编写接口,就如同前面说的,这部分接口最好是让开发上面rest服务的人员开发,这样维护统一些。
package com.zybros.user;
import java.util.Map;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient("feign-server")
public interface UserService {
@RequestMapping(value ="/login/{name}", method = RequestMethod.GET)
public String login(@PathVariable("name") String name);
@RequestMapping(value ="/login2", method = RequestMethod.GET)
public String login2(@RequestParam ("name") String name,@RequestParam ("pwd") String pwd);
@RequestMapping(value ="/login3", method = RequestMethod.GET)
public String login3(@RequestBody Map<String, Object> map);
@RequestMapping(value ="/login4", method = RequestMethod.POST)
public String login4(@RequestBody User user);
}
controller调用feign写的接口,容器为针对接口生成实现类,实现类内部通过restTemplate调用rest服务
package com.zybros.user;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Maps;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@RestController
class UserController {
@Autowired
private UserService userService; // 远程服务
@RequestMapping("/login-test1")
public String login(String name, String pwd) {
return userService.login(name);
}
@HystrixCommand(fallbackMethod="fallback2")
@RequestMapping("/login-test2")
public String login2(String name, String pwd) {
return userService.login2(name,pwd);
}
@RequestMapping("/login-test3")
public String login3(String name, String pwd) {
HashMap<String, Object> map = Maps.newHashMap();
map.put("name", name);
map.put("pwd", pwd);
return userService.login3(map);
}
@RequestMapping("/login-test4")
public String login4(String name, String pwd) {
return userService.login4(new User(name,pwd));
}
public String fallback2(String name,String pwd){
return "===== fallback2 ======= "+name+" "+pwd+" login success ============";
}
}
两个启动后访问 http://127.0.0.1:4200/login-test3?name=stone&pwd=123
这就是通过接口访问传map,也可以传其他类型参数
5. hystrix circuit
maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
代码还是上面feign-client的代码,其中controller中的login-test2
为了测试,可以试着将 feign-server 停掉。访问使用hystrix的login-test2和未使用hystrix的login-test3
http://127.0.0.1:4200/login-test3?name=stone&pwd=123
http://127.0.0.1:4200/login-test2?name=stone&pwd=123
上面只是简单的罗列了些主要内容,详情可以看原示例
代码地址:http://download.csdn.net/detail/stonexmx/9774537