Memcached 作用与使用 基本介绍

1,对于缓存的存取方式,简言之,就是以键值对的形式将数据保存在内存中。在日常业务中涉及的操作无非就是增删改查。加入缓存机制后,查询的时候,对数据进行缓存,增删改的时候,清除缓存即可。这其中对于缓存的闭合就非常重要,如果缓存没有及时得到更新,那用户就会获取到过期数据,就会产生问题。

2,对于单一业务的缓存管理(数据库中只操作单表),只需生成一个key,查询时,使用key,置入缓存;增删改时,使用key,清除缓存。将key与表绑定,操作相对简单。

3,但是在现实业务中,更多的是对关联表的增删改查(数据库多表操作),业务之间互相关联,数据库中的某张表不止一个业务再进行操作,将缓存拦截在service层,对业务进行缓存,对多表进行缓存。

4,业务层缓存实现策略:

  4.1,在缓存中建立一个key为"union_query",value为“hashmap<prefix,uqversion>(‘简称uqmap’)“的缓存,prefix保存的是当前业务操作涉及到的数据库表名的组合(数据库表名的唯一性),使用‘|’分隔(例 prefix=“A|B”,此次业务将操作A表与B表),uqversion是业务版本号,从0开始递增。

  4.2,调用一个查询业务时,对数据进行缓存,设置operation为1,告诉cache对象,这是一个缓存操作,例如调用 queryAB(args[])方法时,cache对象切入,将prefix(即”A|B“)与uqversion(初始化为0),存入uqmap中进行缓存。

  4.3,将prefix,uqversion,方法明+参数,进行拼接,使用md5进行加密后作为一个key,将方法的结果集作为value,进行缓存。至此缓存成功。

  4.4,当第二个请求来调用queryAB时,cache对象切入,首先,查询uqmap对象,使用prefix找到对应的uqversion,然后,通过拼接加密获取key,最后取得结果集进行返回。

  4.5,当有一个updateA方法被调用时,设置operation为4,告诉cache对象,这是一个删除缓存的操作,此时prefix的值为“A”,cache对象切入,获取全局的uqmap,遍历其中的prefix,是否包含了表A的名称:如果包含,则更新此prefix的uqversion进行自增,uqversion一旦发生变化,4.3中组合的key将不复存在,业务缓存也就消失了。(对于复杂的updateAB方法,遍历prefix要复杂一点,可以实现)

  4.6,当第三个请求来调用queryAB时,可以获取到uqversion,组合成key后,但是没有对应的value。此时确定缓存不存在时,继续正常执行方法,获取结果集,返回给客户的同时,将结果集进行缓存。

5,对于缓存的操作,网上有三种api可以选择(memcached client forjava、spymemcached、xmemcached),具体的好坏,本人在这就不做分析。本人使用的是XMemcached api。

具体实现细节:

1,新建 @interface Annotation{ } 定义一个注解 @Annotation,一个注解是一个类。定义缓存策略。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 用于查找的时候,放置缓存信息
* @author shufeng
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface XmemCache{ /**
* 值为当前操作的表名,表名唯一
* 涉及到多表操作,使用|分隔
*/
String prefix() default ""; /*
* 缓存有效期 设置,单位为秒
* 指定间隔时间,默认值为3600秒(1小时)
* */
int interval() default 3600; /**
* 1 从cache里取值,如果未置入cache,则置入
* 2 replace cache value 未扩展
* 3 replace cache value,并返回旧值 未扩展
* 4 remove cache key 从cache里删除对应的缓存        
* 5 remove cache key 从cache里删除对应的缓存,并返回未删除之前的值 未扩展
**/
int operation() default 1;
}

2,memcache基础操作类,对一些常用方法进行封装,对memcachedclient进行配置

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeoutException; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean; import com.node.hlhw.rbac.api.constant.Constant; import net.rubyeye.xmemcached.GetsResponse;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.command.BinaryCommandFactory;
import net.rubyeye.xmemcached.exception.MemcachedException;
import net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator;
import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
import net.rubyeye.xmemcached.utils.AddrUtil; /**
* @author Melody shufeng
* 对memcachedclient进行封装,添加一下常用方法
*/
public class MemcachedOperate implements DisposableBean { /*
* timeout - Operation timeout,if the method is not returned in this
* time,throw TimeoutException timeout - operation timeout,in milliseconds
* exp - An expiration time, in seconds. Can be up to 30 days. After 30
* days, is treated as a unix timestamp of an exact date. value - stored
* data
*/
private static final Logger logger = LoggerFactory.getLogger(MemcachedOperate.class); private static Properties PROPERTIES = new Properties(); private static String MEMCACHED_SETTING = "memcached.properties"; private static MemcachedClient memcachedClient; public static MemcachedClient getClient(){
return memcachedClient;
} /**
* 静态代码块,类加载时,初始化缓存客户端
* 确保只创建一个client实例
* author shufeng
*/
static {
InputStream in = Object.class.getResourceAsStream("/" + MEMCACHED_SETTING);
try {
PROPERTIES.load(in);
} catch (IOException e) {
e.printStackTrace();
}
String servers = PROPERTIES.getProperty("memcached.servers", "");
if (null != servers && !"".equals(servers)) {
try {
logger.debug("启动memcached连接");
MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(servers));
builder.setConnectionPoolSize(100);
builder.setFailureMode(true);
builder.setCommandFactory(new BinaryCommandFactory());
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
builder.setTranscoder(new SerializingTranscoder());
memcachedClient = builder.build();
memcachedClient.setEnableHeartBeat(false); // 关闭心跳
memcachedClient.flushAll(); // 清空缓存
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (MemcachedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* @param key
* @return 获取value
*/
public static Object get(String key) {
Object object = null;
try {
object = memcachedClient.get(key);
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (MemcachedException e) {
e.printStackTrace();
}
return object;
} public static void setWithNoReply(String key, int exp, Object value) {
try {
memcachedClient.setWithNoReply(key, exp, value);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (MemcachedException e) {
e.printStackTrace();
}
} /**
* 查询联表的业务版本号 如果为空,则初始化
*
* @param prefix
* @return
*/
@SuppressWarnings("unchecked") public static Long getUnionQueryVersion(String prefix) {
try {
Map<String, Long> uqmap = null;
GetsResponse<Object> getsresponse = memcachedClient.gets(Constant.UNION_QUERY);
if (getsresponse == null) {
uqmap = new HashMap<String, Long>();
Long uqversion = new Long(1); // 初始化版本号
uqmap.put(prefix, uqversion);
if (memcachedClient.cas(Constant.UNION_QUERY, 0, uqmap, 0)) { // 检测插入之前是否被修改过
return uqversion; // 插入成功
} else { // 插入失败,说明在代码运行期间,已经有其他线程去修改了unionquery的缓存,重新进行查询
return getUnionQueryVersion(prefix);
}
} else { long cas = getsresponse.getCas();
Object uqobj = getsresponse.getValue();
if (uqobj == null) { // 不存在对象
uqmap = new HashMap<String, Long>();
Long uqversion = new Long(1); // 初始化版本号
uqmap.put(prefix, uqversion);
if (memcachedClient.cas(Constant.UNION_QUERY, 0, uqmap, cas)) { // 检测插入之前是否被修改过
return uqversion; // 插入成功
} else { // 插入失败,说明在代码运行期间,已经有其他线程去修改了unionquery的缓存,重新进行查询
return getUnionQueryVersion(prefix);
}
} else {
uqmap = (Map<String, Long>) uqobj;
Long uqversion = uqmap.get(prefix);
if (uqversion == null) { // 不存在此业务版本
uqversion = new Long(1); // 初始化版本号
uqmap.put(prefix, uqversion);
if (memcachedClient.cas(Constant.UNION_QUERY, 0, uqmap, cas)) { // 检测插入之前是否被修改过
return uqversion; // 插入成功
} else { // 插入失败,说明在代码运行期间,已经有其他线程去修改了unionquery的缓存,重新进行查询
return getUnionQueryVersion(prefix);
}
} else {
return uqversion;
} }
}
} catch (TimeoutException | InterruptedException | MemcachedException e) {
e.printStackTrace();
System.err.println("getUnionQueryVersion---Exception");
}
return 1L;
} /**
* 查询单表的业务版本号 如果为空,则初始化
*
* @return
*/
public static Long getVersion(String prefix) {
try {
GetsResponse<Object> getsresponse = memcachedClient.gets(prefix);
if (getsresponse == null) {
Long pfversion = new Long(1);
if (memcachedClient.cas(prefix, 0, pfversion, 0)) {
return pfversion;
} else {
return getVersion(prefix);
}
} else {
Object pfobj = getsresponse.getValue();
long cas = getsresponse.getCas();
if (pfobj == null) {
Long pfversion = new Long(1);
if (memcachedClient.cas(prefix, 0, pfversion, cas)) {
return pfversion;
} else {
return getVersion(prefix);
}
} else {
return (Long) pfobj;
}
}
} catch (TimeoutException | InterruptedException | MemcachedException e) {
e.printStackTrace();
System.err.println("getVersion---Exception");
} return 1L;
} /**
* shufeng 更新 多表版本号
* 由于存在线程安全问题 ,会覆盖uqmap,更新unionquery业务版本号
* 使用cas方法解决线程安全问题
* 更新unionquery中key包含p1或p2或p3的version
* @param prefix
*/
@SuppressWarnings("unchecked")
public static void updateUnionQueryVersion(String prefix) { try {
Map<String, Long> uqmap = null;
GetsResponse<Object> getsresponse = memcachedClient.gets(Constant.UNION_QUERY);
if (getsresponse == null) {
return;
} else {
Object uqobj = getsresponse.getValue();
long cas = getsresponse.getCas();
if (uqobj == null) {
return;
} else {
uqmap = (HashMap<String, Long>) uqobj;
Set<String> uqset = uqmap.keySet(); // 遍历unionquery中的key
Iterator<String> quit = uqset.iterator();
String uqkey = "";
boolean uqflag = false;
while (quit.hasNext()) {
uqkey = quit.next();
if (("|" + uqkey + "|").contains("|" + prefix + "|")) { // key中包含prefix
uqmap.put(uqkey, uqmap.get(uqkey) + 1); // 更新map
uqflag = true;
}
}
if (uqflag) {
if (!memcachedClient.cas(Constant.UNION_QUERY, 0, uqmap, cas)) {
updateUnionQueryVersion(prefix);
}
}
}
}
} catch (TimeoutException | InterruptedException | MemcachedException e) {
e.printStackTrace();
System.err.println("updateUnionQueryVersion---Exception");
}
} /**
* 更新单表版本号
*
* @param prefix
* @return
*/
public static void updateVersion(String prefix) { try {
GetsResponse<Object> getsresponse;
getsresponse = memcachedClient.gets(prefix); if (getsresponse == null) {
return ;
} else {
Object pfobj = getsresponse.getValue();
long cas = getsresponse.getCas();
if (pfobj == null) {
return ;
} else {
Long pfversion = (Long) pfobj;
pfversion += 1;
if (!memcachedClient.cas(prefix, 0, pfversion, cas)) {
updateVersion(prefix);
}
}
}
} catch (TimeoutException | InterruptedException | MemcachedException e) {
e.printStackTrace();
System.err.println("updateVersion---Exception");
}
} public void shutdown() {
try {
memcachedClient.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void destroy() throws Exception {
shutdown();
} }

3,结合spring aop 配置缓存,使用spring aop来切入业务层加入缓存,与业务进行解耦。使用注解进行方便配置。

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSON;
import com.node.hlhw.common.cache.XmemCache;
import com.node.hlhw.common.digest.Md5Utils; @Component
@Aspect
public class MemcachedAop { @Pointcut("execution (* com.node.hlhw.*.service.impl.*.*(..))")
public void pointcut() {
} // 方法执行前调用
@Before("pointcut()")
public void before() {
} // 方法执行的前后调用
/**
*
* 改进建议:使用uuid作为版本号,减少版本号的读取,直接生成uuid,进行缓存
* 线程安全问题:存在线程安全问题,但是针对于缓存,问题不大。
* 多线程同一时间重复覆盖一个业务id,还是可以更新缓存
*
* @param call
* @throws Throwable
*/
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint call) throws Throwable {
Object result = null; // 检测是否存在memcached客户端实例
if (MemcachedOperate.getClient() == null) {
System.err.println("memcached client not exist");
result = call.proceed();
return result;
}
Signature signature = call.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod(); if(!method.isAnnotationPresent(XmemCache.class)){
result = call.proceed();
return result;
}
XmemCache xmemcache = method.getAnnotation(XmemCache.class); // 获取操作方法
int operation = xmemcache.operation();
// 获取注解前缀,实际使用是为各个业务包名称,一般为表名
String prefix = xmemcache.prefix();
// 无前缀
if(prefix==null||"".equals(prefix)){
result = call.proceed();
return result;
} // 获取注解配置memcached死亡时间 秒单位
int interval = xmemcache.interval();
switch (operation) {
case 1: // 1 从cache里取值,如果未置入cache,则置入
// 判断prefix是否涉及多表,查看是否包含|
if (prefix.contains("|")) {
Long uqversion = MemcachedOperate.getUnionQueryVersion(prefix);
String combinedkey = generCombinedKey(prefix, uqversion, method, call.getArgs());
Object resultobj = MemcachedOperate.get(combinedkey);
if(resultobj == null){
result = call.proceed();
MemcachedOperate.setWithNoReply(combinedkey, interval, JSON.toJSONString(result));// 缓存数据
}else{
Class<?> returnType = ((MethodSignature) signature).getReturnType();
result = JSON.parseObject(resultobj.toString(), returnType);
} } else { // 单表操作 Long pfversion = MemcachedOperate.getVersion(prefix);
String combinedkey = generCombinedKey(prefix, pfversion, method, call.getArgs());
Object resultobj = MemcachedOperate.get(combinedkey);
if(resultobj == null){
result = call.proceed();
MemcachedOperate.setWithNoReply(combinedkey, interval, JSON.toJSONString(result));// 缓存数据
}else{
Class<?> returnType = ((MethodSignature) signature).getReturnType();
result = JSON.parseObject(resultobj.toString(), returnType);
}
}
break;
case 2: // 2 replace cache value
break;
case 3:
break;
case 4: // 4 remove cache key 从cache里删除对应 业务版本的缓存
/*
* 更新unionquery业务版本号
* 0,切割 prefix为p1、p2、p3
* 1,更新prefix为p1或p2或p3的version
* 2,更新unionquery中key包含p1或p2或p3的version
*/
if (prefix.contains("|")) { // 表示涉及到多表,需要清除 单表的缓存,与联表中 包含 当前部分的 缓存
String[] prefixs = prefix.split("\\|"); // 0.切割 prefix为p1、p2、p3
for(String pf : prefixs){
MemcachedOperate.updateVersion(pf); // 1,更新prefix为p1或p2或p3的version
MemcachedOperate.updateUnionQueryVersion(pf);
}
}else{ // 没有涉及到多表的时候
MemcachedOperate.updateVersion(prefix);
MemcachedOperate.updateUnionQueryVersion(prefix);
}
result = call.proceed();
break;
default:
result = call.proceed();
break;
}
return result;
} /**
* 组装key值
* @param key
* @param version
* @param method
* @param args
* @return
*/
private String generCombinedKey(String key, Long version, Method method, Object[] args) {
StringBuffer sb = new StringBuffer();
// 获取方法名
String methodName = method.getName();
// 获取参数类型
Object[] classTemps = method.getParameterTypes();
// 存入方法名
sb.append(methodName); for (int i = 0; i < args.length; i++) {
sb.append(classTemps[i] + "&");
if (null == args[i]) {
sb.append("null");
} else if ("".equals(args[i])) {
sb.append("*");
} else {
String tt = JSON.toJSONString(args[i]);
sb.append(tt);
}
}
sb.append(key);
sb.append(version.toString());
String temp = Md5Utils.getMD5(sb.toString());
return temp; }
}

4,properties文件中配置memcached服务器地址

#host1:port1,host2:port2
memcached.servers=192.168.1.1:11211,192.168.1.2:11211

5,修改spring配置文件,声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。

<aop:aspectj-autoproxy proxy-target-class="true"/>

6,service层使用注解方式切入缓存

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map; import org.apache.ibatis.session.RowBounds;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.config.annotation.Service;
import com.node.hlhw.common.cache.XmemCache;
import com.node.hlhw.common.digest.ApplicationUtils;
import com.node.hlhw.core.service.BaseService;
import com.node.hlhw.core.store.IBaseStore;
import com.node.hlhw.core.store.PageParam;
import com.node.hlhw.rbac.api.dao.UserRoleDao;
import com.node.hlhw.rbac.api.entity.UserRole;
import com.node.hlhw.rbac.api.service.UserRoleService; /**
* @author Melody
* 处理用户角色
*/
@Service(version = "1.0.0")
public class UserRoleServiceImpl extends BaseService<UserRole> implements
UserRoleService { private static final Logger logger = Logger
.getLogger(UserRoleServiceImpl.class); @Autowired
public UserRoleDao userRoleDao; @Override
protected IBaseStore<UserRole> getBaseDao() {
return userRoleDao;
} /*
* 单表操作,prefix为表名,operation为4,只进行缓存的删除操作
*/
@XmemCache(prefix="userrole",operation=4)
public void insertUserRole(UserRole userRole) throws Exception {
userRoleDao.insertUserRole(userRole);
logger.info("插入用户角色数据");
} /* (non-Javadoc)
* 此方法操作了两个表,role与userole,使用‘|’进行分隔
* operation为1,表示缓存操作,对结果集进行缓存
* interval表示缓存时间默认不填为3600秒,也可指定具体时长
*/
@Override
@XmemCache(prefix="role|userrole",interval=3600 , operation=1)
public List<Map<String, Object>> selectUserRoleList(UserRole userrole, PageParam pageParam) throws Exception {
RowBounds rowBounds = new RowBounds(pageParam.getOffset(),pageParam.getLimit());
List<Map<String, Object>> list = userRoleDao.selectUserRoleList(userrole,rowBounds);
return list ;
} @Override
@XmemCache(prefix="userrole" , operation=4)
public void modifyUserRole(UserRole userrole, String[] roleids)throws Exception { //删除所包含的角色
userRoleDao.deleteByUserRole(userrole);
for(String roleid : roleids){
if(!StringUtils.isEmpty(roleid)){
userrole.setCreatetime(new Date());
userrole.setRoleid(roleid);
userrole.setUuid(ApplicationUtils.getUUID());
userRoleDao.insertUserRole(userrole);
}
} } @Override
@XmemCache(prefix="userrole" , operation=1)
public boolean existsRef(String roleids)throws Exception {
String [] roleid = roleids.split(",");
List<String> roleidlist = Arrays.asList(roleid);
return userRoleDao.existsRef(roleidlist)>0?true:false;
}
}

  

以上代码就是对业务层缓存的实现,小弟认为上述代码还存在许多问题,可能还隐藏着许多bug,还需要在实践中进行不断改进,还希望各路大神感兴趣的朋友,多多提点

  

spring aop + xmemcached 配置service层缓存策略的更多相关文章

  1. 基于注解的Spring AOP的配置和使用

    摘要: 基于注解的Spring AOP的配置和使用 AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不 ...

  2. Spring AOP 不同配置方式产生的冲突问题

    Spring AOP的原理是 JDK 动态代理和CGLIB字节码增强技术,前者需要被代理类实现相应接口,也只有接口中的方法可以被JDK动态代理技术所处理:后者实际上是生成一个子类,来覆盖被代理类,那么 ...

  3. spring AOP为什么配置了没有效果?

     spring Aop的配置一定要配置在springmvc配置文件中         springMVC.xml 1 <!-- AOP 注解方式 :定义Aspect --> <!-- ...

  4. 【原】Spring AOP实现对Redis的缓存同步

    前言:刚开始采用spring cache作为缓存数据,到后面发现扩展性不灵活,于是基于sprig cache原理自定义一套规则用于缓存数据. 请求过程: 根据请求参数生成Key,后面我们会对生成Key ...

  5. Spring AOP 事务配置(实现转账事务)

    1. 事务特性 事务特性:ACID 原子性:整体 [原子性是指事务包含的所有操作要么全部成功,要么全部失败] 一致性:数据 [一个事务执行之前和执行之后都必须处于一致性状态] 隔离性:并发 [对于任意 ...

  6. 基于注解的Spring AOP的配置和使用--转载

    AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...

  7. spring事务到底用于service层还是dao层

    Spring事务为业务逻辑进行事务管理,保证业务逻辑上数据的原子性. 事务得根据项目性质来细分:事务可以设置到三个层面(dao层.service层和web层). 第一:web层事务,这一般是针对那些安 ...

  8. spring aop 加在Controller层造成类初始化两遍

    写一个测试项目,在配置动态数据源的时候采用的AOP切面到Controller层中,根据参数判断是否切合数据源,结果发现,每次Controller层的类都会初始化两次! 后来测试发现,把切面放到Serv ...

  9. spring---aop(5)---Spring AOP的配置的背后的配置

    写在前面 Spring AOP中Pointcut,dvice 和 Advisor三个概念 1)切入点 Pointcut 在介绍Pointcut之前,有必要先介绍 Join Point(连接点)概念. ...

随机推荐

  1. 调用Live555接收RTSP直播流,转换为Http Live Streaming(iOS直播)协议

    Live555接收RTSP直播流,转换Http Live Streaming(iOS直播)协议 RTSP协议也是广泛使用的直播/点播流媒体协议,之前实现过一个通过live555接收RTSP协议,然后转 ...

  2. 弹性盒布局display:flex详解

    一:弹性盒子 随着响应式设计的流行,网站开发者在设计网页布局时往往要考虑到页面在适配不同分辨率的浏览器时其内部组件位置大小都会产生变化,因此需要设计者根据窗口尺寸来调整布局,从而改变组件的尺寸和位置, ...

  3. linux centos7.0安装subversion

    安装环境以及软件版本如下: subversion使用1.8.17版本,CentOS7.0(64位) 安装svn共需要使用如下软件,apr-1.5.2.tar.gz.apr-util-1.5.4.tar ...

  4. 用php+ajax新建流程(请假、进货、出货等)

    对管理者来说,电脑操作的请假流程要比人工请假流程的效率高出很多,那么如何新建这个流程呢? 首先:需要明确需要操作哪些流程? 其次:每一个流程需要那些人员来执行? 最后:执行该流程人员的顺序是什么?   ...

  5. 元类(meta class)

    元类(meta class),这个名字想必很多人都听过,网上也有很多关于元类的介绍,今天我就按照自己这两天的理解来简单探讨一下这个玩意,有误之处还望指出. 首先,下载objc源码,源码地址:https ...

  6. php实现题目抢答、商品秒杀等类型的需求

    最近和其他部门合作项目,当然我是负责php接口方面的工作,get到一些东西,所以来分享记录一下. 项目需求: 题目将通过主持人ipad投射至大屏幕,选手按'抢答'按钮进行抢答.抢答成功,选手所在组,以 ...

  7. 详解Linux进程(作业)的查看和杀死

    目录: 引入进程 进程 线程 PS命令 TOP命令 其他查看进程命令 进程的优先级 作业控制机制 kill命令 一.引入进程 1.内存划分为:用户空间和内核空间 1.在用户空间里运行的进程,就是用户进 ...

  8. C# DataTable转换成实体列表 与 实体列表转换成DataTable

    /// <summary> /// DataTable转换成实体列表 /// </summary> /// <typeparam name="T"&g ...

  9. Running R jobs quickly on many machines(转)

    As we demonstrated in “A gentle introduction to parallel computing in R” one of the great things abo ...

  10. Drools 6.5 Final 入门

    Drools 6.5 Final学习笔记 最近项目中要涉及到使用规则对数据进行操作,想到自己实现一个完整且能灵活满足业务需求的规则系统太难了,就想了解一下有没有开源的规则引擎可以使用,后来发现Droo ...