Redis related implementation of shopping website


Main contents of this paper:

  • 1. Login cookie
  • 2. Shopping cart cookies
  • 3. Cache database rows
  • 4, test,

Essential knowledge

A WEB application is a server or Service that responds to a request sent by a WEB browser through HTTP.

The typical steps for a WEB server to respond to a request are as follows:

  • 1. The server parses the request sent by the client.

  • 2. The request is forwarded to a predefined handler

  • The processor may fetch data from the database.

  • 4. The processor renders the template based on the retrieved data (Rander).

  • 5. The processor returns the rendered content to the client as a response to the request.

The above shows how a typical Web server works. In this case, the Web request is stateless. The server itself does not remember any information about past requests, which makes it easy to replace a failed server.


Internet services use cookies to record our identity every time we log into them.

Cookies consist of small amounts of data that the site asks our browser to store and send back to the service each time it makes a request.

There are two common ways to store login information in cookies for login purposes:

  • Signed cookies typically store the user’s name, along with the user’S ID, when the user last logged in, and any other information the site finds useful.
  • Token cookie stores a string of random bytes in the cookie as a token, and the server can look up the owner of the token in the database based on the token.

Advantages and disadvantages of signed cookies and token cookies:

* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * | | | advantages to cookie type Faults | * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * | | signature All the information is stored in a cookie validation cookkie need | correctly handle signature is difficult, it's easy to forget | | | | * cookie | | can also contain additional information on data signature or forget to validate data signature, * | | | to the front was easy to | to | * security holes -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * | token | add information is very easy, Cookies are small in size. | need to store more information in the server, | | | | * cookie | mobile client and the slower the client can send the request faster | using relational database, Load the storage cost high | | | * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code

Because the site does not implement the requirement for signed cookies, token cookies are used to reference the entry in the relational database table that is responsible for storing the user’s login information. In addition to login information, it can also store information such as the user’s access duration and the number of products browsed in the database, which is conducive to better promoting products to users


(1) Login and cookie caching

/** * Reimplement login cookie with Redis, replacing the login cookie functionality currently implemented by relational databases * 1. A hash will be used to store login cookie token and the mapping to the login user. * 2, need to find the corresponding user according to the given token, and in the case of login, return the user ID. * /
public String checkToken(Jedis conn, String token) {
    //1, String token = uid.randomuuid ().toString();
    //2. Try to get and return the user corresponding to the token
    return conn.hget("login:", token);
}

Copy the code
/** * 1, each time the user browses the page, the application will update the user's information stored in the login hash, * 2, and add the user's token and current timestamp to the collection of records of the most recently logged user. * 3, if the user is browsing a commodity, the program will also add the commodity to record the user's recently browsed commodity in the ordered set, * 4, if the number of recorded commodities more than 25, to the ordered set pruning. * /
public void updateToken(Jedis conn, String token, String user, String item) {
    // get the current timestamp
    long timestamp = System.currentTimeMillis() / 1000;
    //2. Maintain the mapping between tokens and logged-in users.
    conn.hset("login:", token, user);
    //3. Record the time when the token last appeared
    conn.zadd("recent:", timestamp, token);
    if(item ! =null) {
        //4. Record the commodities browsed by users
        conn.zadd("viewed:" + token, timestamp, item);
        //5. Remove the old record and keep only 25 items that the user has browsed recently
        conn.zremrangeByRank("viewed:" + token, 0, -26);
        // add increment increment to score of member of ordered set key. By passing a negative value increment and subtracting score from the corresponding value,
        conn.zincrby("viewed:", -1, item); }}Copy the code
/** * The amount of memory required to store session data increases over time, so we need to periodically clean up old session data. The session cleanup program consists of a loop that checks the size of the ordered collection stored in the most recent login token each time it executes. * 2, if an ordered set of size exceeds the limit, the program will be removed from the ordered set up to 100 the old token, * 3, and removed from the record the user login information hash deleted token corresponding user information, * 4, and to store the users browse recently cleaned up in an ordered set of goods record. * 5. In contrast, if the number of tokens does not exceed the limit, the program sleeps for a second and then rechecks. * /
public class CleanSessionsThread extends Thread {
    private Jedis conn;
    private int limit = 10000;
    private boolean quit ;

    public CleanSessionsThread(int limit) {
        this.conn = new Jedis("localhost");
        this.conn.select(14);
        this.limit = limit;
    }

    public void quit(a) {
        quit = true;
    }

    public void run(a) {
        while(! quit) {//1, find the number of existing tokens.
            long size = conn.zcard("recent:");
            //2, the number of tokens does not exceed the limit, sleep for 1 second, and then re-check
            if (size <= limit) {
                try {
                    sleep(1000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
                continue;
            }

            long endIndex = Math.min(size - limit, 100);
            // get the ID of the token to be removed
            Set<String> tokenSet = conn.zrange("recent:".0, endIndex - 1);
            String[] tokens = tokenSet.toArray(new String[tokenSet.size()]);

            ArrayList<String> sessionKeys = new ArrayList<String>();
            for (String token : tokens) {
                //4. Build key names for the tokens to be deleted
                sessionKeys.add("viewed:" + token);
            }
            // remove the oldest token
            conn.del(sessionKeys.toArray(new String[sessionKeys.size()]));
            //6. Remove the user information corresponding to the deleted token
            conn.hdel("login:", tokens);
            //7. Remove the user's recent browsing record.
            conn.zrem("recent:", tokens); }}}Copy the code

(2) Use Redis to realize shopping cart

/** * Use cookies to implement shopping cart -- that is, store the entire shopping cart in cookies. * Advantage: No need to write to the database to implement shopping cart function. * disadvantage: the application needs to re-parse and verify the cookie to ensure that the cookie format is correct. And including items that can be purchased * has another disadvantage: since the browser sends cookies with each request, requests can be sent and processed more slowly if the shopping cart is large *. * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * 1, each user's shopping cart is a hash, store the mapping between commodities ID and order number. * 2. If the number of items ordered by the user is greater than 0, then the program will add the ID of the item and the number of items ordered by the user to the hash. * 3, if the user buys an item that already exists in the hash, then the new order quantity overwrites the existing one. * 4. Conversely, if a user orders an item with a quantity less than 0, the program removes that item from the hash. * 5. * /
public void addToCart(Jedis conn, String session, String item, int count) {
    if (count <= 0) {
        //1. Remove the specified item from the shopping cart
        conn.hdel("cart:" + session, item);
    } else {
        // add the specified item to the shopping cart
        conn.hset("cart:"+ session, item, String.valueOf(count)); }}Copy the code

5. We need to update the previous session cleanup function so that when it cleans up the session, it also deletes the user’s shopping cart corresponding to the old session.

Just one more line than CleanSessionsThread, the pseudocode is as follows:

long endIndex = Math.min(size - limit, 100);
// get the ID of the token to be removed
Set<String> tokenSet = conn.zrange("recent:".0, endIndex - 1);
String[] tokens = tokenSet.toArray(new String[tokenSet.size()]);

ArrayList<String> sessionKeys = new ArrayList<String>();
for (String token : tokens) {
    //4. Build key names for the tokens to be deleted
    sessionKeys.add("viewed:" + token);

    // These two lines are added to remove the shopping cart corresponding to the old session.
    sessionKeys.add("cart:" + sess);
}
// remove the oldest token
conn.del(sessionKeys.toArray(new String[sessionKeys.size()]));
//6. Remove the user information corresponding to the deleted token
conn.hdel("login:", tokens);
//7. Remove the user's recent browsing record.
conn.zrem("recent:", tokens);
Copy the code

(3) Data row cache

/** * To handle the heavy workload caused by the promotion, we need to cache the data rows. We need to do this by: * 1. We need to write a continuously running daemon to cache the data rows in Redis, and update them periodically. * 2. The cache function encodes the data rows into JSON dictionaries and stores them in Redis dictionaries. The names of the data columns are mapped to JSON dictionaries, * and the values of the data rows are mapped to JSON dictionary values. * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * programs use two ordered set to record should be when the cache is updated: * 1, the first ordered set is called, its member is the ID of the data row, and the branch is a time stamp, * this time stamp records when the specified data row should be cached in Redis * 2, the second ordered set is delayed, its member is also the ID of the data row. * The score records how many seconds the cache of the specified data row needs to be updated. * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * line in order to make the cache function cached data on a regular basis, The program first needs to add the hangID and the given delay value to the delay-ordered set, * and then add the row ID and the currently specified timestamp to the scheduled ordered set. * /
public void scheduleRowCache(Jedis conn, String rowId, int delay) {
    //1. Set the delay value of the data row
    conn.zadd("delay:", delay, rowId);
    //2
    conn.zadd("schedule:", System.currentTimeMillis() / 1000, rowId);
}
Copy the code

/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** * * 2, if the data is recorded is the remaining number of sale promotion commodity, and participation in promotional activities users much more special, so best updated once every few seconds line data cache: *, on the other hand, if the data does not change often, out of stock or commodity is acceptable, then you can update every few minutes a cache. * /
public class CacheRowsThread
        extends Thread {
    private Jedis conn;
    private boolean quit;

    public CacheRowsThread(a) {
        this.conn = new Jedis("localhost");
        this.conn.select(14);
    }

    public void quit(a) {
        quit = true;
    }

    public void run(a) {
        Gson gson = new Gson();
        while(! quit) {//1, try to get the next row to be cached and the schedule timestamp of that row, return a list of 0 or 1 tuples
            Set<Tuple> range = conn.zrangeWithScores("schedule:".0.0);
            Tuple next = range.size() > 0 ? range.iterator().next() : null;
            long now = System.currentTimeMillis() / 1000;
            //2, no rows need to be cached temporarily, sleep 50 ms.
            if (next == null || next.getScore() > now) {
                try {
                    sleep(50);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
                continue;
            }
            //3. Obtain the delay time of the next scheduling in advance.
            String rowId = next.getElement();
            double delay = conn.zscore("delay:", rowId);
            if (delay <= 0) {
                //4, do not cache this line, remove it from the cache
                conn.zrem("delay:", rowId);
                conn.zrem("schedule:", rowId);
                conn.del("inv:" + rowId);
                continue;
            }
            //5
            Inventory row = Inventory.get(rowId);
            //6. Update the scheduling time and set the cache value.
            conn.zadd("schedule:", now + delay, rowId);
            conn.set("inv:"+ rowId, gson.toJson(row)); }}}Copy the code

test

PS: Need to make up for English!! Need all you can download the official translation Java version here

public class Chapter02 {
    public static final void main(String[] args)
            throws InterruptedException {
            new Chapter02().run();

    }

    public void run(a)
            throws InterruptedException {
        Jedis conn = new Jedis("localhost");
        conn.select(14);

        testLoginCookies(conn);
        testShopppingCartCookies(conn);
        testCacheRows(conn);
        testCacheRequest(conn);
    }

    public void testLoginCookies(Jedis conn)
            throws InterruptedException {
        System.out.println("\n----- testLoginCookies -----");
        String token = UUID.randomUUID().toString();

        updateToken(conn, token, "username"."itemX");
        System.out.println("We just logged-in/updated token: " + token);
        System.out.println("For user: 'username'");
        System.out.println();

        System.out.println("What username do we get when we look-up that token?");
        String r = checkToken(conn, token);
        System.out.println(r);
        System.out.println();
        assertr ! =null;

        System.out.println("Let's drop the maximum number of cookies to 0 to clean them out");
        System.out.println("We will start a thread to do the cleaning, while we stop it later");

        CleanSessionsThread thread = new CleanSessionsThread(0);
        thread.start();
        Thread.sleep(1000);
        thread.quit();
        Thread.sleep(2000);
        if (thread.isAlive()) {
            throw new RuntimeException("The clean sessions thread is still alive? ! ?");
        }

        long s = conn.hlen("login:");
        System.out.println("The current number of sessions still available is: " + s);
        assert s == 0;
    }

    public void testShopppingCartCookies(Jedis conn)
            throws InterruptedException {
        System.out.println("\n----- testShopppingCartCookies -----");
        String token = UUID.randomUUID().toString();

        System.out.println("We'll refresh our session...");
        updateToken(conn, token, "username"."itemX");
        System.out.println("And add an item to the shopping cart");
        addToCart(conn, token, "itemY".3);
        Map<String, String> r = conn.hgetAll("cart:" + token);
        System.out.println("Our shopping cart currently has:");
        for (Map.Entry<String, String> entry : r.entrySet()) {
            System.out.println("" + entry.getKey() + ":" + entry.getValue());
        }
        System.out.println();

        assert r.size() >= 1;

        System.out.println("Let's clean out our sessions and carts");
        CleanFullSessionsThread thread = new CleanFullSessionsThread(0);
        thread.start();
        Thread.sleep(1000);
        thread.quit();
        Thread.sleep(2000);
        if (thread.isAlive()) {
            throw new RuntimeException("The clean sessions thread is still alive? ! ?");
        }

        r = conn.hgetAll("cart:" + token);
        System.out.println("Our shopping cart now contains:");
        for (Map.Entry<String, String> entry : r.entrySet()) {
            System.out.println("" + entry.getKey() + ":" + entry.getValue());
        }
        assert r.size() == 0;
    }

    public void testCacheRows(Jedis conn)
            throws InterruptedException {
        System.out.println("\n----- testCacheRows -----");
        System.out.println("First, let's schedule caching of itemX every 5 seconds");
        scheduleRowCache(conn, "itemX".5);
        System.out.println("Our schedule looks like:");
        Set<Tuple> s = conn.zrangeWithScores("schedule:".0, -1);
        for (Tuple tuple : s) {
            System.out.println("" + tuple.getElement() + "," + tuple.getScore());
        }
        asserts.size() ! =0;

        System.out.println("We'll start a caching thread that will cache the data...");

        CacheRowsThread thread = new CacheRowsThread();
        thread.start();

        Thread.sleep(1000);
        System.out.println("Our cached data looks like:");
        String r = conn.get("inv:itemX");
        System.out.println(r);
        assertr ! =null;
        System.out.println();

        System.out.println("We'll check again in 5 seconds...");
        Thread.sleep(5000);
        System.out.println("Notice that the data has changed...");
        String r2 = conn.get("inv:itemX");
        System.out.println(r2);
        System.out.println();
        assertr2 ! =null;
        assert! r.equals(r2); System.out.println("Let's force un-caching");
        scheduleRowCache(conn, "itemX", -1);
        Thread.sleep(1000);
        r = conn.get("inv:itemX");
        System.out.println("The cache was cleared? " + (r == null));
        assert r == null;

        thread.quit();
        Thread.sleep(2000);
        if (thread.isAlive()) {
            throw new RuntimeException("The database caching thread is still alive? ! ?"); }}}Copy the code

reference

Redis of actual combat

Redis combat related code, currently have Java, JS, Node, Python

2.Redis command reference

The code address

Github.com/guoxiaoxu/r…

Afterword.

If you have the patience to read this far, allow me to clarify:

  • 1. Due to my limited technical ability, I didn’t clear the other two sections, which I was pondering over. Fill in later.

  • Reading books written by foreigners is like reading stories. The more you read, the more wonderful you are. I don’t know if you feel that way?

  • The more I learn, the more I find that I need to add too much knowledge. Give me strength. Welcome to “like”.

  • 4, if you want to have a deeper understanding, it is suggested to see “Redis combat” and then look back at this article, the effect will be better