Redis实战-chapter2

事先说明:文章所使用的代码均为书籍赠送的代码,非本人写的。只是在上面做了点注解与解释

package redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import redis.clients.jedis.ZParams;

import java.nio.channels.SelectionKey;
import java.security.IdentityScope;
import java.util.*;
import java.net.MalformedURLException;
import java.net.URL;
import javax.net.ssl.SSLException;
import javax.print.DocFlavor.STRING;
import javax.security.auth.callback.Callback;

import org.omg.CORBA.PRIVATE_MEMBER;

import com.google.gson.Gson;

public class chapter2 {

	public static void main(String[] args) throws InterruptedException{
		// TODO Auto-generated method stub
		new chapter2().run();
	}
	public void run()throws InterruptedException
	{
		Jedis conn = new Jedis("localhost");
		conn.select(15);
		testLoginCookies(conn);
		testShopppingCartCookies(conn);
		testCacheRows(conn);
        testCacheRequest(conn);
	}
	
	public void testShopppingCartCookies(Jedis conn)throws InterruptedException
	{
		System.out.println("\n2.0----- testShopppingCartCookies -----");
        String token = UUID.randomUUID().toString();

        System.out.println("2.1We'll refresh our session...");
        updateToken(conn, token, "username", "itemX");
        System.out.println("2.2And add an item to the shopping cart");
        addToCart(conn, token, "itemY", 3);
        //hash通过cart:UUID获得购买的商品以及数量(item,count)
        Map r = conn.hgetAll("cart:" + token);
        System.out.println("2.3Our shopping cart currently has:");
        for (Map.Entry entry : r.entrySet()){
            System.out.println("  " + entry.getKey() + ": " + entry.getValue());
        }
        System.out.println();
        assert r.size() >= 1;
        System.out.println("2.4Let'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?!?");
        }
        //hash通过cart+UUID获得用户购物车物品
        r = conn.hgetAll("cart:" + token);
        System.out.println("2.5Our shopping cart now contains:");
        for (Map.Entry entry : r.entrySet()){
            System.out.println("  " + entry.getKey() + ": " + entry.getValue());
        }
        assert r.size() == 0;
	}
	
	public void testCacheRows(Jedis conn)throws InterruptedException
	{
		System.out.println("\n3.0----- testCacheRows -----");
        System.out.println("3.1First, let's schedule caching of itemX every 5 seconds");
      //添加更新时间>0的商品,他将会保留在需要更新目录中
        scheduleRowCache(conn, "itemX", 5);
        System.out.println("3.2Our schedule looks like:");
        //zset通过分值升序将string返回至Set中
        Set s = conn.zrangeWithScores("schedule:", 0, -1);
        for (Tuple tuple : s){
        	//element商品名字,score当前时间戳
            System.out.println("  " + tuple.getElement() + ", " + tuple.getScore());
        }
        assert s.size() != 0;

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

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

        System.out.println("3.5We'll check again in 5 seconds...");
        Thread.sleep(5000);
        System.out.println("3.6Notice that the data has changed...");
        String r2 = conn.get("inv:itemX");
        System.out.println(r2);
        System.out.println();
        assert r2 != null;
        assert !r.equals(r2);

        System.out.println("3.7Let's force un-caching");
        //添加更新时间<=0的商品,它会被自动清除
        
        scheduleRowCache(conn, "itemX", -1);
        Thread.sleep(1000);
        r = conn.get("inv:itemX");
        System.out.println("3.8The 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?!?");
        }
	}
	
	public void testCacheRequest(Jedis conn)
	{
		System.out.println("\n4.0----- testCacheRequest -----");
        String token = UUID.randomUUID().toString();
        //ajax的返回
        Callback callback = new Callback(){
            public String call(String request){
                return "content for " + request;
            }
        };
        updateToken(conn, token, "username","itemX");
        String url = "http://test.com/?item=itemX";
        System.out.println("4.1We are going to cache a simple request against " + url);
        String result = cacheRequest(conn, url, callback);
        System.out.println("4.2We got initial content:\n" + result);
        System.out.println();

        assert result != null;

        System.out.println("4.3To test that we've cached the request, we'll pass a bad callback");
        String result2 = cacheRequest(conn, url, null);
        System.out.println("4.4We ended up getting the same response!\n" + result2);

        assert result.equals(result2);

        assert !canCache(conn, "http://test.com/");
        assert !canCache(conn, "http://test.com/?item=itemX&_=1234536");
	}
	
	public void testLoginCookies(Jedis conn)throws InterruptedException
	{
		System.out.println("\n1.0----- testLoginCookies -----");
		//UUID让分布式系统中的所有元素,都能有唯一的辨识资讯,
		//而不需要透过中央控制端来做辨识资讯的指定。
		//如此一来,每个人都可以建立不与其它人冲突的 UUID。
		//在这样的情况下,就不需考虑数据库建立时的名称重复问题。
		String token = UUID.randomUUID().toString();
		updateToken(conn, token, "username", "itemX");
		System.out.println("1.1We just logged-in/updated token: " + token);
        System.out.println("1.2For user: 'username'");
        System.out.println();

        System.out.println("1.3What username do we get when we look-up that token?");
        String r = checkToken(conn, token);
        System.out.println(r);
        System.out.println();
        assert r != null;
        
        System.out.println("1.4Let's drop the maximum number of cookies to 0 to clean them out");
        System.out.println("1.5We 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("1.6The current number of sessions still available is: " + s);
        assert s == 0;
	}
	
	public String checkToken(Jedis conn,String token)
	{
		//hash通过login:中获取token所对应的user信息
		return conn.hget("login:", token);
	}
	public void updateToken(Jedis conn,String token,String user,String item)
	{
		//这里的taken类似于令牌cookie中的密钥
		long timeStamp = System.currentTimeMillis();
		//hash维持令牌与已登录用户之间的映射,添加user键与UUID值
		conn.hset("login:", token,user);
		//zset是recent:内添加{timeStamp,UUID[cookie/double]}
		conn.zadd("recent:", timeStamp,token);
		if(item!= null){
			//zset是viewed:+token内添加浏览过的商品与日期{item,UUID[cookie/double]}
			conn.zadd("viewed:"+token,timeStamp,item);
			//zet通过排序删除第一个到倒数25个,即只留下最后25个最新浏览记录
			conn.zremrangeByRank("viewed:"+token,0,-26);
			//这段代码是为了进行网页分析,有些网页虽然随时变化,但是可以进行打开次数判别,该网页的热度
			//zset减少viewed:被该商品的次数减少一次。~排序是按照小达到大上排序,优先缓存上面的页面
			conn.zincrby("viewed:", -1, item);
		}
	}
	public void addToCart(Jedis conn,String seesion,String item,int count)
	{
		//通过count数量的多少判断删或增hash内的物品数量
		if(count<=0)
			conn.hdel("cart:"+seesion, item);
		else
			conn.hset("cart:"+seesion, item,String.valueOf(count));
	}
	
	public String cacheRequest(Jedis conn, String request, Callback callback) {
        if (!canCache(conn, request)){
            return callback != null ? callback.call(request) : null;
        }
        //pageKey = cache:hashRequest(类似于唯一内存标识)
        String pageKey = "cache:" + hashRequest(request);
        String content = conn.get(pageKey);

        if (content == null && callback != null){
            content = callback.call(request);
            conn.setex(pageKey, 300, content);
        }

        return content;
    }
	
	public boolean canCache(Jedis conn, String request) {
        try {
            URL url = new URL(request);
            HashMap params = new HashMap();
            if (url.getQuery() != null){
                for (String param : url.getQuery().split("&")){
                    String[] pair = param.split("=", 2);
                    params.put(pair[0], pair.length == 2 ? pair[1] : null);
                }
            }

            String itemId = extractItemId(params);
            if (itemId == null || isDynamic(params)) {
                return false;
            }
            Long rank = conn.zrank("viewed:", itemId);
            return rank != null && rank < 10000;
        }catch(MalformedURLException mue){
            return false;
        }
    }
	
	public boolean isDynamic(Map params) {
        return params.containsKey("_");
    }
	
	public String extractItemId(Map params) {
        return params.get("item");
    }
	
	public String hashRequest(String request)
	{
		return String.valueOf(request.hashCode());
	}
	
	public interface Callback{
		public String call(String request);
	}
	
	public void scheduleRowCache(Jedis conn,String rowId,int delay)
	{
		//zset在delay:中添加{rowId,delay[double]}
		//delay为更新间隔
		conn.zadd("delay:",delay,rowId);
		//zset在schedule:中添加{rowId,time[int]}
		//time为当前时间
		conn.zadd("schedule:", System.currentTimeMillis()/1000,rowId);
	}
	public class CleanFullSessionsThread extends Thread{
		//清理已满的用户令牌以及购物车物品
		private Jedis conn;
		private int limit;
		private boolean quit;
		public CleanFullSessionsThread(int limit) {
			this.conn = new Jedis("localhost");
			this.conn.select(15);
			this.limit = limit;
		}
		public void quit(){
			quit = true;
		}
		@Override
		public void run() {
			while(!quit){
				long size = conn.zcard("recent:");
				if(size <= limit){
					try {
						sleep(1000);
					}catch (InterruptedException e) {
						Thread.currentThread().interrupt();
					}
					continue;
				}
				long endIndex = Math.min(100, size-limit);
				//zset获取至少能空出100的空余,100/{size-limit[>100]}
				Set sessionSet = conn.zrange("recent:", 0,endIndex-1);
				String[] sessions = sessionSet.toArray(new String[sessionSet.size()]);
				ArrayList sessionKeys = new ArrayList();
				for (String session:sessions){
					sessionKeys.add("viewed:"+session);
					sessionKeys.add("cart:"+session);
				}
				//key:删除键为sessionKeys为key的数据项
				conn.del(sessionKeys.toArray(new String[sessionKeys.size()]));
				conn.hdel("login:", sessions);
				conn.zrem("recent:", sessions);
			}
		}
	}
	public class CleanSessionsThread extends Thread
	{
		//清理用户令牌内部类
		private Jedis conn;
		private int limit;
		private boolean quit;
		public CleanSessionsThread(int limit) {
			this.conn = new Jedis("localhost");
			this.conn.select(15);
			this.limit = limit;
		}
		public void quit()
		{
			quit = true;
		}
		@Override
		public void run() {
			while(!quit)
			{
				long size = conn.zcard("recent:");
				if(size <= limit){
					try{
						sleep(1000);
					}catch (InterruptedException e) {
						//当线程阻塞时,调用interrupt方法后,该线程会得到一个interrupt异常,
						//可以通过对该异常的处理而退出线程
						Thread.currentThread().interrupt();
					}
					continue;
				}
				long endIndex = Math.min(100, size-limit);
				//zset获取至少能空出100的空余,100/size-limit{>100}
				Set tokenSet = conn.zrange("recent:",0,endIndex);
				String[] tokens = tokenSet.toArray(new String[tokenSet.size()]);
				
				ArrayList sessionKeys = new ArrayList();
				for(String token:tokens){
					sessionKeys.add("viewed:"+token);
				}
				conn.del(sessionKeys.toArray(new String[sessionKeys.size()]));
				//PS其实这个tokens是UUID也就是cookie
				//hash删除login:中的user-->{user-->UUID[cookie/String]}
				conn.hdel("login:", tokens);
				//zset删除recent:中的旧令牌UUID-->{timeStamp[时间戳]-->UUID[cookie/String]}
				conn.zrem("recent:", tokens);
			}
			
		}
	}
	public class CacheRowsThread extends Thread
	{
		private Jedis conn;
		private boolean quit = false;
		public CacheRowsThread() {
			this.conn = new Jedis("localhost");
			this.conn.select(15);
		}
		public void quit()
		{
			quit = true;
		}
		public void run(){
			Gson gson = new Gson();
			while(!quit){
				//zset获取schedule:{行ID[String/对应inv:xxx-->(xxx为行ID)],时间戳[int/double]}中第一个需要进行更新的(更新时间短)
				//返回的是(行ID)-->{[inv:xxx]-->[xxx即为行ID]}
				Set range = conn.zrangeWithScores("schedule:",0,0);
				//查看range的大小,{[如果>0,则转化游标转到第一个元素],[如果<=0,设置为空]}
				Tuple next = range.size()>0?range.iterator().next():null;
				long now = System.currentTimeMillis()/1000;
				//如果{[第一个元素(行ID)不存在]或者[第一个元素的分值(更新时间)大于现在(下面的线程那一直都在等待时间的到来)]}
				if(next == null || next.getScore()>now){
					try{
						sleep(50);
					}catch (InterruptedException e) {
						Thread.currentThread().interrupt();
					}
					continue;
				}
				//第一个行ID
				
				String rowId = next.getElement();
				//zset计算delay:中rowId(行ID)所对应的值(delay_value)
				double delay = conn.zscore("delay:",rowId);
				if(delay <= 0){
					//zset通过delay:中的rowId(行ID)删除更新间隔(delay_value)
					conn.zrem("delay:", rowId);
					//zset通过schedule:中的rowId(行ID)删除时间戳(time)
					conn.zrem("schedule:", rowId);
					//key删除inv:rowId
					conn.del("inv:"+rowId);
					continue;
				}
				Inventory row = Inventory.get(rowId);
				//zset添加schedule:元素{rowId,time(时间戳/更新时间)}
				conn.zadd("schedule:", now+delay,rowId);
				//set添加/覆盖inv:rowId,将数据商品转化成JSON的String存储进去
				conn.set("inv:"+rowId, gson.toJson(row));
			}
		}
	}
	public static class Inventory{
		private String id;
        private String data;
        private long time;
        public Inventory(String id){
        	this.id = id;
        	this.data = "data to cache...";
        	this.time = System.currentTimeMillis()/1000;
        }
        public static Inventory get(String id) {
            return new Inventory(id);
        }
		
	}

}

运行结果:

Redis实战-chapter2_第1张图片

你可能感兴趣的:(java,redis,java,redis)