Dịch và điều chỉnh các vi dịch vụ Java: Hướng dẫn thực hành . Các phần trước của hướng dẫn:
- những điều cơ bản về microservice và kiến trúc của chúng ;
- triển khai và thử nghiệm microservice .
Làm cách nào để làm cho một microservice Java trở nên linh hoạt?
Hãy nhớ lại rằng khi tạo vi dịch vụ, về cơ bản bạn đang giao dịch các lệnh gọi phương thức JVM để lấy các lệnh gọi HTTP đồng bộ hoặc nhắn tin không đồng bộ. Mặc dù cuộc gọi phương thức hầu hết được đảm bảo hoàn thành (ngăn chặn việc tắt JVM không mong muốn), cuộc gọi mạng theo mặc định là không đáng tin cậy. Nó có thể hoạt động, nhưng cũng có thể không hoạt động vì nhiều lý do: mạng bị quá tải, quy tắc tường lửa mới đã được triển khai, v.v. Để biết điều này tạo nên sự khác biệt như thế nào, chúng ta hãy xem ví dụ về BillingService.Các mẫu khả năng phục hồi HTTP/REST
Giả sử khách hàng có thể mua sách điện tử trên trang web của công ty bạn. Để thực hiện việc này, bạn vừa triển khai một vi dịch vụ thanh toán có thể gọi đến cửa hàng trực tuyến của bạn để tạo hóa đơn PDF thực tế. Hiện tại, chúng tôi sẽ thực hiện lệnh gọi này một cách đồng bộ, qua HTTP (mặc dù sẽ hợp lý hơn khi gọi dịch vụ này là không đồng bộ, vì việc tạo PDF không nhất thiết phải diễn ra ngay lập tức theo quan điểm của người dùng. Chúng tôi sẽ sử dụng ví dụ tương tự trong phần tiếp theo phần và nhìn vào sự khác biệt).@Service
class BillingService {
@Autowired
private HttpClient client;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
// ...
}
}
Tóm lại, đây là ba kết quả có thể xảy ra của lệnh gọi HTTP này.
- OK: cuộc gọi đã thành công, tài khoản đã được tạo thành công.
- TRÌ HOÃN: Cuộc gọi đã được thực hiện nhưng mất quá nhiều thời gian để hoàn thành.
- LỖI. Cuộc gọi không thành công, có thể bạn đã gửi yêu cầu không tương thích hoặc hệ thống có thể không hoạt động.
Mô hình khả năng phục hồi tin nhắn
Chúng ta hãy xem xét kỹ hơn về giao tiếp không đồng bộ. Chương trình BillingService của chúng ta bây giờ có thể trông giống như thế này, giả sử chúng ta đang sử dụng Spring và RabbitMQ để nhắn tin. Để tạo tài khoản, bây giờ chúng tôi gửi tin nhắn đến nhà môi giới tin nhắn RabbitMQ, nơi có một số công nhân đang chờ tin nhắn mới. Những nhân viên này tạo hóa đơn PDF và gửi chúng cho những người dùng thích hợp.@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
// преобразует счет, например, в json и использует его How тело messages
rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
// ...
}
}
Các lỗi tiềm ẩn bây giờ có vẻ hơi khác một chút vì bạn không còn nhận được phản hồi OK hoặc LỖI ngay lập tức giống như bạn đã làm với kết nối HTTP đồng bộ. Thay vào đó, chúng ta có thể có ba tình huống tiềm ẩn có thể xảy ra sai sót, điều này có thể đặt ra các câu hỏi sau:
- Tin nhắn của tôi có được nhân viên gửi và sử dụng không? Hay là nó bị mất? (Người dùng không nhận được hóa đơn).
- Có phải tin nhắn của tôi chỉ được gửi một lần? Hoặc được gửi nhiều lần và chỉ được xử lý một lần? (Người dùng sẽ nhận được nhiều hóa đơn).
- Cấu hình: Từ "Tôi đã sử dụng đúng khóa/tên định tuyến cho trao đổi" đến "Nhà môi giới tin nhắn của tôi có được định cấu hình và duy trì đúng cách hay hàng đợi của nó đã đầy chưa?" (Người dùng không nhận được hóa đơn).
- Nếu bạn đang sử dụng các triển khai JMS như ActiveMQ, bạn có thể đánh đổi tốc độ để đảm bảo các cam kết hai pha (XA).
- Nếu bạn đang sử dụng RabbitMQ, trước tiên hãy đọc hướng dẫn này, sau đó suy nghĩ kỹ về các xác nhận, khả năng chịu lỗi và độ tin cậy của thông báo nói chung.
- Có lẽ ai đó rất thành thạo trong việc định cấu hình máy chủ Active hoặc RabbitMQ, đặc biệt là kết hợp với phân cụm và Docker (có ai không? ;))
Framework nào sẽ là giải pháp tốt nhất cho các dịch vụ vi mô Java?
Một mặt, bạn có thể cài đặt một tùy chọn rất phổ biến như Spring Boot . Nó giúp tạo tệp .jar rất dễ dàng, đi kèm với máy chủ web tích hợp như Tomcat hoặc Jetty và có thể chạy nhanh chóng ở mọi nơi. Lý tưởng để xây dựng các ứng dụng microservice. Gần đây, một cặp khung vi dịch vụ chuyên dụng, Kubernetes hoặc GraalVM , đã xuất hiện, một phần lấy cảm hứng từ lập trình phản ứng. Dưới đây là một số ứng cử viên thú vị hơn: Quarkus , Micronaut , Vert.x , Helidon . Cuối cùng, bạn sẽ phải tự mình lựa chọn, nhưng chúng tôi có thể đưa ra cho bạn một số đề xuất có thể không hoàn toàn tiêu chuẩn: Ngoại trừ Spring Boot, tất cả các khung vi dịch vụ thường được tiếp thị với tốc độ cực kỳ nhanh, gần như khởi động ngay lập tức. , chiếm ít bộ nhớ và khả năng mở rộng đến vô tận. Các tài liệu tiếp thị thường có đồ họa ấn tượng thể hiện nền tảng bên cạnh Spring Boot khổng lồ hoặc với nhau. Về lý thuyết, điều này giúp các nhà phát triển hỗ trợ các dự án cũ, đôi khi phải mất vài phút để tải. Hoặc các nhà phát triển làm việc trên đám mây muốn khởi động/dừng số lượng bộ chứa vi mô mà họ cần trong vòng 50 mili giây. Tuy nhiên, vấn đề là thời gian khởi động và tái triển khai kim loại trần (nhân tạo) này hầu như không đóng góp vào thành công chung của dự án. Ít nhất chúng có ảnh hưởng ít hơn nhiều so với cơ sở hạ tầng khung mạnh mẽ, tài liệu mạnh mẽ, cộng đồng và kỹ năng phát triển mạnh mẽ. Vì vậy, tốt hơn nên nhìn nó theo cách này: Nếu cho đến nay:- Bạn để ORM của mình chạy tràn lan, tạo ra hàng trăm truy vấn cho quy trình công việc đơn giản.
- Bạn cần vô số gigabyte để chạy khối nguyên khối phức tạp vừa phải của mình.
- Bạn có quá nhiều mã và độ phức tạp cao đến mức ứng dụng của bạn phải mất vài phút để tải.
Thư viện nào phù hợp nhất cho lệnh gọi Java REST đồng bộ?
Về mặt kỹ thuật cấp thấp, bạn có thể sẽ sử dụng một trong các thư viện máy khách HTTP sau: HttpClient gốc của Java (kể từ Java 11), HttpClient của Apache hoặc OkHttp . Lưu ý rằng tôi nói "có thể" ở đây vì có các tùy chọn khác, từ máy khách JAX-RS cũ tốt đến máy khách WebSocket hiện đại . Trong mọi trường hợp, xu hướng là tạo ra một ứng dụng khách HTTP, tránh xa việc tự mình loay hoay với các cuộc gọi HTTP. Để làm điều này, bạn cần xem qua dự án OpenFeign và tài liệu của nó làm điểm khởi đầu để đọc thêm.Các nhà môi giới tốt nhất cho tin nhắn Java không đồng bộ là gì?
Rất có thể, bạn sẽ bắt gặp ActiveMQ (Classic hoặc Artemis) , RabbitMQ hoặc Kafka phổ biến .- ActiveMQ và RabbitMQ là các nhà môi giới tin nhắn truyền thống, chính thức. Chúng liên quan đến sự tương tác giữa “nhà môi giới thông minh” và “người dùng ngu ngốc”.
- Về mặt lịch sử, ActiveMQ có lợi ích là nội tuyến dễ dàng (để thử nghiệm), điều này có thể được giảm thiểu bằng cài đặt RabbitMQ/Docker/TestContainer.
- Kafka không thể được gọi là nhà môi giới “thông minh” truyền thống. Thay vào đó, nó là một kho lưu trữ tin nhắn (tệp nhật ký) tương đối "ngu ngốc" đòi hỏi người tiêu dùng thông minh phải xử lý.
Tôi có thể sử dụng những thư viện nào để kiểm tra vi dịch vụ?
Nó phụ thuộc vào ngăn xếp của bạn. Nếu bạn đã triển khai hệ sinh thái Spring, sẽ là điều khôn ngoan khi sử dụng các công cụ cụ thể của khung . Nếu JavaEE giống như Arquillian . Có thể đáng để xem qua Docker và thư viện Testcontainers thực sự tốt , đặc biệt giúp thiết lập cơ sở dữ liệu Oracle một cách dễ dàng và nhanh chóng cho các thử nghiệm tích hợp hoặc phát triển cục bộ. Để thử nghiệm mô phỏng toàn bộ máy chủ HTTP, hãy xem Wiremock . Để kiểm tra tính năng nhắn tin không đồng bộ, hãy thử triển khai ActiveMQ hoặc RabbitMQ, sau đó viết các bài kiểm tra bằng cách sử dụng Awaitility DSL . Ngoài ra, tất cả các công cụ thông thường của bạn đều được sử dụng - Junit , TestNG cho AssertJ và Mockito . Xin lưu ý rằng đây không phải là một danh sách đầy đủ. Nếu bạn không tìm thấy công cụ yêu thích của mình ở đây, vui lòng đăng nó trong phần bình luận.Làm cách nào để kích hoạt tính năng ghi nhật ký cho tất cả các vi dịch vụ Java?
Đăng nhập vào microservices là một chủ đề thú vị và khá phức tạp. Thay vì có một tệp nhật ký mà bạn có thể thao tác bằng lệnh less hoặc grep, giờ đây bạn có n tệp nhật ký và bạn muốn chúng không bị phân tán quá nhiều. Các tính năng của hệ sinh thái ghi nhật ký được mô tả rõ ràng trong bài viết này (bằng tiếng Anh). Hãy nhớ đọc nó và chú ý đến phần Ghi nhật ký tập trung từ góc độ microservices . Trong thực tế, bạn sẽ gặp nhiều cách tiếp cận khác nhau: Quản trị viên hệ thống viết một số tập lệnh nhất định thu thập và kết hợp các tệp nhật ký từ các máy chủ khác nhau thành một tệp nhật ký duy nhất và đặt chúng trên máy chủ FTP để tải xuống. Chạy kết hợp cat/grep/unig/sort trong các phiên SSH song song. Đây chính xác là những gì Amazon AWS thực hiện và bạn có thể cho người quản lý của mình biết. Sử dụng công cụ như Graylog hoặc ELK Stack (Elasticsearch, Logstash, Kibana)Làm cách nào để các vi dịch vụ của tôi tìm thấy nhau?
Cho đến thời điểm hiện tại, chúng ta vẫn giả định rằng các vi dịch vụ của chúng ta biết về nhau và biết IPS tương ứng. Hãy nói về cấu hình tĩnh. Vì vậy, khối ngân hàng của chúng tôi [ip = 192.168.200.1] biết rằng nó cần liên lạc với máy chủ rủi ro [ip = 192.168.200.2], được mã hóa cứng trong tệp thuộc tính. Tuy nhiên, bạn có thể làm mọi thứ năng động hơn:- Sử dụng máy chủ cấu hình dựa trên đám mây mà từ đó tất cả các vi dịch vụ lấy cấu hình của chúng thay vì triển khai các tệp application.properties trên các vi dịch vụ của chúng.
- Vì các phiên bản dịch vụ của bạn có thể thay đổi vị trí một cách linh hoạt, nên bạn nên xem xét các dịch vụ biết dịch vụ của bạn tồn tại ở đâu, IP của chúng là gì và cách định tuyến chúng.
- Bây giờ mọi thứ đều năng động, các vấn đề mới nảy sinh, chẳng hạn như việc tự động bầu chọn một người lãnh đạo: chẳng hạn, ai là người chủ động thực hiện một số nhiệm vụ nhất định để không xử lý chúng hai lần? Ai thay thế người lãnh đạo khi anh ta thất bại? Việc thay thế diễn ra dựa trên cơ sở nào?
Làm cách nào để tổ chức ủy quyền và xác thực bằng microservice Java?
Chủ đề này cũng xứng đáng là một câu chuyện riêng biệt. Một lần nữa, các tùy chọn bao gồm từ xác thực HTTPS cơ bản được mã hóa cứng với các khung bảo mật tùy chỉnh đến chạy cài đặt Oauth2 với máy chủ ủy quyền riêng.Làm cách nào để đảm bảo rằng tất cả môi trường của tôi trông giống nhau?
Điều đúng đối với việc triển khai không có vi dịch vụ cũng đúng đối với việc triển khai có vi dịch vụ. Hãy thử kết hợp Docker/Testcontainers và Scripting/Ansible.Không có câu hỏi: nói ngắn gọn về YAML
Hãy tạm dừng các thư viện và các vấn đề liên quan và xem nhanh Yaml. Trên thực tế, định dạng tệp này được sử dụng làm định dạng để “viết cấu hình dưới dạng mã”. Nó cũng được sử dụng bởi các công cụ đơn giản như Ansible và những công cụ khổng lồ như Kubernetes. Để trải nghiệm sự khó khăn của việc thụt lề YAML, hãy thử viết một tệp Ansible đơn giản và xem bạn phải chỉnh sửa tệp bao nhiêu trước khi nó hoạt động như mong đợi. Và điều này mặc dù định dạng này được hỗ trợ bởi tất cả các IDE chính! Sau đó, quay lại để đọc xong hướng dẫn này.Yaml:
- is:
- so
- great
Còn các giao dịch phân tán thì sao? Kiểm tra năng suất? Chủ đề khác?
Có lẽ một ngày nào đó, trong các phiên bản tương lai của sách hướng dẫn này. Bây giờ, chỉ vậy thôi. Ở lại với chúng tôi!Các vấn đề về khái niệm với microservice
Bên cạnh các vấn đề cụ thể của microservice trong Java, còn có những vấn đề khác, chẳng hạn như những vấn đề xuất hiện trong bất kỳ dự án microservice nào. Họ liên quan chủ yếu đến tổ chức, nhóm và quản lý.Frontend và Backend không khớp
Frontend và Backend không khớp nhau là một vấn đề rất phổ biến trong nhiều dự án microservice. Nó có nghĩa là gì? Chỉ có điều trong các khối nguyên khối cũ, các nhà phát triển giao diện web có một nguồn cụ thể để lấy dữ liệu. Trong các dự án microservice, các nhà phát triển front-end đột nhiên có n nguồn để lấy dữ liệu. Hãy tưởng tượng rằng bạn đang tạo một loại dự án vi dịch vụ IoT (Internet of Things) nào đó trong Java. Giả sử bạn quản lý máy trắc địa và lò công nghiệp trên khắp Châu Âu. Và những lò nướng này gửi cho bạn thông tin cập nhật thường xuyên về nhiệt độ của chúng và những thứ tương tự. Sớm hay muộn bạn có thể muốn tìm lò nướng trong giao diện người dùng quản trị viên, có thể sử dụng vi dịch vụ "tìm kiếm lò nướng". Tùy thuộc vào mức độ nghiêm ngặt mà các đối tác phụ trợ của bạn áp dụng luật thiết kế theo miền hoặc vi dịch vụ, vi dịch vụ “tìm lò” chỉ có thể trả về ID lò chứ không phải các dữ liệu khác như loại, kiểu máy hoặc vị trí. Để thực hiện việc này, các nhà phát triển giao diện người dùng sẽ cần thực hiện một hoặc n lệnh gọi bổ sung (tùy thuộc vào cách triển khai phân trang) trong vi dịch vụ “lấy dữ liệu lò” với ID mà họ nhận được từ vi dịch vụ đầu tiên. Và mặc dù đây chỉ là một ví dụ đơn giản, được lấy từ một dự án (!) có thật, nhưng nó thậm chí còn thể hiện một vấn đề sau: siêu thị đã trở nên cực kỳ phổ biến. Đó là bởi vì với họ bạn không cần phải đi đến 10 nơi khác nhau để mua rau, nước chanh, pizza đông lạnh và giấy vệ sinh. Thay vào đó, bạn đi đến một nơi, dễ dàng hơn và nhanh hơn. Điều tương tự cũng xảy ra với các nhà phát triển front-end và microservice.Kỳ vọng của ban quản lý
Ban quản lý có ấn tượng sai lầm rằng giờ đây họ cần thuê vô số nhà phát triển cho một dự án (tổng thể), vì giờ đây các nhà phát triển có thể làm việc hoàn toàn độc lập với nhau, mỗi người có một dịch vụ vi mô của riêng mình. Chỉ cần một chút công việc tích hợp ở giai đoạn cuối (ngay trước khi ra mắt). Trên thực tế, cách tiếp cận này cực kỳ có vấn đề. Trong các đoạn văn sau chúng tôi sẽ cố gắng giải thích lý do tại sao.“Mảnh nhỏ hơn” không bằng “mảnh tốt hơn”
Sẽ là một sai lầm lớn khi cho rằng mã được chia thành 20 phần nhất thiết sẽ có chất lượng cao hơn toàn bộ một phần. Ngay cả khi chúng tôi đánh giá chất lượng từ quan điểm kỹ thuật thuần túy, các dịch vụ riêng lẻ của chúng tôi vẫn có thể chạy 400 truy vấn Hibernate để chọn người dùng từ cơ sở dữ liệu, duyệt qua các lớp mã không được hỗ trợ. Một lần nữa, chúng ta quay lại câu nói của Simon Brown: nếu bạn không xây dựng các khối nguyên khối đúng cách, sẽ rất khó để xây dựng các dịch vụ vi mô phù hợp. Nói về khả năng chịu lỗi trong các dự án microservice thường là rất muộn. Nhiều đến mức đôi khi thật đáng sợ khi thấy microservices hoạt động như thế nào trong các dự án thực tế. Lý do cho điều này là các nhà phát triển Java không phải lúc nào cũng sẵn sàng nghiên cứu khả năng chịu lỗi, kết nối mạng và các chủ đề liên quan khác ở mức độ thích hợp. Bản thân các “mảnh” nhỏ hơn nhưng “các phần kỹ thuật” lại lớn hơn. Hãy tưởng tượng nhóm microservice của bạn được yêu cầu viết một microservice kỹ thuật để đăng nhập vào hệ thống cơ sở dữ liệu, đại loại như thế này:@Controller
class LoginController {
// ...
@PostMapping("/login")
public boolean login(String username, String password) {
User user = userDao.findByUserName(username);
if (user == null) {
// обработка варианта с несуществующим пользователем
return false;
}
if (!user.getPassword().equals(hashed(password))) {
// обработка неверного пароля
return false;
}
// 'Ю-ху, залогинorсь!';
// установите cookies, делайте, что угодно
return true;
}
}
Bây giờ nhóm của bạn có thể quyết định (và thậm chí có thể thuyết phục những người kinh doanh) rằng việc này quá đơn giản và nhàm chán, thay vì viết một dịch vụ đăng nhập, tốt hơn hết bạn nên viết một microservice UserStateChanged thực sự hữu ích mà không có bất kỳ yêu cầu kinh doanh thực tế và hữu hình nào. Và vì một số người hiện đang coi Java như một con khủng long, hãy viết microservice UserStateChanged của chúng ta bằng Erlang thời thượng. Và hãy thử sử dụng cây đỏ đen ở đâu đó, vì Steve Yegge đã viết rằng bạn phải hiểu rõ về chúng để đăng ký vào Google. Từ góc độ tích hợp, bảo trì và thiết kế tổng thể, điều này cũng tệ như viết các lớp mã spaghetti bên trong một khối duy nhất. Một ví dụ nhân tạo và thông thường? Điều này là đúng. Tuy nhiên, điều này có thể xảy ra trong thực tế.
Ít mảnh hơn - ít hiểu biết hơn
Sau đó, câu hỏi tự nhiên xuất hiện là hiểu toàn bộ hệ thống, các quy trình và quy trình công việc của nó, nhưng đồng thời, bạn, với tư cách là nhà phát triển, chỉ chịu trách nhiệm làm việc trên microservice riêng biệt của mình [95: login-101: updateUserProfile]. Nó phù hợp với đoạn trước, nhưng tùy thuộc vào tổ chức, mức độ tin cậy và giao tiếp của bạn, điều này có thể dẫn đến nhiều nhầm lẫn, nhún vai và đổ lỗi nếu có sự cố vô tình trong chuỗi vi dịch vụ. Và không có ai chịu trách nhiệm hoàn toàn về những gì đã xảy ra. Và đó hoàn toàn không phải là vấn đề thiếu trung thực. Trên thực tế, rất khó để kết nối các bộ phận khác nhau và hiểu được vị trí của chúng trong bức tranh tổng thể của dự án.Truyền thông và dịch vụ
Mức độ giao tiếp và dịch vụ rất khác nhau tùy thuộc vào quy mô của công ty. Tuy nhiên, mối quan hệ chung là hiển nhiên: càng nhiều thì càng có nhiều vấn đề.- Ai điều hành microservice #47?
- Có phải họ vừa triển khai một phiên bản microservice mới, không tương thích? Tài liệu này được ghi lại ở đâu?
- Tôi cần nói chuyện với ai để yêu cầu một tính năng mới?
- Ai sẽ hỗ trợ microservice đó ở Erlang sau khi người duy nhất biết ngôn ngữ này rời công ty?
- Tất cả các nhóm vi dịch vụ của chúng tôi không chỉ làm việc bằng các ngôn ngữ lập trình khác nhau mà còn ở các múi giờ khác nhau! Làm thế nào để chúng ta phối hợp tất cả điều này một cách chính xác?
GO TO FULL VERSION