- 前言
- 第一部分 基础应用开发
- 第 1 章 Spring Boot 入门
- 第 2 章 在 Spring Boot 中使用数据库
- 第 3 章 Spring Boot 界面设计
- 第 4 章 提高数据库访问性能
- 第 5 章 Spring Boot 安全设计
- 第二部分 分布式应用开发
- 第 6 章 Spring Boot SSO
- 第 7 章 使用分布式文件系统
- 第 8 章 云应用开发
- 第 9 章 构建高性能的服务平台
- 第三部分 核心技术源代码分析
- 第 10 章 Spring Boot 自动配置实现原理
- 第 11 章 Spring Boot 数据访问实现原理
- 第 12 章 微服务核心技术实现原理
- 附录 A 安装 Neo4j
- 附录 B 安装 MongoDB
- 附录 C 安装 Redis
- 附录 D 安装 RabbitMQ
- 结束语
8.3 使用动态路由和断路器
8.2 节创建了一个发现服务器,并向其注册了几个微服务(客户端),这一节将介绍如何在这些服务之间,使用动态路由、断路器和故障容错等功能。
当客户端发现服务器注册之后,客户端之间就可以通过 Zuul 路由代理协议,使用应用的名称来访问各自的 REST 资源。
8.3.1 依赖配置
要启用动态路由、断路器和服务监控等功能,首先需要在各个客户端的工程中引用如代码清单 8-14 所示的 Maven 依赖配置。
代码清单 8-14 启用路由和断路器等的依赖配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
其次在各个使用上述功能的工程的主程序中,增加如下注解,即可启用路由代理服务和启用监控服务功能。
@EnableZuulProxy @EnableHystrix
8.3.2 共享 REST 资源
在实例工程的 data 模块,使用 Neo4j 图形数据库,创建了三个节点,分别是单位、用户、角色,并创建了两个关系,即用户隶属于单位的关系和用户拥有角色的关系。有关节点和关系的实体建模如表 8-3 所示。
表 8-3 实体建模列表

节点和关系实体的持久化,可以使用资源库的方式实现。代码清单 8-15 是用户节点的持久化实现,并且在实现持久化的同时也共享了 REST 资源。其中注解 @Reposit-oryRestResource 标注这个接口不但是一个数据库的资源库,也是一个 REST 资源共享,并把资源的名称和路径定义为 users。将在下一小节中介绍如何使用这个 REST 的资源共享。
代码清单 8-15 用户节点的 REST 资源库
@RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends GraphRepository<User> {
User findByName(@Param("name") String name);
@Query("MATCH (u:User) WHERE u.name =~ ('(?i).*'+{name}+'.*') RETURN u")
Collection<User> findByNameContaining(@Param("name") String name);
}
现在可以使用如代码清单 8-16 所示的测试程序来为 Neo4j 数据库生成一些测试数据,以方便后面的测试。测试程序首先清空数据库的记录(如果存在的话),然后创建一个单位命名为“开发部”,创建两个角色分别是 admin 和 manage,创建一个用户,用户名为 user,将用户“安排”到单位“开发部”中,并给它“分配”两个角色为 admin 和 manage。
代码清单 8-16 Neo4j 测试程序
@Autowired
UnitRepository unitRepository;
@Autowired
RoleRepository roleRepository;
@Autowired
UserRepository userRepository;
@Test
public void initData() {
userRepository.deleteAll();
roleRepository.deleteAll();
unitRepository.deleteAll();
Unit unit = new Unit();
unit.setName("开发部
");
unit.setCreate(new Date());
unitRepository.save(unit);
Assert.notNull(unit.getId());
Role role = new Role();
role.setName("admin");
role.setCreate(new Date());
roleRepository.save(role);
Assert.notNull(role.getId());
Role role1 = new Role();
role1.setName("manage");
role1.setCreate(new Date());
roleRepository.save(role1);
Assert.notNull(role1.getId());
User user = new User();
user.setName("user");
user.setSex(1);
user.setEmail("user@email.com");
user.setCreate(new Date());
user.beBelong(unit,"安排
");
user.addOwner(role, "分配
");
user.addOwner(role1, "分配
");
userRepository.save(user);
Assert.notNull(user.getId());
}
运行测试程序,可以生成一些测试数据。数据生成后,启动 data 模块,然后在浏览器中输入网址: http://localhost:9000/users 。
现在可以看到如图 8-3 所示的资源数据,从图中可以看出,可以使用下列链接来访问这些资源数据。
http://localhost:9000/users/132 http://localhost:9000/users/search/findByName?name=user……

图 8-3 节点实体 User 的 REST 资源
如果这时启动 web 模块,然后使用链接 http://localhost:9001/data/users ,同样可以看到类似图 8-3 所示的样子,只不过这时是在 web 模块打开的链接,而不是 data 模块,如图 8-4 所示。也就是说,可以在 web 模块中,像使用本地数据一样使用 data 模块的数据。

图 8-4 在 web 模块中查询的节点实体 User 的 REST 资源
8.3.3 通过路由访问 REST 资源
可以使用下面几种方法通过 Zuul 路由代理方式来访问不同服务中的 REST 资源。
- JavaScript;
- RestTemplate;
- FeignClient。
代码清单 8-17 是在 web 模块中,使用 data 模块中的 REST 资源数据的一个 jQuery 例子,即将用户名称作为参数,调用 data 模块的 findByNameContaining 使用模糊查询的方法,返回符合条件的用户列表。
代码清单 8-17 使用 jQuery 调用 REST 资源的例子
var pageaction = function(){
$.get('/data/users/search/findByNameContaining?name='+$("#name").val(),
function(data){
var currentData = data["_embedded"].users;
fillData(currentData);
});
}
代码清单 8-18 是使用 RestTemplate 的一个例子,即同样使用用户名称作为参数,调用 data 模块的 findByName 方法,返回一个用户对象。
代码清单 8-18 使用 RestTemplate 调用 REST 资源的实例
@Service
public class UserService {
@Autowired
RestTemplate restTemplate;
public User getUserByName(String name) {
Map<String, Object> params = new HashMap<>();
params.put("name", name);
User user = restTemplate.getForObject("http://data/users/search/find-ByName?name={name}", User.class, params);
return user;
}
}
代码清单 8-19 是使用 FeignClient 的一个例子,它使用注解 @FeignClient("data")的方式,创建一个接口,调用 data 模块中的 findById 方法。
代码清单 8-19 使用 FeignClient 调用 REST 资源的实例
@FeignClient("data")
public interface UserClient {
@RequestMapping(method = RequestMethod.GET, value = "/users/{id}")
User findById(@RequestParam("id") Long id);
}
上面各种调用方式,可以按照程序设计的要求选择使用。要使用 FeignClient,还必须在工程的 Maven 依赖中增加“spring-cloud-starter-feign”依赖,并在工程的主程序中增加一个注解:@EnableFeignClients,以启用 FeignClient 的功能。
8.3.4 使用断路器功能
断路器是微服务中的一个故障容错的线路保护开关。如同电路中的负载保护开关一样,断路器将在所调用的服务过载或出现故障时,自动阻断对服务的访问和调用,转而调用备用方法。
当一个系统服务突然出现故障或超载时,后面访问将会陷入无限地延迟和等待的状态之中,由于请求得不到及时响应,访问者可能会因此而不断发送请求,这将造成严重的恶性循环,最终导致整个系统陷入瘫痪状态,甚至会完全崩溃。使用断路器可以避免这种情况发生,起到防患于未然的作用。
代码清单 8-20 是一个使用断路器和故障容错功能的实例。注解 @HystrixCommand 为 getUserByName 方法配备一个备用方法:getUserFallback。由 getUserFallback 的实现代码可知,该方法只构造了一些虚假数据,以便及时返回请求结果。
代码清单 8-20 使用断路器的实例
@Service
public class UserService {
@Autowired @LoadBalanced
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "getUserFallback")
public User getUserByName(String name) {
Map<String, Object> params = new HashMap<>();
params.put("name", name);
User user = restTemplate.getForObject("http://data/users/search/findBy
Name?name={name}", User.class, params);
return user;
}
private User getUserFallback(String name) {
User user = new User();
user.setName(name + " not find");
user.setEmail("user email");
Belong belong = new Belong();
Unit unit = new Unit();
unit.setName("unit name");
belong.setUnit(unit);
user.setBelong(belong);
Owner owner = new Owner();
Role role = new Role();
role.setName("role name");
owner.setRole(role);
List<Owner> owners = new ArrayList<>();
owners.add(owner);
user.setOwners(owners);
return user;
}
}
8.3.5 路由器和断路器测试
现在可以测试路由器和断路器的功能,首先启动实例工程的 discovery 模块,然后启动实例工程的 data 模块和 web 模块,在浏览器中输入网址 http://localhost:9001/ 。
返回一个用户列表的界面,这是在 web 服务中请求了 data 服务,并从中取得用户列表数据的结果。在界面中,单击查看,将向 data 服务请求这个用户的详细资料,如果正常,就打开用户的详细信息界面。图 8-5 是一个正常状态的用户信息查看界面。

图 8-5 服务正常时查看用户信息的界面
如果这时制造人为的故障,直接关闭 data 服务。现在再在界面中单击查看,它不会陷入无限的等待之中,而是立刻就返回了,只是界面上的用户信息是我们上面编写的一些虚假数据,如图 8-6 所示。因为 data 服务出现了故障,所以对 data 服务的请求就触发了断路器的熔断机制。

图 8-6 服务故障时查看用户信息的界面
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论