本文將講解基於Spring Cloud和Ignite的RESTful Web服務的創建過程。該服務是將Ignite用作爲高性能內存數據庫的容器化應用,使用HashiCorp Consul進行服務發現,並通過Spring Data存儲庫抽象與Ignite集羣進行交互,容器化通過Docker實現。
配置和啓動
配置Consul服務註冊
爲了方便起見,可以使用Consul的官方Docker鏡像(需要先安裝Docker):
啓動Consul的Docker鏡像:
docker run -d -p 8500:8500 -p 8600:8600/udp --name=consul consul agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0
創建一個基本的Spring Cloud應用
創建該應用可以從https://start.spring.io入手。
這裏需要Spring Web和Spring Consul Discovery模塊,Spring Web是創建REST應用的最簡單的框架之一,Spring Consul Discovery可以將多個Spring應用實例組合成一個服務。
![](https://www.gridgain.com/sites/default/files/inline-images/image1.pn
- 通過Spring Initializr配置項目(如上圖所示);
- 點擊
Generate
按鈕,然後會下載包含項目工程的壓縮包; - 使用某個IDE打開該壓縮包;
- (可選)將
application.properties
文件替換爲application.yml
,當然可以繼續使用屬性文件格式,但是本文後續會使用YAML格式。
在application.yml
中,爲Sprint Cloud添加了基本的參數,給應用命名並啓用了Consul服務發現:
spring:
application:
name: myapp
instance_id: 1
cloud:
consul:
enabled: true
service-registry:
enabled: true
添加Ignite依賴
可以從Maven中央倉庫獲得Ignite的最新版本,然後將其添加到構建配置文件中:
implementation 'org.apache.ignite:ignite-spring-data_2.2:2.8.1'
compile group: 'org.apache.ignite', name: 'ignite-core', version: '2.8.1'
implementation 'org.apache.ignite:ignite-spring-boot-autoconfigure-ext:1.0.0'
配置Ignite節點發現
如何使Ignite節點相互發現呢?Ignite具有一個IpFinder
組件,該組件用於處理節點的註冊和發現,更多的細節信息,請參見Ignite的文檔。
爲了集成IpFinder和Consul服務註冊,需要使用Spring Cloud Discovery模塊,可以將模塊與大量發現服務一起使用(例如Zookeeper)。
對於HashiCorp Consul,有一個簡單的IpFinder實現,但是在GitHub上有許多IpFinder的第三方實現。
配置Spring Cloud和Ignite
因爲使用了Ignite的自動配置模塊,所以配置是一個相對簡單的過程,將以下內容放入application.yml
文件中:
ignite:
workDirectory: /opt/ignite/
Ignite將其數據存儲在工作目錄中。
現在需要添加對Ignite Spring Data存儲庫的支持。添加支持的方法之一是通過Java的Configuration
:只需添加一個EnableIgniteRepositories
註解,並將存儲庫軟件包作爲參數即可:
@Configuration
@EnableIgniteRepositories(value = "com.github.sammyvimes.bootnite.repo")
public class IgniteConfig {
}
在編譯應用之前,需要解決一些問題。Ignite不支持Spring Data的H2版本。因此必須在構建配置中重置H2的版本(無論是Gradle還是Maven):
ext {
set('h2.version', '1.4.197')
}
另外,Spring Cloud Consul和hystrix還有已知的問題,因此需要將其排除:
implementation ('org.springframework.cloud:spring-cloud-starter-consul-discovery') {
exclude group: 'org.springframework.cloud', module: 'spring-cloud-netflix-hystrix'
}
最後,關於Spring Data BeanFactory,還有一個問題:BeanFactory會查找一個名爲igniteInstance
的bean,而自動配置則提供一個名爲ignite
的bean,後續會解決這個問題。
但是現在,需要對配置類做如下的修改來解決BeanFactory的問題:
@Configuration
@EnableIgniteRepositories(value = "com.github.sammyvimes.bootnite.repo")
public class IgniteConfig {
@Bean(name = "igniteInstance")
public Ignite igniteInstance(Ignite ignite) {
ignite.active(true);
return ignite;
}
}
創建Ignite的Spring Data存儲庫和服務
下面會創建一個基於Ignite Spring Data存儲庫的CRUD服務。
下面會從最常見的示例開始,即Employee
類:
public class Employee implements Serializable {
private UUID id;
private String name;
private boolean isEmployed;
// constructor, getters & setters
}
定義數據模型之後,就要添加配置器bean:
@Bean
public IgniteConfigurer configurer() {
return igniteConfiguration -> {
CacheConfiguration cache = new CacheConfiguration("employeeCache");
cache.setIndexedTypes(UUID.class, Employee.class);
igniteConfiguration.setCacheConfiguration(cache);
};
}
這樣當應用啓動後,會部署employeeCache
緩存,該緩存包含可加快對Employee
實體查詢的索引。
接下來,與其他Spring Data存儲服務一樣,需要創建一個存儲庫和一個服務:
@Repository
@RepositoryConfig(cacheName = "employeeCache")
public interface EmployeeRepository
extends IgniteRepository<Employee, UUID> {
Employee getEmployeeById(UUID id);
}
注意RepositoryConfig
註解,其將存儲庫鏈接到即將使用的緩存,之前已經使用employeeCache
字符串作爲名字創建了一個緩存。
接下來是一個使用該存儲庫的簡單服務:
@Service
@Transactional
public class EmployeeService {
private final EmployeeRepository repository;
// constructor injection FTW
public EmpoyeeService(final EmployeeRepository repository) {
this.repository = repository;
}
public List<Employee> findAll() {
return StreamSupport.stream(repository.findAll().spliterator(), false)
.collect(Collectors.toList());
}
public Employee create(final String name) {
final Employee employee = new Employee();
final UUID id = UUID.randomUUID();
employee.setId(id);
employee.setEmployed(true);
employee.setName(name);
return repository.save(id, employee);
}
}
這裏有2個方法,create
和findAll
,用於演示Ignite和Spring Data的集成。
配置REST控制器
下面會配置一些端點,以便可以訪問和修改數據,一個簡單的控制器就可以了:
@RestController
@RequestMapping("/employee")
public class EmployeeController {
private final EmpoyeeService service;
public EmployeeController(final EmployeeService service) {
this.service = service;
}
@GetMapping
public ResponseEntity<List<Employee>> employees() {
return ResponseEntity.ok(service.findAll());
}
@PostMapping
public ResponseEntity<Employee> create(@RequestParam final String name) {
return ResponseEntity.ok(service.create(name));
}
}
這樣,這個應用就開發完了。
現在就可以啓動應用,通過向/employee
發送POST
請求來創建數據,並通過發送GET
請求來查詢數據。
雖然這個應用性能很高,但是分佈式存儲的優勢在哪裏呢?
在Docker中部署應用
可以通過Docker來創建和管理應用節點。
首先在工程的根目錄,爲應用創建一個Dockerfile:
FROM adoptopenjdk:8-jre-hotspot
RUN mkdir /opt/app && mkdir /opt/ignite
COPY build/libs/bootnite-0.0.1-SNAPSHOT.jar /opt/app/app.jar
CMD ["java", "-jar", "/opt/app/app.jar"]
簡單地說:獲取OpenJDK,爲應用創建目錄,往鏡像中複製應用的二進制文件,並創建默認命令以啓動應用。
現在,在工程的根目錄中,運行gradle build
和docker build -t ignite-test-app:0.1 -f .dockerfile
。
接下來,通過Docker,同時啓動應用的兩個實例。
通過編寫了以下shell腳本,可以幫助啓動節點:
MacOS:
export HOST_IP=$(ipconfig getifaddr en0)
docker run -P -e HOST_IP=${HOST_IP} -e DISCO_PORT=$2 -p $2:47500 --name $1 ignition
Linux:
export HOST_IP=$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+')
docker run -P -e HOST_IP=${HOST_IP} -e DISCO_PORT=$2 -p $2:47500 --name $1 ignition
在這裏所做的是獲取主機IP地址,以便可以使用端口轉發,並使節點之間可以相互通信。使用-p
參數爲Docker容器創建端口轉發規則,並使用-e
參數將外部端口的值保存在容器的環境中,以便稍後可以在如下配置中使用這些值:
spring:
application:
name: myapp
instance_id: 1
cloud:
consul:
enabled: true
host: ${HOST_IP}
service-registry:
enabled: true
ignition:
disco:
host: ${HOST_IP}
port: ${DISCO_PORT}
這裏添加了自定義配置參數ignition.disco.host
和ignition.disco.port
,這些參數將在自定義IP探測器中使用。注意還通過添加主機IP地址來更改Consul的配置。
現在,更改Ignite Java配置後就完成了:
@Bean
public TcpDiscoveryConsulIpFinder finder(final ConsulDiscoveryClient client,
final ConsulServiceRegistry registry,
final ConsulDiscoveryProperties properties,
@Value("${ignition.disco.host}") final String host,
@Value("${ignition.disco.port}") final int port) {
return new TcpDiscoveryConsulIpFinder(client, registry, properties, host, port);
}
@Bean
public IgniteConfigurer configurer(final TcpDiscoveryConsulIpFinder finder) {
return igniteConfiguration -> {
CacheConfiguration cache = new CacheConfiguration("employeeCache");
cache.setIndexedTypes(UUID.class, Employee.class);
igniteConfiguration.setCacheConfiguration(cache);
final TcpDiscoverySpi tcpDiscoverySpi = new TcpDiscoverySpi();
tcpDiscoverySpi.setIpFinder(finder);
igniteConfiguration.setDiscoverySpi(tcpDiscoverySpi);
};
}
執行下面的測試,可以看下是否一切正常:
- 啓動一個應用實例:
./starter/start.sh test1 30000
; - 將一些數據提交到實例;例如
curl -d'name = admin'http:// localhost:32784/employee
,不同的容器使用不同的端口; - 通過在終端中執行
docker ps
確定容器的端口轉發規則(規則類似於0.0.0.0:32784->8080/tcp),確定要使用的端口; - 啓動另一個節點:
./starter/start.sh test2 30001
; - 停止第一個節點(如果是從終端啓動容器,則可以使用
Ctrl + c
); - 執行對第二個節點的
GET
請求,並驗證從此請求獲取的數據和從第一個請求獲取的數據是否相同:curl http://localhost:32785/employee
。
注意,此curl命令中的端口和上一個curl命令中的端口不相同,因爲兩個應用實例佔用不同的端口。
如上所述,通過上述幾段簡單的代碼,就實現了Ignite和Spring Cloud架構的集成。