Lời nói đầu
Có nhiều cách để triển khai tính bình thường của giao diện trong SpringBoot, một trong những cách được sử dụng phổ biến nhất là phương thức token + redis.
Dưới đây tôi sẽ sử dụng mã trường hợp để giúp bạn hiểu logic triển khai này.
nguyên tắc
Giao diện người dùng nhận được máy chủ getToken() -> giao diện người dùng bắt đầu một yêu cầu -> đưa mã thông báo vào tiêu đề -> máy chủ xác minh xem mã thông báo được gửi từ giao diện người dùng có nhất quán với mã thông báo trong redis -> nếu chúng đúng như vậy nhất quán, xóa mã thông báo -> thực thi logic nghiệp vụ.
Trường hợp
1. Sử dụng Token + Redis
Mã lõi như sau:
@RestController public class IdempotentController { @Autowired Private RedisTemplate redisTemplate; /** * Gửi giao diện, bạn cần mang theo các tham số mã thông báo hợp lệ*/ @PostMapping("/submit") public String submit(@RequestParam("token " ) Mã thông báo chuỗi) { // Kiểm tra xem Mã thông báo có hợp lệ hay không if (!isValidToken(token)) { return "Mã thông báo không hợp lệ"; } // Logic xử lý giao diện cụ thể, triển khai logic nghiệp vụ của bạn tại đây // Xóa Mã thông báo sau khi sử dụng deleteToken(token); return "Success" } /** * Kiểm tra xem Mã thông báo có hợp lệ không*/ boolean riêng isValidToken(String token) { // Kiểm tra xem Token có tồn tại trong Redis hay không return redisTemplate.hasKey(token); } /** * Xóa Token */ private void deleteToken(String token) { // Xóa mã thông báo khỏi Redis redisTemplate.delete(token); } /** * Giao diện tạo mã thông báo, được sử dụng để lấy mã thông báo duy nhất */ @GetMapping("/generateToken") public String generateToken() { // Tạo mã thông báo chuỗi mã thông báo duy nhất = UUID.randomUUID().toString(); // Lưu Token vào Redis và đặt thời gian hết hạn (ví dụ: 10 phút) redisTemplate.opsForValue().set(token, "true", Duration.ofMinutes(10)); trả về mã thông báo;
Đoạn mã trên nhất quán với nguyên tắc được mô tả trước đây, nhưng thực tế có một vấn đề, đó là vẫn sẽ có vấn đề về tính bình thường trong các tình huống có tính tương tranh cao. Điều này là do tính nguyên tử của redis không được sử dụng đầy đủ.
2. Tận dụng tính nguyên tử của Redis
Tiếp theo, sử dụng các hoạt động nguyên tử của Redis, chẳng hạn như SETNX và EXPIRE, để đạt được khả năng kiểm soát bình thường đáng tin cậy hơn.
Hãy tối ưu hóa mã như sau:
@RestController public class IdempotentController { @Autowired Private RedisTemplate redisTemplate; /** * Gửi giao diện, bạn cần mang theo các tham số mã thông báo hợp lệ*/ @PostMapping("/submit") public String submit(@RequestParam("token " ) Mã thông báo chuỗi) { // Sử dụng lệnh SETNX để cố gắng lưu Mã thông báo vào Redis. Nếu trả về 1, cài đặt thành công, cho biết rằng nó được gửi lần đầu tiên; nếu không, 0 được trả về, cho biết việc gửi lặp lại thành công = redisTemplate.opsForValue(. ).setIfAbsent(token, "true", Duration .ofMinutes(10)); if (success == null || !success) { return "Gửi trùng lặp" } thử { // Logic xử lý giao diện cụ thể, triển khai logic nghiệp vụ của bạn tại đây return "Thành công"; } cuối cùng { // Sử dụng lệnh DEL để xóa Token redisTemplate.delete(token);
Như bạn có thể thấy, chúng tôi đã sử dụng phương thức setIfAbsent để cố lưu Token vào Redis và đặt thời gian hết hạn (ví dụ: 10 phút). Nếu cài đặt thành công, logic xử lý giao diện cụ thể sẽ được thực thi và Mã thông báo sẽ tự động bị xóa sau khi quá trình xử lý hoàn tất. Nếu cài đặt không thành công, điều đó có nghĩa là Mã thông báo đã tồn tại, nghĩa là việc gửi được lặp lại và thông báo lỗi được trả về trực tiếp.
Lưu ý rằng thao tác xóa Token trong đoạn mã trên được thực thi trong khối cuối cùng bất kể logic xử lý giao diện có thành công hay không, Token sẽ được đảm bảo xóa để tránh các trường hợp ngoại lệ có thể khiến Token bị xóa. không chính xác.
Bằng cách sử dụng các hoạt động nguyên tử của Redis, chúng tôi có thể triển khai tính bình thường của giao diện một cách đáng tin cậy hơn và mang lại hiệu suất cũng như độ chính xác tốt hơn trong các tình huống có tính tương tranh cao.
Tuy nhiên, trong các kịch bản có tính đồng thời cao, điều này thực sự vẫn có vấn đề và vẫn có khả năng xảy ra vấn đề về sự bình thường.
Điều này là do, trong trường hợp có tính tương tranh cao, hai yêu cầu có thể nhận được mã thông báo từ redis cùng một lúc và cả hai đều có thể được xác minh thành công trên máy chủ, cuối cùng sẽ phá hủy tính bình thường.
Vì vậy, vẫn còn chỗ để tối ưu hóa.
3. Kết hợp với tập lệnh Lua
Bạn có thể sử dụng tập lệnh Lua với các thao tác nguyên tử của Redis để đạt được khả năng kiểm soát bình thường đáng tin cậy hơn.
Code hoàn chỉnh sau khi tối ưu hóa như sau:
@RestController public class IdempotentController { @Autowired Private RedisTemplate redisTemplate; /** * Gửi giao diện, bạn cần mang theo các tham số mã thông báo hợp lệ*/ @PostMapping("/submit") public String submit(@RequestHeader("token " ) Mã thông báo chuỗi) { if (StringUtils.isBlank(token)) { return "Thiếu mã thông báo"; } DefaultRedisScript script = new DefaultRedisScript<>(LUA_SCRIPT, Boolean.class); // Sử dụng tập lệnh Lua để thực hiện các phép toán nguyên tử Boolean thành công = redisTemplate.execute(script, Collections.singletonList(token), "true", "600" ); if (thành công == null || !success) { return "Gửi trùng lặp" } thử { // Logic xử lý giao diện cụ thể, triển khai logic nghiệp vụ của bạn tại đây return "Thành công" } cuối cùng { // Sử dụng lệnh DEL để xóa Token redisTemplate.delete(token); } } /** * Tạo giao diện Token để nhận Mã thông báo duy nhất */ @GetMapping("/generateToken") public String generateToken() { // Tạo mã thông báo chuỗi mã thông báo duy nhất = UUID.randomUUID().toString(); Lưu mã thông báo vào Redis và đặt thời gian hết hạn (ví dụ: 10 phút) redisTemplate.opsForValue().set(token, "true", Duration.ofMinutes(10)); return token; LUA_SCRIPT = "if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 thì\n" + " redis.call('EXPIRE', KEYS[1], ARGV[2])\n" + " return true\n" + "else\n" + " return false\n" + "end"; }
Trong số đó, ý nghĩa của chữ Lua này như sau:
-
Đầu tiên, biến chuỗi cuối cùng riêng tư LUA_SCRIPT được xác định để lưu trữ nội dung của tập lệnh Lua.
-
Các lệnh Redis và tham chiếu tham số được sử dụng trong tập lệnh Lua. Đây là lời giải thích từng dòng một:
-
if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 thì: Sử dụng lệnh SETNX của Redis để đặt giá trị thành ARGV[1] trong khóa KEYS[1] (ARGV là một mảng tham số). Nếu giá trị trả về SETNX là 1 (biểu thị cài đặt thành công), khối mã sau sẽ được thực thi.
-
redis.call('EXPIRE', KEYS[1], ARGV[2]): Sử dụng lệnh EXPIRE của Redis để đặt thời gian hết hạn trên phím KEYS[1] thành ARGV[2] giây.
-
return true: Trả về giá trị Boolean true cho người gọi, cho biết rằng cả cài đặt và cài đặt thời gian hết hạn đều thành công.
-
khác: Nếu giá trị trả về SETNX không phải là 1, hãy thực thi khối mã sau.
-
return false: Trả về giá trị Boolean là false cho người gọi, cho biết cài đặt không thành công.
Do đó, mục đích của tập lệnh Lua này là đặt cặp khóa-giá trị trong Redis và đặt thời gian hết hạn cho khóa. Nếu khóa đã tồn tại, tập lệnh sẽ trả về false để cho biết rằng quá trình thiết lập không thành công; nếu khóa không tồn tại, tập lệnh sẽ trả về true để cho biết rằng cả cài đặt thiết lập và thời gian hết hạn đều thành công.
Tóm tắt
Để giải quyết vấn đề bình thường hóa giao diện, cơ chế mã thông báo được sử dụng rộng rãi nhất và cũng là giải pháp có hiệu suất tốt hơn.
Trên thực tế, có một giải pháp tương đối đơn giản, đó là sử dụng khóa phân tán Redission.
Giải pháp này yêu cầu rất ít mã hóa và có thể đạt được hiệu quả, nhưng việc khóa sẽ gây ra tổn thất, do đó hiệu suất tổng thể không tốt bằng giải pháp trong bài viết này. Tuy nhiên, vì cách đóng gói tốt và mã hóa đơn giản nên nó cũng rất hữu ích. phương pháp phổ biến trong các doanh nghiệp.
Trong các bài viết trước đây của tôi, có những bài viết về cách sử dụng Redisson với các chú thích tùy chỉnh để tránh trùng lặp. Nếu bạn quan tâm, bạn có thể đọc nó.
Mặc dù việc triển khai Redisson đơn giản nhưng nó không có lợi cho việc học. Trong giai đoạn học, tôi không khuyên bạn nên bắt đầu trực tiếp với Redisson.
Được rồi, bạn đã học được kiến thức hôm nay chưa?
Cuối cùng, bài viết này về SpringBoot+Redis+Token giải quyết vấn đề bình thường giao diện kết thúc ở đây. Nếu bạn muốn biết thêm về SpringBoot+Redis+Token giải quyết vấn đề bình thường giao diện, vui lòng tìm kiếm bài viết CFSDN hoặc duyệt các bài viết liên quan. bạn sẽ ủng hộ blog của tôi trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!