服务端集群搭建
当单个服务端节点无法满足当前业务吞吐量或者业务要求时,服务端将会采用集群方式对外提供服务。URule属于Web应用,所以URule服务端的集群搭建也和传统的Web应用一致,有一个负载+n个服务端节点。
接下来我们将提供一种比较简便的Redis session共享的方式搭建,并借助nginx实现负载均衡。
1. 服务端集群
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配置的分发策略默认是轮询
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实例节点维护)
- 知识包数据同步更新
在知识包页面,【查看当前已发布的知识包】点击启用后,利用http rpc通知各实例从数据库加载最新的知识包数据到jvm内存中,会弹出窗口(若事先没有配置集群节点数据,那就不会弹出如下窗口)自动显示同步结果,如下图所示:
在上述2个tomcat的控制台可以看到日志 Successfully reload file packet package:310
3.2 多客户端集群同步
(1)主动推送到客户端
- 客户端地址维护
- 查看当前已发布的知识包,在点击【推送到客户端】,如下操作所示
- 点击上图“确定”按钮后,弹出如下图所示界面(若没有配置在客户端地址,是不会显示如下列表)
在客户端tomcat控制台能看到以下日志:
Successfully receive the server side to pushed package:310(fc399b91f72240d89bc6d58fdd50538f)(1.0.3)
(2)被动拉取到客户端
- 在客户端(消费方)通过sdk调用知识包时,会从指定的服务器获取最新的知识包,实际上取决于urule.knowledgeUpdateCycle=?的值,即同步策略。
- 用postman调用客户端服务
- 查看控制台,发现了每次运行知识包时都会从服务端加载最新的
3.3 自定义同步方式
重写PacketPublishListener.java接口,再结合消息中间件MQ来实现消息的发布和订阅等。
todo