私はソケットとMySQLデータベースを使用してチャットサーバーを作成しています。それが機能したら、それを拡張してより複雑なゲームサーバーにしたいと考えています。
デザインに欠けているものがないか知りたい。
具体的には、さまざまなスレッドからのデータベースアクセス、または作成するスレッドが多すぎるかどうか、どこかでボトルネックが発生する可能性があるかどうかが心配です。
私には3つの機能があります。
メイン関数は、ループして受け入れるServerHandler関数の2番目のスレッドを開始します新しいクライアント接続。次に、そのServerHandler関数は、ClientHandler関数のクライアント接続ごとに新しいスレッドを開きます。
コード/疑似コード(これまでのところ、私がまだアーキテクチャーを検討している間に不完全です)は以下のとおりです。 Scalaで書いています。
私の主な質問は、ClientHandler関数と、間違った順序で何かをしているのかどうかです。個別のクライアントスレッドが予期しない順序でデータベースの書き込みと読み取りを行い、再現性のない問題を引き起こすリスクがありますか?
実行するコマンドのリストを含む別のスレッドが必要かどうか疑問に思うので、1つのクライアントが書き込み、次に読み取り、次に別のクライアントが書き込み、読み取りなどを確実に実行できます。いくつかのデータベースの読み取り/書き込みリストを維持しますか?またはそれはデータベースサーバーによって何らかの方法で処理されますか?
ほとんどの場合、さまざまなスレッド間でのデータベースの読み取り/書き込みを理解していないこと、そしてここで私が間違っていることが明らかであるかどうかについて啓蒙されたいと思います。プログラム構造のデザインは良く見えますか?
スレッドに関しては、多くのクライアント接続(1k、10k、100k?)でサーバーをテストし、クライアント接続制限を安全だと思うものに設定する必要がありますか?
// ** ClientHandler **
// This is run in a new thread for each client that connects to the ServerHandler
// This does:
// 1. Set up input and output streams to communicate with 1 client
// 2. Set up connection to database (MySQL server running on the same machine), including:
// a. Establish connection
// b. Read some data from the database (latest version #, # of clients connected, etc)
// 3. Send welcome message to client (latest version #, # of clients connected, etc)
// 4. Set "startTime" variable to current system time in milliseconds to detect client timeout
// 5. Loop and do this (nothing is blocking, so irrelevant steps will be skipped):
// 1. Handle client message (if there is a new one), including:
// a. Parse client message, including:
// i. If we have not verified the client yet, we only accept one command: "connect"
// ii. On "connect", we verify the ID and update the database (most recent log in time)
// b. Database reads/writes/updates as necessary, depending on the command
// c. Send a response message back to the client with the results
// Even if there is no client message received, we do this:
// 2. Check for server-side updates that should be notified to the client, including:
// a. Database reads
// b. Timestamp comparisons, checking when we last notified the client of the server state
// 3. Notify client of any server-side state changes (if necessary)
// 6. If the client times out (~5000ms), update the database that the client has disconnected
// 7. Close database connection
// 8. Close socket
ClientHandler(socket){
// Set up input and output streams to communicate with 1 client
val inputstream = new BufferedReader(new InputStreamReader(socket.getInputStream()))
val outputstream = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
// Set up connection to database (MySQL server running on the same machine)
val db = Database.forConfig("mydb")
// Read some data from the database (latest version #, # of clients connected, etc)
...
// Send welcome message to client (latest version #, # of clients connected, etc)
outputstream.write(...).newLine().flush()
// Set "startTime" variable to current system time in milliseconds to detect client timeout
var startTime = System.currentTimeMillis()
//
...
}
// ** ServerHandler **
// This runs once in its own thread (because it contains a blocking call)
// This creates a new thread to run ClientHandler for each client that connects
ServerHandler(server_socket){
while(true){
val socket = server_socket.accept() // this is a blocking call
val client_handler = new ClientHandler(socket)
val thread = new Thread(client_handler)
thread.start() // when a client connects, start a new thread to handle that client
}
}
// ** main **
// This does 3 things:
// 1. Start a new thread for ServerHandler which sits and waits for clients to connect
// 2. Loop and accept commands from admin via console
// 3. Process server-side logic in real-time
main{
// Start a new thread for ServerHandler
val server_socket = new ServerSocket(port 10000)
val server_handler = new ServerHandler(server_socket)
val thread = new Thread(server_handler)
thread.start() // start a thread for the server handler
// Loop and accept commands from admin via console
while(true){
input match{
case "stats" =>
// print stats (# of clients logged in etc)
case "quit" =>
// close server_socket etc and stop program
case _ =>
// unrecognized command
}
}
}
データベースは、長期間にわたって複数のスレッドからアクセスできるように設計されています。データベースでトランザクションを使用する方法をググリングしてみてください。読み取りと書き込みの同時実行に備えて一貫性を確保する方法がわかります。
また、mysqlを使用していることを確認し、古いmyisamバックエンドを使用していないことを確認してください。多くの一貫性機能のサポートが不足しています。新しいinnodbバックエンドはthouseの問題を共有していません。