feat: redis支持sentinel

This commit is contained in:
meilin.huang
2022-08-23 18:50:07 +08:00
parent b1ee9b65ff
commit 98a4c92576
9 changed files with 50 additions and 11 deletions

View File

@@ -19,7 +19,7 @@
### 介绍 ### 介绍
基于DDD分层实现的web版 **linux(终端 文件 脚本 进程)、数据库mysql postgres、redis(单机 集群)、mongo统一管理操作平台** 基于DDD分层实现的web版 **linux(终端 文件 脚本 进程)、数据库mysql postgres、redis(单机 哨兵 集群)、mongo统一管理操作平台**
### 开发语言与主要框架 ### 开发语言与主要框架

View File

@@ -25,7 +25,7 @@
</div> </div>
</div> </div>
<el-table :data="projects" @current-change="choose" ref="table" style="width: 100%"> <el-table :data="projects" @current-change="choose" ref="table" style="width: 100%">
<el-table-column label="选择" width="50px"> <el-table-column label="选择" width="55px">
<template #default="scope"> <template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id"> <el-radio v-model="chooseId" :label="scope.row.id">
<i></i> <i></i>

View File

@@ -17,12 +17,13 @@
<el-select style="width: 100%" v-model="form.mode" placeholder="请选择模式"> <el-select style="width: 100%" v-model="form.mode" placeholder="请选择模式">
<el-option label="standalone" value="standalone"> </el-option> <el-option label="standalone" value="standalone"> </el-option>
<el-option label="cluster" value="cluster"> </el-option> <el-option label="cluster" value="cluster"> </el-option>
<el-option label="sentinel" value="sentinel"> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item prop="host" label="host:" required> <el-form-item prop="host" label="host:" required>
<el-input <el-input
v-model.trim="form.host" v-model.trim="form.host"
placeholder="请输入host:port,集群模式用','分割" placeholder="请输入host:portsentinel模式为: mastername=sentinelhost:port若集群或哨兵需设多个节点可使用','分割"
auto-complete="off" auto-complete="off"
type="textarea" type="textarea"
></el-input> ></el-input>
@@ -113,7 +114,7 @@ export default defineComponent({
id: null, id: null,
name: null, name: null,
mode: 'standalone', mode: 'standalone',
host: null, host: '',
password: null, password: null,
project: null, project: null,
projectId: null, projectId: null,
@@ -219,6 +220,10 @@ export default defineComponent({
redisForm.value.validate(async (valid: boolean) => { redisForm.value.validate(async (valid: boolean) => {
if (valid) { if (valid) {
const reqForm = { ...state.form }; const reqForm = { ...state.form };
if (reqForm.mode == 'sentinel' && reqForm.host.split('=').length != 2) {
ElMessage.error('sentinel模式host需为: mastername=sentinelhost:sentinelport模式');
return;
}
reqForm.password = await RsaEncrypt(reqForm.password); reqForm.password = await RsaEncrypt(reqForm.password);
redisApi.saveRedis.request(reqForm).then(() => { redisApi.saveRedis.request(reqForm).then(() => {
ElMessage.success('保存成功'); ElMessage.success('保存成功');

View File

@@ -31,7 +31,7 @@
<el-table-column prop="creator" label="创建人" min-width="100"></el-table-column> <el-table-column prop="creator" label="创建人" min-width="100"></el-table-column>
<el-table-column label="更多" min-width="130" fixed="right"> <el-table-column label="更多" min-width="130" fixed="right">
<template #default="scope"> <template #default="scope">
<el-link v-if="scope.row.mode == 'standalone'" type="primary" @click="info(scope.row)" :underline="false">单机信息</el-link> <el-link v-if="scope.row.mode == 'standalone' || scope.row.mode == 'sentinel'" type="primary" @click="info(scope.row)" :underline="false">单机信息</el-link>
<el-link @click="onShowClusterInfo(scope.row)" v-if="scope.row.mode == 'cluster'" type="success" :underline="false" <el-link @click="onShowClusterInfo(scope.row)" v-if="scope.row.mode == 'cluster'" type="success" :underline="false"
>集群信息</el-link >集群信息</el-link
> >

View File

@@ -193,7 +193,7 @@ func (r *Redis) Scan(rc *ctx.ReqCtx) {
kis := make([]*vo.KeyInfo, 0) kis := make([]*vo.KeyInfo, 0)
var cursorRes map[string]uint64 = make(map[string]uint64) var cursorRes map[string]uint64 = make(map[string]uint64)
if ri.Mode == "" || ri.Mode == entity.RedisModeStandalone { if ri.Mode == "" || ri.Mode == entity.RedisModeStandalone || ri.Mode == entity.RedisModeSentinel {
redisAddr := ri.Cli.Options().Addr redisAddr := ri.Cli.Options().Addr
keys, cursor := ri.Scan(form.Cursor[redisAddr], form.Match, form.Count) keys, cursor := ri.Scan(form.Cursor[redisAddr], form.Match, form.Count)
cursorRes[redisAddr] = cursor cursorRes[redisAddr] = cursor

View File

@@ -133,6 +133,14 @@ func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
ri.Close() ri.Close()
panic(biz.NewBizErr(fmt.Sprintf("redis集群连接失败: %s", e.Error()))) panic(biz.NewBizErr(fmt.Sprintf("redis集群连接失败: %s", e.Error())))
} }
} else if redisMode == entity.RedisModeSentinel {
ri = getRedisSentinelCient(re)
// 测试连接
_, e := ri.Cli.Ping(context.Background()).Result()
if e != nil {
ri.Close()
panic(biz.NewBizErr(fmt.Sprintf("redis sentinel连接失败: %s", e.Error())))
}
} }
global.Log.Infof("连接redis: %s", re.Host) global.Log.Infof("连接redis: %s", re.Host)
@@ -177,6 +185,27 @@ func getRedisClusterClient(re *entity.Redis) *RedisInstance {
return ri return ri
} }
func getRedisSentinelCient(re *entity.Redis) *RedisInstance {
ri := &RedisInstance{Id: re.Id, ProjectId: re.ProjectId, Mode: re.Mode}
// sentinel模式host为 masterName=host:port,host:port
masterNameAndHosts := strings.Split(re.Host, "=")
sentinelOptions := &redis.FailoverOptions{
MasterName: masterNameAndHosts[0],
SentinelAddrs: strings.Split(masterNameAndHosts[1], ","),
Password: re.Password, // no password set
DB: re.Db, // use default DB
DialTimeout: 8 * time.Second,
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
WriteTimeout: -1,
}
if re.EnableSshTunnel == 1 {
ri.sshTunnelMachineId = re.SshTunnelMachineId
sentinelOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
}
ri.Cli = redis.NewFailoverClient(sentinelOptions)
return ri
}
func getRedisDialer(machineId uint64) func(ctx context.Context, network, addr string) (net.Conn, error) { func getRedisDialer(machineId uint64) func(ctx context.Context, network, addr string) (net.Conn, error) {
sshTunnel := MachineApp.GetSshTunnelMachine(machineId) sshTunnel := MachineApp.GetSshTunnelMachine(machineId)
return func(_ context.Context, network, addr string) (net.Conn, error) { return func(_ context.Context, network, addr string) (net.Conn, error) {
@@ -227,6 +256,10 @@ func TestRedisConnection(re *entity.Redis) {
ccli := getRedisClusterClient(re) ccli := getRedisClusterClient(re)
defer ccli.Close() defer ccli.Close()
cmd = ccli.ClusterCli cmd = ccli.ClusterCli
} else if re.Mode == entity.RedisModeSentinel {
rcli := getRedisSentinelCient(re)
defer rcli.Close()
cmd = rcli.Cli
} }
// 测试连接 // 测试连接
@@ -247,10 +280,10 @@ type RedisInstance struct {
// 获取命令执行接口的具体实现 // 获取命令执行接口的具体实现
func (r *RedisInstance) GetCmdable() redis.Cmdable { func (r *RedisInstance) GetCmdable() redis.Cmdable {
redisMode := r.Mode redisMode := r.Mode
if redisMode == "" || redisMode == entity.RedisModeStandalone { if redisMode == "" || redisMode == entity.RedisModeStandalone || r.Mode == entity.RedisModeSentinel {
return r.Cli return r.Cli
} }
if r.Mode == entity.RedisModeCluster { if redisMode == entity.RedisModeCluster {
return r.ClusterCli return r.ClusterCli
} }
return nil return nil
@@ -263,7 +296,7 @@ func (r *RedisInstance) Scan(cursor uint64, match string, count int64) ([]string
} }
func (r *RedisInstance) Close() { func (r *RedisInstance) Close() {
if r.Mode == entity.RedisModeStandalone { if r.Mode == entity.RedisModeStandalone || r.Mode == entity.RedisModeSentinel {
if err := r.Cli.Close(); err != nil { if err := r.Cli.Close(); err != nil {
global.Log.Errorf("关闭redis单机实例[%d]连接失败: %s", r.Id, err.Error()) global.Log.Errorf("关闭redis单机实例[%d]连接失败: %s", r.Id, err.Error())
} }

View File

@@ -24,6 +24,7 @@ type Redis struct {
const ( const (
RedisModeStandalone = "standalone" RedisModeStandalone = "standalone"
RedisModeCluster = "cluster" RedisModeCluster = "cluster"
RedisModeSentinel = "sentinel"
) )
func (r *Redis) PwdEncrypt() { func (r *Redis) PwdEncrypt() {

View File

@@ -286,7 +286,7 @@ CREATE TABLE `t_redis` (
DROP TABLE IF EXISTS `t_sys_account`; DROP TABLE IF EXISTS `t_sys_account`;
CREATE TABLE `t_sys_account` ( CREATE TABLE `t_sys_account` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(12) COLLATE utf8mb4_bin NOT NULL, `username` varchar(30) COLLATE utf8mb4_bin NOT NULL,
`password` varchar(64) COLLATE utf8mb4_bin NOT NULL, `password` varchar(64) COLLATE utf8mb4_bin NOT NULL,
`status` tinyint(4) DEFAULT NULL, `status` tinyint(4) DEFAULT NULL,
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',

View File

@@ -4,7 +4,7 @@ import "fmt"
const ( const (
AppName = "mayfly-go" AppName = "mayfly-go"
Version = "v1.2.5" Version = "v1.2.6"
) )
func GetAppInfo() string { func GetAppInfo() string {