1 简介

Sentinel(哨兵)是Redis 的高可用性解决方案:通过哨兵可以创建一个当主服务器出现故障时自动将从服务器升级为主服务器的一个分布式系统。解决了主从复制出现故障时需要人为干预的问题。

这篇介绍哨兵的搭建,以及哨兵是如何进行哨兵发现和主从切换等功能。

2 准备工作

在原先主从的基础上,每台机器启动一个哨兵。架构图如下

2.1 配置

配置文件如下

daemonize yes

bind 0.0.0.0

port 26379
dir "/usr/soft/redis"
loglevel notice
logfile "/usr/soft/redis/sentinel.log" # 修改改成5秒
sentinel monitor learnSentinelMaster 192.168.17.101 6379 2
sentinel down-after-milliseconds learnSentinelMaster 5000
sentinel config-epoch learnSentinelMaster 1

2.2 启动方式

有两种方式

src/redis-sentinel sentinel.conf
src/redis-server sentinel.conf --sentinel

3 开始搭建

哨兵搭建的过程如下

哨兵集群搭建完毕后,日志内容如下

启动后配置文件sentinel.conf会增加内容

daemonize yes

bind 0.0.0.0

port 26379
dir "/usr/soft/redis"
loglevel notice
logfile "/usr/soft/redis/sentinel.log" # 修改改成5秒
sentinel myid b457cbbcda1991f540d56c6e8faea123a668b16c
sentinel monitor learnSentinelMaster 192.168.17.101 6379 2
sentinel down-after-milliseconds learnSentinelMaster 5000
# Generated by CONFIG REWRITE
sentinel config-epoch learnSentinelMaster 1
sentinel leader-epoch learnSentinelMaster 0
sentinel known-slave learnSentinelMaster 192.168.17.102 6379
sentinel known-slave learnSentinelMaster 192.168.17.103 6379
sentinel known-sentinel learnSentinelMaster 192.168.17.101 26379 f0230d4fdf1ffc7865852de71f16b3017cc1617c
sentinel known-sentinel learnSentinelMaster 192.168.17.102 26379 5b1099513713310eba94e69513dba76cf0ac2222
sentinel current-epoch 1

4 启动流程

接下来看看哨兵集群启动过程中,Redis内部发生了什么。步骤如下

  1. 初始化服务器
  2. 使用Sentinel专用代码
  3. 初始化Sentinel状态
  4. 创建连向主服务器的网络连接

4.1 初始化服务器

Sentinel 本质上只是一个运行在特殊模式下的Redis服务器,所以初始化时和不同的Redis服务器初始化没什么较大的区别。有区别的就是哨兵服务器并不会载入RDB文件和AOF文件,还有一些命令功能哨兵服务器不使用。

4.2 使用Sentinel专用代码

初始化服务器之后,哨兵服务器会将一部分普通Redis的服务器使用的代码替换成哨兵专用的代码。以下就是哨兵的命令列表,代码文件在https://github.com/antirez/redis/blob/unstable/src/sentinel.c

struct redisCommand sentinelcmds[] = {
{"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
{"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0},
{"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
{"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};

4.3 初始化Sentinel状态

在应用了哨兵专用的代码之后,哨兵会初始化状态,这个哨兵状态结构包含了服务器中所有和哨兵功能有关的状态。结构体代码位置在也在entinel.c文件中,结构体代码如下

/* Main state. */
struct sentinelState {
char myid[CONFIG_RUN_ID_SIZE+1]; /* 当前哨兵ID. */
uint64_t current_epoch; /* 当前纪元. */
dict *masters; /* 存放哨兵监视的主服务器,key是主服务器的名字,value是指向主服务器的指针tances. */
int tilt; /* 是否处于TILT模式? */
int running_scripts; /* 目前执正在执行的脚本数量 */
mstime_t tilt_start_time; /* 进入TITL开始的时间 */
mstime_t previous_time; /* 最后一次执行时间处理器的时间*/
list *scripts_queue; /* 包含了所有需要执行的用户脚本 */
char *announce_ip; /* 当配置文件中的announce_ip不为空时,记录着这些IP地址 */
int announce_port; /* 配置文件中的announce_port */
unsigned long simfailure_flags; /* 故障模拟 */
int deny_scripts_reconfig; /* 是否允许哨兵在运行时修改脚本位置? */
} sentinel;

启动哨兵出现的日志如下

# oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
# Redis version=4.9.103, bits=64, commit=00000000, modified=0, pid=2100, just started
# Configuration loaded
* Increased maximum number of open files to 10032 (it was originally set to 1024).
* Running mode=sentinel, port=26379.
# WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

哨兵id如下

# Sentinel ID is b457cbbcda1991f540d56c6e8faea123a668b16c

4.4 创建连向主服务器的网络连接

初始化哨兵的最后一步是创建连向被监视的主服务器的网络连接,哨兵将会成为主服务器的客户端。哨兵会向主服务器创建两个异步网络连接

  1. 命令连接,用于向主服务器发送命令,并接受命令。
  2. 订阅连接,专门用于订阅主服务器的_sentinel_:hello频道。

启动哨兵过程到这里就结束了,接下来将进入下个环节。

5 获取信息

获取信息阶段会获取主服务器信息和从服务器信息以及哨兵的相关信息。

5.1 获取主服务器信息

哨兵默认会以10s一次的频率,发送命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息。

监控主服务器

# +monitor master learnSentinelMaster 192.168.17.101 6379 quorum 2

通过分析主服务器返回的信息,可以获取到两方面的信息

  1. 主服务器本身的信息
  2. 从服务器的信息

获取到从服务器信息之后,哨兵会更新保存主服务器实例结构的slaves字典。

5.2 获取从服务器信息

当哨兵发现主服务器有新的从服务器出现时,哨兵会为这个新的从服务器创建相应的实例结构之外,还会创建到从服务器的命令连接和订阅连接。

发现新的从服务器会出现如下日志

* +slave slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379
* +slave slave 192.168.17.103:6379 192.168.17.103 6379 @ learnSentinelMaster 192.168.17.101 6379

在创建命令连接之后,会发送INFO命令获取信息。通过从服务器回复的信息中,可以获得以下内容

  1. 从服务器的运行ID run_id
  2. 从服务器的角色 role
  3. 从服务器的IP地址 master_host,以及主服务器的端口号master_port
  4. 从服务器的连接状态 matser_link_status
  5. 从服务器的优先级 salve_pripority
  6. 从服务器的复制偏移量 slave_repl_offest

获取到这些信息之后,会对之前创建的从服务器实例结构进行更新。

5.3 获取其他Sentinel的信息

在获取其他哨兵的信息之前,先要知道向主服务器和从服务器发送信息接收来自主服务器和从服务器的频道信息

5.3.1 向主服务器和从服务器发送信息

发送的命令格式如下

PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"

PUBLISH是发布消息的命令,__sentinel__:hello是频道的名称,后面就是一些参数,参数信息如下

5.3.2 接收来自主服务器和从服务器的频道信息

当Sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送命令:

SUBSCRIBE __sentinel__:hello

表示哨兵订阅__sentinel__:hello这个频道,接收这个频道的消息。

其他哨兵可以通过接收这个频道的消息来发现其他哨兵的存在。

5.3.3 发现哨兵

通过接收__sentinel__:hello频道的消息可以发现其他哨兵的存在。当哨兵接收到一条来自__sentinel__:hello频道的消息时,会出现下方

  1. 判断该消息是否是自己发送的,是则忽略这条消息
  2. 消息不是自己发送时,说明有新的哨兵
  3. 查看自己是否存有该哨兵的信息,有则更新该哨兵的信息
  4. 没有则创建一个新的哨兵实例结构,并保存到sentinels字典中

注:sentinels字典是专门保存哨兵信息的

5.3.4 创建连向其他哨兵的命令连接

当Sentinel通过频道信息发现一个新的Sentinel时,不仅会在自身的sentinels字典中为新Sentinel创建实例结构,还会创建一个连向新Sentinel的命令连接,同时新的Sentinel也会创建一个连向这个Sentinel的命令连接,最终多个Sentinel将形成一个互相连接的网络。

注:哨兵之间不会创建订阅连接

发现哨兵的日志如下

* +sentinel sentinel f0230d4fdf1ffc7865852de71f16b3017cc1617c 192.168.17.101 26379 @ learnSentinelMaster 192.168.17.101 6379
* +sentinel sentinel 5b1099513713310eba94e69513dba76cf0ac2222 192.168.17.102 26379 @ learnSentinelMaster 192.168.17.101 6379

6 模拟101主服务器掉钱

模拟101主服务器掉钱的过程如下

断线重连的日志内容如下

接下来开始分析断线过程中的每一步骤

  1. 检测主观下线状态
  2. 检测客观下线状态
  3. 选举领头哨兵
  4. 故障转移

6.1 检测主观下线状态

在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(主,从,其他Sentinel)发送  PING命令 ,通过判断返回的内容来判断是否在线,命令分为有效回复和无效回复两种。

  • 有效回复

    • +PING
    • -LOADING
    • -MASTERDOWN
  • 无效回复
    • 除有效回复以外的内容
    • 指定时间内没有回复

配置文件中的down-after-milliseconds参数可以设置指定时间,在这个时间段内没有收到回复则判定该服务器处于主观下线状态。

sentinel down-after-milliseconds learnSentinelMaster 5000

现在101客户端上输入以下命令,让服务器睡眠30秒

debug sleep 30

此时查看哨兵日志,等待5秒后出现以下内容

# +sdown master learnSentinelMaster 192.168.17.101 6379

6.2 检测客观下线状态

当一个哨兵将一个主服务器判断为主观下线之后,会向其他监视该主服务器的哨兵进行询问,当有足够数量的哨兵判定主服务器下线时,会执行故障转移操作 。

注:这里不对哨兵之间互相发送的消息进行说明

在配置中可以决定判定主服务器进入客观下线状态所需要的服务器数量,下方配置的最后一个参数就是所需的哨兵数量,这里填写的是2

sentinel monitor learnSentinelMaster 192.168.17.101 6379 2

下面的日志说明了主服务器101已经进入客观下线状态

# +odown master learnSentinelMaster 192.168.17.101 6379 #quorum 2/2

当前纪元被更新 ,试图故障恢复

# +new-epoch 2
# +try-failover master learnSentinelMaster 192.168.17.101 6379

此时开始准备选举领头哨兵进行故障转移

6.3 选举领头哨兵

当主服务器被判定为客观下线之后,各个哨兵服务器将会选举出一个领头哨兵,有这个领头哨兵对下线服务器进行故障转移操作,选举领头哨兵的规则如下:

  1. 所有在线的Sentinel都有被选为领头Sentinel的资格;
  2. 每次进行选举之后,不论选举是否成功,所有Sentinel的配置纪元都会自增一次;
  3. 在一个配置纪元里,所有Sentinel都有一次将某个Sentinel设置为局部领头Sentinel的机会,并且局部领头一旦设置,在这个配置纪元里就不会再更改;
  4. 每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel;
  5. 当一个Sentinel向另一个Sentinel发送请求命令,并且命令中的runid不是*而是运行id时,这表示源Sentinel要求目标Sentinel将前者设置为后者的局部领头Sentinel。
  6. 设置局部领头Sentinel的原则是先到先得,之后所有的设置要求都会被拒绝;
  7. 目标Sentinel在收到命令后,会返回一条回复,回复中的leader_runid参数和leader_epoch参数分别记录了目标Sentinel的局部领头Sentinel的运行ID和配置纪元;
  8. 源Sentinel在收到回复后,会检查配置纪元与自己是否相等,如果相同,且leader_runid与自己相同,那么表示自己成为了目标的局部领头;
  9. 如果有某个Sentinel被半数以上的Sentinel设置成了局部领头Sentinel,那么它成为领头Sentinel;
  10. 因为领头的产生需要半数哨兵的支持,并且每个哨兵在每个配置纪元只能设置一次局部领头Sentinel,所以在一个配置纪元里面,只会出现一个领头Sentinel;
  11. 如果在给定时限内没有选出领头Sentinel,那么各个Sentinel将在一段时间之后再次进行选举,直到选出来。

下方就是选举领头哨兵的日志内容

# +vote-for-leader b457cbbcda1991f540d56c6e8faea123a668b16c 2
# 5b1099513713310eba94e69513dba76cf0ac2222 voted for b457cbbcda1991f540d56c6e8faea123a668b16c 2
# f0230d4fdf1ffc7865852de71f16b3017cc1617c voted for b457cbbcda1991f540d56c6e8faea123a668b16c 2

6.4 故障转移

在选举出领头哨兵之后,领头哨兵需要执行故障转移操作,操作主要分为三个步骤

  1. 选出新的主服务器
  2. 修改从服务器的复制目标
  3. 将旧的主服务器变为从服务器

6.4.1 选出新的主服务器

此时,领头哨兵需要选出新的主服务器,然后向新的主服务器发送SLAVEOF no one命令,将这个从服务器转换为主服务器。

选择过程会过滤掉不符合要求的服务器:

  1. 处于下线或者断线状态的从服务器
  2. 最近5秒内没有回复过领头哨兵的INFO信息的从服务器
  3. 与已下线主服务器连接断开超过(down-after-milliseconds * 10)毫秒的从服务器。(与主服务器客观下线时间进行比较)

新的主服务器只选择通过上面的测试,并在上面的标准基础上排序:

  1. Slave通过Redis实例的redis.conf文件配置的slave-priority排序。优先级越低越被优先考虑。
  2. 如果优先级相同,检查slave的复制偏移量,并选择接收更多数据的slave。
  3. 如果多个slave有相同的优先级和同样的处理数据过程,就会执行一个更进一步的验证,选择一个有较短run ID的slave。run ID 对于 slave没太大用,但是非常有助于选择slave的过程,而不是随机选择slave。

选出合适的从节点作为新的主节点

2101:X 31 Jul 19:13:35.709 # +failover-state-select-slave master learnSentinelMaster 192.168.17.101 6379
2101:X 31 Jul 19:13:35.793 # +selected-slave slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379

开始讲102转换为主节点

* +failover-state-send-slaveof-noone slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379
* +failover-state-wait-promotion slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379
# +promoted-slave slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379
# +failover-state-reconf-slaves master learnSentinelMaster 192.168.17.101 6379

6.4.2 修改从服务器的复制目标

当新的主服务器出现之后,领头哨兵会向其他从服务器发送slaveof 命令去复制新的主服务器。

下方记录了领头哨兵向从服务器发送 SALVEOF命令去复制新的主服务器。

* +slave-reconf-sent slave 192.168.17.103:6379 192.168.17.103 6379 @ learnSentinelMaster 192.168.17.101 6379
* +slave-reconf-inprog slave 192.168.17.103:6379 192.168.17.103 6379 @ learnSentinelMaster 192.168.17.101 6379
* +slave-reconf-done slave 192.168.17.103:6379 192.168.17.103 6379 @ learnSentinelMaster 192.168.17.101 6379

6.4.3 将旧的主服务器变为从服务器

这时候如果下线的主服务器重启上线了怎么办?这也是故障转移要做的最后一步,将已下线的主服务器设置为新的主服务器的从服务器。当下线的主服务器重新上线时,哨兵就会向它发送SLAVEOF命令,让他成为新的主服务器的从服务器。

此时101服务器上线

# -odown master learnSentinelMaster 192.168.17.101 6379

故障转移成功完成。所有slaves被重新配置为新master的从

# +failover-end master learnSentinelMaster 192.168.17.101 6379
# +switch-master learnSentinelMaster 192.168.17.101 6379 192.168.17.102 6379

转换101状态

* +slave slave 192.168.17.101:6379 192.168.17.101 6379 @ learnSentinelMaster 192.168.17.102 6379
# +sdown slave 192.168.17.101:6379 192.168.17.101 6379 @ learnSentinelMaster 192.168.17.102 6379
# -sdown slave 192.168.17.101:6379 192.168.17.101 6379 @ learnSentinelMaster 192.168.17.102 6379
* +convert-to-slave slave 192.168.17.101:6379 192.168.17.101 6379 @ learnSentinelMaster 192.168.17.102 6379

这时候再次查看配置文件会发现多了一行sentinel current-epoch 2

#后台启动
daemonize yes bind 0.0.0.0 port 26379
dir "/usr/soft/redis"
loglevel notice
logfile "/usr/soft/redis/sentinel.log" # 修改改成5秒
sentinel myid f0230d4fdf1ffc7865852de71f16b3017cc1617c
sentinel monitor learnSentinelMaster 192.168.17.102 6379 2
sentinel down-after-milliseconds learnSentinelMaster 5000
# Generated by CONFIG REWRITE
sentinel config-epoch learnSentinelMaster 2
sentinel leader-epoch learnSentinelMaster 2
sentinel known-slave learnSentinelMaster 192.168.17.103 6379
sentinel known-slave learnSentinelMaster 192.168.17.101 6379
sentinel known-sentinel learnSentinelMaster 192.168.17.103 26379 b457cbbcda1991f540d56c6e8faea123a668b16c
sentinel known-sentinel learnSentinelMaster 192.168.17.102 26379 5b1099513713310eba94e69513dba76cf0ac2222
sentinel current-epoch 2

7 相关配置

# Example sentinel.conf

# 绑定IP地址
# bind 127.0.0.1 192.168.1.1
# 保护模式(是否禁止外部链接,除绑定的ip地址外)
# protected-mode no # 当前Sentinel服务运行的端口
port 26379 #
# sentinel announce-ip <ip>
# sentinel announce-port <port> # Sentinel服务运行时使用的临时文件夹
dir /tmp # 监听地址为ip:port的一个master
sentinel monitor mymaster 127.0.0.1 6379 2 # 设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # 指定了Sentinel认为Redis实例已经失效所需的毫秒数
sentinel down-after-milliseconds mymaster 30000 # 指定了在执行故障转移时,最多可以有多少个从Redis实例在同步新的主实例,在从Redis实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长
sentinel parallel-syncs mymaster 1 # 如果在该时间(ms)内未能完成故障转移操作,则认为该故障转移失败
sentinel failover-timeout mymaster 180000 # 指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,但是很常用
# sentinel notification-script mymaster /var/redis/notify.sh # 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

哨兵的配置文件:https://github.com/rainbowda/learnWay/tree/master/learnRedis/sentinel,有需要的可以下载。

redis系列:哨兵的更多相关文章

  1. redis系列--深入哨兵集群

    一.前言 在之前的系列文章中介绍了redis的入门.持久化以及复制功能,如果不了解请移步至redis系列进行阅读,当然我也是抱着学习的知识分享,如果有什么问题欢迎指正,也欢迎大家转载.而本次将介绍哨兵 ...

  2. Redis系列4:高可用之Sentinel(哨兵模式)

    Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 1 背景 从第三篇 Redis系列3:高可用之主从架构 ,我们知道,为Re ...

  3. redis 系列25 哨兵Sentinel (高可用演示 下)

    一. Sentinel 高可用环境准备 1.1 Sentinel 集群环境 环境 说明 操作系统版本 CentOS  7.4.1708  IP地址 172.168.18.200 网关Gateway 1 ...

  4. Redis系列八:redis主从复制和哨兵

    一.Redis主从复制 主从复制:主节点负责写数据,从节点负责读数据,主节点定期把数据同步到从节点保证数据的一致性 1. 主从复制的相关操作 a,配置主从复制方式一.新增redis6380.conf, ...

  5. 四、redis系列之主从复制与哨兵机制

    1. 绪言 在现实应用环境中,出于数据容量.容灾.性能等因素的考虑,往往不会只使用一台服务器,而是使用集群的方式.Redis 中也有类似的维持一主多从的方式提高 Redis 集群的高可用性的方案,而其 ...

  6. Redis系列-第六篇哨兵模式

    https://blog.csdn.net/niugang0920/article/details/97141175 Redis的主从复制模式下, 一旦主节点由于故障不能提供服务, 需要人工将从节点晋 ...

  7. redis系列:redis介绍与安装

    前言 这个redis系列的文章将会记录博主学习redis的过程.基本上现在的互联网公司都会用到redis,所以学习这门技术于你于我都是有帮助的. 博主在写这个系列是用的是目前最新版本4.0.10,虚拟 ...

  8. Redis系列总结--这几点你会了吗?

    文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 前面几篇已经对Redis中几个关键知识点做了介绍,本篇主要对Redis系列做一下总结以及对Redis中常见面试 ...

  9. Redis系列(三):Redis集群的水平扩展与伸缩

    一.Redis集群的水平扩展 Redis3.0版本以后,有了集群的功能,提供了比之前版本的哨兵模式更高的性能与可用性,但是集群的水平扩展却比较麻烦,接下来介绍下Redis高可用集群如何做水平扩展,在原 ...

  10. 【目录】redis 系列篇

    随笔分类 - redis 系列篇 redis 系列27 Cluster高可用 (2) 摘要: 一. ASK错误 集群上篇最后讲到,对于重新分片由redis-trib负责执行,关于该工具以后再介绍.在进 ...

随机推荐

  1. 西瓜书概念整理(chapter 1-2)熟悉机器学习术语

    括号表示概念出现的其他页码, 如有兴趣协同整理,请到issue中认领章节 完整版见我的github:ahangchen 觉得还不错的话可以点个star ^_^ 第一章 绪论 Page2: 标记(lab ...

  2. 洛谷 P1062 数列

    题目描述 给定一个正整数k(3≤k≤15),把所有k的方幂及所有有限个互不相等的k的方幂之和构成一个递增的序列,例如,当k=3时,这个序列是: 1,3,4,9,10,12,13,… (该序列实际上就是 ...

  3. php写入数据到mysql数据库中出现乱码解决方法

    乱码情况: 在选择数据库前加入一句代码即可 mysql_query("set names utf8"); 最后效果

  4. 使用POI导出excel基础篇

    最近搞了下POI导出Excel,听说很多次,却是第一次搞. 在pom.xml中引入依赖 <dependency> <groupId>org.apache.poi</gro ...

  5. 解决count distinct多个字段的方法

    Distinct的作用是用于从指定集合中消除重复的元组,经常和count搭档工作,语法如下 COUNT( { [ ALL | DISTINCT ] expression ] | * } ) 这时,可能 ...

  6. php 字符串的分割

    http://blog.sina.com.cn/s/blog_71ed1b870102uysa.html

  7. JDK7和JDK8新特性

    参考:http://www.cnblogs.com/langtianya/p/3757993.html JDK 1.7 新特性 1,switch中可以使用字串了 String s = "te ...

  8. POJ3111(最大化平均值)

    K Best Time Limit: 8000MS   Memory Limit: 65536K Total Submissions: 8458   Accepted: 2163 Case Time ...

  9. MYSQL BENCHMARK()函数

    MySQL有一个内置的BENCHMARK()函数,可以测试某些特定操作的执行速度. BENCHMARK(count,expr) BENCHMARK会重复计算expr表达式count次,通过这种方式就可 ...

  10. Excel开发学习笔记:发布VSTO下的Excel开发项目

    遇到一个数据处理自动化的问题,于是打算开发一个基于excel的小工具.在业余时间一边自学一边实践,抽空把一些知识写下来以备今后参考,因为走的是盲人摸象的野路子,幼稚与错误请多包涵. 开发环境基于VST ...