Sử dụng Memcached với Memcached Java Client

Chào mừng các bạn đã đến với thachleblog, ở bài trước, mình đã giới thiệu sơ lược về Memcached đồng thời hướng dẫn cách cài đặt cùng 1 vài thao tác cơ bản với memcached trên terminal. Ở bài này, mình sẽ viết một demo đơn giản để các bạn làm quen với Java memcached Client. Nào chúng ta cùng bắt đầu nhé.

Các bạn có bao giờ gặp tính năng giới hạn truy cập khi thao tác quá nhanh trên một ứng dụng, hoặc nhận thông báo bạn đã truy cập quá số lần cho phép? Để làm tính năng giới hạn truy cập này thì có nhiều cách, chúng ta có thể lưu số lần truy cập của user vào database hoặc thậm chí là 1 biến static. Nhưng cách đơn giản là chúng ta có thể sử dụng memcached.

Có vài lý do để chúng ta chọn memcached cho tính năng này đó là truy cập trên memecached sẽ nhanh hơn so với sử dụng database đồng thời memcached hỗ trợ các tính năng như là tự động expired sau một khoảng thời gian (tự động mở lại cho user truy cập), tăng giá trị cho value… Thêm một lý do khác để chọn memcached thay vì các loại cache local là vì đối với ứng dụng được deploy trên nhiều instance, sử dụng cache local chúng ta sẽ phải handle thêm các bước đồng bộ dữ liệu cache trên các intance. Làm mệt mà dễ có bug, hehe.

Để kết nối memcached bằng Java chúng ta có thể sử dụng các libs spymemcached, xmemcached hoặc gwhalin memcached client… Ở demo này, mình sẽ sử dụng spymemcached.

Việc đầu tiên là tải về spymemcached. Và tất nhiên là đã có memcached. Đầu tiên, mình cần init connect tới memcached (tương tự như database vậy). Với spymemcached chúng ta có thể thao tác như sau:

  1. MemcachedClient memcachedClient = MemcachedClient(new InetSocketAddress(HOST, PORT));

Ở đây, mình thiết lập cho thời gian expire cache là 60s. Mình cần giới hạn user chỉ được phép truy cập tính năng này tối đa là 5 lần 1 phút. Do đó, mỗi lần user truy cập, mình sẽ kiểm tra thông tin username (thông tin duy nhất để phân biệt user, nên là uid) trong cache.

Nếu là lần đầu tiên thì sẽ cho phép truy cập, đồng thời ghi vào cache với thông tinh key – value là username – 1. Tương tự các lần tiếp theo mình sẽ cho phép truy cập và tăng số lần truy cập lên. Cuối cùng, nếu user đó truy cập quá số lần cho phép trong 60s thì sẽ bị chặn. Sau thời gian 60s, dữ liệu sẽ bị expire và user sẽ có truy cập như lúc đầu.

Code demo:

 

package memcached;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import net.spy.memcached.MemcachedClient;
 
/**
 *
 * @author thachlp
 */
public class UserLimitAccess {
 
    private static final int LIMIT_PER_MINUTE = 5;
    private static final int TIME_EXPIRED = 60;
    private static final String HOST = "127.0.0.1";
    private static final int PORT = 11211;
 
    private static MemcachedClient memcachedClient;
 
    public static void initMemcached() {
        try {
            memcachedClient = new MemcachedClient(new InetSocketAddress(HOST, PORT));
            System.out.println("Connection to server sucessful.");
        } catch (IOException ex) {
            System.out.println("Can not connect to memcache server: " + ex.getMessage());
        }
    }
 
    public static boolean isLimitedAccess(String userName) {
        Object accessCount = memcachedClient.get(userName);
        // the fist time user login
        if (accessCount == null) {
            memcachedClient.add(userName, TIME_EXPIRED, "1");
            return true;
        }
 
        int numberAccess = Integer.parseInt((String)accessCount);
        // user access over the limit access
        if (numberAccess > LIMIT_PER_MINUTE) {
            return false;
        } else {
            memcachedClient.incr(userName, 1);
            return true;
        }
    }
 
    public static void main(String[] args) {
        initMemcached();
        // can access
        if (isLimitedAccess("thachle")) {
            System.out.println("Welcome to thachle blog");
        } else {
            System.out.println("You have accessed over limit per minute, please get back after 1 minute");
 
        }
        System.exit(0);
    }
 
}

Ứng dụng đã test lần đầu và thấy ok ^.^.

Vừa rồi là phần demo của mình về cách sử dụng memcached với Java. Quá đơn giản phải không. Tất nhiên trong các ứng dụng thực tế chúng ta cần thêm các config về pool… để tăng tốc độ cho memecached.

Mọi góp ý về cách trình bày, code hoặc bất cứ thứ gì có thể comment bên dưới, mình sẽ theo dõi nhé. Cảm ơn các bạn đã đọc và hẹn gặp lại các bạn ở các bài viết sau.

Sử dụng Map hiệu quả trong Java

Chào mừng các bạn đã quay trở lại với thachleblog. Ở các bài trước, mình đã có chia sẻ về ArrayList, LinkedList, Stack và Queue. Bài viết hôm nay, mình sẽ giới thiệu về Map, một trong những cấu trúc dữ liệu được sử dụng phổ biến và hiệu quả nhất trong Java. Hy vọng qua bài viết này các bạn sẽ hiểu thêm về Map và có thể sử dụng Map hiệu quả hơn. Nào, chúng ta cùng bắt đầu nhé.

Giới thiệu

Map là interface thiết kế để lưu trữ cấu trúc dữ liệu theo dạng (key, value). Cả key và value đều là object (không chấp nhận kiểu dữ liệu primitives). Mỗi key sẽ tương ứng với duy nhất 1 value. Các implementation của Map bao gồm HashMap, HashTable, LinkedHashMap, ConcurrentHashMap, TreeMap… Bài viết hôm nay mình sẽ chủ yếu đi sâu vào cấu trúc HashMap.

Cấu trúc

Cấu trúc dữ liệu của Map dựa vào Array và LinkedList. Mỗi value được lưu trữ trong array dựa vào hash value. Khi một phần tử thêm vào có cùng hash value, phần tử này sẽ chiếm chỗ phần tử cũ và phần tử cũ sẽ được linked đến phần tử mới này.

Cơ chế put and get

Khi chúng ta thêm một phần tử vào Map, phương thức hash code sẽ “hash” key và tạo ra một giá trị (hash code). Giá trị này sẽ dùng để xác định bucket để lưu trữ. Tương tự, method equal() được sử dụng để xác định chính xác vị trí lưu trữ chính xác của phần tử. Do key là thuộc tính duy nhất nên khi thêm phần tử có key trùng với phần tử cũ. Value của key mới sẽ ghi đè lên value key cũ. (Xem thêm bài về hashCode() và equal() tại đây)

Lưu ý hash map chấp nhận key và value null.

Tương tự, khi get một phần tử, phương thức hashCode() và equal() cũng được sử dụng để xác định vị trí của phần tử trong Map.

Cấu trúc lưu trữ của HashMap để put và get

Cấu trúc lưu trữ của HashMap để put và get

Độ phức tạp của phương thức put() và get() là O(1).

Một vài API thường dùng

  • containsKey(Object key): trả và true nếu Map có chứa key, ngược lại trả về false

  • get (Object key): trả về giá trị của key nếu key tồn tại, ngược lại trả về null

  • put (K key, V value): thêm vào một phần tử có key và value tương ứng

Duyệt map

Các bạn có thể xem các cách duyệt Map tại đây, lưu ý là duyệt, các phần tử của Hash Map sẽ không đúng thứ tự như khi thêm vào. Nếu cần Map khi duyệt các phần tử đúng thứ tự khi thêm vào các bạn có thể sử dụng LinkedHashMap.

Sử dụng

Yêu cầu

Giả sử mình cần lưu trữ danh sách danh bạ với tên tương ứng với số điện thoại. Khi nhập tên sẽ in ra số điện thoại tương ứng. Ngược lại in là “Không tìm thấy”.

Input:

3
sam 99912222
tom 11122222
harry 12299933
sam
edward
harry

Output:

sam=99912222
“Không tìm thấy”
harry=12299933

Giải thuật

– Mình sẽ sử dụng method put để thêm tên – số điện thoại vào Map. Sau đó có thể sử dụng:

+ containsKey(String tên) để kiểm tra số điện thoại có tồn tại hay không, nếu có, dùng get(String tên) để in ra, ngược lại in “Không tìm thấy”.

Hoặc

+ get(String tên) để lấy ra số điện thoại. Kiểm tra kết quả nếu khác null in ra giá trị, ngược lại in “Không tìm thấy”

import java.util.Scanner;
 
/**
 *
 * @author thachlp
 */
import java.util.*;
 
class Solution{
    public static void main(String []argh){
        Map<String, String> phoneAddress = new HashMap<>();
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        for (int i = 0; i < n; i++) {
            String name = in.next();
            int phone = in.next();
            phoneAddress.put(name, phone);
 
        }
        while (in.hasNext()) {
            String s = in.next();
            //cach 1
            if(phoneAddress.containsKey(s)){
                System.out.printf("%s=%d\n", s, phoneAddress.get(s));
            } else {
                System.out.println("Không tìm thấy");
            }
 
            //cach 2            
            String number = phoneAddress.get(s);
            if (number != null) {
                System.out.printf("%s=%d\n", s, phoneAddress.get(s));
            } else {
                System.out.println("Không tìm thấy");
            }
        }
        in.close();
    }
}

 

Kết quả 2 cách là như nhau. Tuy nhiên, lưu ý về performence.

Ở cách 1, Map thực hiện 2 thao tác là check (containsKey) và get, nên sẽ tốn resource hơn so với cách 2 là get và so sánh String. Với số lượng phần tử lên đến 100k phần tử, tốc độ chênh lệch rõ.

Dù vậy, cũng cần lưu ý là với giá trị là null, kết quả của cách 2 sẽ là “Không tìm thấy”. Cách 1 sẽ trả về kết quả là true.

Vừa rồi là phần giới thiệu về Map và một vài lưu ý. Về Map thì cũng có rất nhiều thứ để nói. Tạm thời hôm nay mình nói nhiêu đây thôi. Hôm nào có vấn đề gì hay ho mình sẽ nói tiếp. Hẹn gặp lại các bạn ở các bài viết sau nhé. Mọi góp ý vui lòng comment bên dưới, mình sẽ follow. Thanks.

Xem thêm:

https://docs.oracle.com/javase/7/docs/api/java/util/Map.html