博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redis实战:第二章-使用redis构建web应用
阅读量:4127 次
发布时间:2019-05-25

本文共 13584 字,大约阅读时间需要 45 分钟。

对于一个高并发电商网站,如果使用传统的关系型数据库,由于关系型数据库在并发量达到100万时,效率将大大降低,比如对于一个电商网站,用户登录浏览商品,在很短的时间内,用户可能会浏览很多商品,而这些商品就是用户的兴趣点,为了分析用户的潜在需求,我们需要记录所有的访问数据,难点在于,如果有100万个用户都在这样操作,传统的关系型数据库将很难处理这么多的请求,将造成大量的数据丢失,所以,为了快速响应这些请求,redis的快速读写功能就能使用上。

  • redis实现购物车

    购物车的实现有三种方法:

    • 1直接使用关系型数据库,这样不用担心数据的丢失,但是高并发下速度慢
    • 2使用浏览器客户端自带的cookie,缺点,可能禁用cookie,另外如果用户在没有登陆的情况下,添加购物车,之后又由另外一个人登录,那么此时的购物车就是别人的了
    • 3redis缓存,速度快;缺点浪费大量服务器内存,但是一般这种情况,电商系统还是承受的住的,毕竟资源并不高,别人的文章说的,我想的话,这也就是个字段保存
  • 网页缓存

    早期的网站开发,前端的网页由于包含后台的数据,整个网页都是动态生成的,比如jsp;但是实际上,对于大部分网页而言,其中90%的网页都是固定不变的或者只改变一次,所以,就需要使用缓存来临时保存这些网页,而不用反复的动态生成而浪费时间

  • 数据行缓存,对于一个网页,有时候,网页只有其中的部分数据会变,但是网页整体不变。比如,电商促销活动页面,其中固定放出一定的商品用于促销,此时由于包含了商品信息,数据随时可能改变,所以不能使用上面的网页缓存;但是,如果直接访问数据库,速度又受限,这时就可以使用redis进行数据行缓存,只缓存指定的数据,比如促销的商品信息

详细逻辑代码:

import com.google.gson.Gson;import redis.clients.jedis.Jedis;import redis.clients.jedis.Tuple;import java.net.MalformedURLException;import java.net.URL;import java.util.ArrayList;import java.util.HashMap;import java.util.Map;import java.util.Set;import java.util.UUID;/** * @author: ZouTai * @date: 2018/6/28 * @description: 第二章、使用redis构建web应用 * @create: 2018-06-28 10:06 */public class Chapter02 {
public static void main(String[] args) throws InterruptedException { new Chapter02().run(); } private void run() throws InterruptedException { // 1、创建连接,选择数据库15 Jedis conn = new Jedis("localhost"); conn.select(15); // 1、登录和cookie缓存 2、购物车实现 3、网页缓存 4、数据行缓存 testLoginCookies(conn); testShoppingCartCookies(conn); testCacheRequest(conn); testCacheRows(conn); } /** * 2.3 网页内容缓存 * * @param conn */ private void testCacheRequest(Jedis conn) { System.out.println("\n----- testCacheRequest -----"); String token = UUID.randomUUID().toString(); CallBack callBack = new CallBack() { @Override public String call(String request) { return "content for " + request; } }; // 登录 updateToken(conn, token, "username", "item3"); String url = "http://test.com/?item=itemX"; System.out.println("We are going to cache a simple request against " + url); String result = cacheRequest(conn, url, callBack); System.out.println("We got initial content:\n" + result); System.out.println(); assert result != null; System.out.println("To test that we've cached the request, we'll pass a bad callback"); String result2 = cacheRequest(conn, url, null); System.out.println("We 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"); } /** * 对请求进行缓存处理 */ private String cacheRequest(Jedis conn, String request, CallBack callback) { // 不能缓存的请求,直接调用回调函数,返回提示信息 // 回调函数,即通过外部类来实现计算并返回给当前函数 if (!canCache(conn, request)) { return callback != null ? callback.call(request) : null; } String pageKey = "cache:" + hashRequest(request); // 将请求转为hash码 String content = conn.get(pageKey); // 获取连接内容(若存在) // 若不存在,则写入数据库 if (content == null && callback != null) { content = callback.call(request); // 将值 value 关联到 key ,并将 key 的生存时间设为 seconds conn.setex(pageKey, 300, content); } return content; } private String hashRequest(String request) { return String.valueOf(request.hashCode()); } /** * 判断请求是否能被缓存 */ private boolean canCache(Jedis conn, String request) { try { URL url = new URL(request); HashMap
params = new HashMap
(); // getQuery获取url参数,这里即“item=itemX” if (url.getQuery() != null) { for (String param : url.getQuery().split("&")) { // limit设置分割份数,即强制默认两份,如果出现“item=itemX=2”,做“item”和“itemX=2”处理 String[] pair = param.split("=", 2); // 将请求参数添加到parames,如果没有设置参数的值,即length==1,则设置为null params.put(pair[0], pair.length == 2 ? pair[1] : null); } } String itemId = extractItemId(params); // 获取参数的值itemX // 如果参数值为null或者不进行缓存,则返回false if (itemId == null || isDynamic(params)) { return false; } // zrank:有序集成员按 score 值递增(从小到大)顺序排列 Long rank = conn.zrank("viewed:", itemId); // 有商品被访问过:即网页被访问 return rank != null && rank < 10000; } catch (MalformedURLException mue) { return false; } } /** * 用于测试,即请求参数含有"_"的,不进行缓存 */ private boolean isDynamic(HashMap
params) { return params.containsKey("_"); } private String extractItemId(HashMap
params) { return params.get("item"); } /** * 2、使用缓存实现购物车 */ private void testShoppingCartCookies(Jedis conn) throws InterruptedException { System.out.println("\n----- testShoppingCartCookies -----"); 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"); // 2.1 添加到购物车 addToCart(conn, token, "item3", 3); // 2.2 输出所有的购物车 Map
r = conn.hgetAll("cart:" + token); System.out.println("Our shopping cart currently has:"); for (Map.Entry
entry : r.entrySet()) { System.out.println(" " + entry.getKey() + ": " + entry.getValue()); } System.out.println(); assert r.size() >= 1; // 2.3 删除所有的对话 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?!?"); } // 2.4 输出购物车数据 r = conn.hgetAll("cart:" + token); System.out.println("Our shopping cart now contains:"); for (Map.Entry
entry : r.entrySet()) { System.out.println(" " + entry.getKey() + ": " + entry.getValue()); } assert r.size() == 0; // 3、 网页缓存 testCacheRows(conn); } /** * 缓存行数据 * @param conn */ private 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, "item3", 5); // 缓存5分钟 System.out.println("Our schedule looks like:"); // Tuple含有一个score字段用于保存分数比较 Set
scheduleSet = conn.zrangeWithScores("schedule:", 0, -1); // 查找所有分数大于0的,max参数无 System.out.println("按过期时间进行排序输出:"); for (Tuple tuple : scheduleSet) { System.out.println(" " + tuple.getElement() + "," + tuple.getScore()); } assert scheduleSet.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); assert r != 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(); assert r2 != 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?!?"); } } private void scheduleRowCache(Jedis conn, String item, int delay) { // 分别记录缓存总时间和缓存截止时间 conn.zadd("delay:", delay, item); conn.zadd("schedule:", System.currentTimeMillis() / 1000, item); } /** * 添加到购物车 */ private void addToCart(Jedis conn, String token, String item, int count) { if (count <= 0) { conn.hdel("cart:" + token, item); // 用于移除购物车,直接移除商品 } else { conn.hset("cart:" + token, item, String.valueOf(count)); } } /** * 1、登录和cookie缓存 * @param conn * @throws InterruptedException */ private void testLoginCookies(Jedis conn) throws InterruptedException { System.out.println("test LoginCookies:"); // 1.1 生成令牌:代表一次登录(未退出) String token = UUID.randomUUID().toString(); // 1.2 一次商品浏览(可以多次) updateToken(conn, token, "username", "item1"); // 访问商品,可以多次 System.out.println("We just logged-in/updated token: " + token); System.out.println("For user: 'username'"); System.out.println(); // 1.3 查找当前登录用户 System.out.println("What username do we get when we look-up that token?"); String rs = checkToken(conn, token); System.out.println(rs); System.out.println(); assert rs != null; // 1.4 启动cookie检查线程,防止cookie过多 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?!?"); } // 1.5 检查是否还有token long s = conn.hlen("login:"); System.out.println("The current number of sessions still available is: " + s); assert s == 0; } /** * 注意:这里有三个表: * 1、login:记录登录标记-登录用户(token-user) * 2、recent:记录登录标记-登陆时间(token-timestamp) * 3、viewed:记录商品被访问次数(item-int) * 4、"viewed:" + token:记录商品被访问时间(item-timestamp) * * @param conn * @param token * @param user * @param item 浏览商品,生成令牌token记录 */ private void updateToken(Jedis conn, String token, String user, String item) { Long timestamp = System.currentTimeMillis() / 1000; // 2、写入登录表,表名为login,key-token,value-user conn.hset("login:", token, user); // 登录令牌对应user conn.zadd("recent:", timestamp, token); // 添加到当前登录的cookie目录 if (item != null) { conn.zadd("viewed:" + token, timestamp, item); // 增加一个商品时间戳, // 移除旧的记录,只保留用户最近访问的25个商品 // 注意这里反着取,即删除0,-mix,-mix+1,-mix+2....-27,-26 conn.zremrangeByRank("viewed:" + token, 0, -26); // 新建当前商品访问分数为-1,每访问一次,分数-1; // 访问次数越多分数越少,排序越靠前 conn.zincrby("viewed:", -1, item); } } private String checkToken(Jedis conn, String token) { return conn.hget("login:", token); } private class CleanSessionsThread extends Thread { private Jedis conn; private int limit; // 总令牌数量 private boolean quit; // 是否停止 public CleanSessionsThread(int limit) { this.conn = new Jedis("localhost"); conn.select(15); this.limit = limit; } @Override public void run() { while (!quit) { long size = conn.zcard("recent:"); // 获取当前所有的cookie令牌 // 未超过cookie数量限制 if (size <= limit) { try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } continue; // 继续检查 } // 删除多余的,但是删除量不大于100 // 由于是按时间排序的,所以是从前往后删除 long endIndex = Math.min(size - limit, 100); Set
tokenSet = conn.zrange("recent:", 0, endIndex - 1); String[] tokens = tokenSet.toArray(new String[tokenSet.size()]); // 为即将删除的令牌构建键名,用于删除 conn.zrem("recent:", tokens); // 1删除登录记录-时间 conn.hdel("login:", tokens); // 2删除登录记录-用户 // 删除登录记录对应的所有商品访问信息表 ArrayList
sessionsKeys = new ArrayList(); for (String token : tokens) { sessionsKeys.add("viewed:" + token); } conn.del(sessionsKeys.toArray(new String[sessionsKeys.size()])); } } public void quit() { this.quit = true; } } private 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 ie) { Thread.currentThread().interrupt(); } continue; } long endIndex = Math.min(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 sess : sessions) { sessionKeys.add("viewed:" + sess); sessionKeys.add("cart:" + sess); } conn.del(sessionKeys.toArray(new String[sessionKeys.size()])); conn.hdel("login:", sessions); conn.zrem("recent:", sessions); } } } private class CacheRowsThread extends Thread { private Jedis conn; private boolean quit; public CacheRowsThread() { this.conn = new Jedis("localhost"); conn.select(15); } @Override public void run() { while (!quit) { Set
sSet = conn.zrangeWithScores("schedule:", 0, -1); Tuple next = sSet.size() > 0 ? sSet.iterator().next() : null; long now = System.currentTimeMillis() / 1000; // 如果为null或者没有过期,则等待重复检查 if (next == null || next.getScore() > now) { try { sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } continue; } // 如果有过期,则删除对应数据和表 String item = next.getElement(); double delay = conn.zscore("schedule:", item); if (delay <= 0) { conn.zrem("delay:", item); conn.zrem("schedule:", item); continue; } // 封装一个网页数据类 Inventory row = new Inventory(item); conn.zadd("delay:", now + delay, item); Gson gson = new Gson(); conn.set("inv:" + item, gson.toJson(row), item); // 设置对应的网页 } } public void quit() { this.quit = true; } } private class Inventory { private String item; private String data; private long time; public Inventory(String item) { this.item = item; this.data = "data to cache..."; this.time = System.currentTimeMillis() / 1000; } } public interface CallBack { public String call(String request); }}

转载地址:http://feepi.baihongyu.com/

你可能感兴趣的文章
第四章 - 程序计数器
查看>>
第七章 - 本地方法栈
查看>>
第十一章 - 直接内存
查看>>
JDBC核心技术 - 上篇
查看>>
一篇搞懂Java反射机制
查看>>
MySQL主从复制不一致的原因以及解决方法
查看>>
RedisTemplate的key默认序列化器问题
查看>>
序列化与自定义序列化
查看>>
ThreadLocal
查看>>
从Executor接口设计看设计模式之最少知识法则
查看>>
OKhttp之Call接口
查看>>
application/x-www-form-urlencoded、multipart/form-data、text/plain
查看>>
关于Content-Length
查看>>
WebRequest post读取源码
查看>>
使用TcpClient可避免HttpWebRequest的常见错误
查看>>
EntityFramework 学习之一 —— 模型概述与环境搭建 .
查看>>
C# 发HTTP请求
查看>>
启动 LocalDB 和连接到 LocalDB
查看>>
Palindrome Number --回文整数
查看>>
Reverse Integer--反转整数
查看>>