以前的分布式系统基本上都是基于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/from4. 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