服务端集群搭建

​ 当单个服务端节点无法满足当前业务吞吐量或者业务要求时,服务端将会采用集群方式对外提供服务。URule属于Web应用,所以URule服务端的集群搭建也和传统的Web应用一致,有一个负载+n个服务端节点。

接下来我们将提供一种比较简便的Redis session共享的方式搭建,并借助nginx实现负载均衡。

1. 服务端集群

img

2.在集群环境上实现Session共享

关于session共享的方式有多种,常见的有:

(1)通过nginx的ip_hash,根据ip将请求分配到对应的服务器,相当于开启了会话保持功能

(2)基于关系型数据库存储

(3)基于cookie存储

(4)服务器内置的session复制域

(5)基于nosql(memcache、redis都可以)

实现原理也比较简单,在所有的请求之前配置一过滤器,在请求之前操作session,其实spring-session中真正起作用的session过滤器是SessionRepositoryFilter。而spring-session集成了redis与mongodb,若启用redis方式只需要稍加配置即可实现。

2.1 集成Redis实现Session共享

(1)集成spring-session-data-redis

 <!-- 引入redis的集成 -->
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <exclusions>
            <exclusion>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
            </exclusion>
            <exclusion>
                <artifactId>lettuce-core</artifactId>
                <groupId>io.lettuce</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.6.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
<!-- 引入 session与redis的集成:若不用redis请去掉本依赖 -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

(2)配置类添加(非必须)

@Configuration
//不设置默认30分钟
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class RedisSessionConfig {

    @Bean("redisTemplate")
    @ConfigurationProperties(prefix="spring.redis")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        //将key的序列化设置成StringRedisSerializer
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setHashKeySerializer(keySerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

}

@EnableRedisHttpSession:开启Session共享功能。使用此注解之后Session调用会自动通过Redis存储和获取。另外,想要达到Session共享的目的,在其他的系统上只需要做同样的配置即可。 其中maxInactiveIntervalInSeconds参数是设置Session失效时间,在使用Redis Session之后,原Spring Boot的server.session.timeout属性不再生效。

(3)修改Session的储存方式为redis

# session的存储方式配置为redis
spring:
  session:
    store-type: redis

  redis:
    host: localhost
    port: 6379
    database: 1
    timeout: 10000
    jedis:
      pool:
        max-active: 50    # 连接池最大连接数(使用负值表示没有限制)
        max-wait: 5000    # 连接池中连接用完时,新的请求等待时间(毫秒),超过该时间抛出异常JedisConnectionException,(默认-1,负值表示没有限制,不建议使用默认值)
        max-idle: 10      # 连接池中的最大空闲连接,默认8
        min-idle: 2       # 连接池中的最小空闲连接,默认0

2.2 nginx配置负载均衡示例

nginx的配置文件如下

upstream nginx-cluster{
        server localhost:8080;
        server localhost:8081;
    }

 server {
        listen       80;
        server_name  localhost;

        location / {
             proxy_pass http://nginx-cluster;
         }
 }

2.3 测试页面

访问nginx地址http://192.168.1.16:80/,会自动跳由到任一tomcat实例上,通过日志可以发现上述nginx配置的分发策略默认是轮询

img

2.4 实现规则设计器中复制粘贴的多实例同步

@Service
public class SessionRedisClipboardStore implements ClipboardStore {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public void set(String key, String value) {
        String sessionkey = RequestHolder.getRequest().getSession().getId()+key;
        stringRedisTemplate.opsForValue().set(sessionkey,value);
        RequestHolder.getRequest().getSession().setAttribute(sessionkey, value);
    }

    @Override
    public String get(String key) {
        String sessionkey = RequestHolder.getRequest().getSession().getId()+key;
        String val = stringRedisTemplate.opsForValue().get(sessionkey);
        if(StringUtils.hasText(val)){
            return val;
        }
        return (String) RequestHolder.getRequest().getSession().getAttribute(sessionkey);
    }

    @Override
    public void remove(String key) {
        String sessionkey = RequestHolder.getRequest().getSession().getId()+key;

        stringRedisTemplate.delete(sessionkey);
        RequestHolder.getRequest().getSession().removeAttribute(sessionkey);
    }
}

3.知识包数据同步问题

重点说明:

  • 知识包在发布后被编译成一个rete树,RETE算法的核心是以‘内存空间换取执行时间’,故而发布后的知识包是直接存储在jvm虚拟内存中,那么要解决的是各实例内存中知识包数据同步更新问题
  • 解决这个问题参考文档14.6.知识包多环境部署 · GitBook
  • 我们也可以自行实现PacketPublishListener接口,用来拦截发布知识包操作,将状态写入到MQ中来通知其它实例自行加载最新的知识包数据
  • 另外,知识包数量越多(体积越大),占用的jvm内存空间也越大,那么我们在生产环境上设置jvm堆内存大小建议在4g以上

而事实上‘知识包集群环境下内存同步问题与上述session共享并无关联’,session是由于多实例造成缓存不同步问题,而知识包是内存不同步造成的,内存比缓存性能更高。

3.1 多服务端集群同步

在集群环境下,由于服务端会有多个实例节点,各实例(jvm)之间是物理隔离的,那么当在某一个实例上【发布/启用知识包新版本】时,就需要立即同步到其它服务实例的jvm内存中,我们可以在系统管理端中维护集群实例信息来解决这一问题。

更多配置请查看文档3.8.集群管理 · GitBook,,都是在集群环境下内存同步引起的一系列问题。

  • 集群(8080,8081实例节点维护)

img

  • 知识包数据同步更新

在知识包页面,【查看当前已发布的知识包】点击启用后,利用http rpc通知各实例从数据库加载最新的知识包数据到jvm内存中,会弹出窗口(若事先没有配置集群节点数据,那就不会弹出如下窗口)自动显示同步结果,如下图所示:

img

在上述2个tomcat的控制台可以看到日志 Successfully reload file packet package:310

3.2 多客户端集群同步

(1)主动推送到客户端

  • 客户端地址维护

img

  • 查看当前已发布的知识包,在点击【推送到客户端】,如下操作所示

img

  • 点击上图“确定”按钮后,弹出如下图所示界面(若没有配置在客户端地址,是不会显示如下列表)

img

  • 在客户端tomcat控制台能看到以下日志:

    Successfully receive the server side to pushed package:310(fc399b91f72240d89bc6d58fdd50538f)(1.0.3)

(2)被动拉取到客户端

  • 在客户端(消费方)通过sdk调用知识包时,会从指定的服务器获取最新的知识包,实际上取决于urule.knowledgeUpdateCycle=?的值,即同步策略。

img

  • 用postman调用客户端服务

img

  • 查看控制台,发现了每次运行知识包时都会从服务端加载最新的

img

3.3 自定义同步方式

重写PacketPublishListener.java接口,再结合消息中间件MQ来实现消息的发布和订阅等。

todo

results matching ""

    No results matching ""