This is an event create/purchase RESTful API Service. Client can create user, create event, purchase tickets, and transfer tickets. Focus on the replication features of the event service which is a replicated, fault tolerant service.
The design of my service and the demonstration of the testing framework are recorded here: Youtube
The architecture of the service looks like this:
In a system with N data storage servers, you will tolerate the failure of up to N-1 nodes. As long as one data storage server is available, a client request will succeed.
A front end will not receive data older than it has seen before if fresher data is available. If, for example, a client posts a message and then performs a read, the response the client receives must include the most recent messages unless all data storage servers storing the newest data have failed.
The event services will maintain the membership of all the services, including: primary/secondary event service, primary user service, and front end service. Event services will gossip with each other to get the service list, and to add itself to each other's list. Front end services will greet with the primary service to add itself to primary's list. The primary event/user service will be configured when the service starts. Once event service detcets a service is unreachable, it will remove it from its list.
During the gossip between secondaries, start an election when detecting primary is down. Send GET request to other event services with higher rank address. If no reply, send POST request to announce there is a new primary. If received a response, it means that there is an outstanding service, wait for announcement. Each node will only send one election request in one round of the election, unless it timeout to wait for the announcement.
The event service has three states: primary, secondary, and candidate. The service will start with primary or secondary state by configuration. A secondary will trun into candidate state when it detects the primary is down or when it receives a election request. A candidate will turn into primary if there is no outstanding node, and it will turn into secondary when receives an announcement.
A synchronized data structure to maintain the logs of committed request. It contains with Universally Unique IDentifier, Lamport Timestamps, and the committed data. The purpose is to avoid duplicate data, and to maintain the order of replications.
When a front end service receives a write request, it will assign the request with an uuid and pass it to the primary event service. The primary event service will start the write operation, and right after it finished, it will assign the request with a Lamport Timestamp, commit to log, and pass it to the secondary event service. If the primary fails during replication, the front end will hold the request and retry it when a new primary comes up. If a new primary has already committed the write with the same uuid, it will ignore it and pass it with the timestamp it committed to the secondary event service. If a secondary receives a write request with the uuid it already committed, it will match with its timestamp. If the uuid and the timestamp don't match, it will request a full copy from the primary to overwrite the data. Full backup from primary will only happen when new secondary comes up or the above situation.
GET /events
Responses:
Code | Description |
200 | Event Details[ { "eventid": 0, "eventname": "string", "userid": 0, "avail": 0, "purchased": 0 } ] |
400 | No events found |
POST /events/create
Body:
{ "userid": 0, "eventname": "string", "numtickets": 0 }
Responses:
Code | Description |
200 | Event created
{ "eventid": 0 } |
400 | Event unsuccessfully created |
GET /events/{eventid}
Responses:
Code | Description |
200 | Event Details{ "eventid": 0, "eventname": "string", "userid": 0, "avail": 0, "purchased": 0 } |
400 | Event not found |
POST /events/{eventid}/purchase/{userid}
Body:{ "tickets": 0 }
Responses:
Code | Description |
200 | Tickets purchased |
400 | Tickets could not be purchased |
POST /users/create
Body:
{ "username": "string" }
Responses:
Code | Description |
200 | User created{ "userid": 0 } |
400 | User could not be created |
GET /users/{userid}
Responses:
Code | Description |
200 | User Details{ "userid": 0, "username": "string", "tickets": [ { "eventid": 0, "eventname": "string", "userid": 0, "avail": 0, "purchased": 0 } ] } |
400 | User not found |
POST /users/{userid}/tickets/transfer
Body:
{ "eventid": 0, "tickets": 0, "targetuser": 0 }
Responses:
Code | Description |
200 | Event tickets transferred |
400 | Tickets could not be transferred |
GET /greet
Responses:
Code | Description |
200 | Front End Service is running |
POST /election
Body:
{ "port": 0 }
Responses:
Code | Description |
200 | New primary event service has been configured |
POST /create
Body:
{ "userid": 0, "eventname": "string", "numtickets": 0 }
Responses:
Code | Description |
200 | Event created
{ "eventid": 0 } |
400 | Event unsuccessfully created |
GET /list
Responses:
Code | Description |
200 | List of events [ { "eventid": 0, "eventname": "string", "userid": 0, "avail": 0, "purchased": 0 } ] |
GET /{eventid}
Responses:
Code | Description |
200 | Event details{ "eventid": 0, "eventname": "string", "userid": 0, "avail": 0, "purchased": 0 } |
400 | Event not found |
POST /purchase/{eventid}
Body:
{ "userid": 0, "eventid": 0, "tickets": 0 }
Responses:
Code | Description |
200 | Event tickets purchased |
400 | Tickets could not be purchased |
POST /greet/event
Body:
{ "port": 0, }
Responses:
Code | Description |
200 | Service list[ { "service": "event", "address": "10.0.1.9:4599", "primary": true }, { "service": "frontend", "address": "10.0.1.5:4560", "primary": false } ] |
400 | Service unreachable |
POST /greet/frontend
Body:
{ "port": 0, }
Responses:
Code | Description |
200 | Service list[ { "service": "event", "address": "10.0.1.9:4599", "primary": true }, { "service": "frontend", "address": "10.0.1.5:4560", "primary": false } ] |
400 | Service unreachable |
GET /election
Responses:
Code | Description |
200 | A service with higher rank is running |
POST /election
Body:
{ "port": 0, }
Responses:
Code | Description |
200 | New primary event service has been configured |
400 | Service unreachable |
POST /create
Body:
{ "username": "string" }
Responses:
Code | Description |
200 | User created{ "userid": 0 } |
400 | User unsuccessfully created |
GET /{userid}
Responses:
Code | Description |
200 | User details{ "userid": 0, "username": "string", "tickets": [ { "eventid": 0 } ] } |
400 | User not found |
POST /{userid}/tickets/add
Body:
{ "eventid": 0, "tickets": 0 }
Responses:
Code | Description |
200 | Event tickets added |
400 | Tickets could not be added |
POST /{userid}/tickets/transfer
Body:
{ "eventid": 0, "tickets": 0, "targetuser": 0 }
Responses:
Code | Description |
200 | Event tickets transfered |
400 | Tickets could not be transfered |
Start Event Service (Primary)
$ java -cp project4.jar EventService.EventServiceDriver -port <port> -primaryEvent this - primaryUser <address_of_primary_user>
Start Event Service (Secondary)
$ java -cp project4.jar EventService.EventServiceDriver -port <port> -primaryEvent <address_of_primary_event> - primaryUser <address_of_primary_user>
Start Front End Service
$ java -cp project4.jar FrontEndService.FrontEndDriver -port <port> -primaryEvent <address_of_primary_event> - primaryUser <address_of_primary_user>
Test for write and read
$ python test_wr.py <choose_0_to_2_for_different_eventname> <address_of_front_end>
Read directly from backend
$ python3 test_read_backend.py <address_of_event_service>
Test for election and replication of secondaries with different version data
$ python3 test_diff_ver.py <address_of_front_end>
Test for concurrent writes
$ java -cp project4.jar Usage.ConcurrentTest <0_for_create_1_for_purchase> <address_of_front_end> <times_of_test>
This is a course project, not using for any commercial purpose.