test杂谈

发布时间:2024年01月24日

MD5:

1 压缩性:任意长度的数据,算出MD5值长度都是固定的
2 容易计算:从原数据计算出MD5值很容易
3 抗修改性: 对原数据进行修改任何修改,哪怕1个字节,MD5值差异很大
4 强碰撞:想找到两个不同的数据,使他们具有相同的MD5值,非常困难
5 不可逆性:不可反解
1:加盐:
1:通过生成随机数和MD5生成字符串进行组合
2:数据库同时存储MD5和salt(索特)值,验证正确性使用salt进行MD5即可

//生成盐值
//salt, encodedPwd := password.Encode("generic password", nil)
//fmt.Println(salt)
//fmt.Println(encodedPwd)
验证
//check := password.Verify("generic password", salt, encodedPwd, nil)
//fmt.Println(check) // true

// 设置盐值为多少,迭代多少次,key长度多少,md5
options := &password.Options{16, 100, 32, sha512.New}
salt, encodedPwd := password.Encode("generic password", options)
password1 := fmt.Sprintf("$pbkdf2-sha512$%s$%s", salt, encodedPwd)
fmt.Println(len(password1))

fmt.Println(salt)
fmt.Println(encodedPwd)
fmt.Println(password1)
passwordInfo := strings.Split(password1, "$")
fmt.Println(passwordInfo)
check := password.Verify("generic password", passwordInfo[2], passwordInfo[3], options)
fmt.Println(check) // true

go日志库zap

https://github.com/uber-go/zap

1. 安装和基本使用

go get -u go.uber.org/zap
package main
import (
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
url := "https://imooc.com"
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
// Structured context as loosely typed key-value pairs.
"url", url,
"attempt", 3,
)
sugar.Infof("Failed to fetch URL: %s", url)
}

Zap提供了两种类型的日志记录器— Sugared Logger 和 Logger 。
在性能很好但不是很关键的上下文中,使用 SugaredLogger 。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。
在每一微秒和每一次内存分配都很重要的上下文中,使用 Logger 。它甚至比 SugaredLogger 更快,内存分配次数也更少,但它只支持强类型的结构化日志记录

写入日志文件

package main
import (
"go.uber.org/zap"
"time"
)

func NewLogger() (*zap.Logger, error) {
	cfg := zap.NewProductionConfig()
	cfg.OutputPaths = []string{
	"./myproject.log",
	}
	return cfg.Build()
}
func main() {
	//logger, _ := zap.NewProduction()
	logger, err := NewLogger()
	if err != nil {
	panic(err)
	//panic("初始化logger失败")
	}
	su := logger.Sugar()
	defer su.Sync()
	url := "https://imooc.com"
	su.Info("failed to fetch URL",
	// Structured context as strongly typed Field values.
	zap.String("url", url),
	zap.Int("attempt", 3),
	zap.Duration("backoff", time.Second),
	)
}

viper配置

1介绍

Viper是适用于Go应用程序的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。它支持以下特性:

  • 设置默认值
  • 从 JSON 、 TOML 、 YAML 、 HCL 、 envfile 和 Java properties 格式的配置文件读取配置信息
  • 实时监控和重新读取配置文件(可选)
  • 从环境变量中读取
  • 从远程配置系统(etcd或Consul)读取并监控配置变化
  • 从命令行参数读取配置
  • 从buffer读取配置
  • 显式配置值

2. yaml教程

https://www.runoob.com/w3cnote/yaml-intro.html

3. 安装

https://github.com/spf13/viper
go get github.com/spf13/viper

4.使用

package main
import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"time"
)
type MysqlConfig struct {
	Host string `mapstructure:"host"`
	Port int `mapstructure:"port"`
}
type ServerConfig struct{
	Name string `mapstructure:"name"`
	MysqlInfo MysqlConfig `mapstructure:"mysql"`
}
func GetEnvInfo(env string) string {
	viper.AutomaticEnv()
	return viper.GetString(env)
}
func main(){
	data := GetEnvInfo("Debug")
	var configFileName string
	configFileNamePrefix := "config"
	if data == "true" {
	configFileName = fmt.Sprintf("viper_test/%s-debug.yaml", configFileNamePr
	}else{
	configFileName = fmt.Sprintf("viper_test/%s-pro.yaml", configFileNamePref
	}
	serverConfig := ServerConfig{}
	fmt.Println(data)
	v := viper.New()
	v.SetConfigFile(configFileName)
	err := v.ReadInConfig()
	if err != nil {
		panic(err)
	}
	if err := v.Unmarshal(&serverConfig); err != nil {
		panic(err)
	}
	fmt.Println(serverConfig)
	go func() {
		v.WatchConfig()
		v.OnConfigChange(func(e fsnotify.Event) {
		fmt.Println("Config file changed:", e.Name)
		_ = v.ReadInConfig() // 读取配置数据
		_ = v.Unmarshal(&serverConfig)
		fmt.Println(serverConfig)
		})
	}()
	time.Sleep(time.Second*3000)
}

案例使用

package main

import (
	"fmt"
	"github.com/spf13/viper"
)

type ServerConfig struct {
	//底层使用mapstructure
	ServiceName string `mapstructure:"name"`
	Port        int    `mapstructure:"prot"`
}

func main() {
	v := viper.New()
	//文件的路径如何设置
	v.SetConfigFile("viper_test/ch01/config.yaml")
	if err := v.ReadInConfig(); err != nil {
		panic(err)
	}
	serverConfig := ServerConfig{}
	//映射
	err := v.Unmarshal(&serverConfig)
	if err != nil {
		panic(err)
	}

	fmt.Println(serverConfig)
	//fmt.Println(v.Get("name"))
	fmt.Println(v.Get("name"))
}

案例源码

package main

import (
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
	"time"
)

//如何将线上和线下开发配置隔离
//不用改任何代码而且线上和线下的配置文件能隔离开

type ServerConfig struct {
	//底层使用mapstructure
	ServiceName string      `mapstructure:"name"`
	MysqlInfo   MysqlConfig `mapstructure:"mysql"`
}

type MysqlConfig struct {
	Host string `mapstructure:"host"`
	Port int    `mapstructure:"port"`
}

// GetEnvInfo 读取环境变量
func GetEnvInfo(env string) bool {
	viper.AutomaticEnv()
	return viper.GetBool(env)
	//刚才设置的环境变量,想要生效,我们必须重启goland
}

func main() {
	//fmt.Println(GetEnvInfo("CHENGPENG_DEBUG"))
	//设置全局变量
	debug := GetEnvInfo("CHENGPENG_DEBUG")

	configFilePrefix := "config"
	configFileName := fmt.Sprintf("viper_test/ch02/%s-pro.yaml", configFilePrefix)
	if debug {
		configFileName = fmt.Sprintf("viper_test/ch02/%s-debug.yaml", configFilePrefix)
	}

	v := viper.New()
	//文件的路径如何设置
	v.SetConfigFile(configFileName)
	if err := v.ReadInConfig(); err != nil {
		panic(err)
	}
	serverConfig := ServerConfig{}
	//映射
	err := v.Unmarshal(&serverConfig)
	if err != nil {
		panic(err)
	}

	fmt.Println(serverConfig)
	//fmt.Println(v.Get("name"))
	//fmt.Println(v.Get("name"))

	//viper的功能,动态监控变量==>不会阻塞
	v.WatchConfig()
	v.OnConfigChange(func(in fsnotify.Event) {
		fmt.Println("config file channed", in.Name) //监控文件的变化
		_ = v.ReadInConfig()
		_ = v.Unmarshal(&serverConfig)
		fmt.Println(serverConfig)
	})

	time.Sleep(time.Second * 300)
}

在这里插入图片描述

图形验证码

https://zh.mojotv.cn/go/refactor-base64-captcha

consul安装

docker run -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8302:8302 -p 8600:8600/udp consul consul agent -dev -client=0.0.0.0

添加服务

https://developer.hashicorp.com/consul/api-docs/agent/service#register-service

注销服务

https://developer.hashicorp.com/consul/api-docs/agent/service#deregister-service

设置监控检查

https://developer.hashicorp.com/consul/api-docs/agent/check

grpc使用负载均衡

https://github.com/mbobakov/grpc-consul-resolver

文档

https://github.com/grpc/grpc/blob/master/doc/service_config.md

阿里云oss项目

https://help.aliyun.com/zh/oss/developer-reference/go-installation

oss项目源码

https://github.com/aliyun/aliyun-oss-go-sdk

支付宝文档

https://opendocs.alipay.com/common/02kkv7

上传文件

package main

import (
	"fmt"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"os"
)

func handleError(err error) {
	fmt.Println("Error:", err)
	os.Exit(-1)
}
func main() {

	endpoint := "http://xxxxxx"

	accessKeyId := "xxxxxxxxx"
	accessKeySecret := "xxxxxxxx"

	// yourBucketName填写存储空间名称。
	bucketName := "xxxxx"
	// yourObjectName填写Object完整路径,完整路径不包含Bucket名称。
	objectName := "first.jpg"
	// yourLocalFileName填写本地文件的完整路径。
	localFileName := `C:\Users\Administrator\Desktop\yangyan.png`

	// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
	//provider, err := oss.NewEnvironmentVariableCredentialsProvider()
	//if err != nil {
	//	fmt.Println("Error:", err)
	//	os.Exit(-1)
	//}

	// 创建OSSClient实例。
	// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
	//client, err := oss.New(endpoint, accessKeyId, accessKeySecret, oss.SetCredentialsProvider(&provider))
	client, err := oss.New(endpoint, accessKeyId, accessKeySecret)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(-1)
	}

	// 获取存储空间。
	bucket, err := client.Bucket(bucketName)
	if err != nil {
		handleError(err)
	}
	// 上传文件。
	err = bucket.PutObjectFromFile(objectName, localFileName)
	if err != nil {
		handleError(err)
	}
}
https://help.aliyun.com/zh/oss/use-cases/go-1#concept-mhj-zzt-2fb
https://help.aliyun.com/zh/oss/use-cases/obtain-signature-information-from-the-server-and-upload-data-to-oss

for update(sql)–>悲观锁

默认每个语句mysql都是默认提交的
行锁
明确查询条件只需锁住满足条件的数据-->只有在有索引的时候才会这样,如果你没有索引,那么行锁会升级成表锁
一般情况下-->锁只是锁住要更新的语句 
尽量兼容高并发并不是将锁的并发降的最低
如果没有满足条件的结果,并不会锁表(索引字段)-->只有在有索引的时候才会这样,如果你没有索引,那么行锁会升级成表锁

gorm如何实现for update

https://gorm.io/zh_CN/docs/advanced_query.html

gorm实现悲观锁

// Sell 扣减库存 数据库加锁-->悲观锁-->行锁
func (InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*empty.Empty, error) {
	//扣减库存 逐一一些商品进行扣减 本地事务 需要同时扣减成功或者同时扣减失败
	//数据库基本的应用场景:数据一致性(数据库事务)
	//并发情况之下,可能会出现超买
	//开启事务
	tx := global.DBMSQL.Begin()
	//m.Lock() //获取锁 使用悲观锁就不要用互斥锁了
	for _, goodInfo := range req.GoodsInfo {

		var inv model.Inventory
		//if result := global.DBMSQL.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
		if result := tx.Clauses((clause.Locking{Strength: "UPDATE"})).Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 { //使用悲观锁 一旦协程拿到了update锁就进不来了==>这样就实现数据的一致性
			tx.Rollback() //回滚之前的操作
			return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
		}
		//是否小于扣减数量
		if inv.Stocks < goodInfo.Num {
			tx.Rollback() //回滚之前的操作
			return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
		}
		//真正扣减 会出现数据不一致的问题--使用分布式锁
		inv.Stocks -= goodInfo.Num
		tx.Save(&inv) //事务并没有真正提交到数据库

	}
	//提交事务 需要自己手动提交操作
	tx.Commit()
	return &empty.Empty{}, nil
}

// Reback 库存归还
// 1:订单超时归还 2订单创建失败 3手动归还
func (InventoryServer) Reback(ctx context.Context, req *proto.SellInfo) (*empty.Empty, error) {
	//开启事务
	tx := global.DBMSQL.Begin()
	for _, goodInfo := range req.GoodsInfo {
		var inv model.Inventory
		if result := tx.Clauses((clause.Locking{Strength: "UPDATE"})).Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
			tx.Rollback() //回滚之前的操作
			return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
		}
		//真正扣减 会出现数据不一致的问题--使用分布式锁
		inv.Stocks += goodInfo.Num
		tx.Save(&inv)
	}
	//提交事务 需要自己手动提交操作
	tx.Commit()
	return &empty.Empty{}, nil
}

乐观锁
1:没有让数据库加锁
2:不会出现数据不一致
在这里插入图片描述

优缺点
优点:
1. 简单
2. 不需要额外的组件 - 维护,mysql的维护比较简单 - 最合适的才是最好的。 系统的可用性
缺点:
性能

gorm实现mysql乐观锁

// Sell 乐观锁实现
func (InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*empty.Empty, error) {
	//扣减库存 逐一一些商品进行扣减 本地事务 需要同时扣减成功或者同时扣减失败
	//数据库基本的应用场景:数据一致性(数据库事务)
	//并发情况之下,可能会出现超买
	//开启事务
	tx := global.DBMSQL.Begin()
	//m.Lock() //获取锁 使用悲观锁就不要用互斥锁了

	//var test []*int32
	//var goodInfotest[]

	for _, goodInfo := range req.GoodsInfo {

		var inv model.Inventory

		for {
			if result := global.DBMSQL.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 { //使用悲观锁 一旦协程拿到了update锁就进不来了==>这样就实现数据的一致性
				tx.Rollback() //回滚之前的操作
				return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
			}
			//test = &inv.ID
			//是否小于扣减数量
			if inv.Stocks < goodInfo.Num {
				tx.Rollback() //回滚之前的操作
				return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
			}
			//真正扣减 会出现数据不一致的问题--使用分布式锁
			inv.Stocks -= goodInfo.Num
			//根据条件更新
			//update inventory set stocks=stocks-1,version=version+1 where goods=goodsId and version=version
			//这种写法有瑕疵 Gorm不会更新零值 这种会被gorm给忽略掉
			//if result := tx.Model(&model.Inventory{}).Where("goods = ? and version =?", goodInfo.GoodsId, inv.Version).Updates(model.Inventory{Stocks: inv.Stocks, Version: inv.Version + 1}); result.RowsAffected == 0 {
			//	zap.S().Info("库存扣减失败")
			//} else {
			//	break
			//}
			//强制更新零值
			if result := tx.Model(&model.Inventory{}).Select("Stocks", "Version").Where("goods = ? and version =?", goodInfo.GoodsId, inv.Version).Updates(model.Inventory{Stocks: inv.Stocks, Version: inv.Version + 1}); result.RowsAffected == 0 {
				zap.S().Info("库存扣减失败")
			} else {
				break
			}
		}

	}
	//提交事务 需要自己手动提交操作
	tx.Commit()
	//最后把版本号更新正确版本
	for _, info := range req.GoodsInfo {
		//var inv model.Inventory
		global.DBMSQL.Select("Version").Where("goods = ?", info.GoodsId).Updates(model.Inventory{Version: 0})
	}
	return &empty.Empty{}, nil
}

// Reback 库存归还
// 1:订单超时归还 2订单创建失败 3手动归还
func (InventoryServer) Reback(ctx context.Context, req *proto.SellInfo) (*empty.Empty, error) {
	//开启事务
	tx := global.DBMSQL.Begin()
	for _, goodInfo := range req.GoodsInfo {
		var inv model.Inventory

		for {
			if result := tx.Clauses((clause.Locking{Strength: "UPDATE"})).Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
				tx.Rollback() //回滚之前的操作
				return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
			}
			//真正扣减 会出现数据不一致的问题--使用分布式锁
			inv.Stocks += goodInfo.Num
			if result := tx.Model(&model.Inventory{}).Select("Stocks", "Version").Where("goods = ? and version =?", goodInfo.GoodsId, inv.Version).Updates(model.Inventory{Stocks: inv.Stocks, Version: inv.Version + 1}); result.RowsAffected == 0 {
				zap.S().Info("库存扣减失败")
			} else {
				break
			}
		}

	}
	//提交事务 需要自己手动提交操作
	tx.Commit()
	//最后把版本号更新正确版本
	for _, info := range req.GoodsInfo {
		//var inv model.Inventory
		global.DBMSQL.Select("Version").Where("goods = ?", info.GoodsId).Updates(model.Inventory{Version: 0})
	}
	return &empty.Empty{}, nil
}

基于redis实现分布式锁

项目

https://github.com/go-redsync/redsync

分布式锁需要解决的问题:
互斥性:任意时刻只能有一个客户端拥有锁,不能同时多个客户端获取
安全性:锁只能被持有该锁的用户删除,而不能被其他用户删除
死锁:获取锁的客户端因为某些原因而宕机,而未能释放锁,其他客户端无法获取此锁,需要有机制来避
免该类问题的发生

  1. 代码异常,导致无法运行到release
  2. 你的当前服务器网络出问题 - 脑裂
  3. 断电
    容错:当部分节点宕机,客户端仍能获取锁或者释放锁
    如何解决上述问题的发生 - 设置过期时间
    过期设置会产生新的问题:
  4. 当前的线程如果在一段时间后没有执行完, 当前的程序没有执行完,然后key过期了
  5. 不安全
  6. 另一个线程进来以后会将当前的key给删除掉, 另一个线程删除掉了本该属于我设置的值
  7. 如果当前的线程没有执行完,那我的这个线程还应该在适当的时候去续租,将过期时间重新设置
  8. 应该在什么时候去设置过期 - 15的2/3的时候去续租,也就是运行10s以后去将过期时间重新
    设置为15s
  9. 如果去定时的完成这个续租的过程 - 启动一个线程去完成

redsync源码解读

1 setnx的作用

  1. 将获取的设置变成原子性的操作

2. 如果我的服务挂掉了- 死锁
2. 设置过期时间
3. 如果你设置了过期时间,那么如果过期时间到了我的业务逻辑没有执行完怎么办?

1 在过期之前刷新一下
2 需要自己去启动协程完成延时的工作
3 延时的接口可能会带来负面影响 - 如果其中某一个服务hung住了, 2s就能执行完,但是你hung住那么你就会一直去申请延长锁,导致别人永远获取不到锁,这个很要命

3. 分布锁需要解决的问题 - lua脚本去做

1.互斥性 - setnx
2. 死锁
3. 安全性

安全性. 锁只能被持有该锁的用户删除,不能被其他用户删除

1. 当时设置的value值是多少只有当时的g()才能知道
2. 在删除的时取出redis中的值和当前自己保存下来的值对比一下
3. 即使你这样实现了分布式但是还是会有问题 - redlock(红锁)

redis集群问题

在这里插入图片描述
如果主宕机或者网络故障的时候,那么其他的redis会找到一个redis当主机,其他当从机,就出现一个问题,此时setnx还没有同步到setnx上,库存服务2就去redis3里面去setnx,那么就会出现问题—>此时就产生了一个名词红锁
setnx操作应该在多台服务上进行–>就不需要关注同步的问题
谁先拿到多数谁就会成功就解决数据同步的问题
会通过协程异步去拿服务器,如果看是否能拿到,拿到多数服务器是否为拿到多少。拿锁的时候会消耗时间片,还有当前的时钟的时间==>过期时间还有多少

多节点redis实现的分布式锁算法(RedLock):有效防止单点故障

假设有5个完全独立的redis主服务器
1.获取当前时间戳
2.client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。
比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁
3.client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功
4.如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);
5.如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁
算法示意图如下:
在这里插入图片描述

什么是时钟漂移

如果redis服务器的机器时钟发生了向前跳跃,就会导致这个key过早超时失效,比如说客户端1拿到锁后,key的过期时间是12:02分,但redis服务器本身的时钟比客户端快了2分钟,导致key在12:00的时候就失效了,这时候,如果客户端1还没有释放锁的话,就可能导致多个客户端同时持有同一把锁的问题。
RedLock算法是否是异步算法?
可以看成是同步算法;因为 即使进程间(多个电脑间)没有同步时钟,但是每个进程时间流速大致相同;并且时钟漂移相对于TTL叫小,可以忽略,所以可以看成同步算法;(不够严谨,算法上要算上时钟漂移,因为如果两个电脑在地球两端,则时钟漂移非常大)
RedLock失败重试
当client不能获取锁时,应该在随机时间后重试获取锁;并且最好在同一时刻并发的把set命令发送给所有redis实例;而且对于已经获取锁的client在完成任务后要及时释放锁,这是为了节省时间;
RedLock释放锁
由于释放锁时会判断这个锁的value是不是自己设置的,如果是才删除;所以在释放锁时非常简单,只要向所有实例都发出释放锁的命令,不用考虑能否成功释放锁;
RedLock注意点(Safety arguments):
1.先假设client获取所有实例,所有实例包含相同的key和过期时间(TTL) ,但每个实例set命令时间不同导致不能同时过期,第一个set命令之前是T1,最后一个set命令后为T2,则此client有效获取锁的最小时间为TTL-(T2-T1)-时钟漂移;
2.对于以N/2+ 1(也就是一半以 上)的方式判断获取锁成功,是因为如果小于一半判断为成功的话,有可能出现多个client都成功获取锁的情况, 从而使锁失效
3.一个client锁定大多数事例耗费的时间大于或接近锁的过期时间,就认为锁无效,并且解锁这个redis实例(不执行业务) ;只要在TTL时间内成功获取一半以上的锁便是有效锁;否则无效
系统有活性的三个特征
1.能够自动释放锁
2.在获取锁失败(不到一半以上),或任务完成后 能够自动释放锁,不用等到其自动过期
3.在client重试获取哦锁前(第一次失败到第二次重试时间间隔)大于第一次获取锁消耗的时间;
4.重试获取锁要有一定次数限制
RedLock性能及崩溃恢复的相关解决方法
1.如果redis没有持久化功能,在clientA获取锁成功后,所有redis重启,clientB能够再次获取到锁,这样违法了锁的排他互斥性;
2.如果启动AOF永久化存储,事情会好些, 举例:当我们重启redis后,由于redis过期机制是按照unix时间戳走的,所以在重启后,然后会按照规定的时间过期,不影响业务;但是由于AOF同步到磁盘的方式默认是每秒-次,如果在一秒内断电,会导致数据丢失,立即重启会造成锁互斥性失效;但如果同步磁盘方式使用Always(每一个写命令都同步到硬盘)造成性能急剧下降;所以在锁完全有效性和性能方面要有所取舍;
3.有效解决既保证锁完全有效性及性能高效及即使断电情况的方法是redis同步到磁盘方式保持默认的每秒,在redis无论因为什么原因停掉后要等待TTL时间后再重启(学名:延迟重启) ;缺点是 在TTL时间内服务相当于暂停状态;
总结:
1.TTL时长 要大于正常业务执行的时间+获取所有redis服务消耗时间+时钟漂移
2.获取redis所有服务消耗时间要 远小于TTL时间,并且获取成功的锁个数要 在总数的一般以上:N/2+1
3.尝试获取每个redis实例锁时的时间要 远小于TTL时间
4.尝试获取所有锁失败后 重新尝试一定要有一定次数限制
5.在redis崩溃后(无论一个还是所有),要延迟TTL时间重启redis
6.在实现多redis节点时要结合单节点分布式锁算法 共同实现

elasticsearch

在这里插入图片描述

文章来源:https://blog.csdn.net/qq_40432598/article/details/134498935
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。