Web項目在運行時,通常需要從數據庫中進行讀寫。隨着操作數據量的增大,以及訪問量的集中,數據庫的負載增加,數據庫響應變慢,網站訪問速度變慢的情況。Memcached就是用來解決這些問題的。
Memcached是一個開源的高性能的分布式緩存系統。主要用於減輕數據庫負載,加速Web應用訪問。它是基於內存的Key-Value存儲系統,主要存儲Value較小的數據,Value大小不能超過1M。總的來說,Memcached支持的數據結構是鍵值對,在支持的數據結構上要比Redis簡單。
Memcached的使用場景有:
訪問頻度極高的業務,如社交網絡,電子商務,游戲,廣告等,可以將頻繁訪問的數據存儲到Memcached中,從而減少對數據庫的訪問。
促銷類業務,秒殺類業務,這些業務訪問壓力非常大,一般數據庫根本無法承載這么大的業務量。
計數器,如點贊人數,文章閱讀人數等。
存儲小圖片,減輕硬盤存儲系統的訪問壓力。
那么,我們為什么要基於內存來存儲呢?我們先來看一張比較計算機中各種存儲介質處理數據速率的圖片:
內存,磁盤,CPU的運行方式不同。磁盤是毫秒級,內存是微妙級,CPU是納秒級。可以說,比較內存和磁盤的速度差異,內存比磁盤快10萬~100萬倍。傳輸速度和總線的速度差異,雖然有SSD(Solid State Drives,固態硬盤),但是其速度還是無法和內存相比。
在操作中,如果無法在內存中計算的話,就必須搜索磁盤上的數據,但是磁盤I/O輸入輸出非常耗時。一般網站的一個請求響應時間要控制在1秒內。
我們來看MemCached的物理架構:
使用Memcached時的數據讀取流程是:先從Memcached中取數據,如果取不到再從數據庫中取數據,然后把數據保存到Memcached中。
使用Memcached時的數據更新流程是:先更新數據庫,然后再更新Memcached緩存,或者刪除數據緩存。
我們接下來看Memcached的應用:
首先,要安裝Memcached服務器。我們以win7系統為例,切換到Memcached路徑,在DOS窗口的命令行中執行安裝與啟動命令:
#安裝Memcached服務器 memcached -d install #啟動Memcached服務 net strat "memcached server"
安裝與啟動Memcached服務后,我們接下來在idea開發工具中創建一個maven項目,在里面通過代碼查看Memcached的基本操作:
package com.itszt.DemoMC.domain; /** * 訂單實體類,id,名稱,下訂單時間 */ public class OrderRequest { private int orderId; private String orderName,orderTime; public OrderRequest() { } public OrderRequest(int orderId, String orderName, String orderTime) { this.orderId = orderId; this.orderName = orderName; this.orderTime = orderTime; } public int getOrderId() { return orderId; } @Override public String toString() { return "OrderRequest{" + "orderId=" + orderId + ", orderName='" + orderName + '\'' + ", orderTime='" + orderTime + '\'' + '}'; } public void setOrderId(int orderId) { this.orderId = orderId; } public String getOrderName() { return orderName; } public void setOrderName(String orderName) { this.orderName = orderName; } public String getOrderTime() { return orderTime; } public void setOrderTime(String orderTime) { this.orderTime = orderTime; } } ******************************************** package com.itszt.DemoMC; import com.alibaba.fastjson.JSON; import com.itszt.DemoMC.domain.OrderRequest; import net.rubyeye.xmemcached.MemcachedClient; import net.rubyeye.xmemcached.XMemcachedClientBuilder; import net.rubyeye.xmemcached.exception.MemcachedException; import net.rubyeye.xmemcached.utils.AddrUtil; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; /** * MemCached的基本操作 */ public class App { public static void main(String[] args) { //創建操作MemCached的客戶端 XMemcachedClientBuilder memcachedClientBuilder = new XMemcachedClientBuilder(AddrUtil.getAddresses("192.168.1.160:11211")); MemcachedClient memcachedClient =null; try { memcachedClient = memcachedClientBuilder.build(); System.out.println("memcachedClient = " + memcachedClient); boolean booDel = memcachedClient.delete("data1"); if(booDel){ System.out.println("刪除成功"); }else{ System.out.println("無此數據"); } //向MemCached中插入字符串數據 /*memcachedClient.add("data1",0,"測試數據1"); memcachedClient.add("data2",0,"測試數據2");*/ /*memcachedClient.set("data1",0,"測試數據1"); memcachedClient.set("data2",0,"測試數據2");*/ } catch (IOException e) { e.printStackTrace(); } /*catch (InterruptedException e) { e.printStackTrace(); } catch (MemcachedException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); }*/ catch (InterruptedException e) { e.printStackTrace(); } catch (MemcachedException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } //從MemCached中取單個數據 /*try { Object data1 = memcachedClient.get("data1"); System.out.println("data1 = " + data1); } catch (TimeoutException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (MemcachedException e) { e.printStackTrace(); }*/ //取出一批數據 List<String> keys=new ArrayList<>(); keys.add("data1"); keys.add("data2"); try { //從memcached中讀取字符串 Map<String, Object> objectMap = memcachedClient.get(keys); System.out.println("objectMap = " + objectMap); } catch (TimeoutException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (MemcachedException e) { e.printStackTrace(); } //存儲對象 OrderRequest orderRequest = new OrderRequest(1, "大黃", new Date().toString()); System.out.println(JSON.toJSON(orderRequest).toString()); try { memcachedClient.set("order_"+orderRequest.getOrderId(),0, JSON.toJSON(orderRequest).toString()); } catch (TimeoutException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (MemcachedException e) { e.printStackTrace(); } try { String obj = memcachedClient.get("order_" + orderRequest.getOrderId()); OrderRequest orderFromJson = JSON.parseObject(obj, OrderRequest.class); System.out.println("orderFromJson = " + orderFromJson); } catch (TimeoutException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (MemcachedException e) { e.printStackTrace(); } } }
上面代碼是MemCached的基本操作,我們在工作中是要結合數據庫來使用的,為此,我們在maven中配置mybatis環境,同時在數據庫中建一張測試表user(int uid,varchar username,varchar userpwd)。在maven項目的pom.xml文件中的配置如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itszt.DemoMC</groupId> <artifactId>DemoMC</artifactId> <version>1.0</version> <packaging>jar</packaging> <name>DemoMC</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>com.googlecode.xmemcached</groupId> <artifactId>xmemcached</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.44</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.8</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> <build> <finalName>DemoMC</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> </project>
接下來,我們再看mybatis-config.xml配置文件:
<?xml version="1.0" encoding="UTF-8" ?> <!--DTD約束引入--> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--全局配置開始--> <configuration> <!-- 運行環境設置 1.連接 2.事務--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/a0314?useUnicode=true&CharsetEncodig=UTF-8"/> <property name="username" value="root"/> <property name="password" value="2018"/> </dataSource> </environment> </environments> <!-- 引入配置文件2--> <mappers> <package name="com.itszt.DemoMC.demo"></package> </mappers> </configuration>
在將上述配置文件完畢后,我們接下來寫一個小案例,其文件體系如下圖所示:
我們接下來看代碼:
package com.itszt.DemoMC.util; import net.rubyeye.xmemcached.MemcachedClient; import net.rubyeye.xmemcached.XMemcachedClientBuilder; import net.rubyeye.xmemcached.exception.MemcachedException; import net.rubyeye.xmemcached.utils.AddrUtil; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * memcached工具類 */ public class MCUtil { private static MemcachedClient memcachedClient =null; static { XMemcachedClientBuilder memcachedClientBuilder = new XMemcachedClientBuilder(AddrUtil.getAddresses("192.168.1.160:11211")); try { memcachedClient = memcachedClientBuilder.build(); } catch (IOException e) { e.printStackTrace(); } } public static boolean setData(String key,int expire,Object obj){ try { memcachedClient.set(key,expire,obj); return true; } catch (TimeoutException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (MemcachedException e) { e.printStackTrace(); } return false; } public static <T> T getData(String key){ try { return memcachedClient.get(key); } catch (TimeoutException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (MemcachedException e) { e.printStackTrace(); } return null; } } ********************************************* package com.itszt.DemoMC.demo; import java.io.Serializable; /** * 實體類,映射數據庫a0314中的user表 */ public class User implements Serializable{ private int uid; private String username,userpwd; public User() { } @Override public String toString() { return "User{" + "uid=" + uid + ", username='" + username + '\'' + ", userpwd='" + userpwd + '\'' + '}'; } public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getUserpwd() { return userpwd; } public void setUserpwd(String userpwd) { this.userpwd = userpwd; } } ************************************************ package com.itszt.DemoMC.demo; /** * 接口,操作數據庫 */ public interface UserDao { //根據用戶名和密碼查詢用戶 public User findUserByNameAndPwd(String username,String userpwd); //根據uid修改用戶名 public boolean resetUsername(int uid,String username); } ************************************************* package com.itszt.DemoMC.demo; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; /** * 操作數據庫用 */ public interface UserDaoDB extends UserDao{ @Override @Select("select * from user where username=#{username} and userpwd=#{userpwd}") User findUserByNameAndPwd(@Param("username") String username,@Param("userpwd") String userpwd); @Override @Update("update user set username=#{username} where uid=#{uid}") boolean resetUsername(@Param("uid") int uid,@Param("username") String username); } ************************************************ UserDaoDB.xml配置內容: <?xml version="1.0" encoding="UTF-8" ?> <!--引入DTD約束--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itszt.DemoMC.demo.UserDaoDB"> </mapper> ************************************************ package com.itszt.DemoMC.demo; import com.itszt.DemoMC.util.MCUtil; /** * 操作MemCached緩存 */ public class UserDaoCached implements UserDao{ @Override public User findUserByNameAndPwd(String username, String userpwd) { return MCUtil.getData("user_"+username+"_"+userpwd); } @Override public boolean resetUsername(int uid, String username) { boolean boo = MCUtil.setData("user_" + uid, 0, username); return boo; } } ************************************************ package com.itszt.DemoMC.demo; import com.itszt.DemoMC.util.MCUtil; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; /** * UserDao的實現類 */ public class UserDaoImpl implements UserDao{ private UserDaoCached userDaoCached; private UserDaoDB userDaoDB; SqlSession sqlSession; public UserDaoImpl(){ userDaoCached=new UserDaoCached(); String resource="mybatis-config.xml"; InputStream inputStream=null; try { inputStream=Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); sqlSession = sqlSessionFactory.openSession(); userDaoDB= sqlSession.getMapper(UserDaoDB.class); } @Override public User findUserByNameAndPwd(String username, String userpwd) { User userByNameAndPwd=null; //先從緩存查找,若緩存沒有,則再從數據庫查找 userByNameAndPwd=userDaoCached.findUserByNameAndPwd(username,userpwd); if(userByNameAndPwd==null){ System.out.println("緩存里沒有,從數據庫中查找"); userByNameAndPwd=userDaoDB.findUserByNameAndPwd(username,userpwd); if(userByNameAndPwd==null){ System.out.println("數據庫無匹配項"); return null; } //從數據庫查找到后,再添加入緩存 MCUtil.setData("user_"+username+"_"+userpwd,0,userByNameAndPwd); System.out.println("成功保存到緩存"); } System.out.println("從緩存獲取"); return userByNameAndPwd; } @Override public boolean resetUsername(int uid, String username) { //先更新數據庫,再更新緩存 try { boolean boo = userDaoDB.resetUsername(uid, username); if(boo){ sqlSession.commit(); userDaoCached.resetUsername(uid,username); return true; }else{ System.out.println("數據更新失敗"); } } catch (Exception e) { e.printStackTrace(); } return false; } } *********************************************** package com.itszt.DemoMC.demo; /** * 測試類 */ public class Test { public static void main(String[] args) { //操作UserDaoImpl實現類 UserDaoImpl userDao=new UserDaoImpl(); User userByNameAndPwd = userDao.findUserByNameAndPwd("admin", "123456"); System.out.println("userByNameAndPwd = " + userByNameAndPwd); //更新數據庫中的數據,並更新到緩存中 boolean boo = userDao.resetUsername(userByNameAndPwd.getUid(), "admin123"); if(boo){ System.out.println("數據更新完畢"); User user = userDao.findUserByNameAndPwd("admin123", "123456"); System.out.println("user = " + user); }else{ System.out.println("數據更新失敗"); } } }
運行上述代碼中的測試類Test后,顯示結果如下:
從緩存獲取 userByNameAndPwd = User{uid=1, username='admin', userpwd='123456'} 數據更新完畢 緩存里沒有,從數據庫中查找 成功保存到緩存 從緩存獲取 user = User{uid=1, username='admin123', userpwd='123456'}
此時已功地實現了操作MemCached緩存和數據庫。