一、 Memcache的使用
使用Memcache的网站一般流量都是比较大的,为了缓解数据库的压力,让Memcache作为一个缓存区域,把部分信息保存在内存中,在前端能够迅速的进行存取。那么一般的焦点就是集中在如何分担数据库压力和进行分布式,毕竟单台Memcache的内存容量的有限的。我这里简单提出我的个人看法,未经实践,权当参考。
二、 分布式应用
Memcache本来支持分布式,我们客户端稍加改造,更好的支持。我们的key可以适当进行有规律的封装,比如以user为主的网站来说,每个用户都有User ID,那么可以按照固定的ID来进行提取和存取,比如1开头的用户保存在第一台Memcache服务器上,以2开头的用户的数据保存在第二胎Mecache服务器上,存取数据都先按照User ID来进行相应的转换和存取。
三、 但是这个有缺点,就是需要对User ID进行判断,如果业务不一致,或者其他类型的应用,可能不是那么合适,那么可以根据自己的实际业务来进行考虑,或者去想更合适的方法。
四、 减少数据库压力
这个算是比较重要的,所有的数据基本上都是保存在数据库当中的,每次频繁的存取数据库,导致数据库性能极具下降,无法同时服务更多的用户,比如MySQL,特别频繁的锁表,那么让Memcache来分担数据库的压力吧。我们需要一种改动比较小,并且能够不会大规模改变前端的方式来进行改变目前的架构。
五、 我考虑的一种简单方法:
后端的数据库操作模块,把所有的Select操作提取出来(update/delete/insert不管),然后把对应的SQL进行相应的hash算法计算得出一个hash数据key(比如MD5或者SHA),然后把这个key去Memcache中查找数据,如果这个数据不存在,说明还没写入到缓存中,那么从数据库把数据提取出来,一个是数组类格式,然后把数据在set到Memcache中,key就是这个SQL的hash值,然后相应的设置一个失效时间,比如一个小时,那么一个小时中的数据都是从缓存中提取的,有效减少数据库的压力。缺点是数据不实时,当数据做了修改以后,无法实时到前端显示,并且还有可能对内存占用比较大,毕竟每次select出来的数据数量可能比较巨大,这个是需要考虑的因素。
六、 Memcache的安全
我们上面的Memcache服务器端都是直接通过客户端连接后直接操作,没有任何的验证过程,这样如果服务器是直接暴露在互联网上的话是比较危险,轻则数据泄露被其他无关人员查看,重则服务器被入侵,因为Mecache是以root权限运行的,况且里面可能存在一些我们未知的bug或者是缓冲区溢出的情况,这些都是我们未知的,所以危险性是可以预见的。为了安全起见,我做两点建议,能够稍微的防止黑客的入侵或者数据的泄露。
七、 内网访问
最好把两台服务器之间的访问是内网形态的,一般是Web服务器跟Memcache服务器之间。普遍的服务器都是有两块网卡,一块指向互联网,一块指向内网,那么就让Web服务器通过内网的网卡来访问Memcache服务器,我们Memcache的服务器上启动的时候就监听内网的IP地址和端口,内网间的访问能够有效阻止其他非法的访问。
# memcached -d -m 1024 -u root -l 192.168.0.200 -p 11211 -c 1024 -P/tmp/memcached.pid
Memcache服务器端设置监听通过内网的192.168.0.200的ip的11211端口,占用1024MB内存,并且允许最大1024个并发连接
八、 设置防火墙
防火墙是简单有效的方式,如果却是两台服务器都是挂在网的,并且需要通过外网IP来访问Memcache的话,那么可以考虑使用防火墙或者代理程序来过滤非法访问。
一般我们在Linux下可以使用iptables或者FreeBSD下的ipfw来指定一些规则防止一些非法的访问,比如我们可以设置只允许我们的Web服务器来访问我们Memcache服务器,同时阻止其他的访问。
# iptables -F
# iptables -P INPUT DROP
# iptables -A INPUT -p tcp -s 192.168.0.2 –dport 11211 -j ACCEPT
# iptables -A INPUT -p udp -s 192.168.0.2 –dport 11211 -j ACCEPT
上面的iptables规则就是只允许192.168.0.2这台Web服务器对Memcache服务器的访问,能够有效的阻止一些非法访问,相应的也可以增加一些其他的规则来加强安全性,这个可以根据自己的需要来做。
九、
十、memcached安装与运行
下载地址:http://libevent.org/
比如:https://github.com/downloads/libevent/libevent/libevent-2.0.20-stable.tar.gz
安装libevent如果显示没有安装gcc可以参考使用yum安装gcc http://www.linuxidc.com/Linux/2012-08/69360.htm ,然后再安装libevent
下载完成上传到linux上之后,编译、安装:
1.1./configure --prefix=/usr
1.2 编译make
1.3 编译&安装
1.4查看是否已经安装:
ls -al/usr/lib | grep libevent
十一、 安装memcache
下载地址:code.google.com/p/memcached/downloads/list
2.1./configure --with-libevent=/usr
2.2 编译make
2.3 编译&安装
2.4运行
#/usr/local/bin/memcached -d -m 256 -u root -l 192.168.1.1 -p 11211 -c 256 -P/opt/memcached/pid.pid
-d选项是启动一个守护进程,
-m是分配给Memcache使用的内存数量,单位是MB,我这里是10MB,
-u是运行Memcache的用户,我这里是root,
-l是监听的服务器IP地址,如果有多个地址的话,我这里指定了服务器的IP地址192.168.0.200,
-p是设置Memcache监听的端口,我这里设置了12000,最好是1024以上的端口,
-c选项是最大运行的并发连接数,默认是1024,我这里设置了256,按照你服务器的负载量来设定,
-P是设置保存Memcache的pid文件,我这里是保存在 /opt/memcached/pid.pid,
2.5结束Memcache进程,执行:
# kill'cat /opt/memcached/pid.pid'
也可以启动多个守护进程,不过端口不能重复
十二、 memcache客户端开发与应用
a) 在服务端启动并开启监听数据操作情况下,通过建立客户端对数据进行存取操作,完成数据的缓存写入和读取处理。
b) 下载客户端软件JAR包(slf4j-simple-1.6.1.jar,java_memcached-release_2.6.6.jar),建立针对用户信息数据进行缓存处理,定时更新实体类:
package com.gocheck.common.jobtask;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
import com.gocheck.check.bean.DocumentBean;
import com.gocheck.common.basic.MyException;
import com.gocheck.crm.bean.CustomerBean;
import com.gocheck.crm.manager.AccountManager;
import com.gocheck.crm.manager.CustomerManager;
import com.gocheck.crm.manager.LoginInfoManager;
import com.gocheck.crm.manager.OrgManager;
import com.gocheck.param.ParamConstants;
import com.gocheck.param.manager.ParamUtils;
/**
* memcache定时更新缓存客户端实体类
* @project:
* @class:MemcacheTaskJob.java
* @author:heguanhua
* @date:Jun 6, 2013 4:48:01 PM
*/
public class MemcacheTaskJob {
privatestatic Logger log = Logger.getLogger(DocumentSyncJob.class);
privateCustomerManager customerManager;
privateCustomerBean customerBean;
privateLoginInfoManager loginInfoManager;
// 创建全局的唯一实例
protectedstatic MemCachedClient mcc = new MemCachedClient();
protectedstatic MemcacheTaskJob memCachedManager = new MemcacheTaskJob();
// 设置与缓存服务器的连接池
static{
// 服务器列表和其权重
Stringmemcache_host = ParamUtils.getParamValue(ParamConstants.MEMCACHE_HOST_PORT,"");
//服务器监听IP:端口
String[] servers = { memcache_host };
Integer[] weights = { 1 };
// 获取socke连接池的实例对象
SockIOPool pool = SockIOPool.getInstance();
// 设置服务器信息
pool.setServers(servers);
pool.setWeights(weights);
// 设置初始连接数、最小和最大连接数以及最大处理时间
pool.setInitConn(50);
pool.setMinConn(50);
pool.setMaxConn(1000);
pool.setMaxIdle(1000 * 60 * 60 * 6);
// 设置主线程的睡眠时间
pool.setMaintSleep(30);
// 设置TCP的参数,连接超时等
pool.setNagle(false);
pool.setSocketTO(3000);
pool.setSocketConnectTO(0);
// 初始化连接池
pool.initialize();
}
/**
* 保护型构造方法,不允许实例化!
*
*/
protectedMemcacheTaskJob() {}
/**
* 获取唯一实例.
*
*@return
*/
publicstatic MemcacheTaskJob getInstance() {
return memCachedManager;
}
/**
* 执行定时任务每1天执行一次用户与账户数据查询,并保存到memcache服务器中,同时清空前一天的memcache数据缓存。
* @throws MyException
*/
publicvoid executeSearch(){
try{
/**
* 查询最近一周访问过系统的用户CUSTID列表存储到memcache中。
* where DATE_SUB(CURDATE(), INTERVAL 7 DAY) <=date(logindate)
* #最近一个月
* whereDATE_SUB(CURDATE(), INTERVAL INTERVAL 1 MONTH) <= date(logindate);
*/
Stringmemcache_close = ParamUtils.getParamValue(ParamConstants.MEMCACHE_CLOSE,"");
List<CustomerBean>ul = new ArrayList<CustomerBean>();
if(memcache_close!= null && Integer.valueOf(memcache_close) == 1){
ul= customerManager.findMemcacheUserList();
if(mcc.get("customerList")== null){
//log.info("newadd memcache");
mcc.add("customerList",ul);
}else{
//log.info("replacememcache");
mcc.replace("customerList",ul);
}
}else{
mcc.add("customerList",ul);
}
//删除一周以前的登陆用户信息
loginInfoManager.delBySession();
log.info("定时更新memcache中活跃用户数据,是否存在数据列表:"+mcc.keyExists("customerList")+",存在的活跃用户数:"+((List)mcc.get("customerList")).size());
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 添加一个指定的值到缓存中.
*
* @paramkey
* @paramvalue
*@return
*/
publicboolean add(String key, Object value) {
return mcc.add(key, value);
}
publicboolean add(String key, Object value, Date expiry) {
return mcc.add(key, value, expiry);
}
publicboolean replace(String key, Object value) {
return mcc.replace(key, value);
}
publicboolean replace(String key, Object value, Date expiry) {
return mcc.replace(key, value, expiry);
}
/**
* 根据指定的关键字获取对象.
*
* @paramkey
*@return
*/
publicObject get(String key) {
return mcc.get(key);
}
publicstatic void main(String[] args) {
MemcacheTaskJob cache =MemcacheTaskJob.getInstance();
cache.add("hello", 234);
System.out.print("get value : " +cache.get("hello"));
}
publicCustomerManager getCustomerManager() {
returncustomerManager;
}
publicvoid setCustomerManager(CustomerManager customerManager) {
this.customerManager= customerManager;
}
publicCustomerBean getCustomerBean() {
returncustomerBean;
}
publicvoid setCustomerBean(CustomerBean customerBean) {
this.customerBean= customerBean;
}
publicstatic MemcacheTaskJob getMemCachedManager() {
returnmemCachedManager;
}
publicstatic void setMemCachedManager(MemcacheTaskJob memCachedManager) {
MemcacheTaskJob.memCachedManager= memCachedManager;
}
publicLoginInfoManager getLoginInfoManager() {
returnloginInfoManager;
}
publicvoid setLoginInfoManager(LoginInfoManager loginInfoManager) {
this.loginInfoManager= loginInfoManager;
}
}
c) 新用户操作方法:
/**
* 把最新注册用户添加到memcache的缓存中
*/
MemcacheTaskJobcache = MemcacheTaskJob.getInstance();
if(cache.get("customerList") != null){
//增加到memcache中去
customerBean.setFreeIntegral(accountBean.getFreeIntegral());
List<CustomerBean>cl = (List<CustomerBean>) cache.get("customerList");
cl.add(customerBean);
cache.replace("customerList", cl);
log.info("register user "+customerBean.getLoginName()+" replacememcache!");
}