MemCached快取操作
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快取和資料庫。