欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Redis实战-chapter2

程序员文章站 2022-03-05 12:20:59
...

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

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<String,String> r = conn.hgetAll("cart:" + token);
        System.out.println("2.3Our 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("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<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("\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<Tuple>中
        Set<Tuple> 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<String,String> params = new HashMap<String,String>();
            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<String,String> params) {
        return params.containsKey("_");
    }
	
	public String extractItemId(Map<String,String> 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<String> sessionSet = conn.zrange("recent:", 0,endIndex-1);
				String[] sessions = sessionSet.toArray(new String[sessionSet.size()]);
				ArrayList<String> sessionKeys = new ArrayList<String>();
				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<String> tokenSet = conn.zrange("recent:",0,endIndex);
				String[] tokens = tokenSet.toArray(new String[tokenSet.size()]);
				
				ArrayList<String> sessionKeys = new ArrayList<String>();
				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<Tuple> 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