Exemplo n.º 1
0
 protected function getShardConnection($shardID, array $options = [])
 {
     if (!is_numeric($shardID)) {
         throw new Exception('$shardID must be an integer');
     }
     $isWriteQuery = !empty($options['isWriteQuery']);
     $lastLinkFailed = !empty($options['lastLinkFailed']);
     $writeInReadMode = !empty($options['writeInReadMode']);
     // TEMP
     if (get_called_class() == 'Zotero_FullText_DB') {
         $linkID = "FT" . $shardID;
     } else {
         $linkID = $shardID;
     }
     if ($this->isReadOnly($shardID) && $isWriteQuery && !$writeInReadMode) {
         throw new Exception("Cannot get link for writing to shard {$shardID} in read-only mode");
     }
     // Master queries always use the cached link that was created at class construction
     if ($shardID == 0) {
         //error_log($this->connections[$linkID]->link->getConnection()->host_info);
         return $this->connections[$linkID];
     }
     // For read-only mode and read queries, use a cached link if available. Since this is
     // done before checking the latest shard info, it's possible for subsequent read queries
     // in a request to go through even if the shard was since disabled, but that's generally
     // not a big deal, and new requests will check the shard info again and throw.
     //
     // Read-only mode
     if ($this->isReadOnly($shardID) && !$writeInReadMode) {
         // Use a cached replica link if available.
         if (isset($this->replicaConnections[$linkID])) {
             // If the last link failed, try the next one. If no more, that's fatal.
             if ($lastLinkFailed) {
                 $lastHost = $this->replicaConnections[$linkID][0]->host;
                 if (sizeOf($this->replicaConnections[$linkID]) == 1) {
                     throw new Exception("Read failed from replica {$lastHost} -- no more replica connections", Z_ERROR_SHARD_UNAVAILABLE);
                 }
                 error_log("WARNING: Read failed from replica {$lastHost} -- retrying on another replica");
                 array_shift($this->replicaConnections[$linkID]);
             }
             //error_log($this->replicaConnections[$linkID][0]->link->getConnection()->host_info);
             return $this->replicaConnections[$linkID][0];
         }
     } else {
         if (!$isWriteQuery && isset($this->connections[$linkID])) {
             //error_log($this->connections[$linkID]->link->getConnection()->host_info);
             return $this->connections[$linkID];
         }
     }
     $shardInfo = Zotero_Shards::getShardInfo($shardID);
     if (!$shardInfo) {
         throw new Exception("Invalid shard {$shardID}");
     }
     if ($shardInfo['state'] == 'down') {
         throw new Exception("Shard {$shardID} is down", Z_ERROR_SHARD_UNAVAILABLE);
     } else {
         if ($shardInfo['state'] == 'readonly') {
             if ($isWriteQuery && get_called_class() != 'Zotero_Admin_DB') {
                 throw new Exception("Cannot write to read-only shard {$shardID}", Z_ERROR_SHARD_READ_ONLY);
             }
         }
     }
     if ($this->isReadOnly($shardID) && !$writeInReadMode) {
         $replicas = Zotero_Shards::getReplicaInfo($shardInfo['shardHostID']);
         if ($replicas) {
             $writerCacheKey = 'shardHostReplicasWriter_' . $shardInfo['shardHostID'];
             $writerAddress = Z_Core::$MC->get($writerCacheKey);
             $writerConn = null;
             // Randomize replica order
             shuffle($replicas);
             foreach ($replicas as $replica) {
                 $info = $shardInfo;
                 $info['address'] = $replica['address'];
                 $info['port'] = $replica['port'];
                 $conn = new Zotero_DB_Connection();
                 $conn->host = $info['address'];
                 $conn->shardID = $linkID;
                 $conn->link = $this->getLinkFromConnectionInfo($info);
                 if ($replica['address'] == $writerAddress) {
                     $writerConn = $conn;
                 } else {
                     $this->replicaConnections[$linkID][] = $conn;
                 }
             }
             // If we know the writer, add it last, so that all read replicas are tried first
             if ($writerConn) {
                 $this->replicaConnections[$linkID][] = $writerConn;
             } else {
                 try {
                     $results = $this->replicaConnections[$linkID][0]->link->query("SHOW GLOBAL VARIABLES LIKE 'innodb_read_only'");
                     $row = $results->fetch(Zend_Db::FETCH_ASSOC);
                     $connReadOnly = $row ? $row['Value'] : false;
                     // If we found the writer
                     if ($connReadOnly == "OFF") {
                         // Store host in memcached so that every request doesn't need to check
                         // the variables to sort the writer last.
                         //
                         // This can probably be increased, because the only consequence of not
                         // knowing the writer is that a few requests could use the writer
                         // for reads.
                         Z_Core::$MC->set($writerCacheKey, $this->replicaConnections[$linkID][0]->host, 60);
                         // If more than one connection, move writer connection to end and
                         // close it
                         if (sizeOf($this->replicaConnections[$linkID]) > 1) {
                             $conn = array_shift($this->replicaConnections[$linkID]);
                             $this->replicaConnections[$linkID][] = $conn;
                             $conn->link->closeConnection();
                         }
                     }
                 } catch (Exception $e) {
                     error_log("WARNING: Failed checking replica state: {$e}");
                 }
             }
             //error_log($this->replicaConnections[$linkID][0]->link->getConnection()->host_info);
             return $this->replicaConnections[$linkID][0];
         }
     }
     // Host isn't read-only or down, so write queries can use a cached link if available.
     // Otherwise, make a new link.
     if (!isset($this->connections[$linkID])) {
         $conn = new Zotero_DB_Connection();
         $conn->host = $shardInfo['address'];
         $conn->shardID = $linkID;
         $conn->link = $this->getLinkFromConnectionInfo($shardInfo);
         $this->connections[$linkID] = $conn;
     }
     //error_log($this->connections[$linkID]->link->getConnection()->host_info);
     return $this->connections[$linkID];
 }