Leer variables de configuración sin bloqueo

¡Acostúmbrate a escribir juntos! Este es el cuarto día de mi participación en el "Nuevo plan diario de los Nuggets·Desafío de actualización de abril", haz clic para ver los detalles del evento .

introducción al problema

En general, un programa backend tendrá muchas configuraciones dinámicas, es decir, variables que no pueden tener efecto al reiniciar.

¿Por qué se modifican estas variables de configuración y no se permite reiniciar?

Por supuesto, esto se debe a que el costo de reiniciar una vez, por ejemplo, es que el servicio se detiene al menos durante este período de tiempo. (Incluso si tiene una alta disponibilidad, se implementan un montón de servicios y no se debe reiniciar si se puede reiniciar).

Entonces hay un problema, estas variables de configuración no son de solo lectura, sino que pueden cambiar en cualquier momento según la demanda. Después del cambio, debería surtir efecto.

Los programas de servicio real son de subprocesos múltiples, lo que equivale a tener una operación de escritura y varias operaciones de lectura al mismo tiempo.

¿Desbloqueado? ¿Funcionará?

paraGolang un ejemplo

...
var MaxConn = 10000 // 最大连接数,允许10000
...
复制代码

Mirando el código anterior, tenemos una variable MaxConnque limita el número máximo de conexiones que este programa puede aceptar.

Si esta variable no cambia después de que se inicia el programa, al leer esta variable, simplemente:

if (nowConn < MaxConn) {
    return true
}
复制代码

Este código está permitido.

Pero una vez que decimos que esta variable va a cambiar mientras se ejecuta el programa, entonces no.

var MaxConn = 10000
var MaxConnLock sync.Mutex // 这里要一把锁
复制代码
// 读取时
MaxConnLock.Lock()
defer MaxConnLock.Unlock()
if (nowConn < MaxConn) {
    return true
}
复制代码
// 写入时
MaxConnLock.Lock()
defer MaxConnLock.Unlock()
MaxConn = 20000
复制代码

Se puede ver que sí es necesario agregar un candado, y esta forma de escribir es correcta.

Solución 1: operaciones atómicas

Golang proporciona muchas operaciones atómicas en variables.

Qué es una operación atómica, es decir, una operación que no genera competencia entre múltiples hilos, y es una operación segura de lectura y escritura multihilo.

P.ej:

//  声明
var MaxConn int64 = 10000

// 读操作
maxConn = atomic.LoadInt64(&MaxConn)

// 写操作
atomic.StoreInt64(&MaxConn, 20000)

复制代码

El código anterior se puede utilizar en cualquier momento sin bloqueo.

De esta manera, está bien.

Pero mira los siguientes requisitos.

Múltiples variables de configuración para mantener sincronizadas

Lo que se mantiene sincronizado, por ejemplo, tengo dos variables de configuración:

var MaxUser = 10000 // 某业务最大用户数
var MinUser = 8000 // 某业务最小用户数
复制代码

queremos garantizarMaxUser > MinUser

¿Se pueden hacer operaciones atómicas?

Obviamente no, las operaciones atómicas no pueden garantizar una cierta relación entre múltiples variables.

Si pongo nuevo

  • Usuario máximo = 20000
  • Usuario mínimo = 15000

Si configuro MaxUser primero y luego Minuser, está bien.

Sin embargo, si configuro MinUser primero y luego MaxUser, toma un poco de tiempo,

MinUser = 15000 // ya configurado como nuevo

MaxUser = 10000 // aún no configurado como nuevo

Esto no es lo que se requiere.

Alguien puede haber dicho, acabo de configurar MaxUser primero.

Bien, si tienes 100 variables, tienes que tomarlo con calma, quién viene primero. Me temo que todos se desmayarán.

Resuelva la lectura sin bloqueo síncrona de múltiples variables

Aquí hay una solución simple.

Ponemos todas las variables en una estructura:


type ConfigVar struct {
    MaxConn int64
    MinConn int64
    ..
    ..
    ..
}
复制代码

Bueno, esta ConfigVar es un todo, en nuestras necesidades debemos asegurarnos de que,

  • Cuando cada hilo toma su propio valor, es un todo, no separado.
  • Cada escritura es un todo, no separada.

Para hacer ambas cosas, necesitamos algunas variables adicionales.

Podemos tener una ConfigVarmatriz de longitudes 2:

var configVarArr  = make([]*ConfigVar, 2)
复制代码

Sabemos que mientras nos aseguremos de que nadie más esté escribiendo cuando leemos, tendremos éxito.

Mirando la matriz anterior, con dos elementos, ¡solo debemos asegurarnos de que el subíndice que leemos sea diferente del subíndice que escribimos!

entonces:

var configVarArr  = make([]*ConfigVar, 2)
var nowReadIndex int64 = 0
复制代码

nowReadIndexEl significado de esta variable es que el subíndice actualmente utilizado para lectura es 0.

Entonces, al escribir, es natural usar subíndices 1.

Solo necesitamos cambiar este subíndice después de escribir.

Es decir, si el subíndice es 0, lo convierte en 1.

Si el subíndice es 1, que sea 0.

De esta manera, las lecturas y las escrituras nunca usan la misma memoria al mismo tiempo.

Esto logra lectura y escritura sin bloqueo.

Supongo que te gusta

Origin juejin.im/post/7082752962464284679
Recomendado
Clasificación