golang基于redis lua封装的优先级去重队列

前言:

 前两天由于某几个厂商的api出问题,导致后台任务大量堆积,又因为我这边任务流系统会重试超时任务,所以导致队列中有大量的重复任务。这时候我们要临时解决两个事情,一件事情,让一些高质量的任务优先执行; 另一件事情, 要有去重。 rabbitmq不能很好的针对这类情况去重、分优先级。  

 这时候我又想到了我最爱的redis…   去重?  list + set 就可以解决, 优先级,zset + zrange + zrem 也可以解决…  但问题这几个命令非原子,那么怎么让他们原子?   写模块 or redis lua script .   首先在 redis 4.x 写了个简单的module,但写完了发现一件颇为重要的事情,我们线上的是3.2 ….  然后又花了点时间改成redis lua的版本。项目本身的功能实现很简单,复杂的是创意 !!! 

项目名: redis_unique_queue, 项目地址,https://github.com/rfyiamcool/redis_unique_queue

主要功能介绍:

使用redis lua script 封装的去重及优先级队列方法, 达到了组合命令的原子性和节省来往的io请求的目的.

去重队列:

不仅能保证FIFO, 而且去重.

优先级去重队列:

按照优先级获取任务, 并且去重.

使用方法:

# xiaorui.cc

PriorityQueue

NewPriorityQueue(priority int, unique bool, r *redis.Pool)

Push(q string, body string, pri int) (int, error)

Pop(q string) (resp string, err error)
UniqueQueue

NewUniqueQueue(r *redis.Pool) *UniqueQueue

UniquePush(q string, body string) (int, error)

UniquePop(q string) (resp string, err error)

more..

下面是优先级去重队列的例子:

package main

// xiaorui.cc

import (
    "fmt"

    "github.com/rfyiamcool/redis_unique_queue"
)

func main() {
    fmt.Println("start")
    redis_client_config := unique_queue.RedisConfType{
        RedisPw:          "",
        RedisHost:        "127.0.0.1:6379",
        RedisDb:          0,
        RedisMaxActive:   100,
        RedisMaxIdle:     100,
        RedisIdleTimeOut: 1000,
    }
    redis_client := unique_queue.NewRedisPool(redis_client_config)

    qname := "xiaorui.cc"
    body := "message from xiaorui.cc"

    u := unique_queue.NewPriorityQueue(3, true, redis_client)
    // 3: 3个优先级,从1-3级
    // true: 开启unique set

    u.Push(qname, body, 2)
    // 2, 优先级

    fmt.Println(u.Pop(qname))
}

单单使用 去重队列的例子:

package main

import (
    "fmt"

    "github.com/rfyiamcool/redis_unique_queue"
)

func main() {
    fmt.Println("start")
    redis_client_config := unique_queue.RedisConfType{
        RedisPw:          "",
        RedisHost:        "127.0.0.1:6379",
        RedisDb:          0,
        RedisMaxActive:   100,
        RedisMaxIdle:     100,
        RedisIdleTimeOut: 1000,
    }
    redis_client := unique_queue.NewRedisPool(redis_client_config)


    qname := "xiaorui.cc"
    u := unique_queue.NewUniqueQueue(redis_client)
    for i := 0; i < 100; i++ {
        u.UniquePush(qname, "body...")
    }

    fmt.Println(u.Length(qname))

    for i := 0; i < 100; i++ {
        u.UniquePop(qname)
    }

    fmt.Println(u.Length(qname))


    fmt.Println("end")
}

需要改进地址也是很多, 比如 加入批量操作, 对于redis连接池引入方法改进等.

END.

redis批量删除key

最近生产环境的redis服务器由于key过期不及时,现在发现时key的个数已经暴增到5000多万了。然后运维同学那边就报警了,最大内存12G,已经用了9G多了,正好下面快要双11了,让我们快些解决。

redis服务器里面堆积大量的队列状态相关的key,其实这些key可以设置有效期,或者任务完成以后删除或者过期,但是由于我们使用类库的问题,这些key既没有删除也没有过期,堆积到redis里面去了,现在我们要做的就是删除这些无用key。在删除这些keys的过程中,走了不少弯路,这里说一下我最终采用的方案。

redis的del函数可以删除单个key,也可以删除多个key,del函数官方文档可以看这里。在google之后看到目前网络上很多文章的思路是使用keys匹配返回要删除的key,然后调用del函数去删除。这种方案在数据量较小时无可厚非,但如果像我这样面临的处理的数据有5千W时,keys的阻塞问题可能会给线上生产环境带来致命的问题。所以我们需要对这种方案作出一些修改。

可喜的是自从2.8.0以后redis提供scan来遍历key,而且这个过程是非阻塞,不会影响线上生产环境。最终经过修改的方案是用scan遍历要删除的key,然后调用del删除。

下面是我用python写的用来删除key的脚本。

import sys,redis

r = redis.Redis(host="127.0.0.1", port=6379,db=0)

if  len(sys.argv) <= 1:
    print("必须指明匹配key字符串")
    exit(1)
pattern = sys.argv[1]

cursor = 0
num = 1
while 1 :
    resut = r.scan(cursor, pattern, 10000)
    del_keys = []
    for i in resut[1]:
        key = i.decode()
        del_keys.append(key)
    #print("del keys len :%d" % len(result))
    if len(del_keys) == 0:
        break
    r.delete(*del_keys)
    cursor = resut[0]
    print("delete keys num : %dw" % (num))
    num +=1

print("donen")

如何利用我这个脚本删除符合某个规则的key哪,如以king开头的key?

下面的命令即可完成上面的问题。

python3 main.py "king*"

期间我看到网上利用keys+del的lua脚本的方案,花了一段时间把scan+del改成lua脚本来删除。但是可惜的是目前redis并不支持这么做,由于scan返回的结果是不确定的,所以禁止在其后直接调用del操作。

使用docker的redis镜像

前言

docker的官方镜像提供了redis的镜像,为了方便自己随时随地需要的使用,就学习一下,顺便记录下来。

参考Docker官方Redis镜像: https://hub.docker.com/_/redis/

准备工作

  • 一台linux机器(我用windows10自带的hyper-v装了个虚拟机)
  • 安装docker

获取redis镜像

一行命令搞定:

$> docker pull redis

说明: docker的官方镜像都可以在Docker Hub上找到,这里会自动从Docker Hub 上找到对应的镜像并下载。

下载完成之后,可以使用如下命令查看镜像:

$> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
redis               latest              1fb7b6c8c0d0        6 days ago          107MB (1)
oraclelinux         latest              6c33a25f4a29        2 weeks ago         229MB
elasticsearch       latest              d1ac13423d3c        5 weeks ago         580MB
hello-world         latest              1815c82652c0        4 months ago        1.84kB

这个就是我们刚刚获取的镜像。

运行redis镜像

$> docker run --name my-redis -d redis
7dabbc56e3514e9ba1705d6c6ec180e603ceff7e6011f7915ef3b0f1f5b05b35

启动后可以查看redis运行的信息:

$> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
7dabbc56e351        redis               "docker-entrypoint..."   35 minutes ago      Up 35 minutes       6379/tcp            my-redis

连接redis

在虚拟机中,没有ui工具可以连接redis,要测试redis是否正常服务,需要安装redis的命令行客户端工具(redis-cli)。

安装redis-cli

$> wget http://download.redis.io/releases/redis-2.8.17.tar.gz
$> tar xzf redis-2.8.17.tar.gz
$> cd redis-2.8.17
$> make

注意: 这里是下载redis源码进行编译,编译后在src目录下就会有redis-cli工具。 源码编译过程需要使用gcc,如果没有安装的话请先用yum或者apt-get安装gcc。 如:yum install gcc或apt-get install gcc。

编译完成后:

$> cd src
$> ./redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> 

说明已经编译完成,可以使用了。

由于没有参数的情况下,默认是连接127.0.0.1:6379,这里连接失败了,先退出cli:

not connected> exit

连接docker-redis

现在我们需要知道docker中的redis的IP地址:

$> docker inspect --format '' ${容器id}

容器id我们前面已经用docker ps看到了,拷贝过来替换掉${容器id}。

$> docker inspect --format '' 7dabbc56e351
172.17.0.2

连接:

$> ./redis-cli -h 172.17.0.2
172.17.0.2:6379> 

不需要指定端口,使用默认端口6379即可。

正常连接上了,现在测试一下:

172.17.0.2:6379> set first:key "hello redis"
OK
172.17.0.2:6379> get first:key
"hello redis"
172.17.0.2:6379> 

成功!

允许物理机连接

上面成功连接之后,说明我们已经可以正常使用redis了,但是实际上这不是我们想要的效果,因为只能在虚拟机内连接,我们希望可以在物理机上连接,这样才可以进行开发。

现在让我们先停掉虚拟机里的dcoker:

$> docker stop ${容器id}

现在重新启动docker的redis镜像,同时指定主机端口映射容器端口:

$> docker run -p 6379:6379 --name port-map-redis -d redis

现在使用redis-cli直接连接本地:

$> ./redis-cli
127.0.0.1:6379>

说明连接本地成功。

Linux下Redis服务器搭建

一、系统环境

  • 操作系统:CentOS 6.9

  • redis版本:redis-4.0.2

二、安装步骤

1. 安装预环境

运行以下命令安装预环境。

[root@redis02 redis-4.0.2]# yum -y install gcc make

2. 下载redis源代码文件并解压缩

下载完redis源代码后,运行以下命令进行解压缩。

[root@redis02 softwares]# tar -xzf redis-4.0.2.tar.gz

未分类

3. redis编译

运行make命令进行编译。

未分类

make命令执行完成编译后,会在src目录下生成6个可执行文件,分别是redis-server、redis-cli、redis-benchmark、redis-check-aof、redis-check-dump、redis-sentinel。

4. redis安装配置

运行make install命令。

未分类

命令执行后会将make编译生成的可执行文件拷贝到/usr/local/bin目录下,如下图。

未分类

然后,运行./utils/install_server.sh配置向导来配置redis,并且可以将redis服务加到开机自启动中。【重要】

未分类

5. redis服务查看,开启和关闭

此时redis服务已经启动了。可以通过以下命令来操作redis了。

查看redis的运行状态:

[root@redis02 redis-4.0.2]# service redis_6379 status

关闭redis服务:

[root@redis02 redis-4.0.2]# service redis_6379 stop

开启redis服务:

[root@redis02 redis-4.0.2]# service redis_6379 start

最后可以通过redis内置的客户端工具来测试下:

[root@redis02 ~]# redis-cli
127.0.0.1:6379> get name

(nil)

127.0.0.1:6379> set name mcgrady

OK

127.0.0.1:6379> get name

"mcgrady"

127.0.0.1:6379>

可以看到,redis服务已经成功配置好了!

三、注意事项

1. 运行make命令报错?

错误信息如下:

make[3]: gcc: Command not found

/bin/sh: cc: command not found

解决方案:

因为预环境没有安装,运行以下命令安装预环境。

[root@redis02 redis-4.0.2]# yum -y install gcc make

2. 安装完预环境后运行make命令报以下错误?

错误信息:

zmalloc.h:50:31: error: jemalloc/jemalloc.h: No such file or directory

zmalloc.h:55:2: error: #error "Newer version of jemalloc required"

解决方案:

运行以下命令。

make MALLOC=libc

3. 运行make test命令报以下错误?

错误信息:

You need tcl 8.5 or newer in order to run the Redis test

解决方案:

运行以下命令安装tcl。

[root@redis02 redis-4.0.2]# yum -y install tcl

4. 调用ConnectionMultiplexer.Connect创建连接的时候报错?

错误信息:

It was not possible to connect to the redis server(s); to create a disconnected multiplexer, disable AbortOnConnectFail. UnableToResolvePhysicalConnection on TIME

解决方案:

1)关闭保护模式,注意默认是打开的。

未分类

2)绑定IP,注意默认只绑定了127.0.0.1。

未分类

有用命令:

telnet 192.168.1.29 6379,可以直接测试客户端是否能连上服务器,如果通的话,基本上就没有什么问题。

ps -aux | grep redis ,查看redis的进程,看redis是否正常启动。

Git常用命令总结

Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目,可以有效、高速的处理从很小到非常大的项目版本管理。Git是Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

Git常用操作命令

1、远程仓库相关命令

检出仓库:$ git clone git://github.com/jquery/jquery.git
查看远程仓库:$ git remote -v
添加远程仓库:$ git remote add [name] [url]
删除远程仓库:$ git remote rm [name]
修改远程仓库:$ git remote set-url --push [name] [newUrl]
拉取远程仓库:$ git pull [remoteName] [localBranchName]
推送远程仓库:$ git push [remoteName] [localBranchName]

如果想把本地的某个分支test提交到远程仓库,并作为远程仓库的master分支,或者作为另外一个名叫test的分支,如下:

$git push origin test:master         // 提交本地test分支作为远程的master分支
$git push origin test:test              // 提交本地test分支作为远程的test分支

2、分支(branch)操作相关命令

查看本地分支:$ git branch
查看远程分支:$ git branch -r
创建本地分支:$ git branch [name] ----注意新分支创建后不会自动切换为当前分支
切换分支:$ git checkout [name]
创建新分支并立即切换到新分支:$ git checkout -b [name]
删除分支:$ git branch -d [name] ---- -d选项只能删除已经参与了合并的分支,对于未有合并的分支是无法删除的。如果想强制删除一个分支,可以使用-D选项
合并分支:$ git merge [name] ----将名称为[name]的分支与当前分支合并
创建远程分支(本地分支push到远程):$ git push origin [name]
删除远程分支:$ git push origin :heads/[name] 或 $ gitpush origin :[name]

创建空的分支:(执行命令之前记得先提交你当前分支的修改,否则会被强制删干净没得后悔)

$git symbolic-ref HEAD refs/heads/[name]
$rm .git/index
$git clean -fdx

3、版本(tag)操作相关命令

查看版本:$ git tag
创建版本:$ git tag [name]
删除版本:$ git tag -d [name]
查看远程版本:$ git tag -r
创建远程版本(本地版本push到远程):$ git push origin [name]
删除远程版本:$ git push origin :refs/tags/[name]
合并远程仓库的tag到本地:$ git pull origin --tags
上传本地tag到远程仓库:$ git push origin --tags
创建带注释的tag:$ git tag -a [name] -m 'yourMessage'

4、子模块(submodule)相关操作命令

添加子模块:$ git submodule add [url] [path] 如下:

$git submodule add git://github.com/soberh/ui-libs.git src/main/webapp/ui-libs
初始化子模块:$ git submodule init  ----只在首次检出仓库时运行一次就行
更新子模块:$ git submodule update ----每次更新或切换分支后都需要运行一下
删除子模块:(分4步走哦)

1) $ git rm --cached [path]
2) 编辑“.gitmodules”文件,将子模块的相关配置节点删除掉
3) 编辑“ .git/config”文件,将子模块的相关配置节点删除掉
4) 手动删除子模块残留的目录

5、忽略一些文件、文件夹不提交

在仓库根目录下创建名称为“.gitignore”的文件,写入不需要的文件夹名或文件,每个元素占一行即可,如下:

target
bin
*.db

Git 常用命令

git branch 查看本地所有分支
git status 查看当前状态
git commit 提交
git branch -a 查看所有的分支
git branch -r 查看本地所有分支
git commit -am "init" 提交并且加注释
git remote add origin [email protected]:ndshow
git push origin master 将文件给推到服务器上
git remote show origin 显示远程库origin里的资源
git push origin master:develop
git push origin master:hb-dev 将本地库与服务器上的库进行关联
git checkout --track origin/dev 切换到远程dev分支
git branch -D master develop 删除本地库develop
git checkout -b dev 建立一个新的本地分支dev
git merge origin/dev 将分支dev与当前分支进行合并
git checkout dev 切换到本地dev分支
git remote show 查看远程库
git add .
git rm 文件名(包括路径) 从git中删除指定文件
git clone git://github.com/schacon/grit.git 从服务器上将代码给拉下来
git config --list 看所有用户
git ls-files 看已经被提交的
git rm [file name] 删除一个文件
git commit -a 提交当前repos的所有的改变
git add [file name] 添加一个文件到git index
git commit -v 当你用-v参数的时候可以看commit的差异
git commit -m "This is the message describing the commit" 添加commit信息
git commit -a -a是代表add,把所有的change加到git index里然后再commit
git commit -a -v 一般提交命令
git log 看你commit的日志
git diff 查看尚未暂存的更新
git rm a.a 移除文件(从暂存区和工作区中删除)
git rm --cached a.a 移除文件(只从暂存区中删除)
git commit -m "remove" 移除文件(从Git中删除)
git rm -f a.a 强行移除修改后文件(从暂存区和工作区中删除)
git diff --cached 或 $ git diff --staged 查看尚未提交的更新
git stash push 将文件给push到一个临时空间中
git stash pop 将文件从临时空间pop下来
---------------------------------------------------------
git remote add origin [email protected]:username/Hello-World.git
git push origin master 将本地项目给提交到服务器中
-----------------------------------------------------------
git pull 本地与服务器端同步
-----------------------------------------------------------------
git push (远程仓库名) (分支名) 将本地分支推送到服务器上去。
git push origin serverfix:awesomebranch
------------------------------------------------------------------
git fetch 相当于是从远程获取最新版本到本地,不会自动merge
git commit -a -m "log_message" (-a是提交所有改动,-m是加入log信息) 本地修改同步至服务器端 :
git branch branch_0.1 master 从主分支master创建branch_0.1分支
git branch -m branch_0.1 branch_1.0 将branch_0.1重命名为branch_1.0
git checkout branch_1.0/master 切换到branch_1.0/master分支
du -hs
-----------------------------------------------------------
mkdir WebApp
cd WebApp
git init
touch README
git add README
git commit -m 'first commit'
git remote add origin [email protected]:daixu/WebApp.git
git push -u origin master

以上为Git中常用的命令总结。

git add , git commit 添加错文件 撤销

1. git add 添加 多余文件

这样的错误是由于, 有的时候 可能

git add . (空格+ 点) 表示当前目录所有文件,不小心就会提交其他文件

git add 如果添加了错误的文件的话

撤销操作

git status 先看一下add 中的文件
git reset HEAD 如果后面什么都不跟的话 就是上一次add 里面的全部撤销了
git reset HEAD XXX/XXX/XXX.java 就是对某个文件进行撤销了

2. git commit 错误

如果不小心 弄错了 git add后 , 又 git commit 了。

先使用

git log 查看节点

commit xxxxxxxxxxxxxxxxxxxxxxxxxx
Merge:
Author:
Date:

然后

git reset commit_id

over

PS:还没有 push 也就是 repo upload 的时候

git reset commit_id (回退到上一个 提交的节点 代码还是原来你修改的)
git reset –hard commit_id (回退到上一个commit节点, 代码也发生了改变,变成上一次的)

3. 如果要是 提交了以后,可以使用 git revert

还原已经提交的修改
此次操作之前和之后的commit和history都会保留,并且把这次撤销作为一次最新的提交
git revert HEAD 撤销前一次 commit
git revert HEAD^ 撤销前前一次 commit
git revert commit-id (撤销指定的版本,撤销也会作为一次提交进行保存)
git revert是提交一个新的版本,将需要revert的版本的内容再反向修改回去,版本会递增,不影响之前提交的内容。

Git 为单一项目设置 git 用户名和邮箱

自己有自己的开源项目,工作有工作的闭源项目,两者往往通过不同的 Git 帐号进行 commit 操作,那如何为不同的项目设置不同的 Git 帐号呢?

我们一般的操作都是:

git config --global user.name
git config --global user.email

这两步来设置 Git 帐号名与邮箱,但是针对特定项目,比如:

我 Git 的 global 用户名是 liumapp,但在 B 项目下想使用 lm 这个用户名来进行提交。

这种情况下,我们需要进行下列操作:

  • cd .git

  • vim config

添加下列代码:

[user]
    name=lm
    [email protected]
  • :wq 保存文件将立即生效,执行 commit 即可发现提交的用户为 lm。

一个git的使用错误unable to read askpass

今天在git push origin master时,竟然出现了错误 (gnome-ssh-askpass:32737): Gtk-WARNING **: cannot open display: error: unable to read askpass response from ‘/usr/libexec/openssh/gnome-ssh-askpass’根本原因是执行了该脚本

$ cat /etc/profile.d/gnome-ssh-askpass.sh
SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass

解决方式

$ unset SSH_ASKPASS

再次执行git

$ git push origin master

不再提示错误。

Git撤销最后一次提交

Git可以使用reset重置来撤销提交。

方法一

撤销最后一次提交

git reset HEAD~1

执行后,状态重置为上一次提交,且撤回提交的文件的状态变回unstaged,即文件没有被git跟踪。

示例

$ git commit -m 'add test.html'
[master ade6d7e] add test.html
 1 file changed, 1 insertion(+)
 create mode 100644 test.html
$ git reset HEAD~1
$ git status
On branch master
Untracked files:
 (use "git add <file>..." to include in what will be committed)

    test.html

nothing added to commit but untracked files present (use "git add" to track)

撤回后test.html为Untracked files。

方法二

git reset --soft HEAD~1

使用–soft,执行后,状态重置为上一次提交,但撤回提交的文件add到git,被git跟踪。

示例

$ git commit -m 'add test.html'
[master 877b8f0] add test.html
 1 file changed, 1 insertion(+)
 create mode 100644 test.html

$ git reset --soft HEAD~1

clcaza@clcaza MINGW64 /d/webstormProjectsDemo/ngcli-demo (master)
$ git status
On branch master
Changes to be committed:
 (use "git reset HEAD <file>..." to unstage)

    new file: test.html

test.html状态为Changes to be committed

Git初始配置

一、配置提交时的用户名与邮件名称(注:只是标识本次commit是谁提交的)

1.1 通过命令的方式

$ git config --global user.name "songshuiyang"
$ git config --global user.email [email protected]

注: global 全局配置,在此电脑上的所有项目的git提交都会用这个用户名和邮件

1.2 通过修改配置文件的方式

文件路径: 用户目录/.gitconfig 文件

把name email改成(新增)自己的配置即可

[user]
        name = songshuiyang
        email = [email protected]

二、配置 短命令

2.1 通过命令的方式

$ git config --global alias.st status
$ git config --global alias.ci commit
$ git congig --global alias.co checkout
$ git congig --global alias.br branch

2.2 通过修改配置文件的方式

[alias]
    co = checkout
    ci = commit
    st = status
    cm = commit -m
    br = branch
    bm = branch -m
    bd = branch -D
    cb = checkout -b
    df = diff
    ls = log --stat
    lp = log -p
    plo = pull origin
    plode = pull origin develop
    pho = push origin

三、配置文件

Git的三个配置文件

  • 版本库级别的配置文件,文件路径: 项目路径/.git/config

  • 全局配置文件, 文件路径: 用户目录/.gitconfig

  • 系统级配置文件,文件路径: 安装目录/etc目录下

优先级: 版本库级别的配置文件 > 全局配置文件 > 系统级配置文件

四、文件 .git/index

实际上就是一个包括文件索引的目录树,像是一个虚拟的工作区,记录了文件名和文件的状态信息(时间戳和文件长度),文件的内容保存在.git/objects目录下,文件索引建立了文件和对象库中对象实体之间的对应

工作区,版本区,暂存区原理图

未分类