LongLong's Blog

分享IT技术,分享生活感悟,热爱摄影,热爱航天。

安装配置Gitlab

Gitlab是一个用Ruby on Rails实现的开源的并且基于Git的版本管理系统,基本上实现了一个类似于Github的相关功能——更够通过网页浏览代码,并控制代码库的访问修改权限,提交缺陷说明和代码注释,便于开发团队之间的协作。相比Phabricator,其更加注重的是对于代码库的管理。由于其是用Ruby on Rails实现,因此对于只熟悉PHP环境的人来说,安装和配置要显得复杂不少,这里简单记录一下,安装基于Archlinux。

1. 安装配置Ruby环境

一般开源的系统对于相关运行环境的版本都有较高的要求,Ruby要求至少要在1.9以上,如果使用Centos等软件版本较旧的Linux发行版,则需要从源代码编译Ruby或手段安装相关的软件包。安装完成后修改Ruby的gem源到淘宝的源,并安装bundler。

pacman -S ruby
gem sources -r https://rubygems.org/
gem sources -a http://ruby.taobao.org/
gem install --no-user-install bundler

2. 下载编译Gitlab源代码

官方的下载地址为https://gitlab.com/gitlab-org/gitlab-ce,Github的下载地址为https://github.com/gitlabhq/gitlabhq,直接git clone即可。

pacman -S git
git clone https://gitlab.com/gitlab-org/gitlab-ce.git gitlab
git clone https://gitlab.com/gitlab-org/gitlab-shell.git

完成后需要修改一下代码中的gem源,同样修改到淘宝,即将gitlab中Gemfile文件的第一行地址修改为http://ruby.taobao.org/,并安装相关的依赖软件包,完成后开始编译和安装gitlab依赖的相关gem

pacman -S gcc make patch cmake pkg-config
pacman -S icu
pacman -S mysql
cd gitlab && bundle install --deployment --without development test postgres aws

3. 配置gitlab-shell

首先需要为gitlab创建一个系统用户,如果在安装了git后自动创建了用户则需要修改其home目录,并将gitlab和gitlab-shell移动到其home目录下,并修改权限

useradd --system --create-home --comment 'GitLab' git
mv gitlab /home/git/
mv gitlab-shell /home/git/
chown git:git -R /home/git/gitlab
chown git:git -R /home/git/gitlab-shell
cd /home/git
cp gitlab-shell/config.yml.example gitlab-shell/config.yml
sudo -u git gitlab-shell/bin/install

如果redis使用TCP服务,则最后需要注释掉config.yml中redis项目中的socket行

redis:
    bin: /usr/bin/redis-cli
    host: 127.0.0.1
    port: 6379
    # pass: redispass # Allows you to specify the password for Redis
    database: 0
    #socket: /var/run/redis/redis.sock # Comment out this line if you want to use TCP
    namespace: resque:gitlab

4. 安装配置相关的服务

Gitlab需要MySQL、Redis、Nginx的支持才能够正常工作,以此还需要对这些服务进行安装和配置

pacman -S redis nginx mysql
cp lib/support/nginx/gitlab /etc/nginx/

并在nginx.conf的http段中增加

include gitlab;

同时在gitlab文件的server段结尾增加,如果使用了域名还需要修改一下server_name

error_page 404 = @gitlab;

5. 配置及初始化Gitlab

首先初始化一些配置文件

cp config/gitlab.yml.example config/gitlab.yml
cp config/database.yml.mysql config/database.ym
cp config/unicorn.rb.example config/unicorn.rb

然后开始运行初始化,运行前需要启动mysql,并创建gitlab的数据库,同时还需要安装一个js引擎,这里使用nodejs

systemctl start mysqld
mysql -u root -e "CREATE DATABASE IF NOT EXISTS gitlabhq_production DEFAULT CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci'"
pacman -S nodejs
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production

6. 启动gitlab

启动gitlab前需要先启动mysql、redis、nginx,启动脚本在lib/support/init.d/gitlab,复制一份并增加运行权限

systemctl start mysqld
systemctl start redis
systemctl start nginx
cp lib/support/init.d/gitlab gitlab
chmod +x gitlab
#注意这里必须是绝对路径
/home/git/gitlab/gitlab start

第一次运行时会比较慢一些需要耐心等待一下,默认的用户名和密码为admin@example.com/password

7. 修改gitlab的merge行为

gitlab的Merge Request使用的是–no-ff方式的进行merge的,如果合并的分支的commit记录比较乱就会导致合并之后主分支的commit记录也比较混乱,如果想将其改为–squash的方式,需要修改gitlab源代码的一个文件gitlab/lib/gitlab/git/repository.rb,如下修改rugged_merge方法即可

options = {
	#parents: [our_commit, their_commit],
	parents: [our_commit],
	tree: merge_index.write_tree(rugged),
	message: message,
	author: committer,
	committer: committer
}

使用Go语言实现一个简单的Memcache代理服务

之前研究Memcache的源代码主要是想实现一个Memcache的代理服务,实现缓存的异步更新(或过期)操作,但把Memcache的核心代码提取出来还是个不小的工程,其中相当多的代码在做各种错误的处理,估计把其核心代码提取成一个完备的可以使用的项目,估计代码会有几千行之多(这个工作我还会试着继续进行)。这里我们用最近想起的Go语言来实现一个多线程的Memcache代理服务器。

1. Go语言简介

Go语言是Google在2007年开发的一种编程语言,是一种静态类型语法类似C语言,支持垃圾收集,类型安全,并且支持动态类型容器和内置的标准库(变长数组和哈希表),并且目前已经可以运行在大多数硬件平台和操作系统上。Go语言的官方主页和Google一样在中国大陆是访问不了的,一些相关信息可以参考维基百科http://en.wikipedia.org/wiki/Go_(programming_language)。个人对此语言的最大感受就是其试图让编写并行程序编得更加简单。

2. 协程

协程是目前并行编程里一个比较重要的概念,其在使用上比较类似于线程,但其并不一定真正使用了操作系统级别的线程,Lua中的协程就是一种通过栈保存运行状态来模拟的“线程”,因此协程的创建要比创建操作系统级别的线程开销会小很多。其真正的精髓在于协程之间可以通过yeild让出自己的执行,从而降低等待时间,同时相比回调的编程方式要更加直观,更加接近一般的顺序编程。这方面比较好的介绍和文章还比较少,作者对此理解程度也比较有限,前面只是我目前的理解。

Go语言中的协程使用非常简单,只需要将协程中的代码放到一个函数中,并在执行前增加go关键字即可。例如如下代码

package main;

import "fmt";
import "time";

func main() {
    go func() {
        for i := 1; i < 10; i++ {
            fmt.Println(i);
        }
    } ();
    fmt.Println("Hello World!");
    time.Sleep(3 * time.Second);
}

执行的结果一般会如下,可见协程还没有执行完,就执行了主函数的后面的部分

Hello World!
1
2
3
4
5
6
7
8
9

3. Go的安装和使用

一般的Linux软件包中一般都包含了Go语言的运行环境,在ArchLinux中直接通过pacman即可完成安装,可以看到Go的安装包还是比较大的。

pacman -S go

resolving dependencies...
looking for conflicting packages...

Packages (1) go-2:1.4.2-2

Total Download Size:    52.56 MiB
Total Installed Size:  343.07 MiB

Go语言的Hello World,基本和C语言差不多

package main;

import "fmt";

func main() {
	fmt.Println("Hello World!");
}

编译和运行,也可以直接运行(其实是编译一个临时文件,然后运行)

go build hello.go
./hello 
Hello World!

go run hello.go
Hello World!

4. 使用Go语言实现一个Memcache代理

首先使用net包中的Listen创建一个服务地址和端口,然后使用Go语言的Memcache模块和要进行同步的Memcache进行连接,同时将和客户端连接获取数据与向Memcache同步使用两组不同的协程,每和客户端建立一个连接就启动一个协程进行处理,这里解析Memcache协议为了简单使用了正则表达式,如果要提高效率可以模仿Memcache源代码的过程,同时两组协程之间通过keys这个字符串类型的channel进行数据的交互,解析协议完成后,将解析到的key传入channel就会出发同步协程进行工作,比较类似线程中使用消息队列进行数据交互。

可以看到,使用Go实现的Memcache代理代码只需要不到100行,如果增加了对各种出错的处理应该也不会超过200行,并且由于创建协程的代价较小,不需要像线程一样实现线程池的相关逻辑,也不需要实现通过消息队列进行通信的逻辑,大大简化了并行编程的难度,需要做的只是将可以异步处理的代码,放到协程中执行即可。

package main;

import "net";
import "ketama";
import "memcache";
import "regexp";

func main() {
    l, err := net.Listen("tcp", "127.0.0.1:1987");
    handle_error(err);
	//使用ketama一致性散列算法
    selector, err := ketama.NewFromFile("servers.txt");
    handle_error(err);
	//创建一个消息队列
    keys := make(chan string);

	//开启2个同步协程
    n := 2;
    for i := 0; i < n; i++ {
         go func() {
			//创建到需要同步的Memcache之间的连接
    		mc := memcache.NewFromSelector(selector);
             for {
                 key := <-keys;
                 mc.Delete(key);
             }
         } ();
    }

	//程序结束时关闭地址监听
    defer l.Close();
	//接受客户端的连接
    for {
        conn, err := l.Accept();
        handle_error(err);

        go func(conn net.Conn) {
			//结束后关闭和客户端的连接
            defer conn.Close();
            //接收数据,解析命令,并将key放入channel
			for {
                recv := read_network(conn);
                commands := read_commmand(recv);
                if commands != nil {
                    keys <- commands[2];
                    switch commands[1] {
                        case "set":
                            conn.Write([]byte("STORED\r\n"));
                        case "add":
                            conn.Write([]byte("STORED\r\n"));
                        case "replace":
                            conn.Write([]byte("STORED\r\n"));
                        case "delete":
                            conn.Write([]byte("DELETED\r\n"));
                    }
                } else {
                    break;
                }
            }
        } (conn);
    }
}

//读取数据
func read_network(conn net.Conn) string {
    buf := make([]byte, 384);
    n, err := conn.Read(buf);
    handle_error(err);
    recv := string(buf[0:n]);
    return recv;
}

//解析协议
func read_commmand(in string) []string {
    r := regexp.MustCompile("^(set|delete|add|replace) (\\w+).*\r\n");
    matches := r.FindStringSubmatch(in);
    return matches;
}

func handle_error(err error) {
    if err != nil {
        panic(err);
    }
}