Lincxlock Test 测试文档

1、测试环境

测试环境的集群使用本机docker 容器模拟多主机环境进行搭建。

1.1 Redis集群

采用三主三从的模式进行搭建,这里由于主机资源有限,使用docker 启动六个容器模拟六台主机,分别占用端口号6371~6376。

创建文件目录

在·/usr/local/docker-redis/redis-cluster路径下分边创建文件夹名为6371~6376的文件夹,用于存储每个容器的配置文件与data。

编写配置文件

1、编写每个 Redis 容器的配置文件

port {port} #端口从6371-6376
requirepass 1234
masterauth 1234
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-announce-port {port}
cluster-announce-bus-port {port}

2、编写docker-compose文件

# 描述 Compose 文件的版本信息
version: "2.0"
services:
  lockredis1: # 服务名称
    image: redis # 创建容器时所需的镜像
    container_name: lockredis1 # 容器名称
    restart: always # 容器总是重新启动
    network_mode: "host" # host 网络模式
    volumes: # 数据卷,目录挂载
      - /usr/local/docker-redis/redis-cluster/6371/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /usr/local/docker-redis/redis-cluster/6371/data:/data
    command: redis-server /usr/local/etc/redis/redis.conf # 覆盖容器启动后默认执行的命令

  lockredis2:
    image: redis
    container_name: lockredis2
    network_mode: "host"
    volumes:
      - /usr/local/docker-redis/redis-cluster/6372/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /usr/local/docker-redis/redis-cluster/6372/data:/data
    command: redis-server /usr/local/etc/redis/redis.conf

  lockredis3:
    image: redis
    container_name: lockredis3
    network_mode: "host"
    volumes:
      - /usr/local/docker-redis/redis-cluster/6373/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /usr/local/docker-redis/redis-cluster/6373/data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
  lockredis4:
      image: redis
      container_name: lockredis4
      network_mode: "host"
      volumes:
        - /usr/local/docker-redis/redis-cluster/6374/conf/redis.conf:/usr/local/etc/redis/redis.conf
        - /usr/local/docker-redis/redis-cluster/6374/data:/data
      command: redis-server /usr/local/etc/redis/redis.conf
  lockredis5:
      image: redis
      container_name: lockredis5
      network_mode: "host"
      volumes:
        - /usr/local/docker-redis/redis-cluster/6375/conf/redis.conf:/usr/local/etc/redis/redis.conf
        - /usr/local/docker-redis/redis-cluster/6375/data:/data
      command: redis-server /usr/local/etc/redis/redis.conf
  lockredis6:
      image: redis
      container_name: lockredis6
      network_mode: "host"
      volumes:
        - /usr/local/docker-redis/redis-cluster/6376/conf/redis.conf:/usr/local/etc/redis/redis.conf
        - /usr/local/docker-redis/redis-cluster/6376/data:/data
      command: redis-server /usr/local/etc/redis/redis.conf

创建并启动全部容器

在docker-compose文件所在目录,执行下列命令:

docker-compose up -d

创建成功后,可以通过docker-compose ps查看结果:

 ~/lincxlock/redis # docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
lockredis1          "docker-entrypoint.s…"   lockredis1          running
lockredis2          "docker-entrypoint.s…"   lockredis2          running
lockredis3          "docker-entrypoint.s…"   lockredis3          running
lockredis4          "docker-entrypoint.s…"   lockredis4          running
lockredis5          "docker-entrypoint.s…"   lockredis5          running
lockredis6          "docker-entrypoint.s…"   lockredis6          running

启动Redis集群

进入其中一个redis容器,例如lockredis1

# docker exec -it lockredis1 bash

执行以下命令,这里的172.27.76.104是主机的ip地址 :

>redis-cli -a 1234 --cluster create 172.27.76.104:6371 172.27.76.104:6372 172.27.76.104:6373 172.27.76.104:6374 172.27.76.104:6375 172.27.76.104:6376 --cluster-replicas 1

集群创建并启动成功:

LBH1Cd.png

1.2 ETCD集群

启动三个etcd容器,因为每个etcd需要两个端口,所以将机器的20000-20006端口每两个分配给一个etcd容器。

创建文件目录

在·~/etcd/路径下分边创建文件夹名为data1、data2、data3的文件夹,用于存储每个容器的配置文件与data。

编写配置文件

编写docker-compose文件

version: '3'
networks:
  etcd-net:           # 网络
    driver: bridge    # 桥接模式

services:
  locketcd1:
    image: quay.io/coreos/etcd:v3.5.1  # 镜像
    container_name: locketcd1       # 容器名 --name
    restart: always             # 总是重启
    networks:
      - etcd-net                # 使用的网络 --network
    ports:                      # 端口映射 -p
      - "20000:2379"
      - "20001:2380"
    environment:                # 环境变量 --env
      - ALLOW_NONE_AUTHENTICATION=yes                       # 允许不用密码登录
      - ETCD_NAME=locketcd1                                     # etcd 的名字
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://locketcd1:2380  # 列出这个成员的伙伴 URL 以便通告给集群的其他成员
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380           # 用于监听伙伴通讯的URL列表
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379         # 用于监听客户端通讯的URL列表
      - ETCD_ADVERTISE_CLIENT_URLS=http://locketcd1:2379        # 列出这个成员的客户端URL,通告给集群中的其他成员
      - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster             # 在启动期间用于 etcd 集群的初始化集群记号
      - ETCD_INITIAL_CLUSTER=locketcd1=http://locketcd1:2380,locketcd2=http://locketcd2:2380,locketcd3=http://locketcd3:2380        # 为启动初始化集群配置
      - ETCD_INITIAL_CLUSTER_STATE=new                      # 初始化集群状态
      - ETCDCTL_API=3                                       # 升级api版本,使用最新的v3 API
    volumes:
      - $PWD/data1:/etcd-data                       # 挂载的数据卷
      - /etc/localtime:/etc/localtime

  locketcd2:
    image: quay.io/coreos/etcd:v3.5.1
    container_name: locketcd2
    restart: always
    networks:
      - etcd-net
    ports:
      - "20002:2379"
      - "20003:2380"
    environment:
      - ALLOW_NONE_AUTHENTICATION=yes
      - ETCD_NAME=locketcd2
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://locketcd2:2380
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
      - ETCD_ADVERTISE_CLIENT_URLS=http://locketcd2:2379
      - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
      - ETCD_INITIAL_CLUSTER=locketcd1=http://locketcd1:2380,locketcd2=http://locketcd2:2380,locketcd3=http://locketcd3:2380
      - ETCD_INITIAL_CLUSTER_STATE=new
      - ETCDCTL_API=3
    volumes:
      - $PWD/data2:/etcd-data
      - /etc/localtime:/etc/localtime
  locketcd3:
    image: quay.io/coreos/etcd:v3.5.1
    container_name: locketcd3
    restart: always
    networks:
      - etcd-net
    ports:
      - "20004:2379"
      - "20005:2380"
    environment:
      - ALLOW_NONE_AUTHENTICATION=yes
      - ETCD_NAME=locketcd3
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://locketcd3:2380
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
      - ETCD_ADVERTISE_CLIENT_URLS=http://locketcd3:2379
      - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
      - ETCD_INITIAL_CLUSTER=locketcd1=http://locketcd1:2380,locketcd2=http://locketcd2:2380,locketcd3=http://locketcd3:2380
      - ETCD_INITIAL_CLUSTER_STATE=new
      - ETCDCTL_API=3
    volumes:
      - $PWD/data3:/etcd-data
      - /etc/localtime:/etc/localtime

创建并启动全部容器

在docker-compose文件所在目录,执行下列命令:

docker-compose up -d

创建成功后,可以通过docker-compose ps查看结果:

LBql6I.png

1.3 Zookeeper集群

启动三个Zookeeper容器,因为每个etcd需要两个端口,所以将机器的2181-2183端口分别分配给容器。

创建文件目录

在·~/zk/路径下分边创建文件夹名为data1、data2、data3的文件夹,用于存储每个容器的配置文件与data。

编写配置文件

编写docker-compose文件

version: '2' #版本号
services:
    lockzk1: #
        image: zookeeper:latest
        restart: always
        container_name: lockzk1
        ports:
            - "2181:2181"
        environment:
            ZOO_MY_ID: 1 #环境变量 这个id在集群必须是唯一的,该值介于1~255之间
            ZOO_SERVERS: server.1=lockzk1:2888:3888 server.2=lockzk2:2888:3888 server.3=lockzk3:2888:3888 #此变量允许您指定Zookeeper集群的计算机列表;
 
    lockzk2:
        image: zookeeper:latest
        restart: always
        container_name: lockzk2
        ports:
            - "2182:2181"
        environment:
            ZOO_MY_ID: 2
            ZOO_SERVERS: server.1=lockzk1:2888:3888 server.2=lockzk2:2888:3888 server.3=lockzk3:2888:3888
 
    lockzk3:
        image: zookeeper:latest
        restart: always
        container_name: lockzk3
        ports:
            - "2183:2181"
        environment:
            ZOO_MY_ID: 3
            ZOO_SERVERS: server.1=lockzk1:2888:3888 server.2=lockzk2:2888:3888 server.3=lockzk3:2888:3888

创建并启动全部容器

在docker-compose文件所在目录,执行下列命令:

docker-compose up -d

创建成功后,可以通过docker-compose ps查看结果:

LBq0cn.png

2、测试内容

2.1 功能测试

2.1.1 分布式锁功能测试

编写测试代码

var(
	zkserver1 = "172.27.78.70:2181"
    zkserver2 = "172.27.78.70:2182"
    zkserver3 = "172.27.78.70:2183"
	redisserver1 = "172.27.78.70:6371"
    redisserver2 = "172.27.78.70:6372"
    redisserver3 ="172.27.78.70:6373"
	etcdserver1 = "172.27.78.70:20000"
    etcdserver1 = "172.27.78.70:20002"
    etcdserver1 = "172.27.78.70:20004"
)

// 测试三种实现方式的功能
func TestCallLock() {
	CallLock("redislock","lincxlock",[]string{redisserver1,redisserver2,redisserver3})
    CallLock("etcdlock","lincxlock",[]string{etcdserver1,etcdserver2,etcdserver3})
    CallLock("zklock","lincxlock",[]string{zkerver1,zkerver2,zkerver3})
}

// 模拟10个机器争夺分布式锁
func CallLockLock(locktype,lockey string,hosts []string){
	conf,err:=config.NewLockConf(locktype,5,hosts)
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(n int) {

			defer wg.Done()
			
			ll ,err:= lock.NewLincxLock("locklincx",config.LockConf)
			
			if err != nil {
				log.Printf("initlock error:%v", err)
				return
			}
			err = ll.Lock()
			if err != nil {
				log.Printf("number %v gorotine lock error:%v", n, err)
				return
			}
			log.Printf("number %v gorotine get lock\n", n)
			time.Sleep(1 * time.Second)
			log.Printf("number %v gorotine lease lock\n", n)
			//ll.Unlock()
		}(i)
	}
	wg.Wait()
	
}

执行上面的代码,测试结果如下:

redis 测试结果:

LBXTH0.png

etcd测试结果:

LBjKVP.png

zookeeper测试结果:

LBjsxJ.png

2.1.2 RPC服务测试

编写测试代码


func TestRPC(){
    StartServer()
	
	time.Sleep(10*time.Second)
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(n int){
			defer wg.Done()
			RpcCall(n)
		}(i)
		
	}
	wg.Wait()
	sv.Close()
}
func StartServer(){
	sv = rpcserver.NewServer("lincxlock-app",nil,re1,&rpcserver.DefaultRPCServerOption)
	go func ()  {
		defer func ()  {
			if err:=recover();err!=nil{
				log.Printf("panic err %v",err)
			}
		}()
		sv.Serve("tcp",":8881",nil)
	}()
}

func StopServer(){
	sv.Close()
}
func RpcCall(n int){
	log.Printf("provider len %d\n",len(re1.GetServiceList()))
	op := &client.DefaultSGOption
	op.AppKey = "lincxlock-app"
	op.SerializeType = codec.MessagePackType
	op.RequestTimeout = time.Second*10
	op.DialTimeout = time.Second*10
	op.FailMode = client.FailRetry
	op.Retries = 3
	op.Heartbeat = true
	op.HeartbeatInterval = time.Second * 10
	op.HeartbeatDegradeThreshold = 10
	op.Registry = re1
	c := client.NewSGClient(*op)
	lockargs := lock.LockRPCArgs{
		Locktype: "zklock",
		Timeout: 5,
		JobName: "/distributed-lock/lock",
		Hosts: []string{zkserver},
	}
	reply := &lock.LockReply{}
	ctx := context.Background()
	err := c.Call(ctx, "LockRPC.NewLock", lockargs,reply )
	if err != nil {
		log.Println("err!!!" + err.Error())
		return
	} 
	time.Sleep(1*time.Second)
	log.Printf("goroutine %d get lock\n",n)

	unlockargs := lock.UnlockRPCArgs{
		LockID: reply.LockID,
	}
	ulreply := &lock.UnlockReply{}
	err = c.Call(ctx, "LockRPC.Unlock", unlockargs,ulreply )
	if err!=nil{
		log.Println("err!!"+err.Error())
		return
	}
	log.Printf("goroutine %d unlock\n",n)

}

执行上述代码,测试结果如下:

LBxSk6.png

2.1.3 Web服务测试

编写测试代码:

func TestHttp(){
    httpserver.StartHttp(8888) // 启动http服务
	httpserver.StartHttps(8881) //启动https服务
}

执行代码,使用postman请求

测试结果如下:

LDSles.png

LDS40A.png

2.2 性能测试

2.2.1 加解锁性能测试

测试步骤如下:

1、为三种锁的实现分别编写用于性能测试的压测函数,内容为新建一个锁对象,调用加锁方法再调用解锁方法。

2、执行压测命令,查看压测结果,并声称。

3、通过pprof查看具体的方法执行的内存消耗与CPU运行时间。

编写的测试代码:

func BenchmarkCallZkLock(b *testing.B) {
	b.ResetTimer()
	
	for i:=0;i<b.N;i++{
		CallLock("zklock","/lincxlock/lock",[]string{zkserver})
	}
	
	b.StopTimer()
}
func BenchmarkCallEtcdLock(b *testing.B) {
	b.ResetTimer()
	
	for i:=0;i<b.N;i++{
		CallLock("etcdlock","/lincxlock/lock",[]string{etcdserver})
	}
	
	b.StopTimer()
}
func BenchmarkCallRedisLock(b *testing.B) {
	b.ResetTimer()
	for i:=0;i<b.N;i++{
		CallLock("redislock","lincxlock",[]string{redisserver})
	}
	b.StopTimer()
}

func CallLock(locktype,lockey string,hosts []string){
	conf,err:=config.NewLockConf(locktype,5,hosts)
	if err!=nil{
		return
	}
	ll ,err:= lock.NewLincxLock(lockey,conf)
	if err != nil {
		return
	}
	err = ll.Lock()
	if err != nil {
		return
	}
	ll.Unlock()
	
}

redis测试结果

基于Redis加解锁测试结果如下所示,结果显示执行363次,每次消耗的平均时间为4.356796毫秒,每次分配的内存大小为22566 字节(22.56KB)以及调用分配了107次内存。其中图显示,内存占用比较多的方法是获取Redis连接的方法。

LD9RZd.png

LD9IRf.png

etcd测试结果

基于ETCD的分布式锁测试结果如图所示,结果显示执行159次,每次消耗的平均时间为148.108404毫秒,每次分配的内存大小为631627字节(631.627KB)以及调用分配了1192次内存。其中图显示,内存占用比较大的是ETCD客户端的日志模块。

LDCx7d.png

LDPWCt.png

Zookeeper结果

ZK的测试结果如图所示,结果显示执行165次,每次消耗的平均时间为132.909104毫秒,平均每次分配的内存大小为3154434字节(3.1M)以及调用分配了147次内存。其中图显示,Zookeeper客户端的连接与接收数据模块占的内存比较大。

LDiPa9.png

LDiMad.png

2.2.2 RPC性能测试

LINCXRPC框架的性能直接影响了LINCXLOCK的RPC服务,所以对RPC的Call方法进行压测,首先启动服务端,再使用LINCXRPC的Client的Call方法调用服务。

编写测试代码

func BenchmarkMakeCallMSGP(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		MakeCall(codec.MessagePackType)
	}
	b.StopTimer()
}

func MakeCallCall(codec.MessagePackType){
	op := &client.DefaultSGOption
	op.AppKey = "lincxlock-app"
	op.SerializeType = codec.MessagePackType
	op.RequestTimeout = time.Second*10
	op.DialTimeout = time.Second*10
	op.FailMode = client.FailRetry
	op.Retries = 3
	op.Heartbeat = true
	op.HeartbeatInterval = time.Second * 10
	op.HeartbeatDegradeThreshold = 10
	op.Registry = re1
	c := client.NewSGClient(*op)
	lockargs := lock.LockRPCArgs{
		Locktype: "zklock",
		Timeout: 5,
		JobName: "/distributed-lock/lock",
		Hosts: []string{zkserver},
	}
	reply := &lock.LockReply{}
	ctx := context.Background()
	err := c.Call(ctx, "LockRPC.NewLock", lockargs,reply )
	if err != nil {
		log.Println("err!!!" + err.Error())
		return
	} 
	time.Sleep(1*time.Second)
	log.Printf("goroutine %d get lock\n",n)

	unlockargs := lock.UnlockRPCArgs{
		LockID: reply.LockID,
	}
	ulreply := &lock.UnlockReply{}
	err = c.Call(ctx, "LockRPC.Unlock", unlockargs,ulreply )
	if err!=nil{
		log.Println("err!!"+err.Error())
		return
	}
	log.Printf("goroutine %d unlock\n",n)

}

每次消耗的平均时间为3.084287毫秒,每次分配的内存大小为12390字节(12.390KB)以及调用分配了153次内存。

LDFhnS.png

通过分析内存分配情况,如图1所示,可知主要消耗内存的模块是RPC客户端、选择器(负载均衡)。由图2可知,耗时较多的部分是网络通信以及系统调用,系统调用耗时主要与服务端的反射有关。总体来说,LINCXRPC性能表现较好,适用于LINCXLOCK作为RPC框架。

LDFq10.png

LDkVBD.png

3、总结

该文档主要介绍了LINCXLOCK的LINCXLOCK测试的内容,包括搭建三种集群的测试环境,对LINCXLOCK的核心三大板块进行功能测试,最后对加解锁方法与RPC的Call方法使用go语言自带的测试工具进行性能测试。