Tutorial Gửi 10k request chỉ trong 100s với Python

C
Created at
Index progress
Incomplete

Một loạt các series tools, apps, scripts cũng như những bài viết mổ xẻ, hướng dẫn code 1001 thứ khác nhau cùng với khanhne :D
CodeBanana Tutorials tập 2

khanhne

Senior
Joined
Nov 18, 2019
Messages
496
Reactions
435
MR
36.746
Phone Number
Call me! Call me!
Telegram
Chat with me via Yahoo Messenger
Facebook
Follow me on Facebook

1628086935748.png

Requests và Threading - Khá chắc bạn đã biết​


Nếu làm việc với Python đủ lâu, ắt hẳn bạn đã từng nghe qua về thư viện Requests nổi tiếng trong việc xử lí HTTP. Một sự thật không phải bàn cãi là Requests thực sự đơn giản và hiệu quả trong việc xử lí các tác vụ liên quan đến HTTP request, đúng như dòng mô tả "Requests: HTTP for Humans". Server chúng ta sẽ test hôm nay là https://reqbin.com/echo. Chúng ta sẽ sử dụng đoạn code sau để gửi 50 số đến server

Python:
def request_sync():
    start = time.perf_counter()


    for secret in range(50):
        requests.get(url.format(secret))


    duration = time.perf_counter() - start
    print(f"Total time: {duration} s")
    print(f"Time per request: {duration / 50} s")

Chạy đoạn code đó, ta được output như sau
Total time: 14.768508800000001 s Time per request: 0.29537017600000004 s

Tốc độ này khá ổn, với chỉ 0.3s cho mỗi request gửi lên và nhận phản hồi đầy đủ. Tuy nhiên tốc độ này chưa phải là tối đa, vì sau theo cách request này, các request sau sẽ phải đợi request trước đó hoàn thành xong, bao gồm việc gửi thông tin lên server, đợi server phản hồi rồi tải xuống, sau đó mới bắt đầu gửi thông tin request tiếp theo lên server, cách làm này tạo ra rất nhiều thời gian trống không có gì làm. Vì vậy chúng ta có cách tối ưu hơn để tăng tốc việc này, đó là sử dụng Thread.

Python:
def request_10000_async():
    start = time.perf_counter()
    client = requests.Session()


    def request(secret_number):
        client.get(url.format(secret_number))


    tasks = []
    for secret in range(10000):
        # Tạo thread và khởi động chúng
        task = threading.Thread(target=request, args=(secret,))
        task.start()
        tasks.append(task)


    # Đợi tất cả thread thực thi xong
    for task in tasks:
        task.join()


    duration = time.perf_counter() - start
    print(f"Total time: {duration} s")
    print(f"Time per request: {duration / 10000} s")

Và output của nó
Total time: 300.1547591 s Time per request: 0.00301547591 s
Tốc độ tăng đáng kể! Đoạn code gửi thành công đến 10,000 requests chỉ trong vòng hơn 30 giây, một tốc độ đáng nể.

Tốc độ này có vẻ ổn, tuy nhiên giả sử ta cần gửi đến 1,000,000 request cùng lúc, thời gian sẽ cần đến gần 30,000 giây (400 phút)! Liệu có cách nào có thể tối ưu việc này về tốc độ hơn không ta?


Aiohttp và Asyncio - Idol mới nhú giới Python​


Có lẽ một số bạn đã biết về 2 thư viện này, tuy nhiên mình nhận thấy đa phần khi các bạn coder hỏi trên các group Python, forum lập trình,... ai cũng đều nhắc đến cách dùng Requests và Threading, vậy tại sao mình lại khuyên các bạn không dùng cách đó?

1. Tốc độ​


Aiohttp là thư viện xây dựng dựa trên thư viện Asyncio của Python, vì vậy việc quản lý tài nguyên giữa các "thread" với nhau được quản lý hoàn toàn bằng Python và OS một cách tự động, tối ưu nhất, dẫn đến tốc độ cũng vượt trội hơn so với Requests. Lý thuyết là thế nhưng để trực quan nhất mình sẽ viết những đoạn code trên lại bằng Aiohttp, Asyncio để so sánh trực tiếp những task đó giữa các thư viện sẽ khác nhau như thế nào.

Đoạn code gửi từng request một sẽ trông như sau

Python:
async def request_sync():
    start = time.perf_counter()


    async with aiohttp.ClientSession() as client:
        for secret in range(50):
            await client.get(url.format(secret))


    duration = time.perf_counter() - start
    print(f"Total time: {duration} s")
    print(f"Time per request: {duration / 50} s")

Và kết quả là
Total time: 6.816952155 s Time per request: 0.1363390431 s

Tốc độ nhanh gấp đôi so với Requests! Dù vậy, đây vẫn chưa phải là phần hay nhất của Aiohttp. Được xây dựng trên nền tảng Asyncio, việc gửi cùng lúc hàng loạt request mới là thế mạnh thực sự của thư viện này.

Ta có đoạn code sau dùng để gửi cùng lúc 10,000 requests đến server

Python:
async def request_10000_async():
    start = time.perf_counter()


    async with aiohttp.ClientSession() as client:
        tasks = []
        for secret in range(10000):
            # Tạo coroutines
            tasks.append(client.get(url.format(secret)))


        # Đợi tất cả coroutines thực thi xong
        await asyncio.gather(*tasks)


    duration = time.perf_counter() - start
    print(f"Total time: {duration} s")
    print(f"Time per request: {duration / 10000} s")

Không chỉ ngắn gọn hơn so với đoạn code có cùng tính năng bằng Requests, tốc độ request cũng được tăng lên rất nhiều
Total time: 111.80655100000001 s Time per request: 0.00111806551 s


Lần này, so với Requests, Aiohttp thậm chí còn hoàn thành công việc nhanh gấp 3 lần, với trung bình 0.1ms cho mỗi request, so với 0.3ms của Requests. Với tốc độ này, giả sử chúng ta cần chạy 1 triệu request cùng lúc đến server, Aiohttp sẽ làm xong việc đó trong 185 phút, "chỉ mới" đủ cho 12 trận yasuo 15gg mà thôi!


2. Quản lý tài nguyên​


Trong quá trình chạy code, mình để ý đến Resource manager của máy và phát hiện ra rằng, khi chạy Requests + Threading, CPU luôn ở 100% load dù tốc độ mạng chỉ vào khoảng 2-3Mbps, trong khi đó Aiohttp lại chỉ chiếm 100% CPU vào thời gian đầu setup các "thread" và sau đó chỉ còn 10-20% và duy trì tốt mức tốc độ request lên đến 5-6Mbps. Lí do như sau

Aiohttp như đã nói, sử dụng cách quản lý tài nguyên có sẵn của Python, đó là sử dụng event loop. So với Threading, Asyncio có lợi thế hơn hẳn khi thay vì tạo ra thật nhiều Thread cùng lúc và làm CPU choáng ngợp với việc xử lí, canh chừng từng cái một, Asyncio sẽ thông báo đến Hệ điều hành rằng nó cần Hệ điều hành quan sát từng thay đổi một của task nào đó (trong trường hợp này là một request). Sau đó, Hệ điều hành sẽ tự quyết định có nên tạo thêm Thread mới cho việc quan sát này hay không, và tự xử lí việc tạo, dọn dẹp, tối ưu tài nguyên giữa các Thread với nhau. So với việc tự xử lí bằng Threading, cách này tối ưu hơn và đỡ tốn thời gian hơn hẳn.


Tóm lại là​


Requests cũng tốt đấy, nhưng chưa đủ​


Với những công việc đơn giản hoặc những bạn "sợ code", Requests là cách tốt để xử lí công việc cần làm với một tốc độ không quá chậm. Tuy nhiên điểm yếu của Requests nằm ở việc được xây dựng theo hướng blocking (so với non-blocking của Aiohttp), khiến việc xử lí đa nhiệm trở nên khó khăn và kém hiệu quả hơn nhiều.

Với Aiohttp, việc duy nhất coder cần quan tâm là việc làm quen với khái niệm Asyncio, async/await (điều các bạn học Python ở mức cơ bản/học đã lâu và chưa cập nhật thông tin mới sẽ cảm thấy lạ lẫm). Comment cho mình biết nếu các bạn cần những bài hướng dẫn về Asyncio để làm quen với những khái niệm và câu lệnh này, hoặc chia sẻ một cách khác để tối ưu tốc độ request mà bạn biết ngay bên dưới, để cùng nhau học hỏi nhé.

Tìm hiểu thêm​


Tài liệu docs về Aiohttp bằng tiếng Anh được host ở đây: https://docs.aiohttp.org/en/stable/
Tài liệu tiếng Anh về Asyncio: https://docs.python.org/3/library/asyncio.html
Những file code có trong bài viết: https://github.com/KhanhhNe/CodeBanana-Tutorials/tree/main/aiohttp-vs-requests
Tổng hợp code của mình: https://github.com/KhanhhNe/CodeBanana-Tutorials
Ủng hộ mình viết thêm nhiều bài viết như thế này nữa: 1 like và 1 comment là đủ :D
 
Last edited:
10k rq này có tác dụng gì bạn ddos hả
tuỳ nhu cầu của bạn, ví dụ spam 1 dịch vụ nào đó, scan OTP, spam email,... hoặc đơn giản là "test độ bền" (ddos) server bạn/người khác :D
 
10k rq này có tác dụng gì bạn ddos hả
Ví dụ có contest ở 1 site nào đó và bạn muốn chiếm top với ít tiền chẳng hạn, thì canh lúc gần hết time, bạn nạp tiền/làm nhiệm vụ gì đó cho lên đc top và dùng cách 10k request này để "ddos" cái server đó, chiếm top, kiếm tiền :D
Bài viết này phục vụ mục đích giúp các bác coder (chủ yếu là coder Python) biết cách tối ưu code của mình cho tool chạy ngon hơn, ổn định hơn chứ thật sự k thể ứng dụng ngay để kiếm ra tiền được :popo_big_smile:
 
Vẫn quá chậm, nếu mình việc từ Node.js hoặc PHP mình có thể làm nhanh hơn thế. Hồi mình còn làm thứ liên quan API fb nó còn nhanh hơn này nhiều. 1 giây mình send cả 1k rq/s cũng không phủ nhận là python nó nhiều cái hay thật. Bao giờ bạn làm cái gỡ Checkpoint fb thì sẽ hiểu khâu xử lý ảnh để gỡ checkpoint nó ăn nhau tốc độ rất nhiều.

Vẫn là yêu tố quan trọng nhanh, chính xác.
- Thời gian gỡ checkpoint chỉ tối đa từ 1-5s/uid trở lại còn lại trên 5s thì không đạt được tốc độ thỏa mãn. Mỗi UID nó xuất ra cả vài nghìn cái ảnh và đưa AI vào xử lý ảnh và so sánh 3 ảnh giống nhau trùng khớp trong 5 UID/step phải ra UID giống ảnh đó.
 
Vẫn quá chậm, nếu mình việc từ Node.js hoặc PHP mình có thể làm nhanh hơn thế. Hồi mình còn làm thứ liên quan API fb nó còn nhanh hơn này nhiều. 1 giây mình send cả 1k rq/s cũng không phủ nhận là python nó nhiều cái hay thật. Bao giờ bạn làm cái gỡ Checkpoint fb thì sẽ hiểu khâu xử lý ảnh để gỡ checkpoint nó ăn nhau tốc độ rất nhiều.

Vẫn là yêu tố quan trọng nhanh, chính xác.
- Thời gian gỡ checkpoint chỉ tối đa từ 1-5s/uid trở lại còn lại trên 5s thì không đạt được tốc độ thỏa mãn. Mỗi UID nó xuất ra cả vài nghìn cái ảnh và đưa AI vào xử lý ảnh và so sánh 3 ảnh giống nhau trùng khớp trong 5 UID/step phải ra UID giống ảnh đó.
Bạn có thể cho mình thêm thông tin về server request đến (link request đến), tốc độ mong muốn hay đã đạt được bằng những ngôn ngữ khác được không. Mình sẽ tìm hiểu và tối ưu thêm để xem Python có thể làm đc những điều bạn cần hay k nhé.
 

Announcements

Today's birthdays

Forum statistics

Threads
417,443
Messages
7,058,883
Members
169,780
Latest member
wookafrshow

Most viewed of week

Most viewed of week

Back
Top Bottom