Bạn đã bao giờ nghe về vụ Roblox sập 73 giờ liên tục vào tháng 10/2021 chưa? Hàng triệu game thủ như ngồi trên đống lửa, còn giới lập trình thì 'ngứa ngáy' muốn biết chuyện gì đã xảy ra. Không phải tấn công hay lỗi vớ vẩn đâu, mà là một 'món nợ kỹ thuật' cũ rích, ẩn sâu trong lòng cơ sở dữ liệu đã bùng nổ thành cơn ác mộng! Tớ đã 'mổ xẻ' bản báo cáo sau sự cố (post-mortem) của Roblox và phải nói là, nó chi tiết đến kinh ngạc! Đúng là bài học 'xương máu' cho bất kỳ ai làm trong ngành. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/Qz1fQ5D.png' alt='Minh họa báo cáo sau sự cố'> Tưởng tượng Roblox như một thành phố lớn, nơi mỗi tòa nhà là một 'dịch vụ nhỏ' (microservices) chuyên làm một việc cụ thể. Để các tòa nhà này tìm thấy và nói chuyện được với nhau, họ cần một 'bảng chỉ dẫn thần kỳ' tên là Consul của HashiCorp. Nó giống như Google Maps nội bộ của hệ thống vậy. Thế mà, chiều ngày 28/10, một anh chàng Consul bỗng dưng 'thở dốc' (CPU lên cao ngất ngưởng), rồi cả 'bảng chỉ dẫn' này bắt đầu đơ dần, kéo theo cả thành phố Roblox chìm vào bóng tối. Lý do ư? Consul đã vô tình trở thành 'điểm yếu chí mạng' của cả hệ thống. Các kỹ sư Roblox và HashiCorp đã lao vào 'chiến trường' như những thám tử tài ba. Họ thử đủ mọi cách: Nghi ngờ phần cứng: Thay máy chủ Consul mới toanh – không ăn thua! Nghi ngờ traffic: Nâng cấp hẳn lên máy siêu khủng 128 lõi, ổ cứng NVMe tốc độ bàn thờ – vẫn tịt ngóm! Reset lại Consul: Tắt hẳn, khôi phục từ bản lưu trước đó vài tiếng – ban đầu có vẻ ổn, nhưng rồi lại 'ngã bệnh' y như cũ. Giảm tải: Giảm số lượng dịch vụ gọi Consul xuống mức thấp nhất – cứu vãn được vài tiếng, rồi lại 'đâu vào đấy'. Cuộc điều tra sâu hơn: Cuối cùng, qua các bản ghi debug, họ phát hiện ra 'thủ phạm' là tính năng 'streaming' mới của Consul. Tính năng này tưởng ngon, ai dè lại gây tranh chấp tài nguyên kinh khủng trên một 'kênh' duy nhất (Go channel) khi tải nặng. Tắt 'streaming' đi cái là Consul khỏe re luôn! Tối ưu 'bầu cử lãnh đạo': Consul đôi khi tự động 'bầu' lãnh đạo mới (cũng là chuyện thường tình). Nhưng có mấy anh lãnh đạo 'lên voi xuống chó' y hệt. Thế là họ phải 'né' mấy anh lãnh đạo hay làm loạn này. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/DebuggingProcess.png' alt='Minh họa quá trình gỡ lỗi'> Sau hàng loạt biện pháp 'chữa cháy' thần tốc này, cuối cùng hệ thống cũng ổn định trở lại. Cả đội thận trọng 'khai trương' lại Roblox bằng cách khôi phục cache, rồi từ từ cho game thủ kết nối lại theo kiểu 'chọn ngẫu nhiên'. Sau 73 giờ 'đau khổ', Roblox đã trở lại 'sống' bình thường! Nhưng khoan đã, 'thủ phạm' chính của vụ này lại là một thứ khác 'lạnh lùng' hơn nhiều: cái 'ruột gan' của Consul, tức là cơ sở dữ liệu BoltDB, nó bị 'ốm nặng' do một vấn đề hiệu suất cực kỳ thú vị! Consul dùng BoltDB để lưu trữ các 'cuộc nói chuyện' của nó. Giống như mọi database khác, BoltDB có một 'danh sách trống' (freelist) để quản lý các 'trang giấy' (vùng nhớ) đã dùng xong và giờ có thể tái sử dụng. Nghe thì có vẻ ổn đúng không? Nó giúp database không bị phình to và chạy mượt mà hơn. Ấy thế mà, cái 'danh sách trống' của BoltDB lại được 'viết' theo kiểu hơi bị... 'cổ lỗ sĩ'! Nó dùng một mảng (array) để lưu ID của từng trang trống. Tức là, mỗi khi database đọc hay ghi gì đó, nó phải 'quét' cả cái danh sách này từ đầu đến cuối! Cứ tưởng tượng danh sách này dài dằng dặc, thì việc quét tìm càng ngày càng chậm, chậm đến mức 'đứng hình' luôn! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/BoltDBFreelist.png' alt='Minh họa BoltDB Freelist'> Đáng chú ý là, lỗi hiệu suất này đã được báo cáo từ tận năm 2016 rồi (trên GitHub ấy!). Nhưng tác giả của BoltDB lại ngừng bảo trì dự án vào năm 2017. 'Tôi không còn thời gian và năng lượng để tiếp tục nữa. Bolt đang ổn định và đã được dùng thành công nhiều năm rồi.' – Anh ấy nói vậy đó. Thật may, cộng đồng Go đã 'sao chép' BoltDB thành một dự án mới tên là bbolt để tiếp tục phát triển. Và đến năm 2019, cái lỗi 'danh sách trống' khó chịu kia cuối cùng cũng được sửa trong bbolt! Giải pháp đơn giản đến không ngờ: thay vì dùng mảng 'quét từng tí', họ chuyển sang dùng 'hashmap' – một kiểu danh sách giúp tìm kiếm tức thì. (Bạn thấy không, một ý tưởng nhỏ có thể tạo ra cú hích hiệu suất khổng lồ đấy!) <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/HashMapVsArray.png' alt='So sánh Hashmap và Array'> Và bi kịch là ở chỗ này: Vì Consul vẫn đang dùng phiên bản BoltDB 'cổ lổ sĩ' không được bảo trì, nên nó không nhận được bản vá lỗi tuyệt vời của bbolt. Kết quả là, cả Roblox 'sập' tưng bừng 3 ngày trời vào năm 2021! Vụ này còn nhiều 'uẩn khúc' lắm mà tớ vẫn đang tò mò: Sao Roblox không tắt tính năng streaming của Consul sớm hơn? Rõ ràng Consul là 'đầu mối' ngay từ đầu, và đây là một thay đổi lớn cơ mà? Tại sao chỉ một vài máy chủ Consul gặp vấn đề với BoltDB freelist? Về lý thuyết thì chúng phải giống nhau chứ? Tại sao việc khôi phục trạng thái Consul từ snapshot lại không sửa được lỗi? Phải chăng snapshot không 'reset' được cái file `raft.db` của BoltDB, nên cái danh sách trống 'phình to' vẫn còn đó? Tại sao việc giảm tải Consul chỉ có tác dụng tạm thời? Nếu danh sách đã quá lớn rồi thì giảm tải có ích gì đâu? Tại sao tính năng streaming mới hoạt động ngon lành cả ngày trước khi sập? Nó có 'bộ đệm' nào đó che giấu vấn đề ban đầu không, hay có kiểu traffic đặc biệt nào kích hoạt nó? Lỗi freelist đã có từ 2016, tại sao mãi đến 2021 Roblox mới 'đổ bệnh' vì nó? Có phải tính năng streaming mới của Consul đã 'đổ thêm dầu vào lửa', tăng cường ghi dữ liệu vào BoltDB không? Báo cáo này đúng là một 'kho báu' bài học đó các bạn. Tớ khuyến khích mọi người nên tìm đọc để 'khai sáng' thêm! Trên Hacker News cũng có nhiều tranh luận sôi nổi về vụ này, chính tác giả BoltDB cũng tham gia nữa đó. Là một kỹ sư phần mềm, tớ tin rằng khả năng 'xoay sở' trong hệ thống phức tạp và 'chẩn đoán bệnh' dưới áp lực là một đặc điểm của kỹ sư giỏi. Tớ thực sự ngưỡng mộ các kỹ sư của Roblox và HashiCorp đã làm việc không ngừng nghỉ dưới áp lực khổng lồ để tìm ra vấn đề và giải quyết nó. Chúc mừng các anh hùng thầm lặng này! Cảm ơn bạn đã đọc đến đây nhé! Nếu thấy bài này hay, đừng quên chia sẻ với bạn bè nha!
Khám phá nguyên nhân đằng sau vụ sập hệ thống 73 giờ của Roblox vào năm 2021, từ lỗi database BoltDB cũ kỹ đến vấn đề Consul streaming. Bài học về nợ công nghệ và khắc phục sự cố.
Khám phá cách một nghiên cứu mới giải quyết vấn đề LLM 'quên' bối cảnh trong các cuộc trò chuyện dài bằng cách mô phỏng cơ chế trí nhớ chọn lọc và tóm tắt động như con người. Tối ưu hóa hiệu suất và khắc phục giới hạn độ dài hội thoại truyền thống.
Hướng dẫn chi tiết về mô hình lập trình đồng thời (concurrency) của Go, khám phá Goroutines, Channels, Pipelines và WaitGroups để xây dựng ứng dụng hiệu suất cao, không rò rỉ tài nguyên.
Bạn đang băn khoăn giữa MVC và DDD khi xây dựng dự án Go? Bài viết này sẽ 'mổ xẻ' hai kiến trúc phổ biến này, từ cấu trúc thư mục đến cách tổ chức code, giúp bạn hiểu rõ ưu nhược điểm và chọn 'người bạn đồng hành' lý tưởng cho hệ thống của mình.
Tìm hiểu về lập trình Lock-Free trong Go: Cách các cấu trúc dữ liệu không khóa và thao tác nguyên tử (atomic operations) giúp giải quyết tranh chấp khóa (lock contention), tăng hiệu năng và khả năng mở rộng cho ứng dụng đồng thời. Bao gồm ví dụ thực tế về counter, queue, map và cách tránh các vấn đề thường gặp.
Khám phá cách Sponge và DeepSeek R1 thay đổi cuộc chơi phát triển backend, giúp bạn xây dựng ứng dụng 'từ A đến Z' nhanh chóng, hiệu quả và cực kỳ dễ dàng. Biến những ý tưởng phức tạp thành code chỉ trong nháy mắt!
Khám phá bí quyết tối ưu hiệu suất ứng dụng Go, giảm thiểu tiêu tốn RAM và GC pauses bằng cách hiểu rõ cách hoạt động của Slice, Strings.Builder và sử dụng sync.Pool một cách hiệu quả.
Hướng dẫn chi tiết cách thiết kế và triển khai một hệ thống Cache linh hoạt bằng Go, bao gồm các chính sách thải loại (LRU, LFU, FIFO) và quản lý TTL. Kèm mã nguồn GitHub và giải thích code dễ hiểu.
Khám phá những triết lý thiết kế đỉnh cao của Kubernetes, từ cách xử lý đồng thời, trừu tượng hóa mã nguồn, đến quản lý vòng lặp và tránh overengineering. Áp dụng những bài học này vào dự án của bạn để xây dựng hệ thống mạnh mẽ, linh hoạt và dễ bảo trì.
Tìm hiểu về Go Context, cách nó giúp quản lý goroutine, xử lý cancellation, timeout và deadline trong lập trình đồng thời. Khám phá các ví dụ thực tế và mẹo sử dụng hiệu quả để tránh rò rỉ tài nguyên và viết code Go mạnh mẽ hơn.
Khám phá Tool-calling, tính năng giúp các mô hình ngôn ngữ lớn (LLM) vượt ra ngoài giới hạn văn bản, tương tác với thế giới thực và thực hiện hành động. Tìm hiểu cách AI trở thành trợ lý đa năng, từ tra cứu thời tiết đến đặt tour du lịch, cùng những thách thức và tiềm năng.
Khám phá passkey-go, thư viện Go nhẹ nhàng, thuần túy giúp bạn dễ dàng tích hợp xác thực Passkey (WebAuthn) vào backend mà không cần phụ thuộc bên ngoài. An toàn, hiệu quả, dễ dùng!
Bạn có tưởng tượng được không, đôi khi những lời trêu chọc, những bình luận 'vô tri' tưởng chừng chỉ để cho vui trên Reddit, lại có thể bất ngờ trở thành nguồn cảm hứng "bất thình lình" cho các dự án lập trình siêu bá đạo đấy! Đúng vậy, từ một câu nói đùa, một meme hay một màn "cà khịa" tưởng chừng vô nghĩa, các lập trình viên của chúng ta lại có thể nảy ra những ý tưởng đỉnh của chóp, biến chúng thành những ứng dụng, công cụ, hoặc thậm chí là cả một trào lưu công nghệ. Đừng bao giờ đánh giá thấp sức mạnh của sự sáng tạo bất ngờ, dù nó đến từ đâu nhé! <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://images.unsplash.com/photo-1549673898-1e4e1f7c3c7e?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' alt='Ý tưởng lập trình từ nguồn bất ngờ'>
Chào bạn, hôm nay chúng ta sẽ cùng khám phá một “siêu năng lực” cực kỳ thú vị của các Mô hình Ngôn ngữ Lớn (LLM) và API của chúng, đó chính là tính năng "Gọi công cụ" (Tool-calling)! Nghe có vẻ hơi "học thuật" nhưng hãy tưởng tượng thế này: LLM của chúng ta không chỉ là một "bộ não" siêu việt chỉ biết nói chuyện nữa, mà nó còn có thể biến hóa thành một "siêu trợ lý" đa năng, biết dùng đủ mọi công cụ để xử lý công việc!Thay vì cứ "tám chuyện" với bạn chỉ dựa trên mớ kiến thức đã được học, LLM sẽ tự động "nghĩ ra" cách tạo ra yêu cầu đầu vào cho một "công cụ" nào đó. Sau đó, nó sẽ "ra lệnh" cho công cụ ấy hoạt động (tí nữa chúng ta sẽ đào sâu hơn vụ này nhé!). Cuối cùng, LLM sẽ "đọc hiểu" kết quả mà công cụ trả về và "tối ưu" để đưa ra câu trả lời cực kỳ chính xác và hữu ích cho bạn. Tuyệt vời chưa?Tính năng này giúp LLM "lột xác" lên một tầm cao mới! Giờ đây, nó có thể cập nhật thông tin "nóng hổi" theo thời gian thực (như dự báo thời tiết hôm nay ra sao, tỉ số trận đấu đang đến đâu, hay tin tức mới nhất là gì). Thậm chí, LLM còn có thể "trò chuyện" với các hệ thống bên ngoài như cơ sở dữ liệu, các API khác, hay thậm chí là "chạy luôn" cả một đoạn mã lệnh! Đúng là "đa zi năng" phải không nào?<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ToolCallingIntro.png' alt='Mô hình LLM và các công cụ bên ngoài'>Nhờ có "gọi công cụ", chúng ta đang chứng kiến sự ra đời của một loại chương trình hoàn toàn mới! Ở đó, các hoạt động và luồng xử lý không còn bị "trói buộc" bởi những dòng mã cứng nhắc nữa, mà được kiểm soát trực tiếp bởi chính LLM. Điều này tạo ra những ứng dụng cực kỳ linh hoạt và năng động, cứ như chúng có "trí thông minh" riêng vậy! Tuy nhiên, "cái gì cũng có hai mặt", sự linh hoạt này đôi khi lại đánh đổi bằng độ tin cậy và nhất quán. "Làm sao để vừa linh hoạt vừa đáng tin?" – Đây đích thị là bài toán "đau đầu" muôn thưở của các kỹ sư phần mềm mà chúng ta luôn phải tìm cách cân bằng, đúng không nào?<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/FlexibilityReliability.png' alt='Sự cân bằng giữa tính linh hoạt và độ tin cậy'>Vậy, "bí kíp" hoạt động của nó là gì?Thực ra, các LLM "bẩm sinh" chỉ có mỗi khả năng tạo ra... văn bản thôi! Giống như một nhà văn đại tài chỉ biết viết vậy. Để nhà văn này có thể "dùng công cụ", chúng ta cần một "người phiên dịch" hay một "bộ điều khiển" thông minh.Thay vì LLM trực tiếp "gọi" một hàm (kiểu như tự động bấm nút), nó sẽ tạo ra một **dữ liệu có cấu trúc** (thường là JSON) để "mô tả" hàm cần dùng và các tham số đầu vào. Tưởng tượng như nó viết ra một "yêu cầu công việc" vậy đó!Chương trình điều khiển của chúng ta sẽ "đọc hiểu" yêu cầu này, tìm đúng hàm cần thiết và thực thi nó. Sau đó, kết quả từ việc thực thi này lại được "chuyển ngược" về cho LLM (cũng thường dưới dạng JSON). Cuối cùng, LLM sẽ dùng kết quả đó, kết hợp với những gì nó đã được học, để "tổng hợp" và đưa ra câu trả lời "xịn sò" nhất cho người dùng. Đơn giản mà hiệu quả phải không nào?<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/HowToolCallingWorks.png' alt='Cách hoạt động của Tool-calling'>À, nhưng trước khi LLM "cầm" được công cụ, nó phải biết những công cụ đó là gì đã chứ! Nó cần "biết mặt đặt tên" từng hàm, hiểu rõ chức năng của chúng, và đặc biệt là một **"schema" (lược đồ)** để định nghĩa các tham số đầu vào. Nghe cứ như LLM đang học cách sử dụng một cuốn cẩm nang vậy đó!Bạn có thể "dạy" LLM điều này bằng cách sử dụng **"lời nhắc hệ thống" (system prompts)**, nhưng tin vui là nhiều API của mô hình hiện nay đã có sẵn các trường riêng để bạn định nghĩa công cụ cực kỳ tiện lợi rồi.Để dễ hình dung hơn, hãy xem Ollama "biến hình" như thế nào nhé. Ollama cho phép chúng ta tạo một Modelfile để mở rộng mô hình với một system prompt cụ thể hoặc các cấu hình khác. Đây là một "lời nhắc hệ thống" siêu quyền lực mà tôi dùng để hướng dẫn mô hình cách "gọi công cụ" (mà thực ra là tôi sẽ giả lập việc trả lời bằng JSON cho mô hình hiểu):```FROM gemma3:4bSYSTEM """ Một chương trình được dùng để tương tác với bạn và người dùng. Chương trình cho phép bạn sử dụng các hàm/công cụ để lấy thông tin bên ngoài. Khi bạn sử dụng một công cụ, chương trình sẽ phản hồi bằng dữ liệu JSON. Bất cứ thứ gì khác đều đến từ người dùng và nên được coi là tương tác với con người. Để sử dụng một công cụ, hãy phản hồi bằng một đối tượng JSON với cấu trúc sau: { "tool": <tên của công cụ được gọi>, "tool_input": <các tham số cho công cụ khớp với schema JSON ở trên> } Không bao gồm bất kỳ văn bản nào khác trong phản hồi của bạn. Nếu lời nhắc của người dùng yêu cầu một công cụ để có câu trả lời hợp lệ, hãy sử dụng định dạng trên để gọi công cụ. Sau khi nhận được phản hồi từ công cụ, hãy tiếp tục trả lời lời nhắc của người dùng bằng cách sử dụng phản hồi của công cụ. Nếu bạn không có công cụ liên quan cho lời nhắc, hãy trả lời bình thường. Hãy vui vẻ và thú vị nhé. Đây là các công cụ có sẵn của bạn: getCurrentWeather Lấy thời tiết hiện tại ở một địa điểm nhất định { "type": "object", "properties": { "location": {"type": "string", "description": "Thành phố và bang, ví dụ: San Francisco, CA"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} }, "required": ["location", "unit"] } """ ```Giờ thì "đầu bếp" LLM của chúng ta đã có đủ công thức rồi, cùng "trò chuyện" thôi nào:Người dùng hỏi: Thời tiết ở New York City thế nào?Mô hình phản hồi (đoán là cần công cụ):```json{ "tool": "getCurrentWeather", "tool_input": { "location": "New York City", "unit": "fahrenheit" } }```Tôi (đóng vai công cụ) trả lời:```json{ "temperature": 78, "unit": "F" }```Mô hình phản hồi cuối cùng (cho người dùng): Nhiệt độ ở New York City hiện tại là 78°F! Đó là một ngày đẹp trời, ấm áp – thật hoàn hảo để đi dạo qua Công viên Trung tâm hoặc thưởng thức một chiếc xúc xích nóng hổi.Thấy chưa? Thay vì cố gắng "chém gió" hay "bịa" ra một câu trả lời, mô hình đã "ngoan ngoãn" tạo ra yêu cầu gọi công cụ đúng theo định dạng mà chúng ta "dạy" nó. Sau đó, khi nhận được thông tin thời tiết từ tôi (người giả lập công cụ), mô hình đã ngay lập tức "biến" dữ liệu thô thành một câu trả lời thân thiện và tự nhiên.Dù đây không phải là cuộc gọi công cụ "thật" (vì tôi đang "diễn" mà!), nhưng thật thú vị khi thấy **"lời nhắc hệ thống" (system prompt) có sức mạnh lớn lao thế nào trong việc định hình hành vi của mô hình**. Điều này giúp chúng ta "mổ xẻ" và "giải mã" được những gì thực sự đang diễn ra "phía sau cánh gà" của LLM. Hiểu biết nền tảng này cực kỳ có lợi khi bạn muốn "chế tạo" các công cụ phức tạp hơn và xây dựng các ứng dụng "khủng" xoay quanh LLM đấy nhé!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/SystemPromptImpact.png' alt='Tầm quan trọng của System Prompt'>Triển khai "Gọi công cụ" với Ollama + Go: Thực chiến nào!Bước tiếp theo, chúng ta sẽ "tích hợp" việc gọi công cụ vào một chương trình thực tế, thay vì chỉ "tám chuyện" với mô hình. Để "mổ xẻ" sâu hơn cách LLM tương tác, chương trình này sẽ sử dụng trực tiếp API của Ollama và thư viện client Go, thay vì một thư viện tổng quát hơn.Đây là yêu cầu mà chúng ta dùng để "nhắc nhở" mô hình về một công cụ đã được định nghĩa:```go messages := []api.Message{ api.Message{ Role: "user", Content: "What is the weather like in New York City?", }, } req := &api.ChatRequest{ Model: model, Messages: messages, Tools: api.Tools{ api.Tool{ Type: "function", Function: api.ToolFunction{ Name: "getCurrentWeather", Description: "Get the current weather in a given location", Parameters: api.ToolFunctionParameters{ Type: "object", Required: []string{"location"}, Properties: map[string]api.ToolFunctionProperty{ "location": { Type: api.PropertyType{"string"}, Description: "The city and state, e.g. San Francisco, CA", }, }, }, }, }, }, }```Chương trình Go "client" của chúng ta sẽ là "người trung gian" cực kỳ quan trọng. Khi LLM "lên tiếng" cần gọi công cụ, nó sẽ cung cấp thông tin cần thiết. Chương trình của chúng ta sẽ "lắng nghe" thông tin đó, sau đó "chạy" hàm `getCurrentWeather` (đã được định nghĩa riêng trong code Go), lấy kết quả, và "chuyển phát nhanh" kết quả này trở lại cho LLM. LLM lại tiếp tục "xử lý" kết quả đó để tạo ra câu trả lời hoàn chỉnh và đẹp đẽ cho người dùng.Đây chính là cách mà các "bộ não" LLM có thể "kết nối" với thế giới thực thông qua những đoạn code tuy nhỏ bé nhưng đầy quyền năng!```go package main import ( "context" "encoding/json" "fmt" "log" "github.com/ollama/ollama/api" ) const model = "llama3.2:3b" func main() { client, err := api.ClientFromEnvironment() if err != nil { log.Fatal(err) } messages := []api.Message{ api.Message{ Role: "user", Content: "What is the weather like in New York City?", }, } ctx := context.Background() req := &api.ChatRequest{ Model: model, Messages: messages, Tools: api.Tools{ api.Tool{ Type: "function", Function: api.ToolFunction{ Name: "getCurrentWeather", Description: "Get the current weather in a given location", Parameters: api.ToolFunctionParameters{ Type: "object", Required: []string{"location"}, Properties: map[string]api.ToolFunctionProperty{ "location": { Type: api.PropertyType{"string"}, Description: "The city and state, e.g. San Francisco, CA", }, }, }, }, }, }, } } handler := func(resp api.ChatResponse) error { // If there is no tool call, just print the message content if len(resp.Message.ToolCalls) == 0 { fmt.Print(resp.Message.Content) return nil } // Otherwise, process the tool call tc := resp.Message.ToolCalls[0].Function switch tc.Name { case "getCurrentWeather": output, err := getCurrentWeather(tc.Arguments) if err != nil { log.Fatal(err) } messages = append(messages, api.Message{ Role: "tool", Content: output, }) default: log.Fatal(fmt.Errorf("invalid function: %q", tc.Name)) } return nil } // Send initial chat message err = client.Chat(ctx, req, handler) if err != nil { log.Fatal(err) } // The model should have responded with a tool call // and the handler would have appended a response to messages. // Now, call the tool again with the response req.Messages = messages err = client.Chat(ctx, req, handler) if err != nil { log.Fatal(err) } } func getCurrentWeather(input map[string]any) (string, error) { location, ok := input["location"].(string) if !ok { log.Fatalf("bad args: %v", input) } weatherInfo := map[string]any{ "location": location, "temperature": "80", "unit": "fahrenheit", "forecast": []string{"sunny", "windy"}, } b, err := json.Marshal(weatherInfo) if err != nil { return "", err } return string(b), nil }```Và đây là kết quả "mỹ mãn" bạn sẽ nhận được: `The current weather in New York City is mostly sunny, with a windy condition. The temperature is at 80°F.`<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ToolCallGoOutput.png' alt='Kết quả khi chạy Tool-calling với Go'>Tiến thêm một bước nữa: Khi LLM thành "hướng dẫn viên du lịch" của bạn!Tôi tình cờ có một dự án "cưng" để minh họa việc tích hợp LLM đơn giản bằng cách sử dụng các công cụ, đó là "walks-of-italy" (https://github.com/calvinmclean/walks-of-italy). Ban đầu, nó chỉ là một công cụ theo dõi các chuyến du lịch mới có sẵn từ Walks of Italy (https://www.walksofitaly.com). Mặc dù mục tiêu ban đầu là lớn hơn (theo dõi, tìm kiếm, quan sát tour), nhưng tôi đã dừng lại ở tính năng thông báo tour mới.Nhưng giờ đây, với sức mạnh của "gọi công cụ" của LLM, chương trình này có thể "phù phép" và mở rộng theo bất kỳ cách nào mà bạn có thể nghĩ ra! Dưới đây là các công cụ mà tôi đã "trang bị" cho nó:* `getAllTours`: Lấy tên và mã định danh (UUID) của các tour từ cơ sở dữ liệu.* `getTourDetails`: Truy cập API để lấy mô tả chi tiết về tour.* `getTourAvailability`: Dùng API để kiểm tra xem tour có còn chỗ không, giá bao nhiêu, và sức chứa tại các thời điểm cụ thể.Chỉ với vài công cụ "nhỏ mà có võ" này, LLM đã có được những tính năng "thần kỳ" sau:* **Tra cứu ngày tour bằng ngôn ngữ tự nhiên:** "Tình hình tour Pristine Sistine tuần tới thế nào?"* **Hỏi một câu hỏi tổng quát hơn về nhiều tour:** "Các tour Vatican vào ngày 5 tháng 7 có gì hay?"* **Nhận gợi ý du lịch "chuẩn không cần chỉnh":** "Tôi sẽ ở Rome vào ngày 3 và 4 tháng 8. Nên đi tour nào nhỉ?" hay "Nếu chỉ được đi một tour ở Rome, tour nào là 'đỉnh' nhất?"Khi được hỏi "Tình hình tour Pristine Sistine tuần tới thế nào?", mô hình đã "xuất thần" đưa ra câu trả lời chi tiết như một hướng dẫn viên chuyên nghiệp:``` Dưới đây là tổng quan về tình trạng có sẵn của các tour: Ngày 20 tháng 6 năm 2025: Có số lượng chỗ hạn chế cho các tour 7:15 AM và 7:45 AM. Tour 7:15 AM có 1 chỗ trống trong tổng số 20 chỗ. Tour 7:45 AM cũng có 1 chỗ trống trong tổng số 48 chỗ. Tour 6:45 AM đã kín chỗ. Ngày 21 tháng 6 năm 2025: Tour 6:45 AM có 10 chỗ trống trong tổng số 10 chỗ. Tour 7:15 AM có 3 chỗ trống trong tổng số 34 chỗ. Tour 7:45 AM có 1 chỗ trống trong tổng số 28 chỗ. Các ngày khác: Vào ngày 19 tháng 6 năm 2025, tour 7:45 AM đã kín chỗ. Vào ngày 18 tháng 6 năm 2025, tour 7:15 AM đã kín chỗ, và tour 7:45 AM có số lượng chỗ hạn chế. Vào ngày 17 tháng 6 năm 2025, tour 7:15 AM đã kín chỗ, và tour 7:45 AM có số lượng chỗ hạn chế. Vào ngày 16 tháng 6 năm 2025, tour 7:45 AM đã kín chỗ. Nếu bạn đang tìm kiếm chỗ trống, các lựa chọn tốt nhất là vào ngày 20 và 21 tháng 6, với số lượng chỗ hạn chế cho các tour buổi sáng. Hãy cho tôi biết nếu bạn cần giúp đặt chỗ! ```Đây chỉ là vài ví dụ nhỏ thôi nhé. Lợi ích "khổng lồ" của loại chương trình này là chức năng của nó không còn bị "khóa chặt" bởi những gì nhà phát triển đã lập trình sẵn. Thay vào đó, nó chỉ bị giới hạn bởi khả năng của LLM và các công cụ mà nó có thể sử dụng. Nghĩa là, người dùng có thể "nghĩ ra" một trường hợp sử dụng mới toanh và chương trình có thể xử lý ngay lập tức nhờ tính linh hoạt "không tưởng" của nó. Tôi chỉ muốn kiểm tra các tour và ngày cụ thể, nhưng giờ đây tôi có thể làm được nhiều hơn thế rất nhiều!Tất nhiên, hiệu quả và độ chính xác của các phản hồi sẽ "tùy thuộc vào" mô hình bạn dùng. Các câu hỏi phức tạp hơn, yêu cầu nhiều lệnh gọi công cụ hơn, có thể làm giảm độ chính xác của phản hồi. Khi cần nhiều thông tin, chúng ta sẽ cần đến những mô hình "xịn xò" và mạnh mẽ hơn.Trong ví dụ này, chức năng vẫn có thể linh hoạt hơn nữa. Hiện tại, tôi phải thêm tour thủ công. Nếu nó có một chức năng mới để tự động "quét" tour từ trang web Walks of Italy, nó sẽ càng năng động hơn (nhưng cũng khó đoán hơn một tẹo!).<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ExpandingFunctionality.png' alt='Mở rộng chức năng với LLM và công cụ'>Model Context Protocol (MCP): "Siêu nâng cấp" tiếp theo?Model Context Protocol, hay MCP, được xem là "bước tiến hóa tiếp theo" của việc gọi công cụ. Thay vì phải "cực khổ" viết một chương trình tùy chỉnh để cung cấp công cụ, MCP "chuyển giao" việc này sang phía máy chủ. Điều này có nghĩa là mô hình sẽ được sử dụng với một client chung hỗ trợ giao thức và có thể truy cập các hàm được cung cấp bởi bất kỳ máy chủ bên ngoài nào.Nếu ví dụ "walks-of-italy" của tôi sử dụng MCP, thì Walks of Italy sẽ cung cấp một máy chủ MCP, và bất kỳ mô hình nào cũng có thể truy cập các tour của họ chỉ bằng cách sử dụng một client MCP! Nó sẽ "giảm tải" gần như toàn bộ công sức mà tôi đã bỏ ra để triển khai dự án "walks-of-italy" của mình. Nghe có vẻ "đỉnh của chóp" và "nhàn tênh" đúng không nào?<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.com/MCPNextStep.png' alt='Model Context Protocol (MCP) là gì?'>Vài dòng "tâm sự" cuối cùng của tôi...Cho đến gần đây, tôi chủ yếu dùng ChatGPT hay Gemini để hỏi han, thảo luận ý tưởng, hoặc đôi khi nhờ chúng "viết hộ" vài truy vấn SQL hay đoạn code. Khi "AI Agent" (tác nhân AI) ngày càng "phủ sóng" và được tích hợp vào mọi khía cạnh của cuộc sống và công việc, có vẻ như LLM "cái gì cũng làm được".Thật "đã" khi bắt đầu từ những tương tác mô hình cơ bản và rồi "ồ à" khi thực sự hiểu cách hoạt động của việc gọi công cụ. Thực tế thì, LLM vẫn chỉ là một "cỗ máy" đọc đầu vào và tạo ra đầu ra thôi. Chính những đoạn code "bao bọc" chúng, được xây dựng bởi các kỹ sư phần mềm tài ba, mới là thứ thực sự "thổi hồn" cho LLM, giúp chúng làm được mọi việc "phi thường"!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/EngineersBehindTheScene.png' alt='Kỹ sư phần mềm tạo ra khả năng cho LLM'>Xây dựng một ứng dụng xoay quanh LLM và các công cụ có thể mang lại vô vàn lợi ích. Nếu mô hình có quyền truy cập vào một "bộ sưu tập" các công cụ với chức năng tổng quát như truy vấn API và cơ sở dữ liệu (tất nhiên là với các truy vấn SQL đã được định nghĩa trước và an toàn), chức năng của chương trình có thể "biến hóa" và mở rộng theo yêu cầu của người dùng.Điều này đặc biệt "đắc lực" cho những người dùng không chuyên về kỹ thuật. Thay vì phải "đòi hỏi" kỹ sư xây dựng các tính năng mới cho từng trường hợp cụ thể, họ chỉ cần "tinh chỉnh" các lời nhắc và "tám chuyện" với LLM là xong!Trong ví dụ "walks-of-italy" trước đó, chương trình ban đầu chỉ để theo dõi và cảnh báo tour mới hoặc chạy các truy vấn cụ thể qua dòng lệnh. Nhưng với việc "tiêm" vài công cụ và một LLM, phạm vi của chương trình đã "phình to" đáng kể và trở nên cực kỳ hữu ích cho cả những người dùng "mù tịt" về công nghệ. Giờ đây, LLM có thể truy vấn bất kỳ ngày nào và lấy mô tả tour, bạn có thể:* Nhận lời khuyên "chất lừ" để quyết định nên đi tour nào dựa trên giá cả, mô tả và tình trạng còn chỗ thực tế.* Hỏi về tình trạng còn chỗ tổng thể trong tháng tới cho nhiều tour khác nhau.* So sánh tình trạng còn chỗ và giá cả giữa các ngày trong tuần hoặc cuối tuần.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/BenefitsForNonTechnicalUsers.png' alt='Lợi ích của LLM+Tools cho người dùng không chuyên'>Có một điều cực kỳ quan trọng cần nhớ: **mô hình có thể hành xử "khó chiều" và các công cụ không bao giờ được cung cấp quyền truy cập "vô hạn"!** Ví dụ, thay vì cho phép mô hình thực thi bất kỳ câu lệnh SQL nào nó tự nghĩ ra (nguy hiểm lắm!), các kỹ sư nên tạo sẵn một vài hàm như "createResource" hay "updateResource" với các truy vấn đã được định nghĩa trước và kiểm soát chặt chẽ.Các "lời nhắc hệ thống" (system prompts) và định nghĩa công cụ chính là "chiếc phanh" và "tay lái" giúp chúng ta kiểm soát hành vi của LLM hiệu quả hơn. Một kỹ sư có thể xây dựng một chương trình tương đối đáng tin cậy và hữu ích với những kỹ thuật này. Tuy nhiên, khi hệ thống càng phức tạp, tiềm năng LLM làm những điều "khó đoán" cũng càng tăng lên.Thông thường, code rất dễ "bắt bài". Một tập hợp các hàm tồn tại và kỹ sư định nghĩa chính xác khi nào và cách thức chúng được sử dụng. Nhưng giờ đây, các chương trình lại "biến hình" thành một tập hợp các công cụ, và **quyền kiểm soát thực tế lại đang được "trao tay" cho các LLM!**Các kỹ sư luôn phải "cân não" để cân bằng giữa sự phức tạp, khả năng bảo trì và chức năng. Việc sử dụng LLM chỉ làm cho "cán cân" này trở nên tinh tế và "khó nhằn" hơn mà thôi. Dù sao thì, đây cũng là một hành trình công nghệ đầy thú vị, đúng không nào?<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/BalanceInLLMApps.png' alt='Cân bằng trong phát triển ứng dụng LLM'>
Khám phá cách LLM đang cách mạng hóa việc đọc và hiểu mã nguồn mở quy mô lớn. Tìm hiểu về 'Đặc Vụ Đọc Code' giúp bạn tiết kiệm thời gian, dễ dàng khám phá các dự án phức tạp như Kubernetes, Argo CD, Prometheus chỉ với vài phút. Bài viết đi sâu vào cách hoạt động, lợi ích so với phương pháp truyền thống, các thách thức trong quá trình phát triển, và tầm nhìn tương lai của công cụ này.
Bạn ơi, bạn có bao giờ mơ ước tạo ra một 'siêu công cụ' có thể đọc hàng núi tài liệu, tìm ra những điểm tương đồng nhanh như chớp, và đặc biệt là không 'ngốn' quá nhiều bộ nhớ không? À há! Tháng trước, tôi cùng team 'siêu nhân' Elijah, Kevin, Jerome, và Godwin đã biến giấc mơ đó thành hiện thực tại một cuộc thi hackathon đỉnh cao! Nhiệm vụ của chúng tôi là: xây dựng một công cụ lập chỉ mục văn bản siêu tốc, siêu 'khủng' bằng Go. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/hackathon_team.png' alt='Team hackathon'> Kết quả? Một 'chiến binh' mang tên Jam-Text ra đời! Đây là một công cụ lập chỉ mục hoạt động qua dòng lệnh (CLI), được trang bị 'siêu năng lực' SimHash để nhận diện dấu vân tay văn bản, khả năng so sánh vector 'thần sầu' qua các siêu mặt phẳng ngẫu nhiên, và đặc biệt là một chút 'phép thuật' đa luồng. Cùng tôi khám phá hành trình xây dựng Jam-Text, những chướng ngại vật chúng tôi đã vượt qua, và những bài học 'đắt giá' tôi đã gom góp được nhé!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/jamtext_logo.png' alt='Logo Jam-Text'>\n\n**Thử thách: Nhanh, Mạnh, và Độc đáo (trong việc tìm kiếm sự tương đồng)!**\n\nĐề bài hackathon nghe thì đơn giản lắm: xẻ nhỏ file văn bản thành từng 'miếng' (khoảng 4KB mỗi miếng), tạo 'dấu vân tay' SimHash cho chúng, xây dựng chỉ mục trong bộ nhớ, và cho phép tra cứu nhanh. Bonus thêm điểm nếu xử lý song song và có khả năng 'tìm kiếm mờ' (fuzzy matching).<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/puzzle_challenge.png' alt='Thử thách'>\nNhưng ẩn sau sự đơn giản đó là cả một 'con quái vật' thật sự: làm sao để công cụ này vừa nhanh, vừa 'xử lý' được những tập dữ liệu khổng lồ mà bộ nhớ vẫn không 'kêu gào' đòi thêm?\n\nChúng tôi bắt đầu bằng một câu hỏi 'củ chuối': Rốt cuộc thì 'tương đồng' là cái quái gì?\n\n* Nếu là chống đạo văn, bạn muốn tìm những đoạn giống y chang hoặc gần y chang.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/plagiarism_detector.png' alt='Chống đạo văn'>\n* Nếu là tìm kiếm nội dung, 'tương đồng ngữ nghĩa' lại quan trọng hơn (kiểu như 'mèo' với 'mèo con' hay 'mèo nhà' ấy).<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/semantic_search.png' alt='Tìm kiếm ngữ nghĩa'>\n\nThế là chúng tôi quyết định áp dụng một 'chiến thuật' kết hợp 'độc đáo':\n\n* **SimHash:** Dùng cho những lần tìm kiếm 'đúng chuẩn' (theo yêu cầu CLI), cực kỳ nhanh và hiệu quả cho việc phát hiện trùng lặp.\n* **Vector Similarity với Random Hyperplanes:** Dùng cho những cuộc phiêu lưu tìm kiếm 'mờ ảo' (tìm kiếm ngữ nghĩa), cực kỳ 'khủng' và có khả năng mở rộng để xử lý dữ liệu lớn. <img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/hybrid_strategy.png' alt='Chiến thuật kết hợp SimHash và Vector Similarity'>\n\n**Khám phá: Ý tưởng Lớn từ LSH – Chìa khóa vàng cho bài toán 'nghìn tỷ'!**\n\nNguồn cảm hứng 'thần thánh' của chúng tôi đến từ một bài báo của Moses Charikar: "Similarity Estimation Techniques from Rounding Algorithms". Bài báo này đã giới thiệu Locality-Sensitive Hashing (LSH) – một 'chiêu trò' thiên tài để 'đánh dấu vân tay' dữ liệu phức tạp (như các đoạn văn bản dài ngoằng) thành những bản phác thảo ngắn, dễ so sánh, mà vẫn giữ được độ tương đồng.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/lsh_concept.png' alt='Locality-Sensitive Hashing'>\n\nBài báo 'điểm danh' ba 'mùi vị' chính của LSH:\n\n* **Min-Hash:** Tuyệt vời để phát hiện sự trùng lặp chính xác (ví dụ: các bản sao y chang).<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/minhash_concept.png' alt='Min-Hash'>\n* **Random Hyperplanes:** Biến văn bản thành các vector (kiểu như đếm số lần xuất hiện của từ), rồi đo lường độ tương đồng cosine – hoàn hảo cho việc tìm kiếm ý nghĩa 'ẩn sâu' bên trong!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/random_hyperplanes.png' alt='Random Hyperplanes'>\n* **Earth Mover Distance:** Một metric phân phối 'sang chảnh' nhưng với chúng tôi thì hơi 'quá đà' và phức tạp cho mục tiêu hackathon.\n\nChúng tôi 'chấm' Random Hyperplanes vì khả năng 'hiểu nghĩa' của nó, và kết hợp với SimHash để đáp ứng yêu cầu `-h <simhash_value>` của CLI. Giờ thì cùng xem chúng tôi đã 'khâu vá' mọi thứ lại với nhau như thế nào để tạo ra Jam-Text nhé!\n\n**Xây dựng Jam-Text: Hành trình công nghệ 'từ A đến Z'!**\n\n**Bước 1: 'Xẻ thịt' văn bản thành từng 'miếng' (Chunking) – Công việc của người thợ mổ!**\n\nChúng tôi bắt đầu với gói `chunker` để chia file khổng lồ thành các 'miếng' nhỏ xinh có kích thước cố định (mặc định 4KB). Các phiên bản đầu tiên chỉ chạy một luồng – chậm như rùa bò với các file lớn! Thế là chúng tôi 'triệu hồi' một đội quân 'công nhân' (worker pool), chia file ra nhiều 'phân đoạn' và xử lý chúng song song với sức mạnh của `goroutines` (những 'sợi chỉ' siêu nhẹ của Go giúp chạy nhiều việc cùng lúc). Giờ thì nhanh như điện luôn, chẳng khác nào 'cỗ máy thời gian' giúp tiết kiệm công sức!\n\n```go\nfunc (c *Chunker) ProcessFile(filePath string) ([]Chunk, error) {\n fileSize := getFileSize(filePath)\n numWorkers := 4\n chunkChan := make(chan Chunk, 100)\n var wg sync.WaitGroup\n sectionSize := fileSize / int64(numWorkers)\n\n // Spin up workers\n for i := 0; i < numWorkers; i++ {\n start := int64(i) * sectionSize\n end := start + sectionSize\n if i == numWorkers-1 {\n end = fileSize\n }\n wg.Add(1)\n go c.processSection(filePath, start, end, chunkChan)\n }\n\n // Collect chunks\n go func() { wg.Wait(); close(chunkChan) }()\n\n var chunks []Chunk\n for chunk := range chunkChan {\n chunks = append(chunks, chunk)\n }\n return chunks, nil\n}\n```\n<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/text_chunking.png' alt='Quá trình chia nhỏ văn bản'>\n\n**Bước 2: 'Dấu vân tay' đa chiều (Fingerprinting) với một chút 'xoắn' lai tạp – Nghệ thuật nhận diện!**\n\nTiếp theo, chúng tôi 'đánh dấu vân tay' cho từng 'miếng' văn bản trong gói `simhash`:\n\n* **SimHash:** 'Băm' các từ thành một 'dấu vân tay' 64-bit cực kỳ độc đáo – nhanh và siêu nhỏ gọn, giống như mã vạch riêng cho mỗi đoạn văn!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/simhash_fingerprint.png' alt='SimHash fingerprint'>\n* **Vector Similarity:** Mỗi 'miếng' văn bản sẽ biến thành một vector (kiểu như một danh sách đếm số lần xuất hiện của từ trong một 'từ điển' đơn giản), và chúng tôi tạo ra các 'bản phác thảo' 10-bit bằng cách sử dụng các siêu mặt phẳng ngẫu nhiên. Nghe có vẻ 'hại não', nhưng đây chính là 'bí kíp' giúp chúng ta so sánh được ý nghĩa tương đồng giữa các đoạn văn!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/vector_similarity.png' alt='Vector Similarity'>\n\n```go\ntype Fingerprint struct {\n SimHash uint64\n VecSketches []int\n}\n\nfunc Hash(content []byte) Fingerprint {\n vector := toVector(string(content))\n return Fingerprint{\n SimHash: simHash(string(content)),\n VecSketches: vectorSketches(vector, 10),\n }\n}\n```\n\n**Bước 3: Lập chỉ mục với LSH – Sắp xếp như một thư viện khổng lồ!**\n\nGói `index` là nơi 'cất giữ' các 'dấu vân tay' SimHash cho những lần tra cứu chính xác và 'nhóm' LSH (LSH bucketing) cho các bản phác thảo vector. Tưởng tượng như một thư viện siêu to khổng lồ, nơi mỗi cuốn sách (chunk) đều có thẻ thư viện (SimHash) và được xếp vào các kệ (LSH buckets) dựa trên chủ đề (vector sketches) để dễ tìm kiếm!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/indexing_lsh.png' alt='Lập chỉ mục với LSH'>\n\n```go\ntype Index struct {\n Entries map[uint64]IndexEntry\n LSHBuckets map[int][]IndexEntry\n mu sync.RWMutex\n}\n\nfunc (idx *Index) AddEntry(fp simhash.Fingerprint, offset int64) {\n idx.mu.Lock()\n defer idx.mu.Unlock();\n idx.Entries[fp.SimHash] = IndexEntry{Fingerprint: fp, Offsets: []int64{offset}}\n bucketKey := fp.VecSketches[0]\n idx.LSHBuckets[bucketKey] = append(idx.LSHBuckets[bucketKey], IndexEntry{Fingerprint: fp, Offsets: []int64{offset}})\n}\n```\n\n**Bước 4: Phép thuật CLI – 'Thao túng' Jam-Text qua dòng lệnh!**\n\nGói `cli` đã 'thổi hồn' cho Jam-Text, biến nó thành một 'phù thủy' có thể điều khiển qua dòng lệnh với các lệnh như `index` (tạo chỉ mục), `lookup` (tra cứu), `compare` (so sánh), và `fuzzy` (tìm kiếm mờ). Cứ như bạn đang 'vẩy đũa phép' vậy đó, ra lệnh là có kết quả!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/cli_magic.png' alt='CLI magic'>\n\n**Ví dụ cách dùng CLI 'thần thánh' này – Đảm bảo bạn sẽ mê tít!**\n\n```bash\n./textindex -c index -i large_text.txt -s 4096 -o index.idx\n./textindex -c lookup -i index.idx -h 3eff1b2c98a6\n./textindex -c compare -i doc1.txt -i2 doc2.txt -o report.txt\n./textindex -c fuzzy -i testdata.idx -h $HASH -threshold 0.8\n```\n\n**Thử thách và những chiến thắng 'vang dội' – Cơn bão nào cũng có cầu vồng!**\n\n**Cơn đau đầu 'từ vựng' (The Vocabulary Headache) – Khi từ ngữ cũng 'làm mình làm mẩy'!**\n\nVector similarity cần một 'từ điển' để hoạt động, giúp nó biết được mỗi từ là gì và quan trọng đến đâu. Ban đầu, chúng tôi định xây dựng 'từ điển' này 'động' từ dữ liệu, nhưng thấy chậm quá trời! Thế là chúng tôi đành 'lấy mẫu' trước từ một file lớn để có một bộ 'từ điển' cơ bản. Dù không hoàn hảo 100%, nhưng vẫn 'ổn áp' cho bản demo và giúp chúng tôi vượt qua thử thách này!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/vocabulary_headache.png' alt='Từ vựng đau đầu'>\n\n**Cân bằng giữa tốc độ và bộ nhớ – Bài toán 'khó nhằn' khiến lập trình viên 'trằn trọc'!**\n\nĐây đúng là một bài toán đau đầu! SimHash thì 'gầy gò' (chỉ 8 bytes/chunk) vì nó chỉ lưu một con số duy nhất. Nhưng 10 bản phác thảo vector thì 'phình' lên khoảng 18 bytes/chunk. Cứ thêm bản phác thảo là bộ nhớ lại 'kêu gào' to hơn! Chúng tôi đã phải 'điều chỉnh' từ 20 bản phác thảo xuống còn 10 – đủ tốt cho tìm kiếm mờ mà không 'ngốn' RAM quá nhiều, đảm bảo tốc độ và hiệu quả bộ nhớ được cân bằng!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/speed_memory_balance.png' alt='Cân bằng tốc độ và bộ nhớ'>\n\n**Mở rộng quy mô với LSH – 'Đại lộ' tốc độ cho tìm kiếm siêu lớn!**\n\nTìm kiếm tuyến tính (dò từng cái một) thì... chán òm và cực kỳ chậm chạp với dữ liệu lớn! Các 'nhóm' LSH (LSH buckets) đã cải thiện độ chính xác và tốc độ đáng kể bằng cách nhóm các bản phác thảo tương đồng lại với nhau. Tuy nhiên, việc 'tinh chỉnh' kích thước bản phác thảo so với lỗi dương tính giả (tìm nhầm kết quả không khớp) thì 'khó chịu' cực kỳ, đòi hỏi sự kiên nhẫn và thử nghiệm liên tục!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/lsh_scaling.png' alt='Mở rộng quy mô LSH'>\n\n**Tiếp theo là gì? Những cuộc phiêu lưu mới cho Jam-Text!**\n\nChúng tôi đang 'tăm tia' đến những 'chân trời' mới cho Jam-Text:\n\n* **Incremental Indexing:** Để cập nhật chỉ mục 'real-time' (thời gian thực) khi dữ liệu thay đổi, không cần tạo lại từ đầu.\n* **Approximate Nearest Neighbors (ANN):** Để tìm kiếm mờ còn nhanh hơn nữa, đặc biệt với các không gian vector siêu lớn.\n* **Persistent Storage (LSM trees, RocksDB?):** Lưu trữ 'cố định' dữ liệu chỉ mục ra đĩa thay vì chỉ trong bộ nhớ, giúp Jam-Text có thể xử lý các tập dữ liệu khổng lồ mà không sợ 'bay màu' khi tắt máy!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/whats_next.png' alt='Những gì tiếp theo'>\n\nBạn có muốn dùng Jam-Text không? Có ý tưởng gì để cải thiện nó không? Hãy cùng 'tám' trong phần bình luận nhé! 🚀 Hẹn gặp lại trong những bài viết tiếp theo!
Tìm hiểu cách đội ngũ hackathon đã tạo ra Jam-Text, công cụ lập chỉ mục văn bản siêu nhanh và mở rộng bằng Go, sử dụng SimHash và LSH để tìm kiếm tương đồng. Bài viết chia sẻ hành trình phát triển, các thách thức và bài học quý giá về hiệu suất và khả năng mở rộng.
Chào các bạn! Năm 2025 này, thế giới công nghệ đang "phi như bay" với đủ thứ "hot trend" như điện toán đám mây, kiến trúc microservices hay hạ tầng siêu mở rộng. Và đoán xem ngôn ngữ nào được "sinh ra để trị" cái môi trường đầy biến động này? Chính là Go (Golang) đấy! Đơn giản, siêu nhanh, và được thiết kế đặc biệt để giải quyết những thách thức kỹ thuật "khủng" ở quy mô lớn. Nếu bạn đang băn khoăn liệu học Go bây giờ còn "ngon" không, thì câu trả lời ngắn gọn là: CÓ! Và ngay bây giờ, chúng ta sẽ cùng "mổ xẻ" tại sao nhé!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/go_gopher_flying.png' alt='Gopher đang bay trong môi trường công nghệ'>Giới công việc đã lên tiếng: Kỹ sư Go đang là "hàng HOT" phỏng vấn tới tấp!Nếu bạn đang tìm một ngôn ngữ có khả năng "mở cửa" nhiều cơ hội, thì Go chính là lựa chọn sáng giá đó!* Lương cao ngất ngưởng: Theo Glassdoor và Golang Cafe, các lập trình viên Go ở Mỹ kiếm trung bình khoảng 132.651 USD/năm. Thậm chí, ngay cả vị trí mới vào nghề cũng đã khởi điểm tầm 116.000 USD, còn các kỹ sư cấp cao thì dễ dàng đạt trên 170.000 USD. Nghe mà muốn học ngay đúng không nào?<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/go_salary_chart.png' alt='Biểu đồ lương của lập trình viên Go'>* Nhu cầu toàn cầu: Go không chỉ "hot" ở Mỹ đâu nhé! "Em nó" còn đang "càn quét" khắp Châu Âu, Ấn Độ, Úc và cả khu vực Đông Nam Á nữa. Quá dữ dội!* Các "ông lớn" tin dùng: Google, Uber, Dropbox, Cloudflare... toàn là những cái tên đình đám đều tin tưởng Go để xây dựng các hệ thống backend nhanh và có khả năng mở rộng.Tóm lại: Học Go có thể mở ra những cơ hội nghề nghiệp cực kỳ "đã" và đáng giá đấy!Go được sinh ra để đón đầu tương lai!Go được thiết kế để giải quyết các vấn đề hiện đại – và đến nay vẫn "tỏa sáng" rực rỡ!* Xử lý song song (Concurrency) đỉnh cao: Với các "chiêu" độc đáo như goroutines và channels, Go giúp bạn viết code dễ dàng xử lý nhiều tác vụ cùng lúc mà không bị "đau đầu". Tưởng tượng như bạn có cả chục cánh tay cùng làm việc vậy, nhanh và hiệu quả hơn rất nhiều!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/go_goroutines_channels.png' alt='Minh họa goroutines và channels của Go'>* "Cạ cứng" của Cloud-Native: Nhẹ và nhanh, các ứng dụng Go "ăn khớp" một cách hoàn hảo với môi trường container như Docker hay Kubernetes. Đưa lên mây là chạy "vèo vèo" luôn!* Điểm vàng cân bằng: Trong khi Python có thể hơi "rề rà" và Rust thì đôi khi làm bạn "xoắn não" vì độ phức tạp, Go lại là sự kết hợp hoàn hảo giữa tốc độ, sự đơn giản và độ tin cậy. Vừa nhanh, vừa dễ, vừa ổn định – đúng là "chân ái"!Dễ học hơn bạn tưởng!Một trong những điều tuyệt vời nhất của Go? Là bạn sẽ bắt đầu học nó một cách dễ dàng và càng học càng thấy "nghiện"!* Cú pháp đơn giản, dễ đọc: Go giữ mọi thứ cực kỳ sạch sẽ và trực tiếp. Bạn không cần phải "vật lộn" với hàng núi quy tắc để viết ra được một đoạn code chắc chắn đâu.* Thư viện chuẩn "ngon lành cành đào": Cần xây một web server hay xử lý file? Các công cụ tích hợp sẵn của Go giải quyết "ngon ơ", không cần phải lôi cả đống framework bên thứ ba cồng kềnh vào làm gì cho mệt.* Tự động dọn dẹp bộ nhớ (Garbage Collection): Go tự lo việc quản lý bộ nhớ ở "hậu trường", nên bạn có thể tập trung "xây nhà" (phát triển tính năng) thay vì phải "sửa ống nước" (debug lỗi rò rỉ bộ nhớ). Quá tiện lợi!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/go_easy_syntax.png' alt='Ví dụ cú pháp Go đơn giản'>Động lực từ hệ sinh thái: Go không chỉ là một ngôn ngữ!Một ngôn ngữ chỉ thực sự mạnh khi hệ sinh thái của nó phát triển, và Go thì đang cực kỳ "sung sức"!* Cộng đồng mã nguồn mở khổng lồ: Hãy ghé Awesome Go mà xem – bạn sẽ tìm thấy mọi thứ từ các framework web, công cụ CLI, machine learning và ti tỉ thứ hay ho khác. Đủ thấy Go được yêu mến đến mức nào!* "Sức mạnh" của đám mây: Các công cụ "đinh" của giới DevOps như Docker, Kubernetes và Terraform đều được xây dựng bằng Go. Oách chưa?<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/go_cloud_tools.png' alt='Logo các công cụ Docker, Kubernetes, Terraform'>* Ổn định và liên tục phát triển: Go được duy trì và phát hành phiên bản mới thường xuyên, giúp nó luôn hiện đại mà vẫn giữ vững được nền tảng đơn giản, ổn định vốn có. Xem ghi chú phát hành mới nhất của Go tại đây để biết thêm chi tiết.Go thực sự tỏa sáng ở đâu?Điểm mạnh nhất của Go là xây dựng các hệ thống thực tế, có khả năng mở rộng. Đây là những nơi mà nó thực sự "lên đồng":* 🛠 API Backend & Microservices: Sự đơn giản và hiệu suất của Go biến nó thành lựa chọn hàng đầu để xây dựng các API gọn gàng, có tính module hóa cao. Mô hình concurrency của Go giúp việc mở rộng microservices trở nên dễ dàng mà không bị rối rắm.* ☁️ Ứng dụng Cloud-Native: Go sinh ra là để dành cho đám mây! Với thời gian khởi động nhanh chóng, sử dụng bộ nhớ cực ít, các ứng dụng Go nhẹ nhàng và rất dễ dàng "di chuyển" giữa các môi trường. Đây là ngôn ngữ yêu thích của các developer làm việc trên AWS, GCP hay Azure.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/go_use_cases.png' alt='Các trường hợp sử dụng Go nổi bật'>* ⚙️ Công cụ DevOps & Hạ tầng: Hãy thử "nhìn vào bên trong" các công cụ như Docker, Kubernetes và Terraform mà xem, bạn sẽ thấy toàn Go thôi! Nó đã trở thành ngôn ngữ "chuẩn mực" cho các công cụ DevOps nhờ tốc độ, khả năng hỗ trợ concurrency và dễ dàng biên dịch chéo.* 🚀 Hệ thống chịu tải cao, khả năng mở rộng: Khi ứng dụng của bạn cần xử lý hàng triệu yêu cầu mỗi giây, Go có thể giúp bạn xây dựng các dịch vụ web và hệ thống phân tán vừa nhanh như chớp, vừa tiết kiệm tài nguyên.Đường cong học tập: Mượt mà bất ngờ!Học Go sẽ cho bạn cảm giác "tươi mới" đến bất ngờ vì nó rất thẳng thắn và dễ tiếp thu.* Thiết kế để dễ học: "A Tour of Go" là một hướng dẫn tương tác cực kỳ hay, sẽ dẫn dắt bạn qua các kiến thức cơ bản một cách nhẹ nhàng.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/go_tour_docs.png' alt='Gopher đang khám phá tour học Go'>* Tài liệu đỉnh cao: Tài liệu chính thức của Go là một trong những bộ tài liệu dễ đọc và hữu ích nhất mà bạn có thể tìm thấy.* Cộng đồng thân thiện: Các diễn đàn như r/golang, Stack Overflow luôn đầy ắp những lập trình viên tốt bụng sẵn sàng giúp đỡ bạn.Cái gì cũng có hai mặt: Thách thức và hiểu lầmDù "ngon" đến mấy thì Go cũng có những giới hạn riêng:* Không phải "số 1" cho ứng dụng GUI: Mặc dù có thể làm được, nhưng việc xây dựng ứng dụng desktop sẽ mượt mà hơn với các công cụ khác.* Không tập trung vào Khoa học Dữ liệu: Python vẫn là "vua" trong mảng Machine Learning và các công việc liên quan đến dữ liệu nặng.* Một lời đồn thổi "kinh điển" – "Go chỉ dành cho các vấn đề quy mô Google": Hoàn toàn KHÔNG ĐÚNG! Go cực kỳ phù hợp cho các dự án cá nhân, các startup, và cả các ứng dụng quy mô vừa nữa. Đừng tin lời đồn nhé!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/go_myth_busted.png' alt='Gopher gạch bỏ tin đồn về Go'>Lời phán quyết cuối cùng: Có nên học Go vào năm 2025?✅ CÓ!Nếu bạn muốn một ngôn ngữ đơn giản để học, mạnh mẽ để sử dụng và có nhu cầu tuyển dụng cực cao, thì Go chính là một "canh bạc" thông minh cho năm 2025 và xa hơn nữa.Nó đặc biệt tuyệt vời cho:* Các lập trình viên Backend* Kỹ sư Cloud* Chuyên gia DevOps* Người muốn chuyển ngành tìm kiếm một kỹ năng lương cao, "chống chịu" tốt trong tương lai.Hãy học nó, xây dựng các dự án với nó, và theo dõi sự nghiệp của bạn "cất cánh" nhé!
Bạn đã bao giờ "ngứa nghề" muốn xây dựng một dự án Go "khủng bố" với khả năng mở rộng "vô biên", độ tin cậy đáng kinh ngạc và dễ bảo trì đến mức ai cũng mê chưa? Nếu câu trả lời là CÓ thì chúc mừng bạn! Hôm nay, chúng ta sẽ cùng nhau "mổ xẻ" Kubernetes – một trong những dự án mã nguồn mở "siêu to khổng lồ" được viết bằng Go – để xem các "phù thủy" ở đó đã tổ chức mã nguồn của họ như thế nào cho bộ não điều phối container phức tạp này. Tin tôi đi, học hỏi từ "người khổng lồ" này chắc chắn sẽ mang lại cho chúng ta những bài học "đắt giá" về kiến trúc phần mềm đó!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farnlfiz6xjc3lvhyyww8.png' alt='Sơ đồ kiến trúc Kubernetes'>Đầu tiên, hãy cùng "nghía" qua những thư mục cấp cao "chủ chốt" của Kubernetes và xem chức năng chính của chúng là gì nhé. Sau đó, chúng ta sẽ đi sâu vào từng "ngóc ngách" để khám phá bí mật bên trong!- **`api`**: Nghe tên là thấy "giao tiếp" rồi đúng không? Thư mục này chứa các file định nghĩa giao diện (interface protocols) như OpenAPI và Swagger, bao gồm cả các định nghĩa cho JSON và Protocol. Nói nôm na là, đây là nơi Kubernetes "ghi lại" cách các thành phần sẽ "tám chuyện" với nhau.- **`build`**: Nghe đến "build" là biết ngay đây là nơi "xây nhà" rồi! Thư mục này chứa tất cả các script (tập lệnh) cần thiết để "xây dựng" dự án Kubernetes. Từ việc biên dịch từng "viên gạch" của K8s cho đến tạo ra các image cần thiết (như chương trình `pause` bé xíu nhưng quan trọng), tất cả đều nằm ở đây.- **`cmd`**: À há! Đây chính là "điểm khởi đầu" hay "cánh cổng chính" của mỗi ứng dụng, nơi "khai sinh" ra các file thực thi (executable files). Nếu bạn có nhiều ứng dụng cần chạy, mỗi ứng dụng sẽ có một thư mục con riêng trong `cmd`, cứ như mỗi đứa có một "cửa hàng" riêng vậy! Hãy cùng điểm mặt một vài "gương mặt thân quen" trong thư mục `cmd` của Kubernetes nhé: - `kube-proxy`: Anh chàng "canh cổng" này chịu trách nhiệm về các quy tắc liên quan đến mạng, giúp các dịch vụ tìm thấy nhau. - `kube-apiserver`: "Bộ não" trung tâm, phơi bày các API của K8s và xử lý mọi yêu cầu, cung cấp các thao tác CRUD (Tạo, Đọc, Cập nhật, Xóa) cho đủ loại tài nguyên "nóng hổi" như Pod, ReplicaSet, Service, v.v. - `kube-controller-manager`: "Đội trưởng" quản lý các bộ điều khiển, đảm bảo mọi thứ chạy đúng hướng. - `kube-scheduler`: "Anh hùng" này có nhiệm vụ theo dõi các Pod mới được tạo và chọn một "ngôi nhà" (node) phù hợp nhất để chúng có thể "sinh sống" và làm việc hiệu quả. - `kubectl`: Công cụ dòng lệnh "thần thánh", là "chìa khóa vạn năng" để bạn tương tác với cluster K8s. Thấy chưa? Những thành phần quen thuộc của K8s mà chúng ta hay dùng đều "tề tựu" đông đủ ở đây đó!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/KubernetesComponents.png' alt='Sơ đồ các thành phần chính của Kubernetes'>Vậy còn **`pkg`** thì sao? Nếu `cmd` là "bộ mặt" bên ngoài để khởi chạy các ứng dụng, thì `pkg` chính là "bộ não" và "trái tim" chứa đựng toàn bộ logic và triển khai chính của từng thành phần. Thư mục `pkg` chứa cả các thư viện nội bộ lẫn các gói (package) được "xuất khẩu" để các phần khác trong dự án (hoặc thậm chí bên ngoài) có thể "mượn dùng". Nói cách khác, đây là nơi "sản xuất" ra các "linh kiện" cao cấp để lắp ráp thành các ứng dụng "hoành tráng" trong `cmd` đó!Ví dụ một vài "linh kiện" quan trọng trong `pkg` mà bạn nên biết:- `proxy`: Logic triển khai của proxy mạng – "người giao liên" đáng tin cậy.- `kubelet`: "Trái tim" của mỗi Node, giúp duy trì và quản lý các Pod, đảm bảo chúng "khỏe mạnh" và hoạt động trơn tru.- `cm` (Container Manager): Quản lý container, ví dụ như xử lý `cgroups` để phân bổ tài nguyên "công bằng".- `stats`: Thu thập thông tin sử dụng tài nguyên, được triển khai bởi `cAdvisor` – "điệp viên" giám sát hiệu suất.- `scheduler`: Logic triển khai của bộ lập lịch Pod – "kiến trúc sư" sắp xếp Pod vào nơi phù hợp.- `framework`: Các khung sườn chung – "bộ khung xương" giúp xây dựng nhanh hơn.- `controlplane`: Logic của mặt phẳng điều khiển – "trung tâm chỉ huy" mọi hoạt động.- `apiserver`: Các thành phần chính của API server – "bộ não" giao tiếp.Thư mục này chính là nơi chứa đựng trí tuệ và sự phức tạp của Kubernetes, nơi mọi thứ được xây dựng một cách module hóa (chia nhỏ thành các khối độc lập) và tái sử dụng được, y như trò chơi Lego vậy!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/ModularCodeConcept.png' alt='Khái niệm mã nguồn module hóa'>Giờ đến một "góc khuất" khá đặc biệt và thú vị mà không phải dự án nào cũng có: **`staging`**! Đây không phải là nơi chứa mã nguồn cố định, mà giống như một "khu nhà trọ tạm trú" cho những đoạn mã có sự phụ thuộc "chằng chịt" lẫn nhau giữa các thành phần. Các gói trong thư mục `staging` được "liên kết" vào `k8s.io` thông qua các symbolic links (liên kết tượng trưng) – kiểu như một lối tắt vậy!Bạn có thắc mắc tại sao lại phải "làm màu" phức tạp như vậy không? Có hai lý do chính:- **Dự án Kubernetes "khổng lồ":** Vì K8s quá lớn, việc này giúp tránh những "cơn đau đầu" khi phát triển do các kho lưu trữ (repository) bị phân mảnh. Nhờ `staging`, tất cả mã nguồn có thể được gửi và xem xét trong một Pull Request duy nhất, cứ như một "combo tiện lợi" vậy. Điều này vừa đảm bảo tính module hóa, vừa giữ cho kho mã nguồn chính luôn hoàn chỉnh và dễ theo dõi.- **Quản lý phiên bản dễ thở hơn:** Bằng cách sử dụng chỉ thị `replace` trong `go mod`, bạn không cần phải "gắn thẻ" (tag) cho từng phụ thuộc một, giống như không cần phải dán nhãn riêng cho từng món đồ nhỏ. Điều này đơn giản hóa quá trình quản lý phiên bản và phát hành dự án rất nhiều, tiết kiệm "mồ hôi nước mắt" cho các lập trình viên.Tưởng tượng mà xem, nếu không có `staging` và chúng ta chọn cách "monorepo" truyền thống – tức là chia tất cả mã nguồn trong `staging` thành các kho lưu trữ độc lập. Khi mã nguồn của bất kỳ kho con nào thay đổi, chúng ta sẽ phải: gửi mã lên kho con đó trước, sau đó phát hành một tag mới, rồi lại phải cập nhật tag cũ trong `go mod` trước khi tiếp tục phát triển. Nghe thôi đã thấy "toát mồ hôi hột" rồi đúng không? Điều này chắc chắn sẽ làm tăng chi phí phát triển tổng thể lên rất nhiều.Vậy nên, việc "liên kết" các gói trong `staging` vào kho lưu trữ chính qua symbolic links là một giải pháp cực kỳ thông minh, giúp đơn giản hóa việc quản lý phiên bản và quy trình phát hành một cách hiệu quả đó! Quả là một "nước cờ" cao tay!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/GoModStaging.png' alt='Ví dụ về cấu trúc thư mục staging và go mod'>Bây giờ, chúng ta hãy thử "soi" vào một thư mục khá quen thuộc mà bạn có thể đã thấy trong các dự án Go tiêu chuẩn: **`internal`**! Thư mục này được thiết kế để chứa các gói (package) chỉ dành cho việc sử dụng "nội bộ" trong dự án, không được "xuất khẩu" ra bên ngoài. Nguyên tắc của `internal` là: bạn có thể dùng nó thoải mái trong "ngôi nhà" dự án của mình, nhưng các dự án "hàng xóm" thì không thể "nhòm ngó" hay truy cập vào được.Thế nhưng, lạ thay, Kubernetes lại "trắng tay" thư mục `internal` này! Tại sao vậy?Đơn giản thôi: dự án Kubernetes "khởi nghiệp" từ khoảng năm 2014, trong khi khái niệm thư mục `internal` chỉ mới được giới thiệu trong Go 1.4 (phát hành cuối năm 2014). Lúc đó, quy ước sử dụng `internal` chưa phổ biến, và sau này cũng không có một đợt tái cấu trúc (refactoring) "khủng" nào để đưa nó vào.Hơn nữa, một trong những "kim chỉ nam" thiết kế của Kubernetes là tính module hóa và sự tách rời (decoupling). Họ đạt được sự đóng gói (encapsulation) thông qua việc tổ chức gói và cấu trúc mã nguồn rõ ràng, chứ không cần đến các gói `internal` để "khóa chặt" quyền truy cập.Đến đây, chắc hẳn bạn đã hình dung được cấu trúc thư mục cấp cao cơ bản để "đặt nền móng" cho một dự án rồi nhỉ?Một điều thú vị về Go là nó không có một "khuôn mẫu" thư mục "chuẩn quốc tế" như Java. Chính vì vậy, khi bắt đầu các dự án khác nhau, bạn sẽ luôn phải "làm quen" với cấu trúc mã nguồn đặc thù của từng dự án. Ngay cả trong cùng một nhóm, cũng có thể tồn tại những cấu trúc "muôn hình vạn trạng", điều này có thể trở thành một "rào cản" lớn cho những "người mới" khi cố gắng "giải mã" dự án.Những rào cản này khiến việc hợp tác trở nên khó khăn. Một cấu trúc thư mục cấp cao thống nhất giúp chúng ta nhanh chóng tìm thấy mã nguồn và có một điểm khởi đầu chuẩn khi tiếp nhận một dự án, cải thiện hiệu quả phát triển và giảm sự "lạc lối" về vị trí mã nguồn trong quá trình hợp tác.Nhưng liệu một cấu trúc thư mục thống nhất thôi có đủ để tạo nên một dự án quy mô lớn "hoàn hảo không tì vết" không? Câu trả lời tất nhiên là... **KHÔNG**!Việc chỉ dựa vào một cấu trúc thư mục thống nhất không thể "một phát ăn ngay" giải quyết được vấn đề mã nguồn dần "hỏng hóc" và trở nên "hỗn loạn". Chỉ những nguyên tắc thiết kế vững chắc mới là "ngọn hải đăng" giúp giữ cho ngữ cảnh thiết kế luôn rõ ràng khi dự án tiếp tục mở rộng.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/GoInternalDirectory.png' alt='Minh họa thư mục internal trong Go'>Hãy cùng khám phá "triết lý thiết kế khai báo" – một "chìa khóa vàng" xuyên suốt toàn bộ thiết kế mã nguồn của Kubernetes, giúp nó không rơi vào lối tư duy lập trình thủ tục (procedural programming) "khô khan và rắc rối".Ví dụ nhé, khi bạn muốn thay đổi trạng thái của một tài nguyên, thay vì phải "mách nước" cho K8s từng bước một phải làm gì (kiểu "làm ơn đi thẳng, rẽ trái, rồi rẽ phải"), bạn chỉ cần "ra lệnh" cho K8s biết: "Tao muốn trạng thái cuối cùng phải trông như thế này!" (kiểu "đây là đích đến, mày tự lo đi!"). Chính vì thế mà `kubelet rolling-update` đã bị loại bỏ dần, vì thiết kế cũ quá "quan liêu" và can thiệp chi tiết vào toàn bộ quá trình cập nhật một Pod. Bằng cách cho Kubernetes biết trạng thái mong muốn, `kubelet` có thể tự mình thực hiện các hành động phù hợp, mà không cần bất kỳ sự can thiệp "thái quá" nào từ bên ngoài.Đến đây, bạn có thể thắc mắc: "Ủa, sao API khai báo lại giúp giữ cho các module rõ ràng khi dự án mở rộng? Đây không phải là thứ người dùng cảm nhận khi sử dụng Kubernetes sao? Nó liên quan gì đến thiết kế nội bộ?". Một câu hỏi "chất lừ" đấy!Khi chúng ta thiết kế giao diện, nếu ta "phơi bày" toàn bộ quá trình vận hành cho người dùng và để họ can thiệp từng bước một vào cách Pod của chúng ta được cập nhật, thì các module chúng ta thiết kế chắc chắn sẽ trở thành "mớ bòng bong" của lập trình thủ tục. Kết quả là, các module mã nguồn của chúng ta sẽ rất khó để giữ được sự rõ ràng vì chúng bị "gắn chặt" với quá nhiều thao tác của người dùng.Tuy nhiên, bằng cách sử dụng API khai báo, sau khi chúng ta "ra lệnh" cho K8s về trạng thái mong muốn, toàn bộ cluster có thể tự điều phối giữa nhiều thành phần nội bộ để cuối cùng đạt được trạng thái đó. Người dùng chẳng cần biết bên trong mọi thứ được cập nhật "như thế nào, ra sao"! Hơn nữa, khi cần thêm các plugin cộng tác, các module mới có thể được thêm trực tiếp mà không cần phải "bóc trần" thêm API cho người dùng thao tác.Hãy lấy ví dụ về `cAdvisor` – một thành phần "điệp viên" giám sát tài nguyên được triển khai bởi K8s và thu thập các số liệu "sống" về tài nguyên của container. `cAdvisor` hoạt động độc lập, không phụ thuộc vào các thành phần bên ngoài. Sau đó, bộ điều khiển (controller) sẽ so sánh các số liệu này với các mục tiêu mà người dùng đã khai báo để xác định xem có đạt đủ điều kiện để mở rộng (scale up) hay thu hẹp (scale down) không.Vì các module là độc lập, `cAdvisor` chỉ cần tập trung vào việc thu thập và trả về các số liệu giám sát, mà không cần bận tâm đến việc các số liệu này được sử dụng như thế nào – liệu là để quan sát hay làm cơ sở cho việc tự động mở rộng. Cứ như một người chuyên gia chỉ làm đúng nhiệm vụ của mình và không "ngó nghiêng" sang việc của người khác vậy!Đây cũng là một nguyên tắc "vàng" khi thiết kế các thành phần nhiệm vụ khác nhau: hãy xác định rõ ràng các yêu cầu cần đáp ứng; khi truyền thông tin, chỉ tập trung vào đầu vào (input) và đầu ra (output); còn về triển khai nội bộ, nó có thể được đóng gói mà không cần "phơi bày" ra bên ngoài, làm cho việc sử dụng từ các nghiệp vụ bên ngoài trở nên đơn giản nhất có thể.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/DeclarativeVsImperative.png' alt='So sánh thiết kế khai báo và thủ tục'>Tiếp theo, hãy nói về một "căn bệnh" mà không ít lập trình viên mới gặp phải: "Thiết kế quá mức cần thiết" hay còn gọi là **"Over-Engineering"**! Thật ra, thiết kế quá mức thường "phản tác dụng" và tệ hơn cả việc thiết kế chưa đủ đó bạn.Lấy ví dụ từ chính Kubernetes nhé. Phiên bản đầu tiên của K8s (0.4) ban đầu đã có một cách triển khai mạng "chính chủ" khá cụ thể. Nhưng rồi, khi cộng đồng phát triển và các giải pháp mạng "xịn sò" như Flannel, Calico, Weave ra đời và giải quyết được vấn đề mạng hiệu quả hơn, Kubernetes đã không còn cần tự mình "động tay động chân" vào nữa mà thay vào đó, họ đã giới thiệu CNI (Container Network Interface) để chuẩn hóa các plugin mạng, cứ như "nhường sân chơi" cho các chuyên gia vậy.Rõ ràng, Kubernetes không phải được thiết kế "hoàn hảo từ trong trứng nước". Mà ngược lại, khi các vấn đề mới "nhảy dù" xuất hiện, những thiết kế mới đã được đưa ra để thích ứng với sự thay đổi của môi trường. Đây chính là một bài học "xương máu" đó!Khi bắt đầu một dự án, các phụ thuộc thường khá rõ ràng. Do đó, ban đầu hiếm khi xảy ra "phụ thuộc vòng tròn" (circular dependencies) – kiểu như "người này cần người kia, mà người kia cũng cần người này", một "vòng luẩn quẩn" không lối thoát! Nhưng khi dự án lớn dần, những vấn đề này sẽ từ từ "lộ mặt". Các yêu cầu chức năng mới từ sản phẩm sẽ dẫn đến việc các phần mã nguồn tham chiếu chéo lẫn nhau.Dù chúng ta có cố gắng hết sức để hiểu rõ toàn bộ bối cảnh nghiệp vụ và các vấn đề cần giải quyết trước khi bắt đầu, thì những vấn đề mới chắc chắn sẽ phát sinh khi các tính năng sản phẩm thay đổi và chương trình được lặp lại. Điều chúng ta có thể làm là tập trung vào thiết kế module và quản lý phụ thuộc, giữ cho các chức năng có tính "gắn kết" (cohesive) càng nhiều càng tốt, và khi thêm các lớp trừu tượng (abstractions) sau này, hãy tránh việc phải "đại tu" toàn bộ mã nguồn cũ một cách "thô bạo" – kiểu như "đập đi xây lại" vậy.Việc "thiết kế quá đà" một hệ thống chỉ vì muốn nó "có khả năng mở rộng" mà bỏ qua nhu cầu thực tế, đôi khi lại trở thành một "cái ách" cho những thay đổi trong tương lai.Để dễ hình dung, hãy cùng theo dõi quá trình "tiến hóa" thiết kế qua một ví dụ "thực tế" về kịch bản kinh doanh thương mại điện tử nhé!<h3>Giai đoạn 1: Khởi đầu đơn giản</h3>Ban đầu, hệ thống của chúng ta có hai "nhân vật chính":- **Module Đơn hàng (Order Module):** Anh chàng này "đảm nhiệm" xử lý việc tạo đơn hàng, thanh toán, cập nhật trạng thái, v.v. Module này cần thông tin người dùng (như địa chỉ giao hàng, thông tin liên hệ, v.v.) nên nó "phụ thuộc" vào Module Người dùng.- **Module Người dùng (User Module):** "Quản gia" này chịu trách nhiệm quản lý thông tin người dùng, đăng ký, đăng nhập và lưu trữ dữ liệu người dùng. Đặc biệt, "quản gia" này không phụ thuộc vào Module Đơn hàng.Trong thiết kế ban đầu này, sự phụ thuộc là "một chiều" (kiểu như "anh đi trước, em theo sau"): Module Đơn hàng phụ thuộc vào Module Người dùng.Ở giai đoạn này, đừng quá "trừu tượng hóa" mã nguồn làm gì cho "đau đầu" nhé! Nhiều dự án không thể đoán trước được liệu chúng có thành công hay "đứt gánh giữa đường", vì vậy, việc đổ quá nhiều công sức vào thiết kế ngay từ đầu vừa không khả thi về mặt phát hành sản phẩm, vừa có thể trở thành "cái cùm" nếu ý tưởng sản phẩm thay đổi quá nhiều.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/InitialDependency.png' alt='Phụ thuộc một chiều: Order Module phụ thuộc User Module'><h3>Giai đoạn 2: Phát sinh nhu cầu mới và "Vòng lặp phụ thuộc"</h3>Khi yêu cầu phát triển, một nhu cầu mới "nóng hổi" nảy sinh: nền tảng cần đề xuất sản phẩm cá nhân hóa cho người dùng dựa trên lịch sử mua hàng của họ (ghi nhận từ đơn hàng).Để thực hiện đề xuất cá nhân hóa, giờ đây Module Người dùng cần "gọi" API của Module Đơn hàng để lấy lịch sử đơn hàng của một người dùng.Vậy là, các phụ thuộc giờ đây trở thành:- Module Đơn hàng phụ thuộc vào Module Người dùng để lấy thông tin người dùng.- Module Người dùng phụ thuộc vào Module Đơn hàng để lấy lịch sử đơn hàng."Ôi không!" – sự thay đổi này đã tạo ra một "phụ thuộc vòng tròn" (circular dependency) – kiểu như "người này cần người kia, mà người kia cũng cần người này", một "vòng luẩn quẩn" không lối thoát! Đây chính là "cơn ác mộng" của nhiều hệ thống đó bạn.Để giải quyết "phụ thuộc vòng tròn" nan giải này, chúng ta có thể cân nhắc vài giải pháp:**Giải pháp 1: Tách rời trách nhiệm module**Giới thiệu một module mới "đáng tin cậy", ví dụ như **Module Đề xuất (Recommendation Module)**, chuyên trách xử lý logic đề xuất cá nhân hóa. Module Đề xuất này có thể lấy dữ liệu riêng biệt từ Module Người dùng và Module Đơn hàng, từ đó tránh được sự phụ thuộc trực tiếp giữa hai module ban đầu.Bằng cách "trích xuất" (extracting) module, chúng ta đã "gỡ rối" được sự gắn kết (coupling) giữa Module Người dùng và Đơn hàng một cách "thanh tao".<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/CircularDependency.png' alt='Phụ thuộc vòng tròn: Order Module và User Module phụ thuộc lẫn nhau'><img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/DecoupledDependency.png' alt='Tách rời phụ thuộc: Recommendation Module làm trung gian'><h3>Giai đoạn 3: Nhu cầu tức thời và "Kiến trúc hướng sự kiện"</h3>Tuy nhiên, "đời không như mơ" và một yêu cầu mới lại "đổ bộ": trong các đợt khuyến mãi "nóng bỏng", người dùng mua các sản phẩm đặc biệt của chương trình. Quản lý sản phẩm muốn Module Đề xuất có thể ngay lập tức phát hiện các đơn hàng như vậy và cung cấp các đề xuất cho các sản phẩm khuyến mãi liên quan. Ví dụ, nếu người dùng mua một chiếc đồng hồ thể thao giảm giá, và chúng ta cũng "đánh chén" luôn tai nghe Bluetooth thể thao giảm giá, tỷ lệ mua lại của người dùng có thể cao hơn.Trong kịch bản này, việc để Module Đơn hàng trực tiếp gọi Module Đề xuất để truyền dữ liệu rõ ràng là "nguy hiểm". Vì Module Đề xuất đã phụ thuộc vào Module Đơn hàng để lấy dữ liệu mua hàng của người dùng (tạo một phụ thuộc một chiều). Nếu chúng ta lại để Module Đơn hàng gọi Module Đề xuất, thì lại tạo ra một "phụ thuộc vòng tròn" nữa rồi! Lại "chìm đắm" trong vòng lặp!Vậy làm thế nào để Module Đề xuất có thể "nhanh nhạy" cảm nhận được sự thay đổi trong đơn hàng? Đây chính là lúc chúng ta cần đến **Kiến trúc hướng sự kiện (Event-Driven Architecture)** – một "vị cứu tinh" trong thế giới lập trình!Bằng cách sử dụng phương pháp hướng sự kiện, khi người dùng đặt một đơn hàng, Module Đơn hàng sẽ "kích hoạt" (trigger) một sự kiện, cứ như "phát ra tín hiệu" vậy. Và Module Đề xuất sẽ "đăng ký" (subscribe) để lắng nghe các sự kiện liên quan đến đơn hàng của người dùng, giống như "bắt sóng" vậy đó. Bằng cách này, hai module không cần phải gọi trực tiếp API của nhau nữa; thay vào đó, dữ liệu được truyền qua các sự kiện, cứ như "giao tiếp qua bưu điện" vậy!Sau khi nhận được dữ liệu, Module Đề xuất có thể ngay lập tức "huấn luyện lại" một mô hình đề xuất mới và đề xuất các sản phẩm liên quan cho người dùng.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://i.imgur.com/EventDrivenArchitecture.png' alt='Kiến trúc hướng sự kiện giải quyết phụ thuộc vòng tròn'>Từ ví dụ trên, chúng ta có thể thấy một "bí ẩn" lớn trong các ứng dụng doanh nghiệp: mô hình hóa miền nghiệp vụ (business domain modeling). Trong việc mô hình hóa, đó là một quá trình liên tục "nâng cấp" và tối ưu hóa thiết kế khi các yêu cầu liên tục "tiến hóa", cứ như một trò chơi xếp hình không bao giờ kết thúc vậy.Các Module Người dùng, Đơn hàng và Đề xuất được mô tả ở trên cũng là những kịch bản "kinh điển" trong sự "tiến hóa" của hầu hết các sản phẩm To-C (hướng tới người tiêu dùng).Vậy làm thế nào để liên tục tối ưu hóa thiết kế module và cấu trúc mã nguồn của chúng ta trong quá trình tiến hóa "không ngừng nghỉ" và cải thiện tốc độ lặp lại? Đây là điều mà chúng ta cần không ngừng khám phá và "vắt óc" suy nghĩ.Tóm lại, những gì chúng ta đã cùng nhau "học lỏm" được từ "ông lớn" Kubernetes trong bài viết này là:- Khi xây dựng các dự án lớn, một cấu trúc thư mục thống nhất có thể giúp cải thiện hiệu quả cộng tác, nhưng **những nguyên tắc thiết kế vững chắc** mới là "chìa khóa vàng" để giữ cho mã nguồn rõ ràng và dễ mở rộng khi dự án lớn dần.- API khai báo của Kubernetes giúp các module luôn độc lập và tránh được những "cạm bẫy" của lập trình thủ tục "rườm rà".- Thiết kế dự án nên **tiến hóa từng bước** theo nhu cầu thực tế và **tránh việc thiết kế quá mức** (over-engineering) – đừng "tham lam" nhé!- Hãy luôn tập trung vào việc **phân tách trách nhiệm module và quản lý phụ thuộc** một cách hợp lý, và sử dụng **phương pháp hướng sự kiện** để giải quyết sự gắn kết (coupling) giữa các module khi cần thiết, cứ như "bác sĩ gỡ rối" vậy.Và đó là tất cả những gì chúng ta có thể học hỏi từ Kubernetes về cách xây dựng dự án Go quy mô lớn. Hy vọng những chia sẻ "tâm huyết" này sẽ giúp ích cho hành trình phát triển của bạn!À, tiện đây bật mí luôn một "người bạn đồng hành" cực kỳ "xịn sò" cho các dự án Go của bạn nè: Đó chính là **Leapcell** – "ngôi nhà" lý tưởng cho việc hosting các dự án Go của bạn!<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q41mqlzjmnxaee5e343i.png' alt='Leapcell - Nền tảng Serverless thế hệ mới'>Leapcell là Nền tảng Serverless thế hệ mới, hỗ trợ Web Hosting, Async Tasks và Redis với "một rổ" ưu điểm "không thể bỏ qua":- **Hỗ trợ đa ngôn ngữ:** Bạn có thể phát triển với Node.js, Python, Go, hoặc Rust – "ngôn ngữ nào cũng chiều"!- **Triển khai không giới hạn, miễn phí:** Bạn có thể triển khai bao nhiêu dự án tùy thích mà không mất phí ban đầu. Chỉ trả tiền theo mức sử dụng – "dùng bao nhiêu, trả bấy nhiêu", không yêu cầu, không tính phí khi "ngủ đông"!- **Hiệu quả chi phí "khủng khiếp":** Thanh toán theo mức sử dụng, không mất phí khi không hoạt động. Ví dụ: chỉ với 25 đô la, bạn có thể xử lý 6.94 triệu yêu cầu với thời gian phản hồi trung bình 60ms. Ngon, bổ, rẻ đúng không?- **Trải nghiệm phát triển siêu mượt mà:** Giao diện người dùng trực quan, thiết lập dễ dàng "như ăn kẹo". Tự động hóa CI/CD và tích hợp GitOps "một cách hoàn hảo". Có cả số liệu và nhật ký thời gian thực để bạn "nắm trong lòng bàn tay" tình hình.- **Khả năng mở rộng và hiệu suất đỉnh cao:** Tự động mở rộng quy mô để xử lý đồng thời lớn một cách dễ dàng. Không cần lo lắng về vận hành – "mọi chuyện đã có Leapcell lo", bạn chỉ cần tập trung vào việc "sáng tạo" thôi!Khám phá thêm trong phần <a href='https://docs.leapcell.io/'>Tài liệu của Leapcell</a> nhé!Đừng quên theo dõi họ trên X: <a href='https://x.com/LeapcellHQ'>@LeapcellHQ</a>.Hoặc đọc thêm trên blog của Leapcell: <a href='https://leapcell.io/blog/learning-large-scale-go-project-architecture-from-kubernetes'>tại đây</a>.<img src='https://truyentranh.letranglan.top/api/v1/proxy?url=https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mgu11d1z60322sh99aki.png' alt='Thử nghiệm Leapcell ngay hôm nay'>