服務端集群搭建
當單個(gè)服務端節點無法滿足當前業務吞吐量或者業務要求時(shí),服務端将會(huì)采用集群方式對外提供服務。URule屬于Web應用,所以URule服務端的集群搭建也和(hé)傳統的Web應用一緻,有(yǒu)一個(gè)負載+n個(gè)服務端節點。
接下來(lái)我們将提供一種比較簡便的Redis session共享的方式搭建,并借助nginx實現負載均衡。
1. 服務端集群
2.在集群環境上(shàng)實現Session共享
關于session共享的方式有(yǒu)多(duō)種,常見的有(yǒu):
(1)通(tōng)過nginx的ip_hash,根據ip将請(qǐng)求分配到對應的服務器(qì),相當于開(kāi)啓了會(huì)話(huà)保持功能
(2)基于關系型數(shù)據庫存儲
(3)基于cookie存儲
(4)服務器(qì)內(nèi)置的session複制(zhì)域
(5)基于nosql(memcache、redis都可(kě)以)
實現原理(lǐ)也比較簡單,在所有(yǒu)的請(qǐng)求之前配置一過濾器(qì),在請(qǐng)求之前操作(zuò)session,其實spring-session中真正起作(zuò)用的session過濾器(qì)是SessionRepositoryFilter。而spring-session集成了redis與mongodb,若啓用redis方式隻需要稍加配置即可(kě)實現。
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請(qǐng)去掉本依賴 -->
<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:開(kāi)啓Session共享功能。使用此注解之後Session調用會(huì)自動通(tōng)過Redis存儲和(hé)獲取。另外,想要達到Session共享的目的,在其他的系統上(shàng)隻需要做(zuò)同樣的配置即可(kě)。 其中maxInactiveIntervalInSeconds參數(shù)是設置Session失效時(shí)間(jiān),在使用Redis Session之後,原Spring Boot的server.session.timeout屬性不再生(shēng)效。
(3)修改Session的儲存方式為(wèi)redis
# session的存儲方式配置為(wèi)redis
spring:
session:
store-type: redis
redis:
host: localhost
port: 6379
database: 1
timeout: 10000
jedis:
pool:
max-active: 50 # 連接池最大(dà)連接數(shù)(使用負值表示沒有(yǒu)限制(zhì))
max-wait: 5000 # 連接池中連接用完時(shí),新的請(qǐng)求等待時(shí)間(jiān)(毫秒(miǎo)),超過該時(shí)間(jiān)抛出異常JedisConnectionException,(默認-1,負值表示沒有(yǒu)限制(zhì),不建議使用默認值)
max-idle: 10 # 連接池中的最大(dà)空(kōng)閑連接,默認8
min-idle: 2 # 連接池中的最小(xiǎo)空(kōng)閑連接,默認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 測試頁面
2.4 實現規則設計(jì)器(qì)中複制(zhì)粘貼的多(duō)實例同步
@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.知識包數(shù)據同步問題
重點說明(míng):
- 知識包在發布後被編譯成一個(gè)rete樹(shù),RETE算(suàn)法的核心是以‘內(nèi)存空(kōng)間(jiān)換取執行(xíng)時(shí)間(jiān)’,故而發布後的知識包是直接存儲在jvm虛拟內(nèi)存中,那(nà)麽要解決的是各實例內(nèi)存中知識包數(shù)據同步更新問題
- 解決這個(gè)問題參考文檔14.6.知識包多(duō)環境部署 · GitBook
- 我們也可(kě)以自行(xíng)實現PacketPublishListener接口,用來(lái)攔截發布知識包操作(zuò),将狀态寫入到MQ中來(lái)通(tōng)知其它實例自行(xíng)加載最新的知識包數(shù)據
- 另外,知識包數(shù)量越多(duō)(體(tǐ)積越大(dà)),占用的jvm內(nèi)存空(kōng)間(jiān)也越大(dà),那(nà)麽我們在生(shēng)産環境上(shàng)設置jvm堆內(nèi)存大(dà)小(xiǎo)建議在4g以上(shàng)
而事實上(shàng)‘知識包集群環境下內(nèi)存同步問題與上(shàng)述session共享并無關聯’,session是由于多(duō)實例造成緩存不同步問題,而知識包是內(nèi)存不同步造成的,內(nèi)存比緩存性能更高(gāo)。
3.1 多(duō)服務端集群同步
在集群環境下,由于服務端會(huì)有(yǒu)多(duō)個(gè)實例節點,各實例(jvm)之間(jiān)是物理(lǐ)隔離的,那(nà)麽當在某一個(gè)實例上(shàng)【發布/啓用知識包新版本】時(shí),就需要立即同步到其它服務實例的jvm內(nèi)存中,我們可(kě)以在系統管理(lǐ)端中維護集群實例信息來(lái)解決這一問題。
更多(duō)配置請(qǐng)查看文檔3.8.集群管理(lǐ) · GitBook,,都是在集群環境下內(nèi)存同步引起的一系列問題。
- 集群(8080,8081實例節點維護)
- 知識包數(shù)據同步更新
在知識包頁面,【查看當前已發布的知識包】點擊啓用後,利用http rpc通(tōng)知各實例從數(shù)據庫加載最新的知識包數(shù)據到jvm內(nèi)存中,會(huì)彈出窗口(若事先沒有(yǒu)配置集群節點數(shù)據,那(nà)就不會(huì)彈出如下窗口)自動顯示同步結果,如下圖所示:
在上(shàng)述2個(gè)tomcat的控制(zhì)台可(kě)以看到日志(zhì) Successfully reload file packet package:310
3.2 多(duō)客戶端集群同步
(1)主動推送到客戶端
- 客戶端地址維護
- 查看當前已發布的知識包,在點擊【推送到客戶端】,如下操作(zuò)所示
- 點擊上(shàng)圖“确定”按鈕後,彈出如下圖所示界面(若沒有(yǒu)配置在客戶端地址,是不會(huì)顯示如下列表)
在客戶端tomcat控制(zhì)台能看到以下日志(zhì):
Successfully receive the server side to pushed package:310(fc399b91f72240d89bc6d58fdd50538f)(1.0.3)
(2)被動拉取到客戶端
- 在客戶端(消費方)通(tōng)過sdk調用知識包時(shí),會(huì)從指定的服務器(qì)獲取最新的知識包,實際上(shàng)取決于urule.knowledgeUpdateCycle=?的值,即同步策略。
- 用postman調用客戶端服務
- 查看控制(zhì)台,發現了每次運行(xíng)知識包時(shí)都會(huì)從服務端加載最新的
3.3 自定義同步方式
重寫PacketPublishListener.java接口,再結合消息中間(jiān)件MQ來(lái)實現消息的發布和(hé)訂閱等。
todo