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);
}
}
}
运行结果: