Redis 6.x——监控指标

监控指标

1、Redis监控指标

1.1、使用INFO命令

INFO命令会返回Redis服务器的状态信息和统计数据,使用INFO命令可以监控Redis的工作状态。可以通过以下可选参数(可选参数不区分大小写)​,选择查看特定分段的Redis服务器信息,比如INFO server,INFO clients等。

  • server:Redis服务器相关的通用信息。
  • clients:客户端连接的相关信息。
  • memory:内存消耗的相关信息。
  • persistence:RDB(Redis Database)和AOF(Append-Only File)的相关信息。
  • stats:通用统计数据。
  • replication:主/从复制的相关信息。
  • cpu:CPU消耗的统计数据。
  • cluster:Redis集群的相关信息。
  • keyspace:数据库相关的统计数据。

进入Redis客户端输入INFO命令,会返回批量字符串,是多行文本行的集合。文本行包含一个分段名称,以一个#字符开始下面显示属性,所有的属性都是以field:value的格式显示,以\r\n结尾。INFO命令的返回值如下所示。

bash 复制代码
127.0.0.1:6379> INFO 
# Server 
redis_version:6.0.6 
redis_git_sha1:00000000 
redis_git_dirty:0 
redis_build_id:1c5fdf3388fb1cbf
redis_mode:standalone 
os:Linux 4.14.231-173.361.amzn2.x86_64 x86_64 
arch_bits:64 
multiplexing_api:epoll 
atomicvar_api:atomic-builtin 
gcc_version:7.3.1 
process_id:6970 
run_id:19beba68248e699d5ca388d67913ba5795da43da 
tcp_port:6379 
uptime_in_seconds:15088 
uptime_in_days:0 
hz:10 
configured_hz:10 
lru_clock:11766283 
executable:/soft/redis-6.0.6/redis-server 
config_file:/usr/local/redis/conf/redis.conf 
 
# Clients 
connected_clients:1 
client_recent_max_input_buffer:2 
client_recent_max_output_buffer:0 
blocked_clients:0 
tracking_clients:0 
clients_in_timeout_table:0 
 
# Memory 
used_memory:866648 
used_memory_human:846.34K 
used_memory_rss:3489792 
used_memory_rss_human:3.33M 
used_memory_peak:866680 
used_memory_peak_human:846.37K 
used_memory_peak_perc:100.00% 
used_memory_overhead:820146 
used_memory_startup:803016 
used_memory_dataset:46502 
used_memory_dataset_perc:73.08% 
allocator_allocated:1119000 
allocator_active:1478656 
allocator_resident:4292608 
total_system_memory:32891297792 
total_system_memory_human:30.63G 
used_memory_lua:37888 
used_memory_lua_human:37.00K 
used_memory_scripts:0 
used_memory_scripts_human:0B 
number_of_cached_scripts:0 
maxmemory:0 
maxmemory_human:0B 
maxmemory_policy:noeviction
allocator_frag_ratio:1.32 
allocator_frag_bytes:359656 
allocator_rss_ratio:2.90 
allocator_rss_bytes:2813952 
rss_overhead_ratio:0.81 
rss_overhead_bytes:-802816 
mem_fragmentation_ratio:4.23 
mem_fragmentation_bytes:2665656 
mem_not_counted_for_evict:0 
mem_replication_backlog:0 
mem_clients_slaves:0 
mem_clients_normal:16986 
mem_aof_buffer:0 
mem_allocator:jemalloc-5.1.0 
active_defrag_running:0 
lazyfree_pending_objects:0 
 
# Persistence 
loading:0 
rdb_changes_since_last_save:0 
rdb_bgsave_in_progress:0 
rdb_last_save_time:1622363931 
rdb_last_bgsave_status:ok 
rdb_last_bgsave_time_sec:-1 
rdb_current_bgsave_time_sec:-1 
rdb_last_cow_size:0 
aof_enabled:0 
aof_rewrite_in_progress:0 
aof_rewrite_scheduled:0 
aof_last_rewrite_time_sec:-1 
aof_current_rewrite_time_sec:-1 
aof_last_bgrewrite_status:ok 
aof_last_write_status:ok 
aof_last_cow_size:0 
module_fork_in_progress:0 
module_fork_last_cow_size:0 
 
# Stats 
total_connections_received:2 
total_commands_processed:24 
instantaneous_ops_per_sec:0 
total_net_input_bytes:1488 
total_net_output_bytes:55040 
instantaneous_input_kbps:0.00 
instantaneous_output_kbps:0.00 
rejected_connections:0 
sync_full:0 
sync_partial_ok:0 
sync_partial_err:0 
expired_keys:0 
expired_stale_perc:0.00
expired_time_cap_reached_count:0 
expire_cycle_cpu_milliseconds:174 
evicted_keys:0 
keyspace_hits:0 
keyspace_misses:0 
pubsub_channels:0 
pubsub_patterns:0 
latest_fork_usec:0 
migrate_cached_sockets:0 
slave_expires_tracked_keys:0 
active_defrag_hits:0 
active_defrag_misses:0 
active_defrag_key_hits:0 
active_defrag_key_misses:0 
tracking_total_keys:0 
tracking_total_items:0 
tracking_total_prefixes:0 
unexpected_error_replies:0 
 
# Replication 
role:master 
connected_slaves:0 
master_replid:32e87cdc7ba8f2a6eedfb10a1be8df27813a1a99 
master_replid2:0000000000000000000000000000000000000000 
master_repl_offset:0 
second_repl_offset:-1 
repl_backlog_active:0 
repl_backlog_size:1048576 
repl_backlog_first_byte_offset:0 
repl_backlog_histlen:0 
 
# CPU 
used_cpu_sys:4.558479 
used_cpu_user:7.538676 
used_cpu_sys_children:0.000000 
used_cpu_user_children:0.000000 
 
# Cluster 
cluster_enabled:0 
 
# Keyspace 
db0:keys=2,expires=0,avg_ttl=0
1.1.1、server分段的字段含义
bash 复制代码
127.0.0.1:6379> INFO server 
# Server 
redis_version:6.0.6 
redis_git_sha1:00000000 
redis_git_dirty:0 
redis_build_id:1c5fdf3388fb1cbf 
redis_mode:standalone 
os:Linux 4.14.231-173.361.amzn2.x86_64 x86_64 
arch_bits:64 
multiplexing_api:epoll 
atomicvar_api:atomic-builtin 
gcc_version:7.3.1 
process_id:30633 
run_id:5b93f95d05073508b29296e2ca25263bb52c90e9 
tcp_port:6379 
uptime_in_seconds:546 
uptime_in_days:0 
hz:10 
configured_hz:10 
lru_clock:11661872 
executable:/soft/redis-6.0.6/redis-server 
config_file:/usr/local/redis/conf/redis.conf

server分段包含关于Redis服务器相关的信息,server分段中的主要字段的含义如下所示。

  • redis_version:Redis服务器版本。
  • redis_git_sha1:Git SHA1。
  • redis_git_dirty:Git dirty flag。
  • redis_mode:运行模式(单机或集群)。
  • os:Redis服务器的宿主操作系统。
  • arch_bits:架构(32或64位)。
  • multiplexing_api:Redis所使用的事件处理机制。
  • atomicvar_api:Redis使用的Atomicvar API。
  • gcc_version:用来编译这个Redis服务器的GCC编译器的版本。
  • process_id:Redis服务器进程的进程号(PID)。
  • run_id:用于标识Redis服务器的随机标识符(Redis的哨兵模式和集群模式会使用这个随机值)。
  • tcp_port:TCP/IP的监听端口。
  • uptime_in_seconds:自Redis服务器启动以来,经过的秒数。
  • uptime_in_days:自Redis服务器启动以来,经过的天数。
  • lru_clock:以分钟为单位进行自增的时钟,用于LRU(Least Recently Used,最近最少使用)缓存管理管理。
  • executable:Redis服务器的可执行文件的路径。
  • config_file:Redis服务器读取的配置文件的路径。
1.1.2、clients分段的字段含义
bash 复制代码
127.0.0.1:6379> INFO clients 
# Clients 
connected_clients:1 
client_recent_max_input_buffer:2 
client_recent_max_output_buffer:0 
blocked_clients:0
tracking_clients:0 
clients_in_timeout_table:0

clients分段包含已连接的Redis客户端信息,clients分段中的所有字段的含义如下所示。

  • connected_clients:已连接客户端的数量。
  • client_recent_max_input_buffer:当前连接的客户端当中,最大输入缓存。
  • client_recent_max_output_buffer:当前连接的客户端当中,最大输出缓存。
  • blocked_clients:正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量。
  • tracking_clients:被跟踪的客户数量。
  • clients_in_timeout_table:客户端超时表中的客户端数量。
1.1.3、memory分段的字段含义
bash 复制代码
127.0.0.1:6379> INFO memory 
# Memory 
used_memory:865128 
used_memory_human:844.85K 
used_memory_rss:2813952 
used_memory_rss_human:2.68M 
used_memory_peak:866616 
used_memory_peak_human:846.30K 
used_memory_peak_perc:99.83% 
used_memory_overhead:820114 
used_memory_startup:803016 
used_memory_dataset:45014 
used_memory_dataset_perc:72.47% 
allocator_allocated:970680 
allocator_active:1265664 
allocator_resident:4079616 
total_system_memory:32891297792 
total_system_memory_human:30.63G 
used_memory_lua:37888 
used_memory_lua_human:37.00K 
used_memory_scripts:0 
used_memory_scripts_human:0B 
number_of_cached_scripts:0 
maxmemory:0 
maxmemory_human:0B 
maxmemory_policy:noeviction 
allocator_frag_ratio:1.30 
allocator_frag_bytes:294984 
allocator_rss_ratio:3.22 
allocator_rss_bytes:2813952 
rss_overhead_ratio:0.69 
rss_overhead_bytes:-1265664 
mem_fragmentation_ratio:3.41 
mem_fragmentation_bytes:1989840 
mem_not_counted_for_evict:0 
mem_replication_backlog:0
mem_clients_slaves:0 
mem_clients_normal:16986 
mem_aof_buffer:0 
mem_allocator:jemalloc-5.1.0 
active_defrag_running:0 
lazyfree_pending_objects:0

memory分段包含Redis服务器的内存信息,memory分段中的主要字段的含义如下所示。

  • used_memory:Redis分配的内存总量,以字节(Byte)为单位。
  • used_memory_human:以用户可读的格式返回Redis分配的内存总量,以千字节(KB)为单位。
  • used_memory_rss:从操作系统的角度,返回Redis已分配的内存总量(也被称为"常驻集大小")。这个值和top、ps等命令的输出一致,以Byte为单位。
  • used_memory_rss_human:以用户可读的格式返回Redis已分配的内存总量,以兆字节(MB)为单位。
  • used_memory_peak:Redis消耗的的内存峰值,以Byte为单位。
  • used_memory_peak_human:以用户可读的格式返回Redis消耗的内存峰值,以KB为单位。
  • used_memory_peak_perc:used_memory_peak占used_memory的百分比。
  • used_memory_overhead:Redis服务器分配用于管理其内部数据结构的所有开销的总和,以Byte为单位。
  • used_memory_startup:Redis在启动时消耗的初始内存量,以Byte为单位。
  • allocator_allocated:Redis分配器分配的内存。
  • allocator_active:Redis分配器活跃的内存。
  • allocator_resident:Redis分配器常驻的内存。
  • total_system_memory:Redis主机拥有的总内存量,以Byte为单位。
  • total_system_memory_human:以用户可读的格式返回Redis主机拥有的总内存量,以吉字节(GB)为单位。
  • used_memory_lua:Lua引擎所使用的内存大小,以Byte为单位。
  • used_memory_lua_human:以用户可读的格式返回引擎所使用的内存大小,以GB为单位。
  • used_memory_scripts:缓存Lua脚本使用的字节数。
  • used_memory_scripts_human:以用户可读的格式返回缓存Lua脚本使用的字节数。
  • maxmemory:Redis能使用的最大内存上限,0表示没有限制,以Byte为单位。
  • maxmemory_human:以用户可读的格式返回最大内存上限。
  • maxmemory_policy:Redis使用的的内存回收策略,可以是noeviction、allkeys-lru、volatile-lru、allkeys-random、volatile-random或volatile-ttl。
  • mem_fragmentation_ratio:used_memory_rss和used_memory之间的比率。
  • mem_allocator:Redis使用的内存分配器。
  • active_defrag_running:指示活动碎片整理是否处于活动状态的标志。
  • lazyfree_pending_objects:等待被释放的对象数(由于调用UNLINK或带有ASYNC选项的FLUSHDB和FLUSHALL)。

在理想情况下,used_memory_rss的值应该只比used_memory稍微高一点儿。当常驻集内存(rss)远大于已使用内存(used)​,这表示Redis服务器存在(内部或外部的)内存碎片。内存碎片的比率可以通过mem_fragmentation_ratio的值看出。当已使用内存(used)远大于常驻集内存(rss)​,这表示Redis的一部分内存被操作系统换出到磁盘了,在这种情况下,操作可能会产生明显的延迟。

当Redis释放内存时,分配器可能会,也可能不会,将内存返还给操作系统。如果Redis释放了内存,却没有将内存返还给操作系统,那么used_memory的值可能和操作系统显示的Redis内存占用并不一致。这可能是由于Redis正在使用和释放内存,但是释放的内存尚未归还给操作系统而导致的。查看used_memory_peak的值可以验证这种情况是否发生。

1.1.4、persistence分段的字段含义
bash 复制代码
127.0.0.1:6379> INFO persistence 
# Persistence 
loading:0 
rdb_changes_since_last_save:0 
rdb_bgsave_in_progress:0 
rdb_last_save_time:1622274963 
rdb_last_bgsave_status:ok 
rdb_last_bgsave_time_sec:0 
rdb_current_bgsave_time_sec:-1 
rdb_last_cow_size:163840 
aof_enabled:0 
aof_rewrite_in_progress:0 
aof_rewrite_scheduled:0 
aof_last_rewrite_time_sec:-1 
aof_current_rewrite_time_sec:-1 
aof_last_bgrewrite_status:ok 
aof_last_write_status:ok 
aof_last_cow_size:0 
module_fork_in_progress:0 
module_fork_last_cow_size:0

persistence分段包含RDB和AOF的相关信息,persistence分段中的所有字段的含义如下所示。

  • loading:表示Redis是否正在加载一个转储文件的标志。
  • rdb_changes_since_last_save:从最近一次转储至今,RDB的修改次数。
  • rdb_bgsave_in_progress:表示Redis正在保存RDB的标志。
  • rdb_last_save_time:最近一次成功保存RDB的时间戳,基于Epoch时间。
  • rdb_last_bgsave_status:最近一次RDB保存操作的状态。
  • rdb_last_bgsave_time_sec:最近一次RDB保存操作的持续时间,以秒(s)为单位。
  • rdb_current_bgsave_time_sec:如果Redis正在执行RDB保存操作,那么这个字段表示已经消耗的时间,以s为单位。
  • rdb_last_cow_size:最近一次RDB保存操作期间写时复制内存的大小,以Byte为单位。
  • aof_enabled:表示AOF日志记录已激活的标志。
  • aof_rewrite_in_progress:表示AOF重写操作正在进行的标志。
  • aof_rewrite_scheduled:表示一旦Redis正在执行的RDB保存操作完成之后,是否就会调度执行AOF重写操作的标志。
  • aof_last_rewrite_time_sec:最近一次AOF重写操作的持续时间,以s为单位。
  • aof_current_rewrite_time_sec:如果Redis正在执行AOF重写操作,那么这个字段表示已经消耗的时间,以s为单位。
  • aof_last_bgrewrite_status:最近一次AOF重写操作的状态。
  • aof_last_write_status:对AOF的最后一次写操作的状态。
  • aof_last_cow_size:最近一次AOF重写操作期间写时复制内存的大小,以Byte为单位。
  • module_fork_in_progress:表示模块分叉正在进行的标志。
  • module_fork_last_cow_size:最近一次模块fork操作期间写时复制内存的大小,以Byte为单位。
1.1.5、stats分段的字段含义
bash 复制代码
127.0.0.1:6379> INFO stats 
# Stats 
total_connections_received:1 
total_commands_processed:4 
instantaneous_ops_per_sec:0 
total_net_input_bytes:117 
total_net_output_bytes:3594 
instantaneous_input_kbps:0.00 
instantaneous_output_kbps:0.00 
rejected_connections:0 
sync_full:0 
sync_partial_ok:0 
sync_partial_err:0 
expired_keys:0 
expired_stale_perc:0.00 
expired_time_cap_reached_count:0 
expire_cycle_cpu_milliseconds:0 
evicted_keys:0 
keyspace_hits:0 
keyspace_misses:0 
pubsub_channels:0 
pubsub_patterns:0 
latest_fork_usec:0 
migrate_cached_sockets:0 
slave_expires_tracked_keys:0 
active_defrag_hits:0 
active_defrag_misses:0 
active_defrag_key_hits:0 
active_defrag_key_misses:0 
tracking_total_keys:0 
tracking_total_items:0
tracking_total_prefixes:0 
unexpected_error_replies:0

stats分段包含Redis的一般统计信息,stats分段中的主要字段的含义如下所示。

  • total_connections_received:Redis服务器接收的连接总数。
  • total_commands_processed:Redis服务器处理的命令总数。
  • instantaneous_ops_per_sec:Redis服务器每秒处理的命令数。
  • total_net_input_bytes:Redis服务器从网络读取的总字节数。
  • total_net_output_bytes:Redis服务器写入网络的总字节数。
  • instantaneous_input_kbps:每秒读取数据的速率,以KB/s为单位。
  • instantaneous_output_kbps:每秒钟发送数据的速率,以KB/s为单位。
  • rejected_connections:Redis服务器由于maxclients限制而拒绝的连接数量。
  • sync_full:Redis主机和从机进行完全同步的次数。
  • sync_partial_ok:Redis服务器接受PSYNC请求的次数。
  • sync_partial_err:Redis服务器拒绝PSYNC请求的次数。
  • expired_keys:键过期事件的总数。
  • expired_stale_perc:由于maxmemory限制,而被回收内存的键的总数。
  • evicted_keys:由于maxmemory限制,而被回收内存的键的总数。
  • keyspace_hits:在主字典中成功查找键的次数。
  • keyspace_misses:在主字典中键查找失败的次数。
  • pubsub_channels:客户端订阅的发布/订阅频道的总数量。
  • pubsub_patterns:客户端订阅的发布/订阅模式的总数量。
  • latest_fork_usec:最近一次fork操作消耗的时间,以微秒(ms)为单位。
  • migrate_cached_sockets:迁移已缓存的套接字的数量。
1.1.6、replication分段的字段含义
bash 复制代码
127.0.0.1:6379> INFO replication 
# Replication 
role:master 
connected_slaves:0 
master_replid:32e87cdc7ba8f2a6eedfb10a1be8df27813a1a99 
master_replid2:0000000000000000000000000000000000000000 
master_repl_offset:0 
second_repl_offset:-1 
repl_backlog_active:0 
repl_backlog_size:1048576 
repl_backlog_first_byte_offset:0 
repl_backlog_histlen:0

replication分段包含Redis的主/从复制信息,replication分段中的主要字段的含义如下所示。

  • role:如果这个Redis实例是某个Redis主机的从机,那么这个字段的值为"slave"。如果这个实例不是任何Redis主机的从机,那么这个字段的值是"master"。
  • connected_slaves:已连接的Redis从机的数量。
  • master_replid:Redis服务器的复制ID。
  • master_replid2:辅助复制ID,用于故障转移后的PSYNC。
  • master_repl_offset:Redis服务器的当前复制偏移量。
  • second_repl_offset:接受复制ID的偏移量。
  • repl_backlog_active:表示Redis服务器是否为部分同步开启复制备份日志(backlog)功能的标志。
  • repl_backlog_size:备份日志的循环缓冲区的大小。
  • repl_backlog_first_byte_offset:备份日志缓冲区中的首个字节的复制偏移量。
  • repl_backlog_histlen:备份日志的实际数据长度。
1.1.7、cpu分段的字段含义
bash 复制代码
127.0.0.1:6379> INFO cpu 
# CPU 
used_cpu_sys:3.919009 
used_cpu_user:6.572067 
used_cpu_sys_children:0.000000 
used_cpu_user_children:0.000000

cpu分段包含Redis使用的CPU统计信息,cpu分段中的所有字段的含义如下所示。

  • used_cpu_sys:Redis服务器消耗的系统CPU性能。
  • used_cpu_user:Redis服务器消耗的用户CPU性能。
  • used_cpu_sys_children:后台进程消耗的系统CPU性能。
  • used_cpu_user_children:后台进程消耗的用户CPU性能。
1.1.8、cluster分段的字段含义
bash 复制代码
127.0.0.1:6379> INFO cluster 
# Cluster 
cluster_enabled:0 

cluster分段包含Redis集群信息,cluster分段当前只包含一个字段。

  • cluster_enabled:表示是否启用Redis集群功能的标志。
1.1.9、keyspace分段的字段含义
bash 复制代码
127.0.0.1:6379> INFO keyspace 
# Keyspace 
db0:keys=2,expires=0,avg_ttl=0

keyspace分段会提供关于每个数据库的主字典的统计数据。这些统计数据包括键的数量、具有过期时间的键的数量和键的平均生存时间。

对于每个Redis数据库来说,keyspace分段每行的格式如下所示。

bash 复制代码
dbXXX: keys=XXX,expires=XXX

其中,第一个XXX表示数据库的编号,第二个XXX表示键的数量,第三个XXX表示具有过期时间的键的数量,第四个XXX表示键的平均生存时间。

1.2、使用redis-stat

redis-stat是基于Ruby实现的一个简单易用的Redis监控工具。redis-stat的实现原理基于Redis的INFO命令,相比Redis的MONITOR命令来说其对Redis不会产生任何性能影响。

redis-stat的使用需要依赖Ruby环境,但JRuby为我们提供了基于Java的使用方式。在GitHub官网搜索redis-stat,从redis-stat项目主页下载redis-stat-0.4.14.jar文件,如图所示。

执行启动脚本。

bash 复制代码
java -jar redis-stat-0.4.14.jar 127.0.0.1:6379 3 --server=63800 > monitor.log &

启动脚本表示通过http://127.0.0.1:63800访问redis-stat,监控IP地址为127.0.0.1,端口为6379的Redis服务器,并把日志输出到monitor.log文件中。

通过Web页面直接访问http://127.0.0.1:63800,如图所示。

页面中的图形是根据Redis服务器的状态动态变化的,还是可以满足一般的监控需求的。页面指标说明如表所示。

按"F12"键打开浏览器Chrome的控制台,切换到网络选项,可以看到每5~8s刷新了一次页面,如图所示。

2、自定义监控

redis-stat提供了一种简易的方式实现对Redis的监控,但多数场景下可能需要进行定制,例如鉴权方式、统计指标、告警等。为了实现更灵活的控制,可利用类似的方式对INFO信息进行解析,以实现对Redis的统一监控。

在本节我们使用Spring Boot做一个自定义Redis监控平台,前端使用Bootstrap 3页面展示Redis的监控指标,使用WebSocket技术实时监控Redis数据库db0的keys数量,在页面中使用曲线图形动态地显示keys数量。完整的自定义监控页面如图所示。

从图中可以看出自定义监控页面中最多显示5个时间点的key数量。使用IntelliJ IDEA新建一个Spring Boot项目,命名为RedisMonitor,本实例目录为Redis\Chapter09\RedisMonitor,项目目录结构如图所示。

在监控项目RedisMonitor的pom.xml文件里引入必要的依赖包。

xml 复制代码
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-websocket</artifactId> 
</dependency> 
  
<dependency> 
    <groupId>com.alibaba</groupId> 
    <artifactId>fastjson</artifactId> 
    <version>1.2.71</version> 
</dependency> 
  
<dependency> 
    <groupId>redis.clients</groupId> 
    <artifactId>jedis</artifactId> 
    <version>3.1.0</version> 
</dependency> 

2.1、前端页面

2.1.1、Bootstrap 3后台管理系统模板

Redis自定义监控平台的前端页面使用的是开源的模板Start Bootstrap Admin 2,它是基于Bootstrap 3开发的,是一个轻量级的后台管理系统模板。Start Bootstrap Admin 2有强大的功能组件和UI组件,基本能满足后台管理系统的需求,我们在此基础上进行二次开发,如图所示。可以从Start Bootstrap Admin2的官网下载最新的源码包,把源码包压缩后放在监控项目RedisMonitor的\src\main\resources\static目录下。

2.1.2、ECharts

Redis自定义监控平台使用的图表是ECharts。ECharts是开源的商业及数据图表库,它是一个纯JavaScript的图表库,可以流畅地运行在PC和移动设备上,兼容当前绝大部分浏览器(IE 8/9/10/11、Chrome、Firefox、Safari等)​,底层依赖轻量级的Canvas类库ZRender,提供直观、生动、可交互、可高度定制化的数据可视化图表。ECharts中加入了更丰富的交互功能以及更多的可视化效果,并对移动端做了深度的优化。

ECharts是由百度EFE数据可视化团队开发的,是基于JavaScript的数据图表库,在编程的灵活性和图表的丰富性方面非常强大,优点很多。

  • ECharts是一款独立的Web版数据可视化工具,界面友好,提供强大的互动性操作。
  • 对图形参数的修改十分简单、直观,便于初学者使用。
  • 丰富的可视化图表,具有高度互动性,这得益于其完善的文档和简单的JavaScript API。相比Matplotlib的图表,ECharts更加现代和绚丽。
  • 深度的交互式数据探索。ECharts提供了图例、视觉映射、数据区域缩放、Tooltip、数据筛选等开箱即用的交互组件,可以对数据进行多维度筛取、视图缩放、展示细节等交互操作。
  • 移动端优化。

ECharts提供了常规的折线图、柱状图、散点图、饼图、K线图、用于统计的盒形图、用于地理数据可视化的地图、热力图、线图、用于关系数据可视化的关系图、Treemap、多维数据可视化的平行坐标,以及用于BI的漏斗图、仪表盘,并且支持图与图之间的混搭。

可以从ECharts官网下载最新的源码包,ECharts官网如图所示。

ECharts官网实例,请参考图所示页面。

首先从ECharts官网下载需要的版本。根据用户实现的功能和体积上的需求,ECharts官网提供了不同打包的下载,如果体积上没有要求,可以直接下载完整版本。本书下载的是ECharts完整版本,版本是4.1.0,文件名是echarts.min.js,如图所示。

本实例使用ECharts绘制简单的图表。新建echarts_demo01目录,在目录下新建文件夹jslib,把下载的echarts.min.js放入此文件夹中。本实例的目录结构如下所示。

复制代码
echarts_demo01 
    └─ jslib 
        └─ echarts.min.js 
    │─ echarts_demo.html

然后在echarts_demo01目录下新建echarts_demo.html页面。

html 复制代码
<!DOCTYPE html> 
<html>
 <head> 
     <meta charset="utf-8" /> 
     <title></title> 
 <!-- 引入 echarts.js --> 
 <script src="jslib/echarts.min.js"></script> 
 </head> 
<body> 
<!-- 为ECharts准备一个具备高、宽的DOM容器 --> 
<div id="main" style="width: 600px;height:400px;"></div> 
<script type="text/javascript"> 
       // 基于准备好的DOM容器,初始化ECharts实例 
       var myChart = echarts.init(document.getElementById('main')); 
 
       // 指定图表的配置项和数据 
       option = { 
         xAxis: { 
           type: 'category', 
           data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 
           }, 
          yAxis: { 
           type: 'value' 
          }, 
        series: [{ 
           data: [820, 932, 901, 934, 1290, 1330, 1320], 
           type: 'line' 
        }] 
       }; 
       // 使用刚指定的配置项和数据显示图表 
       myChart.setOption(option); 
</script> 
</body> 
</html>

使用浏览器打开echarts_demo.html页面,显示效果如图所示。

本实例中,我们使用ECharts绘制简单的曲线图,有以下知识点需要注意。

1.引入ECharts

ECharts是一个纯JavaScript的图表库,需要像普通的JavaScript库一样用<script>标签引入。

html 复制代码
<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8"> 
<!-- 引入ECharts文件 --> 
<script src="jslib/echarts.min.js"></script> 
</head> 
</html>

2.绘制简单图表

在绘制简单图表前需要为ECharts准备一个具备高、宽的DOM容器。

html 复制代码
<body> 
<!-- 为ECharts准备一个具备高、宽的DOM容器 --> 
<div id="main" style="width: 600px;height:400px;"></div> 
</body>

然后就可以通过echarts.init()方法初始化一个ECharts实例并通过setOption()方法生成一个简单的曲线图。

js 复制代码
<script type="text/javascript"> 
       // 基于准备好的DOM容器,初始化ECharts实例 
       var myChart = echarts.init(document.getElementById('main')); 
 
       // 指定图表的配置项和数据 
      option = { 
         xAxis: { 
           type: 'category', 
           data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 
          }, 
          yAxis: { 
            type: 'value' 
          }, 
        series: [{ 
           data: [820, 932, 901, 934, 1290, 1330, 1320], 
           type: 'line' 
        }] 
       }; 
 
       // 使用刚指定的配置项和数据显示图表 
       myChart.setOption(option); 
</script>

本实例中的图表的配置项参数可以参考ECharts官网文档中的配置项手册,其他图形的配置项也是如此。

本实例使用ECharts绘制了一个简单的曲线图,没有使用任何图片,显示的曲线图是通过ECharts的JavaScript函数绘制的,曲线图中的数据和互动也都是通过JavaScript函数实现的,这些JavaScript函数实现细节都封装在ECharts图表库里。

本实例中绘制的曲线图虽然在显示上是静止的,但在浏览器中是使用ECharts的JavaScript函数绘制的,支持各种动态互动功能。

2.2、WebSocket与消息推送

Redis自定义监控平台后端获得的Redis监控信息是通过WebSocket技术推送到前台页面的。WebSocket是HTML5提供的一种浏览器与服务器间进行全双工通信的网络技术,依靠这种技术可以实现客户端和服务器的长连接,实现双向实时通信,如图所示。

在客户端中,没有必要为WebSocket使用JavaScript库。实现WebSocket的Web浏览器将通过WebSocket对象公开所有必需的客户端功能(主要指支持HTML5的浏览器)​。

以下API用于创建WebSocket对象。

java 复制代码
var Socket = new WebSocket(url, [protocol] );

以上代码中的第一个参数url,指定连接的URL;第二个参数protocol是可选的,指定可接受的子协议。

当创建了WebSocket对象后,可以使用WebSocket对象的相关事件,如表所示。

监控页面index.html的WebSocket核心代码如下。

js 复制代码
<script type="text/javascript"> 
 
 var websocket = null; 
 //判断当前浏览器是否支持WebSocket 
 if ('WebSocket' in window) { 
     websocket = new WebSocket("ws://localhost:8080/monitor/websocket"); 
     //console.log('websocket=' + websocket); 
 } else { 
     alert('当前浏览器 Not support WebSocket') 
 } 
 
 //连接发生错误的回调方法 
 websocket.onerror = function() { 
     setMessageInnerHTML("WebSocket连接发生错误"); 
 }; 
 
 //连接成功建立的回调方法 
 websocket.onopen = function() { 
     setMessageInnerHTML("WebSocket连接成功"); 
     send(); 
 } 
 
 //接收到消息的回调方法 
 websocket.onmessage = function(event) { 
     jsonObj = JSON.parse(event.data); 
     //console.log(jsonObj); 
     renderBoard(jsonObj); 
     renderChat(jsonObj); 
 } 
 
 function trim(x) { 
     return x.replace(/^\s+|\s+$/gm, ''); 
 } 
 
 //连接关闭的回调方法 
 websocket.onclose = function() { 
     setMessageInnerHTML("WebSocket连接关闭"); 
 } 
 
 //监听窗口关闭事件。当窗口关闭时,主动去关闭WebSocket连接,防止连接还没断开就关闭窗口,服务器会抛出异常
 window.onbeforeunload = function() { 
     closeWebSocket(); 
} 
 
 //将消息显示在网页上 
 function setMessageInnerHTML(innerHTML) { 
     document.getElementById('message').innerHTML += innerHTML + '<br/>'; 
 } 
 
 //关闭WebSocket连接 
 function closeWebSocket() { 
     websocket.close(); 
 } 
 
 //发送消息 
 function send() { 
     var message = Math.floor(100000 * Math.random()); 
     websocket.send(message); 
 } 
 </script> 

WebSocket与Java后台交互将返回以下JSON对象格式的数据,jsonObj对象如下。

json 复制代码
{ 
    "Stats_total_commands_processed": "416", 
    "CPU_used_cpu_sys": "0.30", 
    "CPU_used_cpu_user": "0.27", 
    "Clients_connected_clients": "2", 
    "Memory_used_memory": "0.68", 
    "Memory_used_memory_rss": "0.64", 
    "time": "2019-10-07 17:10:27", 
    "db0_keys": "2" 
} 

返回的数据指标及其含义如表所示。

使用renderBoard(jsonObj)函数渲染页面上显示的网格,如图所示。

renderBoard(jsonObj)函数的核心代码如下。

java 复制代码
<script type="text/javascript"> 
 function renderBoard(jsonObj){ 
     //console.log(jsonObj); 
     Memory_used_memory = jsonObj.Memory_used_memory; 
     Clients_connected_clients = jsonObj.Clients_connected_clients ; 
     Memory_used_memory_rss = jsonObj.Memory_used_memory_rss; 
     Stats_total_commands_processed = jsonObj.Stats_total_commands_processed; 
     CPU_used_cpu_sys = jsonObj.CPU_used_cpu_sys; 
     CPU_used_cpu_user = jsonObj.CPU_used_cpu_user; 
 
     document.getElementById('Memory_used_memory').innerHTML = Memory_used_
memory + " M"; 
     document.getElementById('Clients_connected_clients').innerHTML = Clients_
connected_clients ; 
     document.getElementById('Memory_used_memory_rss').innerHTML = Memory_used_
memory_rss + " M" ; 
     document.getElementById('Stats_total_commands_processed').innerHTML = Stats_
total_commands_processed ; 
     document.getElementById('CPU_used_cpu_sys').innerHTML = CPU_used_cpu_sys ; 
     document.getElementById('CPU_used_cpu_user').innerHTML = CPU_used_cpu_user ; 
 } 
</script>

使用renderChat(jsonObj)函数渲染页面上显示的动态曲线图形,如图所示。

使用ECharts绘制动态曲线图形的核心代码如下。

首先,在绘图前需要为ECharts准备一个有高和宽的DOM容器。

js 复制代码
<script src="https://cdn.bootcss.com/echarts/3.7.2/echarts.min.js"></script> 
 
<div id="main" style="width: 100%; height: 400px;"></div>

然后,通过echarts.init()方法初始化一个ECharts实例并通过setOption()方法生成一个动态的曲线图。为了实现动态曲线图的效果,曲线图最多只显示5个元素,交互数据时删除数组中第一个元素。

js 复制代码
<script type="text/javascript"> 
 
 var time_arr = []; 
 var data_arr =[]; 
 
 function initChat(){ 
     // 基于准备好的DOM容器,初始化ECharts实例 
     var myChart = echarts.init(document.getElementById('main')); 
 
     // 指定图表的配置项和数据 
     option = { 
             title : { 
                 text : 'keys数量' 
             }, 
             tooltip : {}, 
            xAxis: { 
               type: 'category', 
               data: time_arr 
             }, 
            yAxis: { 
               type: 'value' 
             }, 
             series: [{ 
               data: data_arr, 
               type: 'line', 
               smooth: true 
             }] 
          }; 
 
 
     // 使用刚指定的配置项和数据显示图表 
     myChart.setOption(option); 
 
 } 
 function renderChat(jsonObj){ 
     keys = jsonObj.db0_keys; 
     time = jsonObj.time; 
     //console.log(keys, time); 
 
     // 使曲线图中最多显示5个元素 
     if (time_arr.length < 5) { 
         time_arr.push(time); 
         data_arr.push(keys); 
     }else{ 
         // 删除数组中的第一个元素 
         time_arr.splice(0, 1); 
         data_arr.splice(0, 1); 
 
         time_arr[time_arr.length] = time; 
         data_arr[data_arr.length] = keys; 
     } 
 
     //console.log(time_arr.length); 
     initChat(); 
 } 
 
 initChat();  
</script>

2.3、创建控制器类

创建控制器类WebSocketController,调用监控类MonitorService获得Redis的性能指标。本实例使用"WebSocketController.java"​,内容如下。

java 复制代码
package com.xinping.RedisMonitor.server; 
  
import com.xinping.RedisMonitor.service.RedisServiceHandler; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.stereotype.Component; 
import javax.websocket.*; 
import javax.websocket.server.ServerEndpoint; 
import java.io.IOException; 
import java.util.concurrent.*; 
import java.util.concurrent.atomic.AtomicInteger; 
  
@ServerEndpoint("/websocket") 
@Component 
public class WebSocketServer { 
  
   private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class); 
  
   /** 
    * 当前在线连接数 
    */ 
   private static AtomicInteger onlineCount = new AtomicInteger(0); 
  
   /** 
    * 使用CopyOnWriteArraySet对象,concurrent包的线程安全Set,来存放每个客户端对应的
WebSocketServer对象。若要实现服务器与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识 
    */ 
   private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new 
CopyOnWriteArraySet<WebSocketServer>(); 
  
   /** 
    * 与某个客户端的连接会话,需要通过它来给客户端发送数据 
    */ 
   private Session session; 
  
   private Thread queryThread = null; 
  
   /**
 * 连接建立成功调用的方法 
    */ 
   @OnOpen 
   public void onOpen(Session session) { 
      this.session = session; 
 
      webSocketSet.add(this); // 加入Set中 
      addOnlineCount(); // 在线连接数加1 
      log.info("有新连接加入!当前在线连接数为" + getOnlineCount()); 
   } 
  
   /** 
    * 连接关闭调用的方法 
    */ 
   @OnClose 
   public void onClose() { 
      webSocketSet.remove(this); // 从Set中删除 
      subOnlineCount(); // 在线连接数减1 
      log.info("有连接关闭!当前在线连接数为" + getOnlineCount()); 
   } 
  
   public void closeQueryThread() { 
      if (null != queryThread) { 
          queryThread.interrupt(); 
      } 
   } 
  
   /** 
    * 收到客户端消息后调用的方法 
    * 
    * @param message 客户端发送过来的消息 
    */ 
   @OnMessage 
   public void onMessage(String message, Session session) { 
      log.info("来自客户端的消息:" + message); 
  
      RedisServiceHandler handler = new RedisServiceHandler(this); 
      queryThread = new Thread(handler); 
      queryThread.start(); 
   } 
  
   /** 
    * 发生错误时调用的方法 
    * 
    * @param session 
    * @param error 
    */ 
   @OnError 
   public void onError(Session session, Throwable error) { 
      log.error("发生错误原因:" + error.getMessage()); 
      error.printStackTrace(); 
   } 
  
   /** 
    * 实现服务器主动推送 
    */ 
   public void sendMessage(String message) throws IOException { 
      if (this.session.isOpen()) 
          this.session.getBasicRemote().sendText(message); 
   } 
  
   public static synchronized AtomicInteger getOnlineCount() { 
      return onlineCount; 
   } 
  
   /** 
    * 在线连接数加1 
    */ 
   public static synchronized void addOnlineCount() { 
      WebSocketServer.onlineCount.getAndIncrement(); 
   } 
  
   /** 
    * 在线连接数减1 
    */ 
   public static synchronized void subOnlineCount() { 
      WebSocketServer.onlineCount.getAndDecrement(); 
   } 
  
} 

@ServerEndpoint("/websocket")注解是一个类层次的注解,它的功能主要是将目前的类定义成一个WebSocket服务器,注解的值将被用于监听用户连接的终端访问的URL地址,客户端可以通过这个URL地址来连接WebSocket服务器。本例创建的项目名称是monitor,部署在端口为8080的Tomcat服务器上,所以本例中WebSocket对象的访问地址是ws://localhost:8080/monitor/websocket,对应的HTML5的WebSocket核心代码如下。

java 复制代码
var websocket = null; 
//判断当前浏览器是否支持WebSocket 
if ('WebSocket' in window) { 
    websocket = new WebSocket("ws://localhost:8080/monitor/websocket"); 
    //console.log('websocket=' + websocket); 
} else { 
    alert('当前浏览器 Not support WebSocket') 
} 

2.4、业务逻辑

创建监控类MonitorService,用来监控Redis状态,调用INFO命令,解析结果,并对结果进行转换处理。本实例使用了"MonitorService.java"​,内容如下。

java 复制代码
package com.dxtd.monitor.service;
import java.util.HashMap; 
import java.util.Map; 
import javax.tools.Tool; 
import com.dxtd.monitor.util.RedisUtil; 
import com.dxtd.monitor.util.Tools; 
import redis.clients.jedis.Jedis; 
 
public class MonitorService { 
 //调用INFO命令 
 public String getInfo() { 
     String infoContent = null; 
     Jedis jedis = null; 
     try { 
         jedis = RedisUtil.getJedis(); 
         infoContent = jedis.info(); 
     } catch (Exception e) { 
         e.printStackTrace(); 
     } finally { 
         if (jedis != null) { 
             jedis.close(); 
         } 
     } 
     return infoContent; 
 } 
 
 public Double getIntValue(Map<String, String> infoMap, String key) { 
     if (null == infoMap) 
         return 0.0; 
 
     Double value = Double.valueOf(infoMap.get(key)); 
     return value; 
 } 
 
 public String getStringValue(Map<String, String> infoMap, String key) { 
     if (null == infoMap) 
         return ""; 
 
     return infoMap.get(key).trim(); 
 } 
 
 public Integer getKeys(String info, String key) { 
     Integer keysNumValue = null; 
     String[] strs = info.split("\n"); 
     if (strs != null && strs.length > 0) { 
         for (int i = 0; i < strs.length; i++) { 
             String s = strs[i].trim(); 
 
             if (s.indexOf(key) > -1) { 
                 String[] str = s.split(","); 
                 if (null != str) { 
                     String[] dbs = str[0].split(":");
                     String[] dbKeys = dbs[1].split("="); 
                     String keyStr = dbKeys[0]; 
                     keysNumValue = Integer.valueOf(dbKeys[1]); 
                     break; 
                 } 
             }else { 
                 keysNumValue = 0; 
             } 
         } 
     } 
     return keysNumValue; 
 } 
 
 //解析结果 
 public Map parseInfo(String content) { 
     String[] lines = content.split("\n"); 
     Map infoMap = new HashMap(); 
     String part = null; 
     for (String line : lines) { 
         if (line.isEmpty()) { 
             continue; 
         } 
 
         if (line.startsWith("#")) { 
             part = line.replace("#", "").trim(); 
             continue; 
         } 
 
         int index = line.indexOf(':'); 
         if (index >= 0) { 
             infoMap.put(part + "_" + line.substring(0, index), line.substring
(index + 1)); 
         } 
     } 
 
     return infoMap; 
 } 
 
 //数据转换处理 
 public void transData(String infoContent) { 
     Map<String, String> infoMap = parseInfo(infoContent); 
     // 内核空间占用CPU的百分比 
     String ucs = getStringValue(infoMap, "CPU_used_cpu_sys"); 
     // 用户空间占用CPU的百分比 
     String ucu = getStringValue(infoMap, "CPU_used_cpu_user"); 
     // 阻塞客户端的数量 
     String cbc = getStringValue(infoMap, "Clients_blocked_clients"); 
     // 连接客户端的数量 
     String ccc = getStringValue(infoMap, "Clients_connected_clients"); 
     // 使用总内存 
     String mum = getStringValue(infoMap, "Memory_used_memory"); 
     // 使用物理内存 
     String mur = getStringValue(infoMap, "Memory_used_memory_rss"); 
     // 运行以来执行过的命令的总数量 
     String cmd = getStringValue(infoMap, "Stats_total_commands_processed"); 
     // 每秒过期key数量 
     String exp = getStringValue(infoMap, "Stats_expired_keys"); 
     // 每秒淘汰key数量 
     String evt = getStringValue(infoMap, "Stats_evicted_keys"); 
     // 每秒命中数量 
     String hit = getStringValue(infoMap, "Stats_keyspace_hits"); 
     // 每秒丢失数量 
     String mis = getStringValue(infoMap, "Stats_keyspace_misses"); 
 
     Integer db0keysNum = getKeys(infoContent, "db0_keys"); 
 
     long thisTs = System.currentTimeMillis(); 
 
     System.out.println("ucs=" + ucs + ",ucu=" + ucu + ",cbc=" + cbc); 
     System.out.println("ccc=" + ccc + ",mum=" + mum + ",mur=" + mur); 
     System.out.println("cmd=" + cmd + ",exp=" + exp); 
     System.out.println("evt=" + evt + ",hit=" + hit + ",mis=" + mis); 
     System.out.println("db0keysNum=" + db0keysNum); 
 } 
 
 public Map<String, Object> getRedisInfo(String infoContent) { 
     Map<String, Object> map = new HashMap<String, Object>(); 
     Map<String, String> infoMap = parseInfo(infoContent); 
     // 使用总内存 
     String mum = getStringValue(infoMap, "Memory_used_memory"); 
     // 连接客户端的数量 
     String ccc = getStringValue(infoMap, "Clients_connected_clients"); 
     // 使用物理内存 
     String mur = getStringValue(infoMap, "Memory_used_memory_rss"); 
     // 运行以来执行过的命令的总数量 
     String cmd = getStringValue(infoMap, "Stats_total_commands_processed"); 
     // 内核空间占用CPU的百分比 
     String ucs = getStringValue(infoMap, "CPU_used_cpu_sys"); 
     // 用户空间占用CPU的百分比 
     String ucu = getStringValue(infoMap, "CPU_used_cpu_user"); 
     Integer db0keysNum = getKeys(infoContent, "db0:keys"); 
 
     map.put("Memory_used_memory", Tools.transByteToMBSize(Integer.valueOf(mum )) ); 
     map.put("Clients_connected_clients", ccc); 
     map.put("Memory_used_memory_rss", Tools.transByteToMBSize(Integer.valueOf
(mur )) ); 
     map.put("Stats_total_commands_processed", cmd); 
     map.put("CPU_used_cpu_sys", ucs); 
     map.put("CPU_used_cpu_user", ucu); 
     map.put("db0_keys", db0keysNum+""); 
     map.put("time", Tools.getCurrntTime()); 
     return map;
 } 
 
}

创建RedisServiceHandler类实现一个线程,在这个线程的run()方法里,生成MonitorService对象来监控Redis的状态,并对INFO命令的返回值进行处理,然后把返回结果返还给WebSocket的前端页面。本实例使用"RedisServiceHandler.java"​,内容如下。

java 复制代码
package com.dxtd.monitor.service; 
 
import java.io.IOException; 
import java.util.Map; 
 
import com.alibaba.fastjson.JSON; 
import com.dxtd.monitor.action.WebSocketController; 
 
public class RedisServiceHandler implements Runnable { 
 
 private WebSocketController webSocket; 
 
 public RedisServiceHandler(WebSocketController webSocket){ 
     this.webSocket = webSocket; 
 } 
  
 ublic void run() { 
     while(true){ 
         try { 
             MonitorService monitor = new MonitorService(); 
             String info = monitor.getInfo(); 
             Map<String,Object> redisInfo = monitor.getRedisInfo(info); 
             //群发消息 
             webSocket.sendMessage(JSON.toJSONString(redisInfo) ); 
 
             //每5s发送一次消息,以便页面更新数据 
             Thread.sleep(1000 * 5 ); 
         } catch (IOException e) { 
             e.printStackTrace(); 
         } catch (InterruptedException e) { 
             e.printStackTrace(); 
         } 
     } 
 } 
 
}

2.5、常用工具类

RedisUtil类是连接Redis的工具类,封装了Jedis连接池的业务逻辑。本实例使用"RedisUtil.java"​,内容如下。

java 复制代码
package com.dxtd.monitor.util; 
 
import redis.clients.jedis.Client;
import redis.clients.jedis.Jedis; 
import redis.clients.jedis.JedisPool; 
import redis.clients.jedis.JedisPoolConfig; 
 
public class RedisUtil { 
 private static JedisPool jedisPool; 
 
 /** 
  * 建立Redis的连接池,如果需要优化可以把连接池的配置参数单独抽取出来 
  *  
  */ 
 private static void createJedisPool() { 
 
      JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); 
         //指定连接池中最大空闲连接数 
         jedisPoolConfig.setMaxIdle(10); 
         //连接池中创建的最大连接数 
         jedisPoolConfig.setMaxTotal(100); 
         //设置创建连接的超时时间 
         jedisPoolConfig.setMaxWaitMillis(2000); 
         //表示连接池在创建连接的时候会先测试一下连接是否可用,这样可以保证连接池中的连接都可用 
         jedisPoolConfig.setTestOnBorrow(true); 
 
      // 创建连接池 
      jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379); 
 } 
 
 /** 
  * 在多线程环境同步初始化 
  */ 
 private static synchronized void poolInit() { 
     if (jedisPool == null) 
         createJedisPool(); 
 } 
 
 /** 
  * 获取一个Jedis对象 
  *  
  * @return 
  */ 
 static { 
     if (jedisPool == null) 
         poolInit(); 
 } 
 
 /** 
  * 归还一个连接 
  *  
  * @param jedis 
  */ 
 public static void returnRes(Jedis jedis) { 
     jedis.close(); 
 } 
 
 public static Jedis getJedis() { 
     if (jedisPool == null) 
         poolInit(); 
 
     Jedis jedis = jedisPool.getResource(); 
     return jedis; 
 } 
 
 /** 
  * 获取Redis服务器信息 
  *  
  * @param String 
  */ 
 public String getRedisInfo() { 
     if (jedisPool == null) 
         poolInit(); 
 
     Jedis jedis = null; 
     try { 
         jedis = jedisPool.getResource(); 
         Client client = jedis.getClient(); 
         client.info(); 
         String info = client.getBulkReply(); 
         return info; 
     } finally { 
         // 返还到连接池 
         if(null != jedis) 
         jedis.close(); 
     } 
 } 
 
} 

常用工具类Tools,用于获得格式化后的时间和转化单位。本实例使用"Tools.java"​,内容如下。

java 复制代码
package com.dxtd.monitor.util; 
 
import java.text.DecimalFormat; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
 
public class Tools { 
 
//获得格式化后的时间 
 public static String getCurrntTime() { 
     SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
     Date now = new Date(); 
     return format.format(now); 
 } 
 
// 转化单位,从Byte到MB 
 public static String transByteToMBSize(int size) { 
     int MB = 1024 * 1024;// 定义1024MB的计算存储单位 
     DecimalFormat df = new DecimalFormat("0.00");// 格式化小数 
     String resultSize = ""; 
     resultSize = df.format(size / (float) MB); 
     return resultSize; 
 } 
 
} 
相关推荐
再让我睡两分钟2 小时前
【无标题】
android·java·数据库·人工智能·prompt·ai应用开发
爸爸6192 小时前
30 原始 SQL 查询与聚合统计:querySql 实战
数据库·sql·华为·harmonyos·鸿蒙系统
砍材农夫2 小时前
spring|spring event|Spring内置事件驱动编程模型
java·数据库·spring
tiancaijiben11 小时前
阿里云SSL证书自动续签与部署完全指南:从托管服务到开源工具全方案解析
数据库
全堆鸿蒙11 小时前
25 数据库设计与表结构规划:5 张表的设计思路
数据库·华为·cocoa·harmonyos
笃行35011 小时前
【金仓数据库产品体验官】_坏得起_实测:备份恢复、PITR 时间穿越与主备切换——KingbaseES V9R3C18 深度体验(三)
数据库
笃行35013 小时前
从兼容到进阶:驱动直连、窗口函数与动态 SQL 实测——KingbaseES V9R3C18 深度体验(二)
数据库
笃行35014 小时前
MySQL 的 JSON 字段迁到金仓还能用吗?——KingbaseES V9R3C18 JSON 操作符与函数兼容实测
数据库
heimeiyingwang14 小时前
【架构实战】缓存一致性:Cache Aside与双写问题的破解
spring·缓存·架构