LVS分析
概述
LVS是章文嵩博士十几年前的开源项目,已经被何如linux kernel 目录十几年了,可以说是国内最成功的kernle 开源项目, 在10多年后的今天,因为互联网的高速发展LVS得到了极大的应用, 是当今国内互联网公司,尤其是大型互联网公司的必备武器之一, 从这一点上来说,LVS名副其实。
搞了这么多年linux 网络开发维护, 由于一直偏通信方向,自己竟然从来没有读过ipvs的代码,不能不说是一个遗憾。这两天花时间研究了一下LVS,阅读LVS的kernel与ipvsadm的代码,终于搞清楚了其工作原理与细节问题,感觉队LVS的认识提高了一个等级,再次感谢章博。
LVS架构以及代码分析
LVS是非常典型的【控制面 + 数据面】的网络体系架构。
控制面
ipvsadm 作为控制面的工具运行在用户空间,其本身是一个非常简单的linux命令。ipvsadm可以使用两种方式和内核进行通信 : netlink 与 raw socket,这两种方式我们在这里不做详细介绍。现在基本上默认都使用netlink的方式,这也是现在绝大多数的用户空间与内核空间通信所选择的方式。ipvsadm的工作原理和代码都非常简单 : 分析命令行,将命令行信息打包进nl_msg,即netlink与内核通信的数据结构,然后发给内核即可。
数据面
LVS的数据面完全在linux kernel中实现, 并且是实现在netfilter的框架中, 对netfilter的介绍并不在本文范围之内。
LVS本身以内核核心模块的方式存在,我们首先来看其初始化函数【static int __init _vs_init(void)】做了些什么 :
---> ip_vs_control_init() : 初始化virtual server HASH表与virtual server firewall HASH表,注册netdevice 的notification 处理相应事件 ip_vs_dst_notifier
---> ip_vs_protocol_init() : 注册协议处理结构,目前支持tcp,udp,sctp,ah,esp
---> ip_vs_conn_init() : 初始化connection HASH表,默认HASH表头4096个
---> register_pernet_subsys() : 注册namespace子系统
---> register_pernet_device() : 注册namespace device 子系统
---> nf_register_hooks() : 注册LVS处理函数到netfilter框架
---> ip_vs_register_nl_ioctl() : 注册netlink处理函数与set/getsockopt处理函数
核心处理流程在netfilter框架中(我们可以暂时不关注namespace相关的操作)。
以IPV4为例,我们看看LVS都在netfilter框架中做了什么 :
1829 static struct nf_hook_ops ip_vs_ops[] __read_mostly = {
1830 /* After packet filtering, change source only for VS/NAT */
1831 {
1832 .hook = ip_vs_reply4,
1833 .owner = THIS_MODULE,
1834 .pf = NFPROTO_IPV4,
1835 .hooknum = NF_INET_LOCAL_IN,
1836 .priority = NF_IP_PRI_NAT_SRC - 2,
1837 },
1838 /* After packet filtering, forward packet through VS/DR, VS/TUN,
1839 * or VS/NAT(change destination), so that filtering rules can be
1840 * applied to IPVS. */
1841 {
1842 .hook = ip_vs_remote_request4,
1843 .owner = THIS_MODULE,
1844 .pf = NFPROTO_IPV4,
1845 .hooknum = NF_INET_LOCAL_IN,
1846 .priority = NF_IP_PRI_NAT_SRC - 1,
1847 },
1848 /* Before ip_vs_in, change source only for VS/NAT */
1849 {
1850 .hook = ip_vs_local_reply4,
1851 .owner = THIS_MODULE,
1852 .pf = NFPROTO_IPV4,
1853 .hooknum = NF_INET_LOCAL_OUT,
1854 .priority = NF_IP_PRI_NAT_DST + 1,
1855 },
1856 /* After mangle, schedule and forward local requests */
1857 {
1858 .hook = ip_vs_local_request4,
1859 .owner = THIS_MODULE,
1860 .pf = NFPROTO_IPV4,
1861 .hooknum = NF_INET_LOCAL_OUT,
1862 .priority = NF_IP_PRI_NAT_DST + 2,
1863 },
1864 /* After packet filtering (but before ip_vs_out_icmp), catch icmp
1865 * destined for 0.0.0.0/0, which is for incoming IPVS connections */
1866 {
1867 .hook = ip_vs_forward_icmp,
1868 .owner = THIS_MODULE,
1869 .pf = NFPROTO_IPV4,
1870 .hooknum = NF_INET_FORWARD,
1871 .priority = 99,
1872 },
1873 /* After packet filtering, change source only for VS/NAT */
1874 {
1875 .hook = ip_vs_reply4,
1876 .owner = THIS_MODULE,
1877 .pf = NFPROTO_IPV4,
1878 .hooknum = NF_INET_FORWARD,
1879 .priority = 100,
1880 },
我们可以看到,LVS使用了netfilter五个HOOK点中的三个,分别是 : LOCAL_IN,LOCAL_OUT,FORWARD,我们分别介绍 :
LOCAL_IN 节点
LOCAL_IN节点LVS一共注册了两个函数,分别是 ip_vs_reply4 和 ip_vs_remote_request4。
ip_vs_reply4 : 只用于 LVS NAT 模式,并且只能处理TCP,UDP,SCTP
---> ip_vs_out()
---> ip_vs_fill_iph_skb() : 得到IP头
---> 判断是否是ICMP,如果是则调用ip_vs_out_icmp()函数处理
---> ip_vs_proto_data_get() : 取四层处理结构
---> 检查处理分片
---> 调用proto->conn_out_get() 得到当前connection
---> 如果得到connection,则调用handle_response()处理response
---> handle_response()
--->
---> 如果没有connecton,则检测是否有VS和这个包匹配,如果有则再次检测这个包是否是TCP或RST的包,如果不是则发送ICMP目的不可达消息
ip_vs_remote_request4 : For DRand TUN模式
---> ip_vs_in()
---> 首先是合法性检测,ignore不合法的包
---> ipvs_fill_iph_skb() : 得到ip头信息
---> 过滤掉RAW SOCKET的包
---> 处理ICMP包
---> ip_vs_proto_data_get() 找到proto结构,这个结构保存在 net->ipvs->proto_data_table[hash] 表中
---> 调用proto结构的 conn_in_get() 取的connection, connection保存在全局的表 ip_vs_conn_tab[hash] 中
---> 查找失败则调用 proto->conn_schedule() 创建connection
---> ip_vs_scheduler() : 找到与此包匹配的调度策略,创建connection
---> sched->schedule() : 调用调度策略函数,按照既定的调度测率找到real server
---> ip_vs_conn_new() : 创建新的connection
---> kmem_cache_alloc() : 为connection分配内存
---> 初始化connection
---> ip_vs_bind_xmit() : 根据LVS类型绑定connection的发送函数
---> 将此connection加入ip_vs_conn_tab[hash] 表
---> ip_vs_conn_stats() : 更新connection统计信息
---> ip_vs_in_stats() : 更新统计信息
---> connection->packet_xmit() : 发包
---> synchronization 工作
LOCAL OUT 节点
LOCAL OUT 节点注册了两个函数 : ip_vs_local_reply4 和 ip_vs_local_request4
ip_vs_local_reply4 :
---> ip_vs_out() :同上 ip_vs_reply4()
ip_vs_local_request4 :
---> ip_vs_in() : 同上 ip_vs_remote_request4()
FORWARD节点
FORWARD节点注册了两个函数 : ip_vs_reply4 和 ip_vs_forward_icmp
ip_vs_reply4 :
---> ip_vs_out() : 同上
ip_vs_forward_icmp :
---> ip_vs_in_icmp() : 处理 outside to inside 方向的ICMP报文
我们可以看到,其实LVS在内核中核心的函数其实就两个 : ip_vs_in() 与 ip_vs_out()。
数据流过程分析
我们通过一个数据包在LVS架构中的处理流程来分析LVS的工作过程。
假设我们添加了这样的规则 :
ipvsadm -A -t 192.168.132.254:80 -s rr -p 120
我们可以看到,此规则为一个VS : 192.168.132.254:80, 两个real server 分别是 192.168.132.64:80 与 192.168.132.68:80, LVS模式为DR,调度算法为Round Robin。
配置过程我们ignore掉。
加入一个client要访问此虚拟服务器,那么一个TCP发起包为 : 192.168.132.111:2345 -》 192.168.132.254:80, 我们看看这个包的处理流程。
---> LVS 收到这个包, 然后路由发现此包是到本地虚拟server地址的数据包,然后将其上送到LOCAL_IN HOOK 点。
---> ip_vs_reply4() 首先被调用,因为其优先级高
---> 尝试找到与此包关联的connection,因为是第一个包,所以找不到
---> return NF_ACCEPT, 进入下一个HOOK点处理
---> ip_vs_remote_request4() 被调用,
---> Call ip_vs_in() 函数
---> 首先是包的预处理工作,找到IP头,找到protocol处理结构
---> 然后尝试按照此包找到一个已经存在的connection,由于是第一个包,所以失败
---> 然后调用proto->conn_schedule() 创建一个新的connection
---> TCP : tcp_conn_schedule()
---> 首先取得TCP 头
---> 调用ip_vs_service_find() 找到虚拟服务器的管理结构
---> 调用 ip_vs_schedule()
---> 找到此VS所使用的调度器的管理结构,执行调度函数,找到目的real server地址
---> 调用 ip_vs_conn_new() 创建新的connection,将次connection加入全局HASH表
---> 调用 ip_vs_bind_xmit() 为此 connection 绑定发送函数
---> 然后调用 connection->packet_xmit()发包
---> 对于DR模式来说,发送函数是 ip_vs_dr_xmit()
---> 调用 __ip_vs_get_out_rt() 来确定新的路由并设置到此skb包中关联
---> 调用 ip_vs_send_or_cont() 来最终将此包发送到dst指定的real server
---> 重要的是设置skb->ipvs_property = 1
---> 发送过程中要经过LOCAL_OUT hook 点
---> 调用ip_vs_out() 函数,直接返回 NF_ACCEPT
---> 再调用ip_vs_in() 函数,直接返回NF_ACCEPT
---> dst_output() 最终发包
---> 最后更新connection状态
我们的例子是DR模式,real server收到包后可以直接向client返回数据,不必经过LVS server。其他的模式NAT, TUNNEL和DR模式大同小异,在生产环境中DR模式用的多一点,毕竟DR模式在性能上还是有优势的。
总结
LVS是非常好的,基于国内的,linux开源软件,我在上面大致分析了其数据面,即kernel中的数据处理流程,总的来说LVS的设计以及实现非常的简单但是高效,稳定,是一款优秀的linux open src项目。希望我的分析能够为大家起到抛砖引玉的作用 ;)
最近在用 LVS做 LB,发现一个问题客户端总是出现session丢失问题,采用常用配置,均衡策略使用wlc, 看了一下wlc的策略相同的客户端都有可能轮训到不同的后台机器,在后台服务器上并没有对session进行复制,那样的却会导致客户端访问不同的服务器而导致在session丢失。
本简单的以为通过调整均衡策略就可以确保对同一客户端映射到相同的服务器,均衡策略参考(点击打开链接),而策略里面只有Source Hashing Scheduling 看起来可以达到这个目的,但是这个策略并不是推荐的策略。
在查看ipvsadmin的参数的时候,发现了参数-p,
-p, --persistent [timeout]:设置持久连接,这个模式可以使来自客户的多个请求被送到同一个真实服务器
感觉-p 参数和Source Hashing Scheduling 的策略有点类似,还是看代码来解决问题
IPVS也叫LVS
LVS 是属于内核模块中的,代码直接就可以在内核代码中找到,在内核中的名字是IPVS,我们下面还是以IPVS来称呼
Ipvs 的代码就挂载在/net/netfilter/ipvs中,在这里我们也可以看出IPVS是基于Netfilter框架实现的内核模块,Linux 中的netfilter的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK),而在每个检测点上登记了一些处理函数进行处理(如包过滤,NAT等,甚至可以是用户自定义的功能)。
Netfilter实现
Netfilter的状态图:
而Ipvs 在Netfilter中的几个状态中注册了钩子函数
- static struct nf_hook_ops ip_vs_ops[] __read_mostly = {
- /* After packet filtering, forward packet through VS/DR, VS/TUN,
- * or VS/NAT(change destination), so that filtering rules can be
- * applied to IPVS. */
- {
- .hook = ip_vs_in,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_INET_LOCAL_IN,
- .priority = 100,
- },
- /* After packet filtering, change source only for VS/NAT */
- {
- .hook = ip_vs_out,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_INET_FORWARD,
- .priority = 100,
- },
- /* After packet filtering (but before ip_vs_out_icmp), catch icmp
- * destined for 0.0.0.0/0, which is for incoming IPVS connections */
- {
- .hook = ip_vs_forward_icmp,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_INET_FORWARD,
- .priority = 99,
- },
- /* Before the netfilter connection tracking, exit from POST_ROUTING */
- {
- .hook = ip_vs_post_routing,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_INET_POST_ROUTING,
- .priority = NF_IP_PRI_NAT_SRC-1,
- },
而均衡策略主要实现在状态NF_INET_LOCAL_IN所对应的钩子函数ip_vs_in
IPVS中的2个结构体
1. ip_vs_conn 里面记录了客户端的地址,IPVS 所建立的虚拟地址,对应到真实的服务器的地址
2. ip_vs_protocol 记录在不同的协议(TCP, UDP)中的不同的钩子函数,比如使用什么类型的调度,什么函数接受数据
- struct ip_vs_protocol ip_vs_protocol_tcp = {
- .name = "TCP",
- .protocol = IPPROTO_TCP,
- .num_states = IP_VS_TCP_S_LAST,
- .dont_defrag = 0,
- .appcnt = ATOMIC_INIT(0),
- .init = ip_vs_tcp_init,
- .exit = ip_vs_tcp_exit,
- .register_app = tcp_register_app,
- .unregister_app = tcp_unregister_app,
- .conn_schedule = tcp_conn_schedule,
- .conn_in_get = tcp_conn_in_get,
- .conn_out_get = tcp_conn_out_get,
- .snat_handler = tcp_snat_handler,
- .dnat_handler = tcp_dnat_handler,
- .csum_check = tcp_csum_check,
- .state_name = tcp_state_name,
- .state_transition = tcp_state_transition,
- .app_conn_bind = tcp_app_conn_bind,
- .debug_packet = ip_vs_tcpudp_debug_packet,
- .timeout_change = tcp_timeout_change,
- .set_state_timeout = tcp_set_state_timeout,
- };
这是一个TCP下的ip_vs_protocol 的结构体,里面重要的是conn_in_get 函数也是在钩子函数ip_vs_in里调用的
- static unsigned int
- ip_vs_in(unsigned int hooknum, struct sk_buff *skb,
- const struct net_device *in, const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- ...
- pp = ip_vs_proto_get(iph.protocol);
- if (unlikely(!pp))
- return NF_ACCEPT;
- /*
- * Check if the packet belongs to an existing connection entry
- */
- cp = pp->conn_in_get(af, skb, pp, &iph, iph.len, 0);
- ...
- }
我们已常见的TCP为例,最终调用了函数 tcp_conn_in_get
ip_vs_conn的全局数组ip_vs_conn_tab和链表c_list
这是一张全局的ip_vs_conn数组,保存这所有的以连接。通过客户端ip,port算出的hash,来计算到保存的 ip_vs_conn数组。
ip_vs_conn本身也保存一个链表c_list,这是个链表结构保存ip_vs_conn
初始化
在IPVS初始化的时候,就初始化了数组的大小,大小是不可变的(1<<12 )4096
当客户端hash算出相同的时候,通过遍历ip_vs_conn里面的c_list链表找到匹配的客户端(地址和端口相同)
如果找不到对应的ip_vs_conn, 这时候才调用
- if (!pp->conn_schedule(af, skb, pp, &v, &cp))
- return v;
而对tcp 中的conn_schedule 的钩子函数就是tcp_conn_schedule,也就是我们前面说到的调度算法的核心函数
- static int
- tcp_conn_schedule(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- int *verdict, struct ip_vs_conn **cpp)
- {
- struct ip_vs_service *svc;
- struct tcphdr _tcph, *th;
- struct ip_vs_iphdr iph;
- ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
- th = skb_header_pointer(skb, iph.len, sizeof(_tcph), &_tcph);
- if (th == NULL) {
- *verdict = NF_DROP;
- return 0;
- }
- if (th->syn &&
- (svc = ip_vs_service_get(af, skb->mark, iph.protocol, &iph.daddr,
- th->dest))) {
- if (ip_vs_todrop()) {
- /*
- * It seems that we are very loaded.
- * We have to drop this packet :(
- */
- ip_vs_service_put(svc);
- *verdict = NF_DROP;
- return 0;
- }
- /*
- * Let the virtual server select a real server for the
- * incoming connection, and create a connection entry.
- */
- *cpp = ip_vs_schedule(svc, skb); //调用了ip_vs_schedule
- if (!*cpp) {
- *verdict = ip_vs_leave(svc, skb, pp);
- return 0;
- }
- ip_vs_service_put(svc);
- }
- return 1;
- }
函数ip_vs_schedule
- struct ip_vs_conn *
- ip_vs_schedule(struct ip_vs_service *svc, const struct sk_buff *skb)
- {
- ...
- /*
- * Persistent service
- */
- if (svc->flags & IP_VS_SVC_F_PERSISTENT)
- return ip_vs_sched_persist(svc, skb, pptr);
- /*
- * Non-persistent service
- */
- if (!svc->fwmark && pptr[1] != svc->port) {
- if (!svc->port)
- pr_err("Schedule: port zero only supported "
- "in persistent services, "
- "check your ipvs configuration\n");
- return NULL;
- }
- <span style="white-space:pre"> </span>....
- return cp;
- }
我们看到了IP_VS_SVC_F_PERSISTENT, 也就是参数persistent
参数persistent 的实现
创建ip_vs_conn 模版
保证在一定的时间内相同的客户端ip还是连接原来的服务器,那就意味着需要保留原来的客户端ip的上次的连接的真实服务器。
在IPVS里并没有生成另一个数组去保留这个状态,而是引入了一个ip_vs_conn 模版,而这个ip_vs_conn 模版仍旧保存在前面提到的全局表中的ip_vs_conn_tab
既然保存在同一个全局表,那么这个模版和普通的ip_vs_conn有什么区别?很简单,这里只要设置客户端的port 为0,而把连到真实的server的IP 保存到ip_vs_conn中去
具体实现参考函数:ip_vs_sched_persist
什么时候清除ip_vs_conn 模版?
-p 参数有指定时间,ip_vs_conn结构体中存在一个timer和timeout的时间,在函数新建连接的时候,设置了timer的执行函数ip_vs_conn_expire
- struct ip_vs_conn *
- ip_vs_conn_new(int af, int proto, const union nf_inet_addr *caddr, __be16 cport,
- const union nf_inet_addr *vaddr, __be16 vport,
- const union nf_inet_addr *daddr, __be16 dport, unsigned flags,
- struct ip_vs_dest *dest)
- {
- <span style="white-space:pre"> </span>......
- setup_timer(&cp->timer, ip_vs_conn_expire, (unsigned long)cp);
- ......
- return cp;
- }
函数ip_vs_conn_expire里从ip_vs_conn_tab移除了ip_vs_conn超时的模版
而在ip_vs_sched_persist 函数里,当每次创建新的连接的时候,同时也更新了ip_vs_conn模版的timer的触发时间(当前时间+-p 参数的timeout时间),实现在函数ip_vs_conn_put中。
完整流程图
IPVS的debug日志
需要从新编译内核,设置config 里的参数
CONFIG_IP_VS_DEBUG=Y
编译后,还要修改参数
/proc/sys/net/ipv4/vs/debug_level
设置为12 ,日志打印到dmesg中
IPVS ip_vs_conn_tab 里的entry
内容可以通过 /proc/net/ip_vs_conn_entries 访问
IPVS 不设置persistent参数
如果不设置persistent参数,也就是意味着不需要保证同一个客户端在一个固定时间段中连接到同一个真实得服务器,那么ipvs会使用调度算法去调度每一个来自客户端得新连接。
函数ip_vs_conn里面,当没有设置persistent参数得时候,会直接调用dest = svc->scheduler->schedule(svc, skb);来调度新得连接。
LVS分析的更多相关文章
- 负载均衡软件LVS分析四(测试)
一.启动LVS集群服务LVS负载均衡管理和使用有两种方式,一种是以ipvsadm命令行脚步与ldirectord监控方式,一种是以Piranha工具进行管理和使用.下面分别介绍. 1.利用ipvsad ...
- 负载均衡软件LVS分析三(配置)
LVS集群有DR.TUN.NAT三种配置模式,可以对www服务.FTP服务.MAIL服务等做负载均衡,下面通过搭建www服务的负载均衡实例,讲述基于DR模式的LVS集群配置. 一. Director ...
- 负载均衡软件LVS分析二(安装)
一. 安装LVS软件 1.安装前准备工作操作系统:统一采用Centos4.4版本.地址规划,如表1所示:表1 更详细的信息如图2所示: 图2 LVS DR模式安装部署结构图 图2中的VIP指的是虚 ...
- 负载均衡软件LVS分析一(概念)
一. LVS简介LVS是Linux Virtual Server的简称,也就是Linux虚拟服务器, 是一个由章文嵩博士发起的自由软件项目,它的官方站点是www.linuxvirtualserver. ...
- 搭建DHProxy服务器
集群与存储 HAProxy简介 ...
- lvs源代码分析
以linux-2.6.21为例. 数据结构介绍: ip_vs_conn 对于某个连接记录其N元组, (client, vserver, rserver) & (address, port) Q ...
- Nginx负载均衡和LVS负载均衡的比较分析
LVS和Nginx都可以用作多机负载的方案,它们各有优缺,在生产环境中需要好好分析实际情况并加以利用. 首先提醒,做技术切不可人云亦云,我云即你云:同时也不可太趋向保守,过于相信旧有方式而等别人来帮你 ...
- lvs、haproxy、nginx 负载均衡的比较分析
lvs和nginx都可以用作多机负载的方案,它们各有优缺,在生产环境中需要好好分析实际情况并加以利用. 首先提醒,做技术切不可人云亦云,我云即你云:同时也不可太趋向保守,过于相信旧有方式而等别人来帮你 ...
- lvs(+keepalived)、haproxy(+heartbeat)、nginx 负载均衡的比较分析
目前使用比较多的就是标题中提到的这两者,其实lvs和haproxy都是实现的负载均衡的作用,keepalived和heartbeat都是提高高可用性的,避免单点故障.那么他们为什么这么搭配,而又有什么 ...
随机推荐
- element ui input框不能输入的问题(实时学习)
解决: 在input的上面添加数据v-model 既可以 1.菜单中api (2018年8月14号) :default-active 默认根据当前路由选中菜单,值需要和 el-submenu 的属 ...
- mysql 批量 insert 数据丢失问题
这两天发现mysql 批量insert 比如600条数据后,页面马上select就查询到580条,但是等几秒钟再查询就有600条(也有部分情况是永久只能查到580条了) 查看mybatis的日志发现循 ...
- Windows命令行命令总结
转载地址:https://www.cnblogs.com/accumulater/p/7110811.html 1. gpedit.msc-----组策略 2. sndrec32-------录音 ...
- IntelliJ IDEA 接口类跳转到实现类及实现类跳转到接口
接口和实现类的互相跳转是使用IntelliJ IDEA过程中常用的操作,在此记录一下: 1.Service接口跳转到实现类 操作:在接口类的方法上使用快捷键Ctrl+Alt+B,或者点击下图所示位置 ...
- 盗取连接你wifi的人的qq
#本文内容仅供个人娱乐学习 思路: 使用wireshark监听笔记本的wifi热点,拦截捕获连接你的wifi热点的人的手机qq网络数据包,从网络数据包中分析取出两个qq空间的两个coookie值,使用 ...
- dart 函数练习
import 'dart:convert'; import 'dart:html'; void main() { _getIPAddress() { final url = 'https://http ...
- 建立一个可以不停地接收客户端新的连接,但不能处理复杂的业务的C/S网络程序
在Windows平台上主要有两个版本的Socket Api函数:WinSock 1.1和WinSock 2.2 , 2.2版本默认兼容1.1版本,1.1 winsock.h wsock32.lib w ...
- 数据防泄密(DLP)
数据防泄密(DLP)近几年已经成为国内非常热门的关键词. 目前市场上DLP产品主要可以分为三大类,各类产品都来源于不同的技术体系,防护效果也各有优缺点. 第一类,以监控审计为主,对进出的数据进行过滤, ...
- 代码检查工具sonarqube介绍
SonarQube 是一款用于代码质量管理的开源工具,它主要用于管理源代码的质量.通过插件形式,可以支持众多计算机语言. 比如 java, C#, go,C/C++, PL/SQL,Cobol,Jav ...
- 完全平方数 HYSBZ - 2440 (莫比乌斯函数容斥)
完全平方数 HYSBZ - 2440 小 X 自幼就很喜欢数.但奇怪的是,他十分讨厌完全平方数.他觉得这些 数看起来很令人难受.由此,他也讨厌所有是完全平方数的正整数倍的数.然而 这丝毫不影响他对其他 ...