很多情况下我们需要服务自定义路由,比如需要灰度发布时线上验证环境、生产环境的服务实例路由是需要区分的,还有在SAAS化应用中,经常会把租户分成一个个组,每组分配几个服务实例,就是说组内服务实例共享,组间是隔离的。
本文在Spring Cloud的基础上,给出了一个服务分组和自定义路由的方案,并提供了范例代码,代码开源地址为:
https://github.com/tangaiyun/custom-routing-for-Spring-Cloud-service
方案的基本思路为:
- 服务发布时注册到Eureka上,服务发布时必须指定appGroupName,常用的指定方法为:
1. 在java程序启动参数中添加
--eureka.instance.app-group-name=group_1
2. docker 启动的话,在docker运行环境参数中添加:
-e "eureka.instance.app-group-name=group_1"
以上都是要指定服务实例的appGroupName为group_1。 - 服务访问时必须通过ZUUL网关按服务名字访问
比如http://localhost:8060/microservice-provider-user/1
8086为ZUUL的端口,microservice-provider-user为服务的名字 - 服务访问时必须在HTTP header 或cookie中提供一个路由码,本例中它的名字为“ROUTECODE”
- 自定义ZUUL,实现一个父类为AbstractLoadBalancerRule的类,本案中名字为“MyCustomRule”
- 重点定义MyCustomRule的 public Server choose(ILoadBalancer lb, Object key)方法
1. 读取ZK中的配置,初始化对象,并监控ZK中配置的变化
2. 获取request中cookie或header中名为"ROUTECODE"属性值
3. 根据ROUTECODE值映射到一个服务分组或者映射失败则使用默认分组
4. 获取所有服务实例,按按照appGroupName过滤并排序
5. 基于RoundRibbon算法选择一个服务实例import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.NodeCache; import org.apache.curator.framework.recipes.cache.NodeCacheListener; import org.apache.curator.retry.ExponentialBackoffRetry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; import com.netflix.zuul.context.RequestContext; public class MyCustomRule extends AbstractLoadBalancerRule { private ConcurrentHashMap<