Redis分布式锁

1.关于锁:

在单进程的系统中,当多个线程可以同时改变某个变量时,就需要对变量或代码块做同步。多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。

分布式最大的不同在于不是多线程而是多进程,而多个进程之间可能都不在同一台物理机上,所以这时候加锁,就需要有一个公共内存,比如Redis。

2.使用redis的setNX命令实现分布式锁

a.原理

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。

 

b.Redis基本命令解析

1)setNX(SET if Not eXists)

语法:

       SETNX key value     设置成功,返回 1 ,设置失败,返回 0 。

 

        2)getSET

语法:

       GETSET key value    将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 key 不存在时,返回 nil 。

 

3.Redis锁:

基本锁:

原理:利用Redis的setnx如果不存在某个key则设置值,设置成功(返回1)则表示取得锁成功。如果释放锁的话,执行del操作。

缺点:如果该线程加锁后挂了(没有释放锁),则锁永远不会释放。

 

改进型:

改进:setnx后设置expire,保证即使获取锁的进程不主动释放锁,过一段时间后也能自动释放。

缺点:setnx与expire不是一个原子操作,可能执行完setnx该进程就挂了,则该值一直不会过期,则锁也永远不会释放了。

官方做法:利用Lua脚本,将setnx与expire变成一个原子操作,该方法可以保证原子性,但是操作起来比较麻烦。可供参考的实现库.:Redisson

 

再改进:

改进:在expire基础上,设置value为该锁的过期时间,多个线程都可以去判断该锁有没有过期,如果过期,则可以抢占锁。

缺点:还是锁过期的问题。如果两个线程a和b,a先获取到了锁,并设置了过期时间,但是a可能处理过程中被挂起了(直到锁过期了),这时候线程b检测到锁过期了,所以设置了新的过期时间,然后持有了锁。但是这时a线程处理完了,执行del操作时,会把b设置的值del。所以在每个线程释放锁时,还需要判断该值是不是自己设置的值。

 

再再改进:

改进:既要能自动过期,还要能被其他线程检测是否过期,还要在释放锁时,判断该值是否是自己设置的。所以在expire基础上,设置value为该锁的过期时间,并且要保证value唯一。

缺点:expire时间与代码执行时间的问题,还可能会存在问题,后续再观察下。

 

4.Redlock

Redlock是Redis的作者antirez给出的集群模式的Redis分布式锁,节点完全互相独立,不存在主从复制或者其他集群协调机制。这个目前还在研究,感兴趣的同学可以去看一下。

关于Redis分布式锁的安全性问题,在antirez给出Redlock算法后还和分布式系统专家Martin Kleppmann发生过一场争论,Martin认为Redlock则是个过重的实现,不管是为了正确性还是效率都不适用,感兴趣的同学可以了解一下。

Martin的文章:https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

中文分析:http://mp.weixin.qq.com/s/1bPLk_VZhZ0QYNZS8LkviA

5.代码

public class RedisLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLock.class);

    private RedisClient redisClient;

    /**
     * Lock key path.
     */
    private String lockKey;

    private String randomValue;


    public RedisLock(RedisClient redisClient, String lockKey) {
        this.redisClient = redisClient;
        this.lockKey = lockKey;
    }

    /**
     * 获取Redis锁
     *
     * @return
     */
    public boolean tryLock() {
        //尝试重新获取锁时间:10s
        int timeoutMsecs = RedisConstants.WAIT_LOCK_TIME;
        while (timeoutMsecs >= 0) {
            //失效时间 用来手动判断是否已失效
            long expires = System.currentTimeMillis() + RedisConstants.EXPIRE_LOCK_TIME_MILLIS + 1;
            //redis的value:随机字符串(时间戳+三位随机数字字符串) 但是仍然不严谨,最好是客户端id+时间戳
            String redisValueStr = String.valueOf(expires) + OpenApiCoreUtils.generateThreeDigitNumberStr();
            LOGGER.info("Redis加锁,lockKey:" + lockKey + ",value:" + redisValueStr);
            if (redisClient.setnx(lockKey, redisValueStr) == 1) {
                //有效期三秒
                redisClient.expire(lockKey, RedisConstants.EXPIRE_LOCK_TIME);
                randomValue = redisValueStr;
                LOGGER.info("加锁成功!");
                return true;
            }

            String currentValueStr = redisClient.get(lockKey);
            LOGGER.info("Redis加锁setNX失败时,检查当前key:" + lockKey + ",当前值:" + currentValueStr);
            if (currentValueStr != null) {
                long oldExpireTime = Long.parseLong(currentValueStr) / 1000;
                LOGGER.info("Redis解析到过期时间:" + oldExpireTime);
                if (oldExpireTime < System.currentTimeMillis()) {
                    //锁超时
                    String oldValueStr = redisClient.getSet(lockKey, redisValueStr);
                    LOGGER.info("解析到旧key过期,尝试获取锁!");
                    //getSet是原子操作,但当多个线程同时访问时,可能其他线程又用getSet方法重置了oldValueStr的值
                    //所以通过判断当前线程get到的值和该线程写操作之前的值是否一致,来判断中间有没有其他线程进行过写操作
                    if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                        redisClient.expire(lockKey, RedisConstants.EXPIRE_LOCK_TIME);
                        LOGGER.info("获取锁成功:key:" + lockKey + ",value:" + redisValueStr);
                        return true;
                    }
                    LOGGER.info("竞争锁失败,等待重试!");
                }

            }
            Random random = new Random();
            //睡眠时间:80-120随机数,防止饥饿进程
            int sleepTime = random.nextInt(40) + 80;
            timeoutMsecs -= sleepTime;
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                LOGGER.error(e.getMessage(), e);
            }

        }
        return false;
    }

    /**
     * Acquired lock release.
     */

    public void unlock() {
        //从redis取值
        String redisValue = redisClient.get(lockKey);
        if (redisValue != null && redisValue.equals(randomValue)) {
            redisClient.del(lockKey);
        }
    }
}

6.效果

 

参考资料:

http://redis.cn/topics/distlock.html

http://www.redis.cn/commands/setnx.html

http://www.cnblogs.com/0201zcr/p/5942748.html

Linux下安装MongoDB各种坑记录及教程

1.MongoDB2.x和3.x命令不同,3.x使用 db.createUser({user: “user”,pwd: “userpassword”,roles: [ { role: “readWrite”, db: “test” } ]})创建角色

注:创建角色前要选择对应db,b不然可能存在创建的用户再选择了某一db后无法登录的情况。

2.有些版本不同无法复制数据库

3.6.2无法copyDatabase3.2.6

3.MongoDB2.x认证方式为MONGODB-CR,3.x为 SCRAM-SHA-1

教程:

一、下载对应安装包https://www.mongodb.com/download-center#community (因为是CentOS,所以下载rhel版本,另外右下角可以找到对应历史版本)

二、上传到服务器,解压缩到某一文件下,在其中的bin目录下创建文件mongod.conf,

其内容为:

#数据库数据存放目录
dbpath=/data/mongodb/db
#数据库日志存放目录
logpath=/data/mongodb/logs/mongodb.log
#以追加的方式记录日志
logappend = true
#端口号 默认为27017
port=27017
#以后台方式运行进程
fork=true
#开启用户认证
auth=true
bind_ip = 0.0.0.0

注:确认是否存在对应dbpath和logpath路径,不存在则创建

三、执行./mongod -f mongod.conf命令

出现如上页面证明服务成功启动。

四、执行./mongo ,然后执行use admin,再执行 db.createUser( { user: “root”, pwd: “root密码”, roles: [ { role: “root”, db: “admin” } ] } ); 创建用户。

注:此时已经可以用远程登录了

五、创建不同身份用户

注:命令:

db.createUser( { user: “root”, pwd: “123”, roles: [ { role: “root”, db: “admin” } ] } );
db.createUser({user:”admin”,pwd:”123″,roles:[ { role: “userAdminAnyDatabase”, db: “admin” } ]})
db.createUser({user: “lsj”,pwd: “123”,roles: [ { role: “readWrite”, db: “laosiji” } ]})

六、关闭服务

七、退出

exit

@Async失效

参考:https://segmentfault.com/a/1190000008981884

 

1、异步方法和调用类不要在同一个类中
2、注解扫描时,要注意过滤,避免重复实例化,因为存在覆盖问题,@Async就失效了

 

失效原因:A类中有一个异步方法,调用了B中的一个方法b1(非异步),然后b1调用了B中的异步方法b2,此时,b2异步失效。

 

Node.js和npm安装教程

1.直接去官网https://nodejs.org/en/安装最新版,现在最新版会同时安装npm,还会加到环境变量。

2.安装完之后用 node -v 和npm -v测试

3.在node目录新建两个文件夹node_global和node_cache

然后执行:

npm config set prefix “D:\node.js\node_global”
以及
npm config set cache “D:\node.js\node_cache”

加环境变量:在系统的path中加入 ;D:\node.js\node_global

 

安装grunt:npm install -g grunt-cli

npm install -g grunt

测试是否安装成功: grunt -version,显示版本号即成功。

 

安装bower:npm install -g bower

之后到项目下执行npm install

bower install

然后将 127.0.0.1   mu.saas.hualala.com  添加到hosts文件

grunt serve运行即可

安装完成。

 

备用命令:npm uninstall bower

 

其他前端项目使用时,用npm install等命令会安装到上面两个文件夹下,安装grunt-cli等还要加node_path环境变量。

安装bower:npm install -g bower

项目下执行:bower install 之后按照提示选择安装1.2.28版本

bower安装指定angular版本:bower install angular#1.2.28

https://www.cnblogs.com/xianrongbin/p/6206091.html

Java线程池 ExecutorService使用

ExecutorService有如下几个执行方法:

- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(...)
- invokeAll(...)

execute方法:

@Test
    public void testUser() {
        ExecutorService threadPool = Executors.newScheduledThreadPool(3);

        threadPool.execute(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 10; i++) {
                    System.out.println("jkl");
                }

            }
        });

        System.out.println("结束");
        threadPool.shutdown();
    }

如上只会打印:结束

不会打印jkl字符串,因为这个方法就是没有办法获知task的执行结果。

 

 

submit(Runnable)execute(Runnable)区别是前者可以返回一个Future对象,通过返回的Future对象,我们可以检查提交的任务是否执行完毕,请看下面执行的例子:

Future future = executorService.submit(new Runnable() {
public void run() {
    System.out.println("Asynchronous task");
}
});

future.get();  //returns null if the task has finished correctly.

如果任务执行完成,future.get()方法会返回一个null。注意,future.get()方法会产生阻塞。

API接口管理平台-rap使用教程

官网视频教程:http://vodcdn.video.taobao.com/player/ugc/tb_ugc_bytes_core_player_loader.swf?version=1.0.20170327&vid=11622279&uid=11051796&p=1&t=1&rid=http%3A%2F%2Frap.hualala.com%2Fworkspace%2FmyWorkspace.do%3FprojectId%3D113&random=6666

初步使用感受:

可能相较于markdown、wiki这类编辑器,rap可以生成测试用例,方便前端人员进行测试。两类软件各有优点吧,编辑器偏向于快速的文档编辑,rap偏向于前端接口请求数据的生成。