Saat ini, jika Anda menulis semacam aplikasi Python, maka kemungkinan besar Anda harus melengkapinya dengan fungsi klien HTTP yang dapat berkomunikasi dengan server HTTP. Di mana-mana API REST telah menjadikan alat HTTP fitur yang disegani dalam proyek perangkat lunak yang tak terhitung jumlahnya. Itu sebabnya setiap programmer perlu memiliki pola yang bertujuan mengatur kerja optimal dengan koneksi HTTP.

Ada banyak klien HTTP untuk Python. Yang paling umum di antara mereka, dan, selain itu, yang mudah dikerjakan, bisa disebut
permintaan . Saat ini, pelanggan ini adalah standar de facto.
Koneksi Permanen
Optimalisasi pertama yang dipertimbangkan ketika bekerja dengan HTTP adalah menggunakan koneksi persisten ke server web. Koneksi persisten telah menjadi standar sejak HTTP 1.1, tetapi banyak aplikasi masih tidak menggunakannya. Kelemahan ini mudah dijelaskan, karena mengetahui bahwa ketika menggunakan pustaka
requests
dalam mode sederhana (misalnya, menggunakan metode
get
), koneksi ke server ditutup setelah menerima respons dari itu. Untuk menghindari hal ini, aplikasi perlu menggunakan objek
Session
, yang memungkinkan penggunaan kembali koneksi terbuka:
import requests session = requests.Session() session.get("http://example.com")
Koneksi disimpan di kumpulan koneksi (standarnya adalah 10 koneksi secara default). Ukuran kolam dapat disesuaikan:
import requests session = requests.Session() adapter = requests.adapters.HTTPAdapter( pool_connections=100, pool_maxsize=100) session.mount('http://', adapter) response = session.get("http://example.org")
Menggunakan kembali koneksi TCP untuk mengirim beberapa permintaan HTTP memberi aplikasi banyak manfaat kinerja:
- Mengurangi beban pada prosesor dan mengurangi kebutuhan untuk RAM (karena fakta bahwa koneksi lebih sedikit terbuka pada saat yang sama).
- Mengurangi keterlambatan saat menjalankan permintaan yang datang satu demi satu (tidak ada prosedur jabat tangan TCP).
- Pengecualian dapat dibuang tanpa waktu tambahan untuk menutup koneksi TCP.
HTTP 1.1 juga mendukung
pipelining permintaan. Ini memungkinkan Anda untuk mengirim beberapa permintaan dalam koneksi yang sama tanpa menunggu respons terhadap permintaan yang sebelumnya dikirim (yaitu, mengirim permintaan dalam "paket"). Sayangnya, pustaka
requests
tidak mendukung fitur ini. Namun, permintaan pemipaan mungkin tidak secepat memprosesnya secara paralel. Dan, selain itu, patut untuk memperhatikan hal ini: balasan untuk permintaan "paket" harus dikirim oleh server dalam urutan yang sama dengan saat ia menerima permintaan ini. Hasilnya bukan skema pemrosesan permintaan yang paling efisien berdasarkan pada prinsip FIFO ("masuk pertama, keluar pertama" - "pertama datang, pertama pergi").
Pemrosesan query paralel
requests
juga memiliki kelemahan serius lainnya. Ini adalah perpustakaan yang sinkron. Metode panggilan seperti
requests.get("http://example.org")
memblokir program hingga respons server HTTP penuh diterima. Fakta bahwa aplikasi harus menunggu dan tidak melakukan apa pun dapat dianggap sebagai minus dari skema interaksi dengan server. Apakah mungkin membuat program melakukan sesuatu yang bermanfaat, bukan hanya menunggu?
Aplikasi yang dirancang dengan cerdas dapat mengurangi masalah ini dengan menggunakan kumpulan thread, mirip dengan yang disediakan oleh
concurrent.futures
. Ini memungkinkan Anda untuk memparalelkan permintaan HTTP dengan cepat:
from concurrent import futures import requests with futures.ThreadPoolExecutor(max_workers=4) as executor: futures = [ executor.submit( lambda: requests.get("http://example.org")) for _ in range(8) ] results = [ f.result().status_code for f in futures ] print("Results: %s" % results)
Pola yang sangat berguna ini diterapkan di pustaka
permintaan-berjangka . Pada saat yang sama, penggunaan objek
Session
transparan bagi pengembang:
from requests_futures import sessions session = sessions.FuturesSession() futures = [ session.get("http://example.org") for _ in range(8) ] results = [ f.result().status_code for f in futures ] print("Results: %s" % results)
Secara default, pekerja dengan dua utas dibuat, tetapi program dapat dengan mudah mengatur nilai ini dengan meneruskan argumen
FuturSession
atau bahkan pelaksana sendiri ke objek
FuturSession
. Misalnya, mungkin terlihat seperti ini:
FuturesSession(executor=ThreadPoolExecutor(max_workers=10))
Bekerja tidak sinkron dengan permintaan
Seperti yang telah disebutkan, pustaka
requests
sepenuhnya sinkron. Ini mengarah ke pemblokiran aplikasi sambil menunggu respons dari server, yang mempengaruhi kinerja dengan buruk. Salah satu solusi untuk masalah ini adalah dengan menjalankan permintaan HTTP di utas terpisah. Tetapi penggunaan utas merupakan beban tambahan pada sistem. Selain itu, ini berarti pengenalan skema pemrosesan data paralel ke dalam program, yang tidak cocok untuk semua orang.
Dimulai dengan Python 3.5, fitur bahasa standar termasuk pemrograman asinkron menggunakan
asyncio
. Pustaka
aiohttp menyediakan pengembang dengan klien HTTP asinkron berdasarkan
asyncio
. Pustaka ini memungkinkan aplikasi untuk mengirim serangkaian permintaan dan terus bekerja. Pada saat yang sama, untuk mengirim permintaan lain, Anda tidak perlu menunggu respons terhadap permintaan yang dikirim sebelumnya. Tidak seperti pipelining permintaan HTTP,
aiohttp
mengirimkan permintaan secara paralel menggunakan beberapa koneksi. Ini menghindari "masalah FIFO" yang dijelaskan di atas. Inilah yang menggunakan
aiohttp
seperti:
import aiohttp import asyncio async def get(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return response loop = asyncio.get_event_loop() coroutines = [get("http://example.com") for _ in range(8)] results = loop.run_until_complete(asyncio.gather(*coroutines)) print("Results: %s" % results)
Semua pendekatan yang dijelaskan di atas (menggunakan
Session
, aliran,
concurrent.futures
atau
asyncio
) menawarkan berbagai cara untuk mempercepat klien HTTP.
Performa
Kode berikut adalah contoh di mana klien HTTP mengirim permintaan ke server
httpbin.org
. Server mendukung API yang dapat, antara lain, mensimulasikan sistem yang membutuhkan waktu lama untuk menanggapi permintaan (dalam hal ini, 1 detik). Di sini, semua teknik yang dibahas di atas diterapkan dan kinerjanya diukur:
import contextlib import time import aiohttp import asyncio import requests from requests_futures import sessions URL = "http://httpbin.org/delay/1" TRIES = 10 @contextlib.contextmanager def report_time(test): t0 = time.time() yield print("Time needed for `%s' called: %.2fs" % (test, time.time() - t0)) with report_time("serialized"): for i in range(TRIES): requests.get(URL) session = requests.Session() with report_time("Session"): for i in range(TRIES): session.get(URL) session = sessions.FuturesSession(max_workers=2) with report_time("FuturesSession w/ 2 workers"): futures = [session.get(URL) for i in range(TRIES)] for f in futures: f.result() session = sessions.FuturesSession(max_workers=TRIES) with report_time("FuturesSession w/ max workers"): futures = [session.get(URL) for i in range(TRIES)] for f in futures: f.result() async def get(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: await response.read() loop = asyncio.get_event_loop() with report_time("aiohttp"): loop.run_until_complete( asyncio.gather(*[get(URL) for i in range(TRIES)]))
Berikut adalah hasil yang diperoleh setelah memulai program ini:
Time needed for `serialized' called: 12.12s Time needed for `Session' called: 11.22s Time needed for `FuturesSession w/ 2 workers' called: 5.65s Time needed for `FuturesSession w/ max workers' called: 1.25s Time needed for `aiohttp' called: 1.19s
Berikut adalah bagan hasil.
Hasil studi tentang kinerja berbagai metode untuk membuat permintaan HTTPTidak mengherankan bahwa skema eksekusi query tersinkronisasi yang paling sederhana ternyata paling lambat. Intinya di sini adalah bahwa di sini kueri dieksekusi satu per satu, tanpa menggunakan kembali koneksi. Akibatnya, dibutuhkan 12 detik untuk menyelesaikan 10 kueri.
Menggunakan objek
Session
, dan, sebagai akibatnya, menggunakan kembali koneksi, menghemat 8% dari waktu. Ini sudah sangat bagus, dan untuk mencapai ini sangat sederhana. Siapa pun yang peduli dengan kinerja harus menggunakan setidaknya objek
Session
.
Jika sistem dan program Anda memungkinkan Anda untuk bekerja dengan utas, maka ini adalah alasan yang bagus untuk berpikir tentang menggunakan utas untuk memparalelkan permintaan. Streaming, bagaimanapun, membuat beberapa beban tambahan pada sistem, mereka, dengan demikian, tidak "bebas". Mereka harus dibuat, dijalankan, Anda harus menunggu penyelesaian pekerjaan mereka.
Jika Anda ingin menggunakan klien HTTP asinkron cepat, maka kecuali Anda menulis pada versi Python yang lebih lama, Anda harus memberi perhatian paling serius pada
aiohttp
. Ini adalah solusi tercepat, terukur terbaik. Itu mampu menangani ratusan permintaan bersamaan.
Alternatif untuk
aiohttp
, bukan alternatif yang sangat baik adalah mengelola ratusan utas secara paralel.
Pemrosesan Data Stream
Optimalisasi lain untuk bekerja dengan sumber daya jaringan, yang mungkin berguna dalam hal meningkatkan kinerja aplikasi, adalah menggunakan data streaming. Skema pemrosesan permintaan standar terlihat seperti ini: aplikasi mengirim permintaan, setelah itu badan permintaan ini dimuat dalam sekali jalan. Parameter
stream
, yang mendukung pustaka
requests
, serta atribut
content
pustaka
aiohttp
, memungkinkan Anda untuk menjauh dari skema ini.
Berikut tampilan organisasi pemrosesan data streaming menggunakan
requests
:
import requests
Berikut cara streaming data menggunakan
aiohttp
:
import aiohttp import asyncio async def get(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.content.read() loop = asyncio.get_event_loop() tasks = [asyncio.ensure_future(get("http://example.com"))] loop.run_until_complete(asyncio.wait(tasks)) print("Results: %s" % [task.result() for task in tasks])
Menghilangkan kebutuhan akan pemuatan instan dari konten respons penuh adalah penting dalam kasus di mana Anda perlu mencegah kemungkinan kemungkinan alokasi ratusan megabyte memori yang tidak berguna. Jika program tidak membutuhkan akses ke jawaban secara keseluruhan, jika ia dapat bekerja dengan masing-masing fragmen jawaban, maka mungkin lebih baik menggunakan metode streaming pekerjaan dengan permintaan. Misalnya, jika Anda akan menyimpan data dari respons server terhadap file, maka membaca dan menulisnya dalam bagian-bagian akan jauh lebih efisien dalam hal penggunaan memori daripada membaca seluruh badan respons, mengalokasikan sejumlah besar memori dan kemudian menulis semuanya ke disk.
Ringkasan
Saya harap pembicaraan saya tentang berbagai cara untuk mengoptimalkan operasi klien HTTP akan membantu Anda memilih yang paling sesuai dengan aplikasi Python Anda.
Pembaca yang budiman! Jika Anda masih tahu cara lain untuk mengoptimalkan pekerjaan dengan permintaan HTTP di aplikasi Python, silakan bagikan.
