何以抉择高速存款和储蓄、查询数据库

乘机未来分布式架构越来越流行,在诸多场景下须求选用到分布式锁。分布式锁的达成有无数种,比如依据数据库、
zookeeper 等,本文重要介绍使用 Redis 做分布式锁的点子,并封装成spring
boot starter,方便使用

作者:方圆
链接:https://www.zhihu.com/question/20010554/answer/15863274
来源:知乎
文章权归小编全部,转发请联系小编得到授权。

 

一 、 PostgreSQL 的稳定性极强, Innodb
等引擎在崩溃、断电之类的患难场景下抗打击能力有了长足提升,但是不少 MySQL
用户都赶上过Server级的数据库丢失的光景——mysql系统库是MyISAM的,相比较之下,PG数据库那地点要好一些。
② 、任何系统都有它的属性极限,在高并发读写,负载逼近极限下,PG的质量目标还能够保持双曲线甚至对数曲线,到终极其后不再降低,而
MySQL
显明出现2个波峰后降落(5.5本子之后,在同盟社级版本中有个插件可以改革广大,不过须要付费)。
三 、PG 多年来在 GIS
领域处于优势地位,因为它有加上的几何类型,实际上不止几何类型,PG有多量字典、数组、bitmap
等数据类型,相比之下mysql就差很多,instagram便是因为PG的空中数据库扩充POSTGIS远远强于MYSQL的my
spatial而采纳PGSQL的。
④ 、PG 的“无锁定”性格万分杰出,甚至包涵 vacuum
那样的整治数据空间的操作,那些和PGSQL的MVCC达成有关系。
⑤ 、PG
的能够动用函数和标准化索引,那使得PG数据库的调优分外灵活,mysql就一贯不这几个效果,条件索引在web应用中很主要。
6、PG有无限强悍的 SQL 编制程序能力(9.x
图灵完备,援救递归!),有相当丰裕的计算函数和总结语法补助,比如分析函数(ORACLE的叫法,PG里叫window函数),还足以用两种语言来写存储进程,对于Lacrosse的支撑也很好。那点上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都是专擅有商业店铺,而且都不是3个铺面。一大半开发者,都是拿薪酬的。
说mysql的履行进度比pgsql快很多是有失常态的,速度接近,而且不少时候取决于你的布置。
对于仓储进程,函数,视图之类的成效,今后四个数据库都能够协助了。
别的二十多线程架构和多进度框架结构之间没有断然的上下,oracle在unix上是多进程架构,在windows上是二十四线程架构。
众多pg应用也是24/7的采用,比如skype. 近期多少个版本VACUUM基本不影响PGSQL
运维,8.0之后的PGSQL不必要cygwin就能够在windows上运营。
关于说对于事情的协助,mysql和pgsql都不曾难题。

一. Redis 分布式锁的贯彻以及存在的难题

锁是针对有个别能源,保险其访问的互斥性,在实际应用个中,这么些财富一般是3个字符串。使用 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.");}

迄今,大家就到位了3个针锋相对可信赖的 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

发表评论

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