分类目录归档:Dev

Golang Rate Limit流控

通常一个稳健的系统需要有一定的熔断或流控机制,用来防止突发或异常请求造成系统过高占用, 致使系统无法正常运作.
Go中无需使用第三方库,就可以很容易就可以做到流控.

QPS控制,10 QPS

通过Ticker触发消息,达到流控目的

import "time"

rate := time.Second / 10
throttle := time.Tick(rate)
for req := range requests {
  <-throttle  // rate limit our Service.Method RPCs
  go client.Call("Service.Method", req, ...)
}
import "time"

rate := time.Second / 10
burstLimit := 100
tick := time.NewTicker(rate)
defer tick.Stop()
throttle := make(chan time.Time, burstLimit)
go func() {
  for t := range tick.C {
    select {
      case throttle <- t:
      default:
    }
  }  // does not exit after tick.Stop()
}()
for req := range requests {
  <-throttle  // rate limit our Service.Method RPCs
  go client.Call("Service.Method", req, ...)
}

Aliyun 支持IPv6

启用 IPv6 的主要有四步:

  • 注册并创建 IPv6 通道
  • 配置 ECS 使其支持 IPv6
  • 配置 Nginx 使其监听 IPv6 端口
  • 配置 DNS 使其支持 IPv6 解析

第一步:注册并创建 IPv6 通道

注册 https://www.tunnelbroker.net/ (需要邮箱验证)
点击 Create Regular Tunnel
在IPv4 Endpoint (Your side)处填上 ECS 的 IPv4 地址
在Available Tunnel Servers中选择Hong Kong, HK(如果你面向海外用户,可以选择更接近目标用户的地区)
点击Create Tunnel后,通道就创建完成了

第二步:配置 ECS 使其支持 IPv6

编辑/etc/sysctl.conf,将以下三项的配置改成0

  net.ipv6.conf.all.disable_ipv6 = 0
  net.ipv6.conf.default.disable_ipv6 = 0
  net.ipv6.conf.lo.disable_ipv6 = 0

在/etc/network/interfaces底部加上以下内容(注:下面大写的处,需要替换成你在 HE 得到的Server IPv6 Address,但不包括最后的::1/64,如:

2001:470:100:100
  auto he-ipv6
  iface he-ipv6 inet6 v4tunnel
  address <IPV6>::2
  netmask 64
  remote <HE 的 Server IPv4 Address>
  local <阿里云的 IPv4 地址(内网IP)>
  endpoint any
  ttl 255
  gateway <IPv6>::1
  up ip -6 route add 2000::/3 via ::<HE 的 Server IPv4 Address> dev he-ipv6
  up ip -6 addr add <IPv6>::1:1/128 dev he-ipv6
  up ip -6 addr add <IPv6>::2:1/128 dev he-ipv6
  down ip -6 route flush dev he-ipv6

重启服务器
执行ifup he-ipv6确认 IPv6 已启用

第三步:配置 Nginx 使其监听 IPv6 端口

server {
  listen 80; // 监听 IPv4 的 80 端口
  listen [::]:80; // 监听 IPv6 的 80 端口
}

server {
  listen 443 ssl http2; // 监听 IPv4 的 443 端口
  listen [::]:443 ssl http2; // 监听 IPv6 的 443 端口
}

第四步:配置 DNS 使其支持 IPv6 解析

这步最简单,只需给相应的域名加上AAAA解析,值填 HE 里的Client IPv6 Address,去掉最后的/64即可,如

2001:470:100:100::2

Unity Android 编译依赖 Gradle 的版本

Unity项目导出Android编译经常遇到的问题就是Gradle版本不匹配的问题。默认情况下Android Studio会自作聪明地取最新版本的Gradle。
Gradle Build Error: Gradle version 2.10 is required. Current version is 4.0.1

2017.1 和 2017.2 使用
Android plugin version 2.1.0
Gradle version 2.14

2017.4 和 2018.1 使用
Android plugin version 2.3.0
Gradle version 4.0.1

2018.2 使用
Android plugin version 3.0.1
Gradle version 4.2.1

2018.3 使用
Android plugin version 3.2
Gradle version 4.6

C# for 和 foreach 性能测试

for与foreach从性能上来讲几乎是一样的,主要区别在栈内存的使用上。foreach会涉及迭代器,而迭代器会使用栈内存。从某种程度来说,foreach其实是一种语法糖,甚至连微软也推荐在循环中操作集合涉及到添加或移除项时只使用for,否则可能会产生不可预知的副作用。

测试方式是for 和 foreach 分别遍历一个数组一百万次。

测试1:遍历数组时,每个元素只访问一次

测试2:遍历数组时,每个元素访问多次

测试环境

1. macOS
2. Mono Runtime

Program.cs

using System;
using System.Diagnostics;

class Program {
    static void Main() {
        Console.WriteLine ("Runing Csharp Loop Performace Test.");
        const int loop_count = 1000 * 1000 * 100;
        int[] array = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
        // 测试 for 性能
        var watch_1 = Stopwatch.StartNew();
        for (int i = 0; i < loop_count; i++) {
            TestCase.case_for(array);
        }
        watch_1.Stop();
        // 测试 foreach 性能
        var watch_2 = Stopwatch.StartNew();
        for (int i = 0; i < loop_count; i++) {
            TestCase.case_foreach(array);
        }
        watch_2.Stop();

        Console.WriteLine ("case for: " +
                           ((double)(watch_1.Elapsed.TotalMilliseconds * 1000000) / loop_count).ToString("0.0 ns"));
        Console.WriteLine ("case foreach: " +
                           ((double)(watch_2.Elapsed.TotalMilliseconds * 1000000) / loop_count).ToString("0.0 ns"));
    }
}

测试1[for 测试]

class TestCase {
    public static int case_for(int[] array) {
        int result = 0;
        for (int i = 0; i < array.Length; i++) {
            result += array[i];
        }
        return result;
    }
}

测试1 [foreach 测试]

class TestCase {
    public static int case_foreach(int[] array) {
        int result = 0;
        foreach (int value in array) {
            result += value;
        }
        return result;
    }
}

测试1耗时

Runing Csharp Loop Performace Test.
case for: 12.1 ns
case foreach: 12.6 ns

测试2 [for 测试]

class TestCase {
    public static int case_foreach(int[] array) {
        int result = 0;
        foreach (int value in array) {
            result += value;
            result += value;
            result += value;
        }
        return result;
    }
}

测试2 耗时

uning Csharp Loop Performace Test.
case for: 20.5 ns
case foreach: 15.4 ns

结论

for的性能是要比foreach要高出一些,但测试1和测试2对比。 需要频繁去访问集合中的元素

result += array[i];

反而会比用foreach还要慢,因为foreach已经把元素取出后无需再次访问集合,元素已经在栈内存中。

所以在使用循环时,也需要考虑实际场景。如果循环内只会访问一次元素,则用for,如果访问多次元素则用foreach,但从易用性以及风格统一的考虑推荐使用foreach,除非有特殊需求再考虑使用for循环。

Git子模块的使用

github在大半年前就允许个人付费用户创建无限个数的私有git仓库,对于个人开发者来说这是一个很好福利。 不过每个仓库是有大小限制的,最大不超过1G。但是一个完整的项目,带上文档和资源很轻松就超过1G。 超过后就必须再付费,每50G/5美元为单位进行再付费。所以为了节省成本,可以把项目拆分到不同的子模块中, 在不同仓库进行分存就可以不超过github的限制。

子模块其实也是一个完整的git仓库,如果单独去维护子模块则跟普通git仓库的操作一致。 但是从父项目的角度来维护,则有一些不同之处。以下是子模块的基本用法和常见的问题。

创建子模块

git submodule add git://github.com/Someone/SomeRepo.git

创建完成后,会生成.gitmodules文件

cat .gitmodule
[submodule "SomeRepo"]
      path = SomeRepo
      url = git://github.com/Someone/SomeRepo.git

更新子模块

git submodule update

本质上,子模块和父项目是不同的仓库。这里有个坑。 git pull并不会更新子模块,此时执行git status发现子模块有更新,则需要执行 git submodule update. 所以正确的更新方式是

git pull
git submodule update

修改子模块

默认情况下,进入子模块HEAD是处理游离状态(detached HEAD), 所以如果要更新子模块,就先要checkout到master

cd submodule_folder
git checkout master
do some mod
git add files
git commit -m 'Mod submodule'
git push

如果开始一开始忘记checkout到master并且有进行commit, 则可以采用如下办法

mod submodule files and commited
git log #查看commit hash id 假定id是12345
git checkout master
git cherry-pick 12345
git push

删除子模块

最简单的方法就是编辑.gitmodule文件把相关信息删除,然后再从文件系统把子模块相关目录删除。

- [submodule "SomeRepo"]
-       path = SomeRepo
-       url = git://github.com/Someone/SomeRepo.git