订阅
纠错
加入自媒体

基于Redis实现Spring Cloud Gateway的动态管理

2019-08-22 09:30
EAWorld
关注

2.网关数据管理

实现一个适合自已的网关,对数据管理需要考虑哪些方面的东西呢?

1.首先,我们要考虑一下,我们需要管理些什么数据。

SCG本身对数据管理的管理是很弱的。它没有提供数据的持久化方案,它所有的数据都来自初始化,来自它的配置文件(application.yml)。它本身虽然也对外提供了一些管理接口(Actuator API)能力不够,但能力不够,且这些修改都是暂时的,网关一停,数据就消失了。这就要求我们要用一套更完善的方案,把网关的这些数据管理起来,不能让它只能写在配置文件中,而要支持持久化,支持动态变更。再有就是我们对各微服务的治理数据。网关只用来做路由转发,那就太浪费了,统一认证,统一鉴权,访问日志记录,应用访问统计,黑白名单过滤,API订阅管理,流量限制,甚至数据格式转换,网络协议转换,都可以在网关中来做。而所有的这些能力,无不需要数据的支持。因此,这些服务的治理配置,也是网关需要管理的数据。

2.数据有了,我们还得考虑怎么把它保存起来,不能网关一重启,所有数据就没了。

3.还得再考虑一下数据的读取。网关对性能的要求是很高的,每次对过关的数据进行治理,都需要去读取这些配置信息。如果配置信息读取太消耗资源,无疑对网关是不利的。所以,我们还得考虑数据如何缓存,以提高数据的读取性能。

4.单个网关,可以处理的请求量是有上限的。为了应对大的流量,我们可能会需要对网关做水平扩容。当多个网关实例共存时,如何保障对网关的修改,能快速同步到每个网关实例呢?数据变更通知也得考虑。

5.最多,我们还得考虑一下方案的扩展,数据存储能不能改个地方,通知能不能换种方式?

综合考虑了这些方面之后,我们的网关的架构如下:

基于Redis实现Spring Cloud Gateway的动态管理

gateway-arch

如图,以上就是我们网关的整体设计。方案设计要点如下:

网关对外提供治理数据管理接口, 微服务治理平台可通过这些接口, 将治理配置推送到网关

网关通过治理数据统一存储接口, 将治理配置数据保持至治理数据持久存储(这里我们默认为Redis)

Redis通过发布订阅能力, 将数据的变更通知到各网关实例

各网关实例收到通知后, 将数据从持久存储同步至内部高速缓存

内部缓存在网关启动时, 会自动从持久存储加载对应配置进入缓存. 同时它也支持清空, 以及按需加载

外部业务请求经过网关时, 对数据执行鉴权,处理转换, 以及灰度策略时,所需要治理配置,都从内部缓存中获取, 以提升性能

方案中, 外部持久存储(默认用的Redis, 可以换成Mysql, 文件, Appolo等), 以及数据变更通知(默认使用的是Redis的发布订阅, 可以换成Appolo通知, 消息队列, 定时扫描等), 都是可以扩展的

3.实现细节

动态路由管理

Spring Cloud Gateway作为所有请求流量的入口,在实际生产环境中为了保证高可靠和高可用,尽量避免重启, 需要实现Spring Cloud Gateway动态路由配置。实现动态路由其实很简单, 重点在于 RouteDefinitionRepository 这个接口. 这个接口继承自两个接口, 其中 RouteDefinitionLocator 是用来加载路由的. 它有很多实现类, 其中的 PropertiesRouteDefinitionLocator 就用来实现从yml中加载路由. 另一个 RouteDefinitionWriter 用来实现路由的添加与删除. 通过查看spring cloud gateway的源码可以发现, 在 org.springframework.cloud.gateway.config.GatewayAutoConfiguration中这么一段:

@Bean@ConditionalOnMissingBean(RouteDefinitionRepository.class)public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {    return new InMemoryRouteDefinitionRepository();}

可以看出, 网关中如果没有RouteDefinitionRepository的Bean, 就会采用InMemoryRouteDefinitionRepository做为实现。这个 InMemoryRouteDefinitionRepository有一个问题, 就是数据没有持久化, 网关重启之后,原来通过接口设置的路由就会丢失了。

这当然是不可接受的, 所以我们需要实现自已的 RouteDefinitionRepository, 来提供路由配置信息。如使用redis做为存储, 来实现路由的存储。实现请参考文章:https://dwz.cn/tsHfKwMe

除此以外, 每当路由更改之后, 还需要通知网关刷新路由。这需要发送 RefreshRoutesEvent 来通知网关。如下列示例:

@Componentpublic class RouteDynamicService implements ApplicationEventPublisherAware {    private ApplicationEventPublisher publisher;

@Override    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {      this.publisher = publisher;    }

/**    * 刷新路由表    */    public void refreshRoutes() {      publisher.publishEvent(new RefreshRoutesEvent(this));    }}

刷新可以通过消息通知机制来触发, 当然, 也可以对外接供rest接口, 手动触发。### 数据存储

基于Redis实现Spring Cloud Gateway的动态管理

如上述类图所示, IGovernDataRepository为治理数据统一存储接口。RedisGovernDataRepository为实现的它的抽像类, 它需要依赖两个, 一个是StringRedisTemplate,用来实现redis数据的存储。另一个为 RedisKeyGenerator, 用来为各治理对象生成对应的key。RedisGovernDataRepository下面则为各个治理数据存储的实现类。使用Redis做为持久存储时, 需要注意以下几点:

为对象生成key时, 建议为key添加一个命名空间(就是加一段有意义的前缀)

在redis中进行模糊搜索时, 提供给Redis的pattern, 不能是一个正则的通配, 它支持三种通配 *(多个), (单个)

如果数据量比较大, 不建议使用keys进行模糊查询, 应该使用scan方式

数据缓存

我们提供了内部缓存,它处于使用者与持久存储之间,缓存数据以提升性能。缓存的实现主要有如下几点:

实现了 InitializingBean 以实现在网关启动时, 自动加载数据

内部使用了ConcurrentHashMap, 保证写时的线程同步, 又保证了get时的高效(get整个过程不需要加锁)

从缓存中取数据时, 如果需要懒加载, 当从持久存储中加载不到数据时, 建议使用空数据, 或空集合占位, 避免每次都去持久存储中查询

代码示例如下:

/**  * 根据 appCode 获取流量策略  *   * @param appCode  * @return  */public Set<ApplicationTrafficPolicy> getAppTrafficPolicies(String appCode) {  // 从缓存加载  Map<String, ApplicationTrafficPolicy> map = policyMap.get(appCode);  // 缓存中没有  if (map == null) {    // 尝试从持久存储中加载所有此网关的流量策略    Set<ApplicationTrafficPolicy> policies = trafficPolicyRepository.fuzzyQuery();    // 持久存储中没有任何流量策略,占个位置,防止缓存重复去加载    if (policies == null || policies.size() == 0) {      map = new ConcurrentHashMap<>();      policyMap.put(appCode, map);    } else {      // 持久存储中有流量策略,放入缓存      for (ApplicationTrafficPolicy policy : policies) {        setTrafficPolicy(policy);      }      // 重新从缓存中加载一次      map = policyMap.get(appCode);      // 如果还是没有,使用空 map 占位子      if (map == null) {        map = new ConcurrentHashMap<>();        policyMap.put(appCode, map);      }    }  }  return map.values().stream().collect(Collectors.toSet());}

事件通知

事件通知,这里我们使用的是redis的发布与订阅能力。Redis默认是不发送事件的,要让它发布事件,需要先修改它的配置文件redis.conf,添加一个配置:

notify-keyspace-events "K$g"

上面的配置将使得Redis中发生数据的添加,修改或删除时,发送set或del事件。

然后,我们需要配置一个RedisMessageListenerContainer,用来订阅我们感兴趣的事件。

@BeanRedisMessageListenerContainer container(MessageListenerAdapter listenerAdapter) {  String gtwReidsPattern = "__keyspace@*__:" + GTW + keyGenerator.getGatewayCode() + "]*";  String cofRedisPattern = "__keyspace@*__:" + COF + cacheKey.getKeyNameSpace() + USER_NAME + "*";  log.info("Add gateway redis message listener, patternTopic is {}", gtwReidsPattern);  log.info("Add coframe redis message listener, patternTopic is {}", cofRedisPattern);  RedisMessageListenerContainer container = new RedisMessageListenerContainer();  container.setConnectionFactory(redisTemplate.getConnectionFactory());  // PatternTopic 参考:http://redisdoc.com/topic/notification.html  container.addMessageListener(listenerAdapter, Arrays.asList(new PatternTopic(PatternUtil.fmt(gtwReidsPattern)), new PatternTopic(PatternUtil.fmt(cofRedisPattern))));  return container;}当redis事件订阅好了之后, 每次其中我们关心的数据有变更, 都会发送set或del事件.我们需要定义一个 MessageListener, 来接收事件:@Service(value = RedisMessageListener.REDIS_LISTENER_NAME)public class RedisMessageListener implements MessageListener {  @Override  public void onMessage(Message message, byte[] pattern) {    String ops = new String(message.getBody());    String channel = new String(message.getChannel());    String key = channel.split(":")[1];

if ("set".equals(ops)) {      String value = redisTemplate.opsForValue().get(key);      handleSet(key, value);    } else if ("del".equals(ops)) {      handleDel(key);    }  }  ...}

接收到事件后,会调用相应的内部缓存,更新内部缓存中的数据,以实现治理数据变更的及时生效。

精选提问:

问1:当前网关实例因为网络的原因,如果没有订阅到消息,消息会重发吗?

答:不会。但内存缓存会定期清理,以解决这种数据不同步的问题。也可以主动清理。

问2:网关使用了zuul了吗?还是自己实现的网关?

答:网关于Spring Cloud Gateway开发,他就是一个类似于zuul的API网关。

问3:netttyserver是干嘛的?

答:那是Spring Cloud Gateway本身使用的组件, 用来接收与处理请求的。

问4:文件上传的接口也通过网关吗?

答:这个要看具体需求。也可以走网关,  但会对性能有一定影响。不走网关, 就得在应用那一层来控制权限。网关控制权限, 只是相当于把权限校验前移与统一化了。

问5:在微服务化之后,网关路由到服务,调用会有超时的情况怎么处理?有些接口是必须要这么长时间,例如批量操作 。只能通过加大超时时间吗?

答:这个一个考虑适当增大超时时间,  另一个,  你可以考虑采用异步模式, 比如用任务来处理。

问6:我想提问下,目前gateway我看实现是基于netty实现的http协议的,通过相关的mapping处理断言然后处理过滤器。那有基于netty的tcp协议的实现方案吗?基于tcp怎么整合断言和过滤器呢?

答:TCP的我们也在考虑, 有这方面的需求.  但是直接基于TCP实现断言与过滤, 工作量估计会比较大.  现在倾向的方案是在网关前做一层TCP的协议转换, 将TCP将成 http 再发往网关. 这样可以直接利用网关现有能力。

<上一页  1  2  
声明: 本文由入驻维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。

发表评论

0条评论,0人参与

请输入评论内容...

请输入评论/评论长度6~500个字

您提交的评论过于频繁,请输入验证码继续

暂无评论

暂无评论

    云计算 猎头职位 更多
    文章纠错
    x
    *文字标题:
    *纠错内容:
    联系邮箱:
    *验 证 码:

    粤公网安备 44030502002758号