禹鼎侯 查看完整档案

_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

从一个问题引入

``````#include <stdio.h>

int main(void)
{
float f_num1 = 21.75;
float f_num2 = 13.45;
printf("f_num1 = %f\n", f_num1);
printf("f_num2 = %f\n", f_num2);
printf("f_num1 + f_num2 = %f\n", f_num1 + f_num2);

return 0;
}``````

``````f_num1 = 21.750000
f_num2 = 13.450000
f_num1 + f_num2 = 35.200001``````

`f_num1``f_num2`的结果和我们预想的一样，之所以后面多了四个0，是因为`%f`默认保留6位有效数字。但是`f_num1 + f_num2`的结果是什么鬼，这个`35.200001`是从哪里来的？

如果是C++呢

``````#include<iostream>
using namespace std;

int main(void)
{
float f_num1 = 21.75;
float f_num2 = 13.45;
cout << "f_num1 = " << f_num1 << endl;
cout << "f_num2 = " << f_num2 << endl;
cout << "f_num1 + f_num2 = " << f_num1 + f_num2 << endl;
return 0;
}``````

``````f_num1 = 21.75
f_num2 = 13.45
f_num1 + f_num2 = 35.2``````

cout的神奇之处

``````#include <iostream>
using namespace std;

int main(void)
{
float num1 = 5;
float num2 = 5.00;
float num3 = 5.14;
float num4 = 5.140000;
float num5 = 5.123456;
float num6 = 5.987654321;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
cout << "num3 = " << num3 << endl;
cout << "num4 = " << num4 << endl;
cout << "num5 = " << num5 << endl;
cout << "num6 = " << num6 << endl;

return 0;
}``````

``````num1 = 5
num2 = 5
num3 = 5.14
num4 = 5.14
num5 = 5.12346
num6 = 5.98765``````

`num1``num2``num3``num4`这两组结果可以知道，`cout`对于`float`类型数值小数点后面的0是直接省去了的（这点和C语言格式化输出的%g有点像）。
`num5``num6`两组结果不难分析出，`cout`对于浮点型数值，最多保留6位有效数字。

``````#include<iostream>
using namespace std;

int main(void)
{
float f_num1 = 21.75;
float f_num2 = 13.45;
cout.setf(ios_base::fixed, ios_base::floatfield);
cout << "f_num1 = " << f_num1 << endl;
cout << "f_num2 = " << f_num2 << endl;
cout << "f_num1 + f_num2 = " << f_num1 + f_num2 << endl;
return 0;
}``````

``````f_num1 = 21.750000
f_num2 = 13.450000
f_num1 + f_num2 = 35.200001``````

答案呼之欲出

``````#include<iostream>
using namespace std;

int main(void)
{
float f_num1 = 21.75;
float f_num2 = 13.45;
cout.setf(ios_base::fixed, ios_base::floatfield);
int billion = 1E9;
float f_num10 = f_num1 * billion;
float f_num20 = f_num2 * billion;
cout << "f_num1 = " << f_num1 << endl;
cout << "f_num2 = " << f_num2 << endl;

cout << "f_num10 = " << f_num10 << endl;
cout << "f_num20 = " << f_num20 << endl;
return 0;
}``````

``````f_num1 = 21.750000
f_num2 = 13.450000
f_num10 = 21749999616.000000
f_num20 = 13449999360.000000``````

再看一个例子

``````#include<iostream>
using namespace std;

int main(void)
{
float num1 = 2.3410E23;
float num2 = num1 + 1.0f;
cout << "num2 - num1 = " << num2 - num1 << endl;
return 0;
}``````

``num2 - num1 = 0``

怎么解决

首先，当然推荐大家在编程时尽量使用高精度的浮点类型

``````#include <stdio.h>

int main(void)
{
double f_num1 = 21.75;
double f_num2 = 13.45;
printf("f_num1 = %lf\n", f_num1);
printf("f_num2 = %lf\n", f_num2);
printf("f_num1 + f_num2 = %lf\n", f_num1 + f_num2);

return 0;
}``````

`double`类型可以解决大部分精度丢失问题，基本上满足日常使用了，但是仍然不能避免精度丢失（`double`也有精度限制），这时候就需要想另外的方法来解决了。

不同数据库的支持

mysql

date2020-10-20Nselect *, datediff(date, '1970-01-01') as days from tbl_time where datediff(date, '1970-01-01') > :sql_last_valuedaysnumericselect *, datediff(date, '1970-01-01') as days from tbl_time where datediff(date, '1970-01-01') > 1603244266
datetime timestamp2020-10-20 06:12:01Yselect * from tbl_time where time > :sql_last_valuetimetimestampselect * from tbl_time where time > '2020-10-20 06:12:01'

sqlserver

date2020-10-21N
time14:00:00.0000000N
datetime2020-10-21 13:59:40.000Y
datetime22020-10-21 14:00:00Y
smalldatetime2020-10-21 14:00:00Y
datetimeoffset2020-10-21 14:00:00.0000000 +08:00Y

db2

date2020-10-21N
time14:00:00.0000000N
timestamp2020-10-21 13:59:40.000Y

Oracle

date2010-2-12N
timestamp12-FEB-10 01.24.52.234123211 PMYselect *,to_char(sysdate,'YYYY-MM-DD HH24:MI:SS') as time d1 from dual where to_char(sysdate,'YYYY-MM-DD HH24:MI:SS') > :sql_last_value

Postgre

date1997-01-01N
timestamp2020-06-17 10:01:08.03282Y
time12:00:00N

sybase

dateJul 24 2014N
timestamp0x00000000000a8b75N
datetime待验证
smalldatetime待验证

总结

`````` statement => "select *, datediff(s, '1970-01-01', date)  from tbl_time where datediff(s, '1970-01-01', date) > :sql_last_value"
tracking_column => "datediff(s, '1970-01-01', date)"
tracking_column_type => "numeric"``````

配置示例

MySQL数据库表结构如下：

``````mysql> desc tbl_time;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id        | int(11)      | NO   | PRI | NULL    |       |
| bigid     | bigint(20)   | YES  |     | NULL    |       |
| name      | varchar(255) | YES  |     | NULL    |       |
| date      | date         | YES  |     | NULL    |       |
| time      | datetime     | YES  |     | NULL    |       |
| timestamp | timestamp    | YES  |     | NULL    |       |
| shjnch    | bigint(255)  | YES  |     | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
7 rows in set (0.00 sec)``````

``````mysql> select * from tbl_time;
+----+-------+-------+------------+---------------------+---------------------+------------+
| id | bigid | name  | date       | time                | timestamp           | shjnch     |
+----+-------+-------+------------+---------------------+---------------------+------------+
|  1 |     1 | time1 | 2020-10-21 | 2020-10-21 09:37:31 | 2020-10-21 09:37:34 | 1603244266 |
+----+-------+-------+------------+---------------------+---------------------+------------+
1 row in set (0.00 sec)``````

``````input {
jdbc {
jdbc_driver_class => "com.mysql.jdbc.Driver"
jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/mytest?useSSL=false"
jdbc_user => "root"
statement => "select *, FROM_UNIXTIME(shjnch, '%Y-%m-%d %h:%i:%s') as timestamp from tbl_time where FROM_UNIXTIME(shjnch, '%Y-%m-%d %h:%i:%s') < :sql_last_value""
schedule => "*/1 * * * *"
connection_retry_attempts => 5
connection_retry_attempts_wait_time => 1
tracking_column => "timestamp"
tracking_column_type => "timestamp"
columns_charset => {
"message" => "utf-8"
}
use_column_value => true
lowercase_column_names => false
record_last_run => true
"@topic" => "fc491237449424896"
"@tags" => []
"@ip" => "127.0.0.1"
}
}
}``````

``{"@topic":"fc491237449424896","timestamp":"2020-10-21 09:37:46","@ip":"127.0.0.1","date":"2020-10-21T00:00:00.000+08:00","bigid":1,"time":"2020-10-21T09:37:31.000+08:00","@timestamp":"2020-10-21T15:36:02.526+08:00","id":1,"shjnch":1603244266,"name":"time1"}``

1 谈谈 RPC 框架

RPC(Remote Procedure Call，远程过程调用)是一种计算机通信协议，允许调用不同进程空间的程序。RPC 的客户端和服务器可以在一台机器上，也可以在不同的机器上。程序员使用时，就像调用本地程序一样，无需关注内部的实现细节。

• Restful 接口需要额外的定义，无论是客户端还是服务端，都需要额外的代码来处理，而 RPC 调用则更接近于直接调用。
• 基于 HTTP 协议的 Restful 报文冗余，承载了过多的无效信息，而 RPC 通常使用自定义的协议格式，减少冗余报文。
• RPC 可以采用更高效的序列化协议，将文本转为二进制传输，获得更高的性能。
• 因为 RPC 的灵活性，所以更容易扩展和集成诸如注册中心、负载均衡等功能。

2 RPC 框架需要解决什么问题

RPC 框架需要解决什么问题？或者我们换一个问题，为什么需要 RPC 框架？

3 关于 GeeRPC

Go 语言广泛地应用于云计算和微服务，成熟的 RPC 框架和微服务框架汗牛充栋。`grpc``rpcx``go-micro` 等都是非常成熟的框架。一般而言，RPC 是微服务框架的一个子集，微服务框架可以自己实现 RPC 部分，当然，也可以选择不同的 RPC 框架作为通信基座。

为什么需要内存控制？

• 站在一个普通Linux开发者的角度，如果能控制一个或者一组进程所能使用的内存数，那么就算代码有bug，内存泄漏也不会对系统造成影响，因为可以设置内存使用量的上限，当到达这个值之后可以将进程重启。

• 站在一个系统管理者的角度，如果能限制每组进程所能使用的内存量，那么不管程序的质量如何，都能将它们对系统的影响降到最低，从而保证整个系统的稳定性。

内存控制能控制些什么？

• 限制cgroup中所有进程所能使用的物理内存总量

• 限制cgroup中所有进程所能使用的物理内存+交换空间总量(CONFIG_MEMCG_SWAP)： 一般在server上，不太会用到swap空间，所以不在这里介绍这部分内容。

• 限制cgroup中所有进程所能使用的内核内存总量及其它一些内核资源(CONFIG_MEMCG_KMEM)： 限制内核内存有什么用呢？其实限制内核内存就是限制当前cgroup所能使用的内核资源，比如进程的内核栈空间，socket所占用的内存空间等，通过限制内核内存，当内存吃紧时，可以阻止当前cgroup继续创建进程以及向内核申请分配更多的内核资源。由于这块功能被使用的较少，本篇中也不对它做介绍。

内核相关的配置

• 由于memory subsystem比较耗资源，所以内核专门添加了一个参数cgroup_disable=memory来禁用整个memory subsystem，这个参数可以通过GRUB在启动系统的时候传给内核，加了这个参数后内核将不再进行memory subsystem相关的计算工作，在系统中也不能挂载memory subsystem。

• 上面提到的CONFIG_MEMCG_SWAP和CONFIG_MEMCG_KMEM都是扩展功能，在使用前请确认当前内核是否支持，下面看看ubuntu 16.04的内核：

``````#这里CONFIG_MEMCG_SWAP和CONFIG_MEMCG_KMEM等于y表示内核已经编译了该模块，即支持相关功能
dev@dev:~\$ cat /boot/config-`uname -r`|grep CONFIG_MEMCG
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
# CONFIG_MEMCG_SWAP_ENABLED is not set
CONFIG_MEMCG_KMEM=y``````
• CONFIG_MEMCG_SWAP控制内核是否支持Swap Extension，而CONFIG_MEMCG_SWAP_ENABLED（3.6以后的内核新加的参数）控制默认情况下是否使用Swap Extension，由于Swap Extension比较耗资源，所以很多发行版（比如ubuntu）默认情况下会禁用该功能（这也是上面那行被注释掉的原因），当然用户也可以根据实际情况，通过设置内核参数swapaccount=0或者1来手动禁用和启用Swap Extension。

怎么控制？

``````#如果这里发现有多行结果，说明这颗cgroup数被绑定到了多个地方，
#不过不要担心，由于它们都是指向同一颗cgroup树，所以它们里面的内容是一模一样的
dev@dev:~\$ mount|grep memory
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)``````

创建子cgroup

``````#--------------------------第一个shell窗口----------------------
dev@dev:~\$ cd /sys/fs/cgroup/memory
dev@dev:/sys/fs/cgroup/memory\$ sudo mkdir test
dev@dev:/sys/fs/cgroup/memory\$ ls test
cgroup.clone_children  memory.kmem.failcnt             memory.kmem.tcp.limit_in_bytes      memory.max_usage_in_bytes        memory.soft_limit_in_bytes  notify_on_release
cgroup.event_control   memory.kmem.limit_in_bytes      memory.kmem.tcp.max_usage_in_bytes  memory.move_charge_at_immigrate  memory.stat                 tasks
cgroup.procs           memory.kmem.max_usage_in_bytes  memory.kmem.tcp.usage_in_bytes      memory.numa_stat                 memory.swappiness
memory.failcnt         memory.kmem.slabinfo            memory.kmem.usage_in_bytes          memory.oom_control               memory.usage_in_bytes
memory.force_empty     memory.kmem.tcp.failcnt         memory.limit_in_bytes               memory.pressure_level            memory.use_hierarchy``````

`````` cgroup.event_control       #用于eventfd的接口
memory.usage_in_bytes      #显示当前已用的内存
memory.limit_in_bytes      #设置/显示当前限制的内存额度
memory.failcnt             #显示内存使用量达到限制值的次数
memory.max_usage_in_bytes  #历史内存最大使用量
memory.soft_limit_in_bytes #设置/显示当前限制的内存软额度
memory.stat                #显示当前cgroup的内存使用情况
memory.use_hierarchy       #设置/显示是否将子cgroup的内存使用情况统计到当前cgroup里面
memory.force_empty         #触发系统立即尽可能的回收当前cgroup中可以回收的内存
memory.pressure_level      #设置内存压力的通知事件，配合cgroup.event_control一起使用
memory.swappiness          #设置和显示当前的swappiness
memory.move_charge_at_immigrate #设置当进程移动到其他cgroup中时，它所占用的内存是否也随着移动过去
memory.oom_control         #设置/显示oom controls相关的配置
memory.numa_stat           #显示numa相关的内存``````

添加进程

“创建并管理cgroup”中介绍的一样，往cgroup中添加进程只要将进程号写入cgroup.procs就可以了

``````#--------------------------第二个shell窗口----------------------
#重新打开一个shell窗口，避免相互影响
dev@dev:~\$ cd /sys/fs/cgroup/memory/test/
dev@dev:/sys/fs/cgroup/memory/test\$ echo \$\$
4589
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo \$\$ >> cgroup.procs"
#运行top命令，这样这个cgroup消耗的内存会多点，便于观察
dev@dev:/sys/fs/cgroup/memory/test\$ top
#后续操作不再在这个窗口进行，避免在这个bash中运行进程影响cgropu里面的进程数及相关统计``````

设置限额

``````#--------------------------第一个shell窗口----------------------
#回到第一个shell窗口
dev@dev:/sys/fs/cgroup/memory\$ cd test
#这里两个进程id分别时第二个窗口的bash和top进程
dev@dev:/sys/fs/cgroup/memory/test\$ cat cgroup.procs
4589
4664
#开始设置之前，看看当前使用的内存数量，这里的单位是字节
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.usage_in_bytes
835584

#设置1M的限额
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 1M > memory.limit_in_bytes"
#设置完之后记得要查看一下这个文件，因为内核要考虑页对齐, 所以生效的数量不一定完全等于设置的数量
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.limit_in_bytes
1048576

#如果不再需要限制这个cgroup，写-1到文件memory.limit_in_bytes即可
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo -1 > memory.limit_in_bytes"
#这时可以看到limit被设置成了一个很大的数字
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.limit_in_bytes
9223372036854771712``````

``````#--------------------------第一个shell窗口----------------------
#先用free看下当前swap被用了多少
dev@dev:/sys/fs/cgroup/memory/test\$ free
total        used        free      shared  buff/cache   available
Mem:         500192       45000       34200        2644      420992      424020
Swap:        524284          16      524268
#设置内存限额为400K
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 400K > memory.limit_in_bytes"

#再看当前cgroup的内存使用情况
#发现内存占用少了很多，刚好在400K以内，原来用的那些内存都去哪了呢？
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.usage_in_bytes
401408

#再看swap空间的占用情况，和刚开始比，多了500-16=384K，说明内存中的数据被移到了swap上
dev@dev:/sys/fs/cgroup/memory/test\$ free
total        used        free      shared  buff/cache   available
Mem:         500192       43324       35132        2644      421736      425688
Swap:        524284         500      523784

#这个时候再来看failcnt，发现有453次之多(隔几秒再看这个文件，发现次数在增长)
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.failcnt
453

#再看看memory.stat（这里只显示部分内容），发现物理内存用了400K，
#但有很多pgmajfault以及pgpgin和pgpgout，说明发生了很多的swap in和swap out
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.stat
total_pgpgin 4166
total_pgpgout 4066
total_pgfault 7558
total_pgmajfault 419

#从上面的结果可以看出，当物理内存不够时，就会触发memory.failcnt里面的数量加1，
#但进程不会被kill掉，那是因为内核会尝试将物理内存中的数据移动到swap空间中，从而让内存分配成功``````

``````#--------------------------第一个shell窗口----------------------
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 1K > memory.limit_in_bytes"
#进程已经不在了（第二个窗口已经挂掉了）
dev@dev:/sys/fs/cgroup/memory/test\$ cat cgroup.procs
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.usage_in_bytes
0
#从这里的结果可以看出，第二个窗口的bash和top都被kill掉了``````

触发控制

``````#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MB (1024 * 1024)

int main(int argc, char *argv[])
{
char *p;
int i = 0;
while(1) {
p = (char *)malloc(MB);
memset(p, 0, MB);
printf("%dM memory allocated\n", ++i);
sleep(1);
}

return 0;
}``````

``````#--------------------------第一个shell窗口----------------------
#编译上面的文件
dev@dev:/sys/fs/cgroup/memory/test\$ gcc ~/mem-allocate.c -o ~/mem-allocate
#设置内存限额为5M
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 5M > memory.limit_in_bytes"
#将当前bash加入到test中，这样这个bash创建的所有进程都会自动加入到test中
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo \$\$ >> cgroup.procs"
#默认情况下，memory.oom_control的值为0，即默认启用oom killer
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.oom_control
oom_kill_disable 0
under_oom 0
#为了避免受swap空间的影响，设置swappiness为0来禁止当前cgroup使用swap
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 0 > memory.swappiness"
#当分配第5M内存时，由于总内存量超过了5M，所以进程被kill了
dev@dev:/sys/fs/cgroup/memory/test\$ ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
Killed

#设置oom_control为1，这样内存达到限额的时候会暂停
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 1 >> memory.oom_control"
#跟预期的一样，程序被暂停了
dev@dev:/sys/fs/cgroup/memory/test\$ ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated

#--------------------------第二个shell窗口----------------------
#再打开一个窗口
dev@dev:~\$ cd /sys/fs/cgroup/memory/test/
#这时候可以看到memory.oom_control里面under_oom的值为1，表示当前已经oom了
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.oom_control
oom_kill_disable 1
under_oom 1
#修改test的额度为7M
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 7M > memory.limit_in_bytes"

#--------------------------第一个shell窗口----------------------
#再回到第一个窗口，会发现进程mem-allocate继续执行了两步，然后暂停在6M那里了
dev@dev:/sys/fs/cgroup/memory/test\$ ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
5M memory allocated
6M memory allocated
``````

1. 利用函数eventfd()创建一个efd;

2. 打开文件memory.oom_control，得到ofd;

3. 往cgroup.event_control中写入这么一串：`<efd> <ofd>`

4. 通过读efd得到通知，然后打印一句话到终端

``````#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/eventfd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static inline void die(const char *msg)
{
fprintf(stderr, "error: %s: %s(%d)\n", msg, strerror(errno), errno);
exit(EXIT_FAILURE);
}

#define BUFSIZE 256

int main(int argc, char *argv[])
{
char buf[BUFSIZE];
int efd, cfd, ofd;
uint64_t u;

if ((efd = eventfd(0, 0)) == -1)
die("eventfd");

snprintf(buf, BUFSIZE, "%s/%s", argv[1], "cgroup.event_control");
if ((cfd = open(buf, O_WRONLY)) == -1)
die("cgroup.event_control");

snprintf(buf, BUFSIZE, "%s/%s", argv[1], "memory.oom_control");
if ((ofd = open(buf, O_RDONLY)) == -1)
die("memory.oom_control");

snprintf(buf, BUFSIZE, "%d %d", efd, ofd);
if (write(cfd, buf, strlen(buf)) == -1)
die("write cgroup.event_control");

if (close(cfd) == -1)
die("close cgroup.event_control");

for (;;) {
if (read(efd, &u, sizeof(uint64_t)) != sizeof(uint64_t))
}

return 0;
}``````

``````#--------------------------第二个shell窗口----------------------
#编译程序
dev@dev:/sys/fs/cgroup/memory/test\$ gcc ~/oom_listen.c -o ~/oom_listen
#启用oom killer
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 0 >> memory.oom_control"
#设置限额为2M，缩短测试周期
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 2M > memory.limit_in_bytes"
#启动监听程序
dev@dev:/sys/fs/cgroup/memory/test\$ ~/oom_listen /sys/fs/cgroup/memory/test

#--------------------------第一个shell窗口----------------------
#连续运行两次mem-allocate，使它触发oom killer
dev@dev:/sys/fs/cgroup/memory/test\$ ~/mem-allocate
1M memory allocated
Killed
dev@dev:/sys/fs/cgroup/memory/test\$ ~/mem-allocate
1M memory allocated
Killed

#--------------------------第二个shell窗口----------------------
#回到第二个窗口可以看到，收到了两次oom事件
dev@dev:/sys/fs/cgroup/memory/test\$ ~/oom_listen /sys/fs/cgroup/memory/test

其他

进程迁移（migration）

``````enable： echo 1 > memory.move_charge_at_immigrate
disable：echo 0 > memory.move_charge_at_immigrate``````

memory.use_hierarchy

``````#当前cgroup和父cgroup里都是1
dev@dev:/sys/fs/cgroup/memory/test\$ cat memory.use_hierarchy
1
dev@dev:/sys/fs/cgroup/memory/test\$ cat ../memory.use_hierarchy
1

#由于父cgroup里面的值为1，所以修改当前cgroup的值失败
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 0 > ./memory.use_hierarchy"
sh: echo: I/O error

#由于父cgroup里面有子cgroup（至少有当前cgroup这么一个子cgroup），
#修改父cgroup里面的值也失败
dev@dev:/sys/fs/cgroup/memory/test\$ sudo sh -c "echo 0 > ../memory.use_hierarchy"
sh: echo: I/O error``````

memory.soft_limit_in_bytes

1. 当系统内存充裕时，soft limit不起任何作用

2. 当系统内存吃紧时，系统会尽量的将cgroup的内存限制在soft limit值之下（内核会尽量，但不100%保证）

memory.pressure_level

critical

1. 利用函数eventfd(2)创建一个event_fd

2. 打开文件memory.pressure_level，得到pressure_level_fd

3. 往cgroup.event_control中写入这么一串：`<event_fd> <pressure_level_fd> <level>`

4. 然后通过读event_fd得到通知

Memory thresholds

1. 利用函数eventfd(2)创建一个event_fd

2. 打开文件memory.usage_in_bytes，得到usage_in_bytes_fd

3. 往cgroup.event_control中写入这么一串：`<event_fd> <usage_in_bytes_fd> <threshold>`

4. 然后通过读event_fd得到通知

stat file

• 里面total开头的统计项包含了子cgroup的数据（前提条件是memory.use_hierarchy等于1）。

• 文件（动态库和可执行文件）及共享内存可以在多个进程之间共享，不过它们只会统计到他们的owner cgroup中的file_mapped去。（不确定是怎么定义owner的，但如果看到当前cgroup的file_mapped值很小，说明共享的数据没有算到它头上，而是其它的cgroup）

情景还原

`memory.limit_in_bytes`中设置如下：

`10485760`表示的是字节数，也就是等于 `1024 * 1024 * 10 = 10M`

问题解决

``\$ echo 0 >  memory.swappiness``

总结

`cgroup``Linux`系统内核提供的一种资源限制的策略，使用起来十分方便。鼎鼎大名的`Docker`容器就是基于此技术，平时工作中对这种技术缺乏钻研和积累，只知道简单的使用，并没有深入研究每个参数到底代表什么，其内部原理又是什么，所以才有了遇到这种问题耗时耗力的情况。

Sentinel-Go 集成 Nacos 实现外部动态数据源

1. 将sentinel流控规则定义在代码内部 实现限流效果。
2. 将sentinel流控规则定义在nacos配置中心，实现限流效果以及在nacos中动态更新规则,实现动态流控。

1. Sentinel

Sentinel 具有以下特征:

• 丰富的应用场景：Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景，例如秒杀（即突发流量控制在系统容量可以承受的范围）、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
• 完备的实时监控：Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据，甚至 500 台以下规模的集群的汇总运行情况。
• 广泛的开源生态：Sentinel 提供开箱即用的与其它开源框架/库的整合模块，例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
• 完善的 SPI 扩展点：Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

1.1 Sentinel 的历史

• 2012年，Sentinel 诞生，主要功能为入口流量控制。
• 2013-2017年，Sentinel 在阿里巴巴集团内部迅速发展，成为基础技术模块，覆盖了所有的核心场景。Sentinel 也因此积累了大量的流量归整场景以及生产实践。
• 2018年，Sentinel 开源，并持续演进。
• 2019年，Sentinel 在多语言扩展的方向上逐步探索，陆续推出 C++ 原生版本、Envoy 集群流量控制支持。
• 2020年，Sentinel 推出 Go 原生版本，期待在云原生领域继续突破。https://github.com/alibaba/sentinel-golang

2. Nacos

Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理的平台，Nacos脱胎于阿里巴巴内部的ConfigServer和Diamond，是它们的开源实现。经历过双十一流量峰值和阿里巴巴经济体超大规模容量的考验，沉淀了阿里巴巴软负载团队在这个领域十年的经验，在稳定性和功能性上都有很好的保障。

（Sentinel-Go集成Nacos动态数据源架构）

3. Sentinel-Go 限流 Demo

3.1 安装

go get github.com/alibaba/sentinel-golang

3.2 Demo样例

1. 对 Sentinel 进行相关配置并进行初始化
2. 埋点（定义资源）
3. 配置规则
``````package main

import (
"fmt"
"log"
"math/rand"
"time"

sentinel "github.com/alibaba/sentinel-golang/api"
"github.com/alibaba/sentinel-golang/core/base"
"github.com/alibaba/sentinel-golang/core/flow"
"github.com/alibaba/sentinel-golang/util"
)

func main() {
// We should initialize Sentinel first.
err := sentinel.InitDefault()
if err != nil {
log.Fatalf("Unexpected error: %+v", err)
}

{
Resource:        "some-test",
MetricType:      flow.QPS,
Count:           10,
ControlBehavior: flow.Reject,
},
})
if err != nil {
log.Fatalf("Unexpected error: %+v", err)
return
}

ch := make(chan struct{})

for i := 0; i < 10; i++ {
go func() {
for {
e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound))
if b != nil {
// Blocked. We could get the block reason from the BlockError.
time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)
} else {
// Passed, wrap the logic here.
fmt.Println(util.CurrentTimeMillis(), "passed")
time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)

// Be sure the entry is exited finally.
e.Exit()
}

}
}()
}
<-ch
}``````

4. Sentinel-Go 集成Nacos

Sentinel-Go集成Nacos实现外部动态数据源功能.

4.1 部署Nacos

4.1.2 预备环境准备

Nacos 依赖 Java 环境来运行。如果您是从代码开始构建并运行Nacos，还需要为此配置 Maven环境，请确保是在以下版本环境中安装使用:

1. 64 bit OS，支持 Linux/Unix/Mac/Windows，推荐选用 Linux/Unix/Mac。
2. 64 bit JDK 1.8+；下载 & 配置
3. Maven 3.2.x+；下载 & 配置

4.1.3 下载源码或者安装包

``````git clone https://github.com/alibaba/nacos.git
cd nacos/
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
ls -al distribution/target/
// change the \$version to your actual path
cd distribution/target/nacos-server-\$version/nacos/bin``````

``````unzip nacos-server-\$version.zip 或者 tar -xvf nacos-server-\$version.tar.gz
cd nacos/bin``````

4.1.4 启动服务器

Linux/Unix/Mac

`sh startup.sh -m standalone`

`bash startup.sh -m standalone`

Windows

`cmd startup.cmd`

4.2 Sentinel限流配置到Nacos

1. 登录到nacos web
2. 在配置管理中，新建配置
3. 输入dataId,group（dataId,group 创建时可以自定义,本文创建的dataId=flow,group=sentinel-go）
4. 将数据源样例粘贴到配置内容中。

4.2.1 Nacos 外部数据源样例

``````[
{
"resource": "some-test",
"metricType": 1,
"count": 100.0,
"controlBehavior":0
}
]``````

4.3 Nacos数据源集成

4.3.1 创建项目

1. 版本

1. sentinel-golang 版本使用0.6.0，nacos-sdk-go 使用1.0.0
2. go.mod
``````module sentinel-go-nacos-example

go 1.13

require (
github.com/alibaba/sentinel-golang v0.6.0
github.com/nacos-group/nacos-sdk-go v1.0.0
)
``````
1. main.go
``````package main

import (
"fmt"
"math/rand"
"sync/atomic"
"time"

sentinel "github.com/alibaba/sentinel-golang/api"
"github.com/alibaba/sentinel-golang/core/base"
"github.com/alibaba/sentinel-golang/ext/datasource/nacos"
"github.com/alibaba/sentinel-golang/util"
"github.com/nacos-group/nacos-sdk-go/clients"

"github.com/alibaba/sentinel-golang/ext/datasource"
"github.com/nacos-group/nacos-sdk-go/common/constant"
)

type Counter struct {
pass  *int64
block *int64
total *int64
}

func main() {
//流量计数器,为了流控打印日志更直观,和集成nacos数据源无关。
counter := Counter{pass: new(int64), block: new(int64), total: new(int64)}

//nacos server地址
sc := []constant.ServerConfig{
{
ContextPath: "/nacos",
Port:        8848,
},
}
//nacos client 相关参数配置,具体配置可参考https://github.com/nacos-group/nacos-sdk-go
cc := constant.ClientConfig{
TimeoutMs: 5000,
}
//生成nacos config client(配置中心客户端)
client, err := clients.CreateConfigClient(map[string]interface{}{
"serverConfigs": sc,
"clientConfig":  cc,
})
if err != nil {
fmt.Printf("Fail to create client, err: %+v", err)
return
}
//注册流控规则Handler
h := datasource.NewFlowRulesHandler(datasource.FlowRuleJsonArrayParser)
//创建NacosDataSource数据源
//sentinel-go 对应在nacos中创建配置文件的group
//flow 对应在nacos中创建配置文件的dataId
nds, err := nacos.NewNacosDataSource(client, "sentinel-go", "flow", h)
if err != nil {
fmt.Printf("Fail to create nacos data source client, err: %+v", err)
return
}
//nacos数据源初始化
err = nds.Initialize()
if err != nil {
fmt.Printf("Fail to initialize nacos data source client, err: %+v", err)
return
}
//启动统计

//模拟流量
ch := make(chan struct{})
for i := 0; i < 10; i++ {
go func() {
for {
//some-test 对应在nacos 流控配置文件中的resource
e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound))
if b != nil {
// Blocked. We could get the block reason from the BlockError.
time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)
} else {
time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)

// Be sure the entry is exited finally.
e.Exit()
}

}
}()
}
<-ch
}

//statistic print
fmt.Println("begin to statistic!!!")
var (
oldTotal, oldPass, oldBlock int64
)
for {
time.Sleep(1 * time.Second)
oneSecondTotal := globalTotal - oldTotal
oldTotal = globalTotal

oneSecondPass := globalPass - oldPass
oldPass = globalPass

oneSecondBlock := globalBlock - oldBlock
oldBlock = globalBlock
fmt.Println(util.CurrentTimeMillis()/1000, "total:", oneSecondTotal, " pass:", oneSecondPass, " block:", oneSecondBlock)
}
}
``````

总结

``````    h := datasource.NewFlowRulesHandler(datasource.FlowRulesJsonConverter)
nds, err := nacos.NewNacosDataSource(client, "sentinel-go", "flow", h)
if err != nil {
fmt.Printf("Fail to create nacos data source client, err: %+v", err)
return
}
err = nds.Initialize()
if err != nil {
fmt.Printf("Fail to initialize nacos data source client, err: %+v", err)
return
}``````

Nacos Go 微服务生态系列（一）| Dubbo-go 云原生核心引擎探索

• 生态：各注册中心对 Go 语言都有支持，但是 Nacos、 Consul、Etcd 社区活跃，zookeeper 和 Eureka 社区活跃度较低；
• 易用性：Nacos、Eureka、Consul 都有现成的管控平台，Etcd、zookeeper 本身作为 kv 存储，没有相应的管控平台，Nacos 支持中文界面，比较符合国人使用习惯；
• 场景支持：CP 模型主要针对强一致场景，如金融类，AP 模型适用于高可用场景，Nacos 可以同时满足两种场景，Eureka 主要满足高可用场景，Consul、Zookeepr、Etcd 主要满足强一致场景，此外 Nacos 支持从其它注册中心同步数据，方便用户注册中心迁移；
• 功能完整性：所有注册中心都支持健康检查，Nacos、Consul 支持的检查方式较多，满足不同应用场景，Zookeeper 通过 keep alive 方式，能实时感知实例变化；Nacos、Consul 和 Eureka 都支持负载均衡策略，Nacos 通过 Metadata selector 支持更灵活的策略；此外，Nacos、Eureka 都支持雪崩保护，避免因为过多的实例不健康对健康的实例造成雪崩效应。

• Dubbo-go 云原生核心引擎探索；
• Sentinel-go 外部动态数据源初探；
• go-micro 集成 Nacos 实践；

引言

Dubbo-go 目前是 Dubbo 多语言生态中最火热的一个项目，从 2016 年发布至今，已经走过 5 个年头。最近，Dubbo-go 发布了 v1.5 版本，全面兼容 Dubbo 2.7.x 版本，支持了应用维度的服务注册与发现，和主流的注册模型保持一致，标志着 Dubbo-go 向云原生迈出了关键的一步。

服务实例心跳维持

Dubbo-go 的 Provider 在向 Nacos 注册应用服务实例信息后，需要主动上报心跳，让 Nacos 服务端感知实例的存活与否，以判断是否将该节点从实例列表中移除。维护心跳的工作是在 Nacos-SDK-go 完成的，从图 5 代码中可以看到，当 Dubbo-go 调用 RegisterInstance 注册一个服务实例时，SDK 除了调用 Nacos 的 Register API 之外，还会调用 AddBeatInfo，将服务实例信息添加到本地缓存，通过后台协程定期向 Nacos 发送服务实例信息，保持心跳。

订阅服务实例变化

Dubbo-go 的 Consumer 在启动的时候会调用 Nacos-SDK-go 的 Subscribe 接口，该接口入参如图 6，订阅的时候只需要传递 ServiceName 即应用名和回调函数 SubscribeCallback，Nacos 在服务实例发生变化的时候即可通过回调函数通知 Dubbo-go。Nacos-SDK-go 是如何感知 Nacos 的服务实例变化的呢？主要有两种方式：

• Nacos 服务端主动推送，Nacos-SDK-go 在启动的时候会监听一个 UDP 端口，该端口在调用 Nacos Register API 的时候作为参数传递，Nacos 会记录 Ip 和端口，当服务实例发生变化时，Nacos 会对所有监听该服务的 Ip 和端口发送 UDP 请求，推送变化后的服务实例信息；
• Nacos-SDK-go 定期查询，SDK 会对订阅的服务实例定时调用查询接口，如果查询有变化则通过回调接口通知 Dubbo-go。作为兜底策略保证 Nacos 服务端推送失败后，仍能感知到变化。

范例实践

2. Server 端搭建

remote 配置，这里使用公共的 Nacos 服务，address 支持配置多个地址，用逗号分割。params 参数配置 nacos-sdk 的日志目录。

``````remote:
nacos:
timeout: "5s"
params:
logDir: "/data/nacos-sdk/log"``````

configCenter 配置：

``````config_center:
protocol: "nacos"

``````export CONF_PROVIDER_FILE_PATH=server端的server.yml文件路径
export APP_LOG_CONF_FILE=server端的log.yml文件路径``````

3. Client 端搭建

client 的配置文件在 registry/servicediscovery/nacos/go-server/profiles 目录下，需要修改的地方跟 server 端一样，这里不赘述。

``````export CONF_CONSUMER_FILE_PATH=client端的server.yml文件路径
export APP_LOG_CONF_FILE=client端的log.yml文件路径``````

阿里巴巴云原生

认证与成就

• 获得 25 次点赞
• 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

(ﾟ∀ﾟ　)