如何选择高速存储、查询数据库

就今分布式搭越来越流行,在许多景象下要用及分布式锁。分布式锁的贯彻有广大栽,比如根据数据库、
zookeeper 等,本文主要介绍以 Redis 做分布式锁之计,并封装成spring
boot starter,方便使用

作者:方圆
链接:https://www.zhihu.com/question/20010554/answer/15863274
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

 

平、 PostgreSQL 的安澜极强, Innodb
等引擎在倒、断电之类的不幸场景下抗打击能力来矣长足进步,然而不少 MySQL
用户还赶上过Server级的数据库丢失的气象——mysql系统库是MyISAM的,相比之下,PG数据库及时方面如果好有的。
次、任何系统都产生其的特性极限,每当大并发读写,负载逼近极限下,PG的性能指标仍可维持双曲线甚至对数曲线,到极限其后不再下降,而
MySQL
明显出现一个波峰后下降(5.5本之后,在商家级版本中发出个插件可以改善广大,不过需要付费)。
其三、PG 多年来当 GIS
领域处于优势地位,因为她来添加的几乎哪里类型,实际上不止几哪类型,PG有恢宏字典、数组、bitmap
等数据类型,相比之下mysql就差多,instagram就是坐PG的空间数据库扩展POSTGIS远远强吃MYSQL的my
spatial而使PGSQL的。
季、PG 的“无锁定”特性非常突出,甚至包括 vacuum
这样的整治数据空间的操作,这个与PGSQL的MVCC实现有关系。
五、PG
的好用函数和极索引,这让PG数据库的调优非常灵活,mysql就从未这效果,条件索引在web应用被那个要紧。
六、PG有最大胆的 SQL 编程能力(9.x
图灵完备,支持递归!),有非常丰富的统计函数和统计语法支持,比如分析函数(ORACLE的叫法,PG里吃window函数),还可以用多种语言来描写存储过程,对于R的支持为要命好。这或多或少臻MYSQL就差之充分远,很多分析功能还不支持,腾讯内部数据存储主要是MYSQL,但是数量解析主要是HADOOP+PGSQL(听李元佳说过,但是尚未征了)。
七、PG 的产生强集群架构可以选取,plproxy
可以支持语句级的镜像或分片,slony 可以开展字段级的联手设置,standby
可以构建WAL文件级或流式的读写分离集群,同步频率与集群策略调整惠及,操作非常简单。
八、一般涉及项目数据库的字符串有限量长度8k横,无限长 TEXT
类型的力量受限,只能当作外部大数额访问。而 PG 的 TEXT
类型可以一直看,SQL语法内置正则表达式,可以索引,还可全文检索,或使xml
xpath。用PG的言语,文档数据库都好省了。
九,对于WEB应用来说,复制的特征很要紧,mysql到现在为是异步复制,pgsql可以就并,异步,半一同复制。还有mysql的同台是基于binlog复制,类似oracle
golden
gate,是根据stream的复制,做到一起很不方便,这种措施更适合异地复制,pgsql的复制基于wal,可以完成同复制。同时,pgsql还提供stream复制。
十,pgsql对于numa架构的支持比mysql强有,比MYSQL对于读之属性更好有,pgsql提交可以完全异步,而mysql的外存表不敷实用(因为表锁的因由)
末段说一样下自家感到 PG 不如 MySQL 的地方。
第一,MySQL有一对实用的运维支持,如 slow-query.log
,这个pg肯定可以定制出来,但是如果可以安排利用就重新好了。
亚凡是mysql的innodb引擎,可以尽量优化利用系统所有内存,超大内存下PG对内存以的免那么尽管,
其三触及,MySQL的复制可用一连串从仓库,但是于9.2之前,PGSQL不克为此由库带从库。
季触及,从测试结果高达看,mysql
5.5之习性提升大要命,单机性能高被pgsql,5.6应当会强更多.
第五接触,对于web应用来说,mysql 5.6 的放开MC
API功能非常好用,PGSQL差有。
另外一些:
pgsql和mysql都是冷有买卖公司,而且还无是一个供销社。大部分开发者,都是将工资的。
说mysql的履进度较pgsql快多是颠三倒四的,速度接近,而且多辰光在你的部署。
对仓储过程,函数,视图之类的作用,现在个别个数据库都得以支持了。
此外多线程架构和多进程架构之间莫断的优劣,oracle在unix上是基本上进程架构,在windows上是多线程架构。
成千上万pg应用也是24/7之下,比如skype. 最近几只版VACUUM基本未影响PGSQL
运行,8.0过后的PGSQL不待cygwin就可以在windows上运行。
关于说对于工作的支持,mysql和pgsql都尚未问题。

一. Redis 分布式锁的实现和存在的题目

吊是针对性某资源,保证其访问的互斥性,在事实上行使中,这个资源一般是一个字符串。使用 Redis 实现锁,主要是以资源放到 Redis 当中,利用该原子性,当其他线程访问时,如果 Redis 中曾经是是资源,就未允后的片段操作。spring
boot行使 Redis 的操作主要是由此 RedisTemplate 来促成,一般步骤如下:

以锁资源放入 Redis (注意是当key不有时时才能够扩成功,所以使用 setIfAbsent 方法):

redisTemplate.opsForValue().setIfAbsent("key", "value");

装过时

redisTemplate.expire("key", 30000, TimeUnit.MILLISECONDS);

释放锁

redisTemplate.delete("key");

相似情况下,这样的兑现就能满足锁的要求了,但是要在调用 setIfAbsent 方法后线程挂掉了,即没有被锁定的资源设置过时,默认是毫不过期,那么这个锁就是会见一直在。所以待确保设置锁及其过期时少于单操作的原子性,spring
data的 RedisTemplate 当中并从未这么的主意。但是当jedis当中是发这种原子操作的章程的,需要通过 RedisTemplate 的 execute 方法得到到jedis里操作命令的目标,代码如下:

String result = redisTemplate.execute(new RedisCallback<String>() {    @Override
    public String doInRedis(RedisConnection connection) throws DataAccessException {
        JedisCommands commands = (JedisCommands) connection.getNativeConnection();        return commands.set(key, "锁定的资源", "NX", "PX", expire);
    }
});

注意: Redis 从2.6.12版本开始 set 命令支持 NX 、 PX 这些参数来达成 setnx 、 setex 、 psetex 命令的成效,文档参见: http://doc.redisfans.com/string/set.html

NX: 表示除非当锁定资源不在的时才能够 SET 成功。利用 Redis 的原子性,保证了只有首先单请求的线程才会获锁,而其后的有线程在锁定资源为释放前都未能够得到锁。

PX: expire 代表锁定的资源的机动过期时,单位凡毫秒。具体过期时根据实际状况而迟早

这么于获锁的下便可知管设置 Redis 值和过时的原子性,避免前面提到的一定量不善 Redis 操作中出现意外而招致的吊不可知自由的题材。但是这样要可能会见设有一个题目,考虑如下的状况顺序:

线程T1获取锁

线程T1履工作操作,由于一些原因阻塞了较长时间

吊自动过期,即钉自动释放了

线程T2获取锁

线程T1事务操作完,释放锁(其实是假释的线程T2的锁)

按这样的气象顺序,线程T2的事情操作实际即便没沿提供保障体制了。所以,每个线程释放锁的上只能放自己的吊,即钉得使出一个拥有者的标记,并且为急需保证释放锁的原子性操作。

据此当赢得锁之早晚,可以很成一个自由不唯的错放入当前线程中,然后还放入 Redis 。释放锁的时先判断锁对应之价值是否和线程中的价相同,相同时才做去操作。

Redis 从2.6.0始发通过嵌入的 Lua 解释器,可以以 EVAL 命令对 Lua 脚本进行求值,文档参见: http://doc.redisfans.com/script/eval.html

故我们可由此 Lua 脚本来达到释放锁的原子操作,定义 Lua 脚本如下:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])else
    return 0end

现实意思可以参见上面提供的文档地址

动用 RedisTemplate 执行的代码如下:

// 使用Lua脚本删除Redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁// spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本Long result = redisTemplate.execute(new RedisCallback<Long>() {    public Long doInRedis(RedisConnection connection) throws DataAccessException {
        Object nativeConnection = connection.getNativeConnection();        // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
        // 集群模式
        if (nativeConnection instanceof JedisCluster) {            return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
        }        // 单机模式
        else if (nativeConnection instanceof Jedis) {            return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
        }        return 0L;
    }
});

代码中分成集群模式以及单机模式,并且两者的不二法门、参数还无异,原因是spring封装的实施脚本的方法吃( RedisConnection 接口继承给 RedisScriptingCommands 接口的 eval 方法),集群模式之办法直接抛来了不支持实施脚本的慌(虽然事实上是支撑的),所以不得不拿到 Redis 的connection来推行脚本,而 JedisCluster 和 Jedis中的方而尚未兑现联机之接口,所以不得不分开调用。

spring封装的集群模式推行脚本方法源码:

# JedisClusterConnection.java/**
 * (non-Javadoc)
 * @see org.springframework.data.redis.connection.RedisScriptingCommands#eval(byte[], org.springframework.data.redis.connection.ReturnType, int, byte[][])
 */
@Override
public <T> T eval(byte[] script, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {    throw new InvalidDataAccessApiUsageException("Eval is not supported in cluster environment.");}

时至今日,我们就算到位了一个对立可靠的 Redis 分布式锁,但是,在集群模式的无限情况下,还是可能会见在有的题目,比如如下的面貌顺序( 正文暂时不深入开展 ):

线程T1获取取锁成功

Redis 的master节点挂掉,slave自动顶上

线程T2到手取锁,会自slave节点上去判断锁是否是,由于Redis的master
slave复制是异步的,所以此时线程T2可能成功取到锁

为了好下扩展为使另外方法来贯彻分布式锁,定义了接口及抽象类,所有的源码如下:

# DistributedLock.java 顶级接口/**
 * @author fuwei.deng
 * @date 2017年6月14日 下午3:11:05
 * @version 1.0.0
 */public interface DistributedLock {    
    public static final long TIMEOUT_MILLIS = 30000;    
    public static final int RETRY_TIMES = Integer.MAX_VALUE;    
    public static final long SLEEP_MILLIS = 500;    public boolean lock(String key);    
    public boolean lock(String key, int retryTimes);    
    public boolean lock(String key, int retryTimes, long sleepMillis);    
    public boolean lock(String key, long expire);    
    public boolean lock(String key, long expire, int retryTimes);    
    public boolean lock(String key, long expire, int retryTimes, long sleepMillis);    
    public boolean releaseLock(String key);
}

# AbstractDistributedLock.java 抽象类,实现基本的方法,关键方法由子类去实现/**
 * @author fuwei.deng
 * @date 2017年6月14日 下午3:10:57
 * @version 1.0.0
 */public abstract class AbstractDistributedLock implements DistributedLock {    @Override
    public boolean lock(String key) {
        return lock(key, TIMEOUT_MILLIS, RETRY_TIMES, SLEEP_MILLIS);
    }    @Override
    public boolean lock(String key, int retryTimes) {
        return lock(key, TIMEOUT_MILLIS, retryTimes, SLEEP_MILLIS);
    }    @Override
    public boolean lock(String key, int retryTimes, long sleepMillis) {
        return lock(key, TIMEOUT_MILLIS, retryTimes, sleepMillis);
    }    @Override
    public boolean lock(String key, long expire) {
        return lock(key, expire, RETRY_TIMES, SLEEP_MILLIS);
    }    @Override
    public boolean lock(String key, long expire, int retryTimes) {
        return lock(key, expire, retryTimes, SLEEP_MILLIS);
    }

}

# RedisDistributedLock.java Redis分布式锁的实现import java.util.ArrayList;import java.util.List;import java.util.UUID;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.dao.DataAccessException;import org.springframework.data.redis.connection.RedisConnection;import org.springframework.data.redis.core.RedisCallback;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.util.StringUtils;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisCluster;import redis.clients.jedis.JedisCommands;/**
 * @author fuwei.deng
 * @date 2017年6月14日 下午3:11:14
 * @version 1.0.0
 */public class RedisDistributedLock extends AbstractDistributedLock {    
    private final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);    
    private RedisTemplate<Object, Object> redisTemplate;    
    private ThreadLocal<String> lockFlag = new ThreadLocal<String>();    
    public static final String UNLOCK_LUA;    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }    public RedisDistributedLock(RedisTemplate<Object, Object> redisTemplate) {        super();        this.redisTemplate = redisTemplate;
    }

    @Override    public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {        boolean result = setRedis(key, expire);        // 如果获取锁失败,按照传入的重试次数进行重试
        while((!result) && retryTimes-- > 0){            try {
                logger.debug("lock failed, retrying..." + retryTimes);
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {                return false;
            }
            result = setRedis(key, expire);
        }        return result;
    }    
    private boolean setRedis(String key, long expire) {        try {
            String result = redisTemplate.execute(new RedisCallback<String>() {
                @Override                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                    String uuid = UUID.randomUUID().toString();
                    lockFlag.set(uuid);                    return commands.set(key, uuid, "NX", "PX", expire);
                }
            });            return !StringUtils.isEmpty(result);
        } catch (Exception e) {
            logger.error("set redis occured an exception", e);
        }        return false;
    }

    @Override    public boolean releaseLock(String key) {        // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
        try {
            List<String> keys = new ArrayList<String>();
            keys.add(key);
            List<String> args = new ArrayList<String>();
            args.add(lockFlag.get());            // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
            // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本

            Long result = redisTemplate.execute(new RedisCallback<Long>() {                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    Object nativeConnection = connection.getNativeConnection();                    // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                    // 集群模式
                    if (nativeConnection instanceof JedisCluster) {                        return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }                    // 单机模式
                    else if (nativeConnection instanceof Jedis) {                        return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }                    return 0L;
                }
            });            
            return result != null && result > 0;
        } catch (Exception e) {
            logger.error("release lock occured an exception", e);
        }        return false;
    }

}

 

二. 基于 AOP 的 Redis 分布式锁

当事实上的采用过程被,分布式锁得包好后下于术级别,这样虽甭每个地方还失去抱锁和释放锁,使用起来更有益。

第一定义个注解:

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 fuwei.deng
 * @date 2017年6月14日 下午3:10:36
 * @version 1.0.0
 */@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic @interface RedisLock {    /** 锁的资源,redis的key*/
    String value() default "default";    
    /** 持锁时间,单位毫秒*/
    long keepMills() default 30000;    
    /** 当获取失败时候动作*/
    LockFailAction action() default LockFailAction.CONTINUE;    
    public enum LockFailAction{        /** 放弃 */
        GIVEUP,        /** 继续 */
        CONTINUE;
    }    
    /** 重试的间隔时间,设置GIVEUP忽略此项*/
    long sleepMills() default 200;    
    /** 重试次数*/
    int retryTimes() default 5;
}

配分布式锁之bean

import org.springframework.boot.autoconfigure.AutoConfigureAfter;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.RedisTemplate;import com.itopener.lock.redis.spring.boot.autoconfigure.lock.DistributedLock;import com.itopener.lock.redis.spring.boot.autoconfigure.lock.RedisDistributedLock;/**
 * @author fuwei.deng
 * @date 2017年6月14日 下午3:11:31
 * @version 1.0.0
 */@Configuration@AutoConfigureAfter(RedisAutoConfiguration.class)
public class DistributedLockAutoConfiguration {

    @Bean
    @ConditionalOnBean(RedisTemplate.class)
    public DistributedLock redisDistributedLock(RedisTemplate<Object, Object> redisTemplate){        return new RedisDistributedLock(redisTemplate);
    }

}

概念切面(spring boot配置方式)

import java.lang.reflect.Method;import java.util.Arrays;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.AutoConfigureAfter;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.context.annotation.Configuration;import org.springframework.util.StringUtils;import com.itopener.lock.redis.spring.boot.autoconfigure.annotations.RedisLock;import com.itopener.lock.redis.spring.boot.autoconfigure.annotations.RedisLock.LockFailAction;import com.itopener.lock.redis.spring.boot.autoconfigure.lock.DistributedLock;/**
 * @author fuwei.deng
 * @date 2017年6月14日 下午3:11:22
 * @version 1.0.0
 */@Aspect@Configuration@ConditionalOnClass(DistributedLock.class)@AutoConfigureAfter(DistributedLockAutoConfiguration.class)public class DistributedLockAspectConfiguration {    
    private final Logger logger = LoggerFactory.getLogger(DistributedLockAspectConfiguration.class);    
    @Autowired
    private DistributedLock distributedLock;    @Pointcut("@annotation(com.itopener.lock.redis.spring.boot.autoconfigure.annotations.RedisLock)")
    private void lockPoint(){

    }    
    @Around("lockPoint()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        RedisLock redisLock = method.getAnnotation(RedisLock.class);
        String key = redisLock.value();        if(StringUtils.isEmpty(key)){
            Object[] args = pjp.getArgs();
            key = Arrays.toString(args);
        }
        int retryTimes = redisLock.action().equals(LockFailAction.CONTINUE) ? redisLock.retryTimes() : 0;
        boolean lock = distributedLock.lock(key, redisLock.keepMills(), retryTimes, redisLock.sleepMills());        if(!lock) {
            logger.debug("get lock failed : " + key);            return null;
        }        
        //得到锁,执行方法,释放锁
        logger.debug("get lock success : " + key);        try {            return pjp.proceed();
        } catch (Exception e) {
            logger.error("execute locked method occured an exception", e);
        } finally {
            boolean releaseResult = distributedLock.releaseLock(key);
            logger.debug("release lock : " + key + (releaseResult ? " success" : " failed"));
        }        return null;
    }
}

spring boot
starter还待以 resources/META-INF 中上加 spring.factories 文件

# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itopener.lock.redis.spring.boot.autoconfigure.DistributedLockAutoConfiguration,\
com.itopener.lock.redis.spring.boot.autoconfigure.DistributedLockAspectConfiguration

这样封装后,使用spring
boot开发之类别,直接依赖之starter,就可以当措施上加以 RedisLock 注解来兑现分布式锁之功用了,当然如果欲协调主宰,直接注入分布式锁之bean即可

@Autowiredprivate DistributedLock distributedLock;

若急需采取其他的分布式锁实现,继承 AbstractDistributedLock 后实现获取锁和释放锁的方即可

源码地址
https://gitee.com/itopener/springboot (目录:itopener-parent /
spring-boot-starters-parent / lock-redis-spring-boot-starter-parent)

文章来源:https://my.oschina.net/dengfuwei/blog/1600681

更多参考情节:http://www.roncoo.com/article/index?tn=SpringBoot

MySQL 处理树状回复的计划性会生复杂, 而且需要写过多代码, 而 Pg
可以快捷处理培养结构: 
http://www.slideshare.net/quipo/trees-in-the-database-advanced-data-structures

它可快速处理图结构, 轻松实现 “朋友的意中人的意中人” 这种力量:

FDW–它可以把 70 种外部数据源 (包括 Mysql, Oracle, CSV, hadoop …)
当成自己数据库被之表来查询: 
https://wiki.postgresql.org/wiki/FDW?nocache=1

图片 1

 

 

 

postgres数据库
性能测试:http://blog.csdn.net/bigbigtreewhu/article/details/51545288

postrgres数据库 高可用性,负载均衡,复制与集群方案介绍  :
https://my.oschina.net/liuyuanyuangogo/blog/497746

 

阿里SQL介绍: 
http://www.infoq.com/cn/news/2016/09/AliSQL-ali-cloud-AliSQL

TokuDB的性状验证 : http://www.tuicool.com/articles/vAbIFjb

阿里SQL测试报告样例 :
https://github.com/alibaba/AliSQL/wiki/AliSQL-Performance-benchmark

阿里SQL秒杀场景测试报告样例: 
https://github.com/alibaba/AliSQL/wiki/AliSQL-Performance-benchmark-for-inventory

发表评论

电子邮件地址不会被公开。 必填项已用*标注