当前位置: 首页 > news >正文

40分钟学 Go 语言高并发:微服务架构设计

微服务架构设计

一、知识要点总览

核心模块重要性掌握程度
服务拆分深入理解DDD领域驱动设计
接口设计掌握RESTful和gRPC设计规范
服务治理理解服务注册、发现、熔断、限流等机制
部署策略掌握Docker+K8s容器化部署方案

二、详细内容讲解

1. 服务拆分

服务拆分是微服务架构设计的第一步,我们通常采用DDD(Domain-Driven Design)领域驱动设计的方法进行服务边界划分。

1.1 拆分原则
  • 单一职责原则
  • 高内聚低耦合
  • 服务粒度适中
  • 业务边界清晰

让我们通过一个电商系统的服务拆分示例来理解:
在这里插入图片描述

2. 接口设计

我们将通过一个具体的商品服务示例来展示Go微服务的接口设计。

// product.proto
syntax = "proto3";
package product;
option go_package = "./product";service ProductService {rpc CreateProduct (CreateProductRequest) returns (CreateProductResponse);rpc GetProduct (GetProductRequest) returns (GetProductResponse);rpc UpdateProduct (UpdateProductRequest) returns (UpdateProductResponse);rpc ListProducts (ListProductsRequest) returns (ListProductsResponse);
}message Product {string id = 1;string name = 2;string description = 3;double price = 4;int32 stock = 5;string category = 6;repeated string images = 7;
}message CreateProductRequest {Product product = 1;
}message CreateProductResponse {Product product = 1;
}message GetProductRequest {string id = 1;
}message GetProductResponse {Product product = 1;
}message UpdateProductRequest {Product product = 1;
}message UpdateProductResponse {Product product = 1;
}message ListProductsRequest {int32 page = 1;int32 page_size = 2;string category = 3;
}message ListProductsResponse {repeated Product products = 1;int32 total = 2;
}// product_service.go
package serviceimport ("context""database/sql"pb "yourproject/product""google.golang.org/grpc/codes""google.golang.org/grpc/status"
)type ProductService struct {pb.UnimplementedProductServiceServerdb *sql.DB
}func NewProductService(db *sql.DB) *ProductService {return &ProductService{db: db}
}func (s *ProductService) CreateProduct(ctx context.Context, req *pb.CreateProductRequest) (*pb.CreateProductResponse, error) {if req.Product == nil {return nil, status.Error(codes.InvalidArgument, "product is required")}// 开启事务tx, err := s.db.BeginTx(ctx, nil)if err != nil {return nil, status.Error(codes.Internal, "failed to begin transaction")}defer tx.Rollback()// 插入商品信息query := `INSERT INTO products (name, description, price, stock, category)VALUES ($1, $2, $3, $4, $5)RETURNING id`var id stringerr = tx.QueryRowContext(ctx, query,req.Product.Name,req.Product.Description,req.Product.Price,req.Product.Stock,req.Product.Category,).Scan(&id)if err != nil {return nil, status.Error(codes.Internal, "failed to create product")}// 提交事务if err := tx.Commit(); err != nil {return nil, status.Error(codes.Internal, "failed to commit transaction")}req.Product.Id = idreturn &pb.CreateProductResponse{Product: req.Product,}, nil
}func (s *ProductService) GetProduct(ctx context.Context, req *pb.GetProductRequest) (*pb.GetProductResponse, error) {query := `SELECT id, name, description, price, stock, categoryFROM productsWHERE id = $1`product := &pb.Product{}err := s.db.QueryRowContext(ctx, query, req.Id).Scan(&product.Id,&product.Name,&product.Description,&product.Price,&product.Stock,&product.Category,)if err == sql.ErrNoRows {return nil, status.Error(codes.NotFound, "product not found")}if err != nil {return nil, status.Error(codes.Internal, "failed to get product")}return &pb.GetProductResponse{Product: product,}, nil
}

3. 服务治理

服务治理是确保微服务架构可靠运行的关键。让我们实现一个包含服务注册、发现和熔断器的完整示例。

// service_registry.go
package registryimport ("context""sync""time""github.com/go-kit/kit/sd/etcdv3""go.etcd.io/etcd/client/v3"
)type ServiceRegistry struct {client     *clientv3.Clientregistrar  *etcdv3.Registrarmutex      sync.RWMutexinstances  map[string]string
}func NewServiceRegistry(endpoints []string) (*ServiceRegistry, error) {client, err := clientv3.New(clientv3.Config{Endpoints:   endpoints,DialTimeout: 5 * time.Second,})if err != nil {return nil, err}return &ServiceRegistry{client:    client,instances: make(map[string]string),}, nil
}func (sr *ServiceRegistry) Register(ctx context.Context, serviceName, serviceAddr string) error {registrar := etcdv3.NewRegistrar(sr.client, etcdv3.Service{Key:   "/services/" + serviceName,Value: serviceAddr,}, log.NewNopLogger())sr.mutex.Lock()sr.registrar = registrarsr.instances[serviceName] = serviceAddrsr.mutex.Unlock()return registrar.Register()
}func (sr *ServiceRegistry) Deregister(ctx context.Context, serviceName string) error {sr.mutex.Lock()defer sr.mutex.Unlock()if sr.registrar != nil {sr.registrar.Deregister()}delete(sr.instances, serviceName)return nil
}// circuit_breaker.go
package circuitbreakerimport ("github.com/sony/gobreaker""time"
)type CircuitBreaker struct {cb *gobreaker.CircuitBreaker
}func NewCircuitBreaker(name string) *CircuitBreaker {settings := gobreaker.Settings{Name:        name,MaxRequests: 3,Interval:    10 * time.Second,Timeout:     60 * time.Second,ReadyToTrip: func(counts gobreaker.Counts) bool {failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)return counts.Requests >= 3 && failureRatio >= 0.6},OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {// 状态变更记录log.Printf("Circuit Breaker %s state change from %s to %s", name, from, to)},}return &CircuitBreaker{cb: gobreaker.NewCircuitBreaker(settings),}
}func (c *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {return c.cb.Execute(req)
}// rate_limiter.go
package ratelimitimport ("golang.org/x/time/rate""context"
)type RateLimiter struct {limiter *rate.Limiter
}func NewRateLimiter(rps float64, burst int) *RateLimiter {return &RateLimiter{limiter: rate.NewLimiter(rate.Limit(rps), burst),}
}func (r *RateLimiter) Allow() bool {return r.limiter.Allow()
}func (r *RateLimiter) Wait(ctx context.Context) error {return r.limiter.Wait(ctx)
}

4. 部署策略

在这里插入图片描述

现在让我们看一个具体的部署配置示例:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: product-servicenamespace: microservices
spec:replicas: 3selector:matchLabels:app: product-servicetemplate:metadata:labels:app: product-servicespec:containers:- name: product-serviceimage: your-registry/product-service:v1.0.0ports:- containerPort: 50051resources:limits:cpu: "1"memory: "1Gi"requests:cpu: "500m"memory: "512Mi"readinessProbe:grpc:port: 50051initialDelaySeconds: 5periodSeconds: 10livenessProbe:grpc:port: 50051initialDelaySeconds: 15periodSeconds: 20env:- name: DB_HOSTvalueFrom:configMapKeyRef:name: product-service-configkey: db_host- name: DB_PASSWORDvalueFrom:secretKeyRef:name: product-service-secretskey: db_password---
# service.yaml
apiVersion: v1
kind: Service
metadata:name: product-servicenamespace: microservices
spec:selector:app: product-serviceports:- port: 50051targetPort: 50

让我们继续深入微服务架构设计的其他重要方面:

5. 服务间通信模式

在微服务架构中,服务间通信是一个核心问题,我们来看几种主要的通信模式:
在这里插入图片描述

让我们实现一个基于消息队列的异步通信示例:

// event/event.go
package eventtype OrderEvent struct {EventID     string    `json:"event_id"`EventType   string    `json:"event_type"`OrderID     string    `json:"order_id"`UserID      string    `json:"user_id"`ProductID   string    `json:"product_id"`Quantity    int       `json:"quantity"`TotalPrice  float64   `json:"total_price"`Timestamp   int64     `json:"timestamp"`
}// kafka/producer.go
package kafkaimport ("context""encoding/json""github.com/Shopify/sarama"
)type Producer struct {producer sarama.SyncProducertopic    string
}func NewProducer(brokers []string, topic string) (*Producer, error) {config := sarama.NewConfig()config.Producer.RequiredAcks = sarama.WaitForAllconfig.Producer.Retry.Max = 5config.Producer.Return.Successes = trueproducer, err := sarama.NewSyncProducer(brokers, config)if err != nil {return nil, err}return &Producer{producer: producer,topic:    topic,}, nil
}func (p *Producer) PublishOrderEvent(ctx context.Context, event *OrderEvent) error {eventJSON, err := json.Marshal(event)if err != nil {return err}msg := &sarama.ProducerMessage{Topic: p.topic,Key:   sarama.StringEncoder(event.OrderID),Value: sarama.ByteEncoder(eventJSON),}_, _, err = p.producer.SendMessage(msg)return err
}// kafka/consumer.go
package kafkaimport ("context""encoding/json""github.com/Shopify/sarama""log"
)type Consumer struct {consumer sarama.ConsumerGrouptopic    stringhandler  func(event *OrderEvent) error
}func NewConsumer(brokers []string, groupID string, topic string, handler func(event *OrderEvent) error) (*Consumer, error) {config := sarama.NewConfig()config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRoundRobinconfig.Consumer.Offsets.Initial = sarama.OffsetOldestgroup, err := sarama.NewConsumerGroup(brokers, groupID, config)if err != nil {return nil, err}return &Consumer{consumer: group,topic:    topic,handler:  handler,}, nil
}func (c *Consumer) Start(ctx context.Context) error {topics := []string{c.topic}handler := &consumerGroupHandler{handler: c.handler}for {err := c.consumer.Consume(ctx, topics, handler)if err != nil {log.Printf("Error from consumer: %v", err)}if ctx.Err() != nil {return ctx.Err()}}
}type consumerGroupHandler struct {handler func(event *OrderEvent) error
}func (h *consumerGroupHandler) Setup(_ sarama.ConsumerGroupSession) error   { return nil }
func (h *consumerGroupHandler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil }
func (h *consumerGroupHandler) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {for message := range claim.Messages() {var event OrderEventif err := json.Unmarshal(message.Value, &event); err != nil {log.Printf("Error unmarshaling message: %v", err)continue}if err := h.handler(&event); err != nil {log.Printf("Error handling message: %v", err)continue}session.MarkMessage(message, "")}return nil
}// main.go 使用示例
func main() {ctx := context.Background()brokers := []string{"localhost:9092"}topic := "order_events"// 创建生产者producer, err := NewProducer(brokers, topic)if err != nil {log.Fatal(err)}// 创建消费者consumer, err := NewConsumer(brokers, "order-processor", topic, func(event *OrderEvent) error {log.Printf("Processing order event: %+v", event)// 处理订单事件的业务逻辑return nil})if err != nil {log.Fatal(err)}// 启动消费者go func() {if err := consumer.Start(ctx); err != nil {log.Printf("Consumer error: %v", err)}}()// 发布订单事件event := &OrderEvent{EventID:    "evt_123",EventType:  "order_created",OrderID:    "ord_123",UserID:     "usr_123",ProductID:  "prod_123",Quantity:   2,TotalPrice: 199.99,Timestamp:  time.Now().Unix(),}if err := producer.PublishOrderEvent(ctx, event); err != nil {log.Printf("Error publishing event: %v", err)}
}

6. API 网关设计

API网关是微服务架构中的重要组件,负责请求路由、认证授权、限流等功能。让我们看一个实现示例:

// gateway/main.go
package mainimport ("context""github.com/gin-gonic/gin""google.golang.org/grpc""log""net/http""time"
)type Gateway struct {router         *gin.EngineproductClient  pb.ProductServiceClientorderClient    pb.OrderServiceClientrateLimiter    *ratelimit.RateLimitercircuitBreaker *circuitbreaker.CircuitBreaker
}func NewGateway() *Gateway {router := gin.Default()// 初始化限流器rateLimiter := ratelimit.NewRateLimiter(100, 20) // 每秒100个请求,突发20个// 初始化熔断器circuitBreaker := circuitbreaker.NewCircuitBreaker("gateway")return &Gateway{router:         router,rateLimiter:    rateLimiter,circuitBreaker: circuitBreaker,}
}func (g *Gateway) InitRoutes() {// 中间件g.router.Use(g.authMiddleware())g.router.Use(g.rateLimitMiddleware())// API路由v1 := g.router.Group("/api/v1"){products := v1.Group("/products"){products.GET("", g.ListProducts)products.GET("/:id", g.GetProduct)products.POST("", g.CreateProduct)products.PUT("/:id", g.UpdateProduct)}orders := v1.Group("/orders"){orders.POST("", g.CreateOrder)orders.GET("/:id", g.GetOrder)}}
}// 认证中间件
func (g *Gateway) authMiddleware() gin.HandlerFunc {return func(c *gin.Context) {token := c.GetHeader("Authorization")if token == "" {c.JSON(http.StatusUnauthorized, gin.H{"error": "missing authorization header"})c.Abort()return}// 验证tokenclaims, err := validateToken(token)if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})c.Abort()return}c.Set("user_id", claims.UserID)c.Next()}
}// 限流中间件
func (g *Gateway) rateLimitMiddleware() gin.HandlerFunc {return func(c *gin.Context) {if !g.rateLimiter.Allow() {c.JSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})c.Abort()return}c.Next()}
}// API处理函数
func (g *Gateway) ListProducts(c *gin.Context) {result, err := g.circuitBreaker.Execute(func() (interface{}, error) {ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)defer cancel()req := &pb.ListProductsRequest{Page:     1,PageSize: 10,}return g.productClient.ListProducts(ctx, req)})if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}response := result.(*pb.ListProductsResponse)c.JSON(http.StatusOK, response)
}func main() {gateway := NewGateway()gateway.InitRoutes()if err := gateway.router.Run(":8080"); err != nil {log.Fatal(err)}
}

7. 监控和追踪

微服务架构中的监控和追踪也是非常重要的部分,我们来看看如何集成OpenTelemetry进行分布式追踪:

// monitoring/metrics.go
package monitoringimport ("github.com/prometheus/client_golang/prometheus""github.com/prometheus/client_golang/prometheus/promauto""sync"
)type MetricsCollector struct {requestCounter   *prometheus.CounterVecrequestDuration  *prometheus.HistogramVecactiveRequests   *prometheus.GaugeVecerrorCounter     *prometheus.CounterVecqueueLength      *prometheus.GaugeVeccpuUsage         *prometheus.GaugeVecmemoryUsage      *prometheus.GaugeVecmu               sync.RWMutex
}func NewMetricsCollector(serviceName string) *MetricsCollector {return &MetricsCollector{requestCounter: promauto.NewCounterVec(prometheus.CounterOpts{Name: "requests_total",Help: "Total number of requests processed",},[]string{"service", "method", "status"},),requestDuration: promauto.NewHistogramVec(prometheus.HistogramOpts{Name:    "request_duration_seconds",Help:    "Request duration in seconds",Buckets: prometheus.DefBuckets,},[]string{"service", "method"},),activeRequests: promauto.NewGaugeVec(prometheus.GaugeOpts{Name: "active_requests",Help: "Number of requests currently being processed",},[]string{"service"},),errorCounter: promauto.NewCounterVec(prometheus.CounterOpts{Name: "errors_total",Help: "Total number of errors",},[]string{"service", "type"},),queueLength: promauto.NewGaugeVec(prometheus.GaugeOpts{Name: "queue_length",Help: "Current length of the request queue",},[]string{"service", "queue_name"},),cpuUsage: promauto.NewGaugeVec(prometheus.GaugeOpts{Name: "cpu_usage_percent",Help: "Current CPU usage in percentage",},[]string{"service"},),memoryUsage: promauto.NewGaugeVec(prometheus.GaugeOpts{Name: "memory_usage_bytes",Help: "Current memory usage in bytes",},[]string{"service"},),}
}func (m *MetricsCollector) RecordRequest(service, method, status string) {m.requestCounter.WithLabelValues(service, method, status).Inc()
}func (m *MetricsCollector) RecordDuration(service, method string, duration float64) {m.requestDuration.WithLabelValues(service, method).Observe(duration)
}func (m *MetricsCollector) UpdateActiveRequests(service string, delta float64) {m.activeRequests.WithLabelValues(service).Add(delta)
}func (m *MetricsCollector) RecordError(service, errorType string) {m.errorCounter.WithLabelValues(service, errorType).Inc()
}func (m *MetricsCollector) UpdateQueueLength(service, queueName string, length float64) {m.queueLength.WithLabelValues(service, queueName).Set(length)
}func (m *MetricsCollector) UpdateCPUUsage(service string, usage float64) {m.cpuUsage.WithLabelValues(service).Set(usage)
}func (m *MetricsCollector) UpdateMemoryUsage(service string, usage float64) {m.memoryUsage.WithLabelValues(service).Set(usage)
}// tracing/tracer.go
package tracingimport ("context""fmt""go.opentelemetry.io/otel""go.opentelemetry.io/otel/attribute""go.opentelemetry.io/otel/exporters/jaeger""go.opentelemetry.io/otel/sdk/resource"tracesdk "go.opentelemetry.io/otel/sdk/trace"semconv "go.opentelemetry.io/otel/semconv/v1.7.0""go.opentelemetry.io/otel/trace"
)type Tracer struct {tracer     trace.TracerserviceName string
}func NewTracer(serviceName, jaegerEndpoint string) (*Tracer, error) {// 创建Jaeger导出器exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(jaegerEndpoint),))if err != nil {return nil, fmt.Errorf("failed to create jaeger exporter: %w", err)}// 创建资源res, err := resource.New(context.Background(),resource.WithAttributes(semconv.ServiceNameKey.String(serviceName),attribute.String("environment", "production"),),)if err != nil {return nil, fmt.Errorf("failed to create resource: %w", err)}// 创建TracerProvidertp := tracesdk.NewTracerProvider(tracesdk.WithBatcher(exporter),tracesdk.WithResource(res),tracesdk.WithSampler(tracesdk.AlwaysSample()),)// 设置全局TracerProviderotel.SetTracerProvider(tp)return &Tracer{tracer:      tp.Tracer(serviceName),serviceName: serviceName,}, nil
}func (t *Tracer) StartSpan(ctx context.Context, name string) (context.Context, trace.Span) {return t.tracer.Start(ctx, name)
}// 中间件 - gRPC拦截器
func (t *Tracer) UnaryServerInterceptor() grpc.UnaryServerInterceptor {return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {spanCtx, span := t.StartSpan(ctx, info.FullMethod)defer span.End()span.SetAttributes(attribute.String("service", t.serviceName),attribute.String("method", info.FullMethod),)return handler(spanCtx, req)}
}// health/health_checker.go
package healthimport ("context""sync""time"
)type HealthChecker struct {services    map[string]ServiceHealthmu          sync.RWMutexmetrics     *monitoring.MetricsCollector
}type ServiceHealth struct {Status    stringLastCheck time.TimeError     error
}func NewHealthChecker(metrics *monitoring.MetricsCollector) *HealthChecker {return &HealthChecker{services: make(map[string]ServiceHealth),metrics:  metrics,}
}func (h *HealthChecker) RegisterService(serviceName string) {h.mu.Lock()defer h.mu.Unlock()h.services[serviceName] = ServiceHealth{Status:    "UNKNOWN",LastCheck: time.Now(),}
}func (h *HealthChecker) UpdateStatus(serviceName, status string, err error) {h.mu.Lock()defer h.mu.Unlock()h.services[serviceName] = ServiceHealth{Status:    status,LastCheck: time.Now(),Error:     err,}if err != nil {h.metrics.RecordError(serviceName, "health_check")}
}func (h *HealthChecker) GetStatus(serviceName string) ServiceHealth {h.mu.RLock()defer h.mu.RUnlock()return h.services[serviceName]
}// 使用示例
func main() {// 初始化metrics collectormetrics := monitoring.NewMetricsCollector("product-service")// 初始化tracertracer, err := tracing.NewTracer("product-service", "http://jaeger:14268/api/traces")if err != nil {log.Fatal(err)}// 初始化health checkerhealthChecker := health.NewHealthChecker(metrics)healthChecker.RegisterService("product-service")// 创建gRPC服务器并添加拦截器server := grpc.NewServer(grpc.UnaryInterceptor(tracer.UnaryServerInterceptor()),)// 启动指标收集go func() {for {metrics.UpdateActiveRequests("product-service", float64(runtime.NumGoroutine()))var m runtime.MemStatsruntime.ReadMemStats(&m)metrics.UpdateMemoryUsage("product-service", float64(m.Alloc))time.Sleep(15 * time.Second)}}()// 启动健康检查go func() {for {// 执行健康检查err := checkDependencies()status := "UP"if err != nil {status = "DOWN"}healthChecker.UpdateStatus("product-service", status, err)time.Sleep(30 * time.Second)}}()// 启动HTTP服务器暴露指标http.Handle("/metrics", promhttp.Handler())go func() {if err := http.ListenAndServe(":9090", nil); err != nil {log.Printf("Metrics server error: %v", err)}}()// 启动gRPC服务器lis, err := net.Listen("tcp", ":50051")if err != nil {log.Fatalf("Failed to listen: %v", err)}log.Printf("Server listening at %v", lis.Addr())if err := server.Serve(lis); err != nil {log.Fatalf("Failed to serve: %v", err)}
}func checkDependencies() error {// 检查数据库连接if err := checkDatabase(); err != nil {return fmt.Errorf("database check failed: %w", err)}// 检查缓存服务if err := checkCache(); err != nil {return fmt.Errorf("cache check failed: %w", err)}// 检查消息队列if err := checkMessageQueue(); err != nil {return fmt.Errorf("message queue check failed: %w", err)}return nil
}

8. 服务配置管理

在微服务架构中,配置管理是一个重要的话题。我们来看看如何实现统一的配置管理:

// config/config.go
package configimport ("context""encoding/json""github.com/coreos/etcd/clientv3""time"
)type Config struct {Database struct {Host     string `json:"host"`Port     int    `json:"port"`User     string `json:"user"`Password string `json:"password"`DBName   string `json:"dbname"`} `json:"database"`Redis struct {Host     string `json:"host"`Port     int    `json:"port"`Password string `json:"password"`DB       int    `json:"db"`} `json:"redis"`Service struct {Port         int    `json:"port"`Environment string `json:"environment"`LogLevel    string `json:"log_level"`} `json:"service"`
}type ConfigManager struct {client  *clientv3.Clientprefix  stringconfig  *Configwatches map[string]clientv3.WatchChan
}func NewConfigManager(endpoints []string, prefix string) (*ConfigManager, error) {client, err := clientv3.New(clientv3.Config{Endpoints:   endpoints,DialTimeout: 5 * time.Second,})if err != nil {return nil, err}return &ConfigManager{client:  client,prefix:  prefix,config:  &Config{},watches: make(map[string]clientv3.WatchChan),}, nil
}func (cm *ConfigManager) LoadConfig(ctx context.Context) error {resp, err := cm.client.Get(ctx, cm.prefix, clientv3.WithPrefix())if err != nil {return err}for _, kv := range resp.Kvs {if err := json.Unmarshal(kv.Value, cm.config); err != nil {return err}}return nil
}func (cm *ConfigManager) WatchConfig(ctx context.Context, callback func(*Config)) {watchChan := cm.client.Watch(ctx, cm.prefix, clientv3.WithPrefix())go func() {for watchResp := range watchChan {for _, event := range watchResp.Events {switch event.Type {case clientv3.EventTypePut:if err := json.Unmarshal(event.Kv.Value, cm.config); err != nil {continue}callback(cm.config)}}}}()
}// 使用示例
func main() {ctx := context.Background()// 创建配置管理器cm, err := NewConfigManager([]string{"localhost:2379"}, "/config/product-service")if err != nil {log.Fatal(err)}// 加载初始配置if err := cm.LoadConfig(ctx); err != nil {log.Fatal(err)}// 监听配置变更cm.WatchConfig(ctx, func(config *Config) {log.Printf("Configuration updated: %+v", config)// 这里可以进行配置热更新的相关操作})
}

9. 服务优雅关闭

在微服务架构中,服务的优雅关闭是保证系统稳定性的重要环节。让我们看看如何实现:

// server/server.go
package serverimport ("context""log""net/http""os""os/signal""sync""syscall""time"
)type Server struct {httpServer     *http.ServergrpcServer     *grpc.ServermetricsServer  *http.Serverregistry       *registry.ServiceRegistryconnections    sync.WaitGroupshutdownTimeout time.Duration
}func NewServer(config *Config) *Server {return &Server{httpServer: &http.Server{Addr:    config.HTTPAddr,Handler: config.HTTPHandler,},grpcServer: config.GRPCServer,metricsServer: &http.Server{Addr:    config.MetricsAddr,Handler: config.MetricsHandler,},registry:        config.Registry,shutdownTimeout: 30 * time.Second,}
}func (s *Server) Start() error {// 启动HTTP服务go func() {log.Printf("Starting HTTP server on %s", s.httpServer.Addr)if err := s.httpServer.ListenAndServe(); err != http.ErrServerClosed {log.Printf("HTTP server error: %v", err)}}()// 启动gRPC服务go func() {log.Printf("Starting gRPC server")if err := s.grpcServer.Serve(lis); err != nil {log.Printf("gRPC server error: %v", err)}}()// 启动metrics服务go func() {log.Printf("Starting metrics server on %s", s.metricsServer.Addr)if err := s.metricsServer.ListenAndServe(); err != http.ErrServerClosed {log.Printf("Metrics server error: %v", err)}}()return nil
}func (s *Server) WaitForShutdown() {// 监听系统信号quit := make(chan os.Signal, 1)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)sig := <-quitlog.Printf("Received signal: %v", sig)ctx, cancel := context.WithTimeout(context.Background(), s.shutdownTimeout)defer cancel()// 从服务注册中心注销if err := s.registry.Deregister(ctx); err != nil {log.Printf("Failed to deregister service: %v", err)}// 优雅关闭HTTP服务if err := s.httpServer.Shutdown(ctx); err != nil {log.Printf("HTTP server shutdown error: %v", err)}// 优雅关闭gRPC服务s.grpcServer.GracefulStop()// 优雅关闭metrics服务if err := s.metricsServer.Shutdown(ctx); err != nil {log.Printf("Metrics server shutdown error: %v", err)}// 等待所有连接处理完成done := make(chan struct{})go func() {s.connections.Wait()close(done)}()select {case <-ctx.Done():log.Printf("Shutdown timeout: forcing exit")case <-done:log.Printf("All connections have been closed")}
}// 连接处理中间件
func (s *Server) ConnectionMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {s.connections.Add(1)defer s.connections.Done()next.ServeHTTP(w, r)})
}// 使用示例
func main() {config := &Config{HTTPAddr:       ":8080",MetricsAddr:    ":9090",HTTPHandler:    httpRouter,GRPCServer:    grpcServer,MetricsHandler: metricsHandler,Registry:      serviceRegistry,}server := NewServer(config)if err := server.Start(); err != nil {log.Fatal(err)}server.WaitForShutdown()
}

10. 服务安全

微服务架构中的安全性同样重要,让我们实现一个包含认证和授权的安全模块:

// security/jwt.go
package securityimport ("github.com/dgrijalva/jwt-go""time"
)type Claims struct {UserID    string   `json:"user_id"`Username  string   `json:"username"`Roles     []string `json:"roles"`jwt.StandardClaims
}type JWTManager struct {secretKey     []bytetokenDuration time.Duration
}func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager {return &JWTManager{secretKey:     []byte(secretKey),tokenDuration: tokenDuration,}
}func (m *JWTManager) GenerateToken(userID, username string, roles []string) (string, error) {claims := &Claims{UserID:   userID,Username: username,Roles:    roles,StandardClaims: jwt.StandardClaims{ExpiresAt: time.Now().Add(m.tokenDuration).Unix(),IssuedAt:  time.Now().Unix(),},}token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)return token.SignedString(m.secretKey)
}func (m *JWTManager) ValidateToken(accessToken string) (*Claims, error) {token, err := jwt.ParseWithClaims(accessToken,&Claims{},func(token *jwt.Token) (interface{}, error) {_, ok := token.Method.(*jwt.SigningMethodHMAC)if !ok {return nil, fmt.Errorf("unexpected token signing method")}return m.secretKey, nil},)if err != nil {return nil, fmt.Errorf("invalid token: %w", err)}claims, ok := token.Claims.(*Claims)if !ok {return nil, fmt.Errorf("invalid token claims")}return claims, nil
}// security/rbac.go
package securitytype Permission struct {Resource stringAction   string
}type Role struct {Name        stringPermissions map[Permission]bool
}type RBAC struct {roles map[string]*Role
}func NewRBAC() *RBAC {return &RBAC{roles: make(map[string]*Role),}
}func (r *RBAC) AddRole(roleName string) {if _, exists := r.roles[roleName]; !exists {r.roles[roleName] = &Role{Name:        roleName,Permissions: make(map[Permission]bool),}}
}func (r *RBAC) AddPermission(roleName string, resource string, action string) error {role, exists := r.roles[roleName]if !exists {return fmt.Errorf("role %s does not exist", roleName)}permission := Permission{Resource: resource,Action:   action,}role.Permissions[permission] = truereturn nil
}func (r *RBAC) CheckPermission(roles []string, resource string, action string) bool {permission := Permission{Resource: resource,Action:   action,}for _, roleName := range roles {if role, exists := r.roles[roleName]; exists {if allowed := role.Permissions[permission]; allowed {return true}}}return false
}// middleware/auth.go
package middlewareimport ("context""google.golang.org/grpc""google.golang.org/grpc/codes""google.golang.org/grpc/metadata""google.golang.org/grpc/status"
)type AuthInterceptor struct {jwtManager *security.JWTManagerrbac       *security.RBAC
}func NewAuthInterceptor(jwtManager *security.JWTManager, rbac *security.RBAC) *AuthInterceptor {return &AuthInterceptor{jwtManager: jwtManager,rbac:       rbac,}
}// Unary interceptor for gRPC servers
func (i *AuthInterceptor) Unary() grpc.UnaryServerInterceptor {return func(ctx context.Context,req interface{},info *grpc.UnaryServerInfo,handler grpc.UnaryHandler,) (interface{}, error) {if !requiresAuth(info.FullMethod) {return handler(ctx, req)}claims, err := i.authorize(ctx)if err != nil {return nil, err}resource, action := extractResourceAndAction(info.FullMethod)if !i.rbac.CheckPermission(claims.Roles, resource, action) {return nil, status.Errorf(codes.PermissionDenied, "no permission to access this RPC")}return handler(ctx, req)}
}// Stream interceptor for gRPC servers
func (i *AuthInterceptor) Stream() grpc.StreamServerInterceptor {return func(srv interface{},stream grpc.ServerStream,info *grpc.StreamServerInfo,handler grpc.StreamHandler,) error {if !requiresAuth(info.FullMethod) {return handler(srv, stream)}claims, err := i.authorize(stream.Context())if err != nil {return err}resource, action := extractResourceAndAction(info.FullMethod)if !i.rbac.CheckPermission(claims.Roles, resource, action) {return status.Errorf(codes.PermissionDenied, "no permission to access this RPC")}return handler(srv, stream)}
}func (i *AuthInterceptor) authorize(ctx context.Context) (*security.Claims, error) {md, ok := metadata.FromIncomingContext(ctx)if !ok {return nil, status.Errorf(codes.Unauthenticated, "metadata is not provided")}values := md["authorization"]if len(values) == 0 {return nil, status.Errorf(codes.Unauthenticated, "authorization token is not provided")}accessToken := values[0]claims, err := i.jwtManager.ValidateToken(accessToken)if err != nil {return nil, status.Errorf(codes.Unauthenticated, "access token is invalid: %v", err)}return claims, nil
}func requiresAuth(method string) bool {// 配置不需要认证的方法noAuthMethods := map[string]bool{"/user.UserService/Login":    true,"/user.UserService/Register": true,"/health.HealthCheck/Check":  true,}return !noAuthMethods[method]
}func extractResourceAndAction(method string) (string, string) {// 从方法名中提取资源和动作// 例如: "/product.ProductService/CreateProduct" -> resource: "product", action: "create"parts := strings.Split(method, "/")if len(parts) != 3 {return "", ""}serviceParts := strings.Split(parts[1], ".")if len(serviceParts) != 2 {return "", ""}resource := strings.ToLower(serviceParts[0])action := strings.ToLower(strings.TrimPrefix(parts[2], serviceParts[1]))return resource, action
}// 使用示例
func main() {// 初始化JWT管理器jwtManager := security.NewJWTManager("your-secret-key", 24*time.Hour)// 初始化RBACrbac := security.NewRBAC()// 添加角色和权限rbac.AddRole("admin")rbac.AddRole("user")// 为管理员添加权限rbac.AddPermission("admin", "product", "create")rbac.AddPermission("admin", "product", "update")rbac.AddPermission("admin", "product", "delete")rbac.AddPermission("admin", "order", "view")// 为普通用户添加权限rbac.AddPermission("user", "product", "view")rbac.AddPermission("user", "order", "create")// 创建认证拦截器authInterceptor := middleware.NewAuthInterceptor(jwtManager, rbac)// 创建gRPC服务器,并添加拦截器server := grpc.NewServer(grpc.UnaryInterceptor(authInterceptor.Unary()),grpc.StreamInterceptor(authInterceptor.Stream()),)// 注册服务pb.RegisterProductServiceServer(server, &ProductService{})pb.RegisterOrderServiceServer(server, &OrderService{})// 启动服务器lis, err := net.Listen("tcp", ":50051")if err != nil {log.Fatalf("failed to listen: %v", err)}log.Printf("Server listening at %v", lis.Addr())if err := server.Serve(lis); err != nil {log.Fatalf("failed to serve: %v", err)}
}

让我们继续完成微服务架构设计的最后几个重要部分:

11. 分布式事务处理

在微服务架构中,分布式事务的处理是一个重要的挑战,我们来看看如何使用Saga模式处理:

// saga/saga.go
package sagaimport ("context""fmt""sync"
)type TransactionStep struct {Execute   func(ctx context.Context) errorCompensate func(ctx context.Context) error
}type Saga struct {steps    []TransactionStepmutex    sync.Mutexexecuted []bool
}func NewSaga() *Saga {return &Saga{steps:    make([]TransactionStep, 0),executed: make([]bool, 0),}
}func (s *Saga) AddStep(step TransactionStep) {s.mutex.Lock()defer s.mutex.Unlock()s.steps = append(s.steps, step)s.executed = append(s.executed, false)
}func (s *Saga) Execute(ctx context.Context) error {for i, step := range s.steps {if err := step.Execute(ctx); err != nil {// 执行补偿操作s.compensate(ctx, i)return fmt.Errorf("step %d failed: %w", i, err)}s.executed[i] = true}return nil
}func (s *Saga) compensate(ctx context.Context, failedStep int) {// 从失败步骤开始,反向执行补偿操作for i := failedStep; i >= 0; i-- {if s.executed[i] {if err := s.steps[i].Compensate(ctx); err != nil {// 记录补偿操作失败fmt.Printf("compensation failed for step %d: %v\n", i, err)}}}
}// order/order.go - 订单服务中使用Saga的示例
type OrderService struct {productClient  pb.ProductServiceClientinventoryClient pb.InventoryServiceClientpaymentClient   pb.PaymentServiceClient
}func (s *OrderService) CreateOrder(ctx context.Context, order *pb.Order) error {saga := NewSaga()// 检查商品库存saga.AddStep(TransactionStep{Execute: func(ctx context.Context) error {req := &pb.CheckInventoryRequest{ProductId: order.ProductId,Quantity: order.Quantity,}_, err := s.inventoryClient.CheckInventory(ctx, req)return err},Compensate: func(ctx context.Context) error {// 库存检查不需要补偿return nil},})// 扣减库存saga.AddStep(TransactionStep{Execute: func(ctx context.Context) error {req := &pb.DeductInventoryRequest{ProductId: order.ProductId,Quantity: order.Quantity,}_, err := s.inventoryClient.DeductInventory(ctx, req)return err},Compensate: func(ctx context.Context) error {req := &pb.RestoreInventoryRequest{ProductId: order.ProductId,Quantity: order.Quantity,}_, err := s.inventoryClient.RestoreInventory(ctx, req)return err},})// 处理支付saga.AddStep(TransactionStep{Execute: func(ctx context.Context) error {req := &pb.ProcessPaymentRequest{OrderId: order.Id,Amount: order.TotalAmount,}_, err := s.paymentClient.ProcessPayment(ctx, req)return err},Compensate: func(ctx context.Context) error {req := &pb.RefundPaymentRequest{OrderId: order.Id,Amount: order.TotalAmount,}_, err := s.paymentClient.RefundPayment(ctx, req)return err},})return saga.Execute(ctx)
}

12. 日志聚合与分析

在微服务架构中,统一的日志收集和分析系统非常重要:

// logger/logger.go
package loggerimport ("context""encoding/json""github.com/olivere/elastic/v7""go.uber.org/zap""go.uber.org/zap/zapcore""time"
)type LogEntry struct {Timestamp   time.Time              `json:"timestamp"`Level       string                 `json:"level"`Service     string                 `json:"service"`TraceID     string                 `json:"trace_id"`SpanID      string                 `json:"span_id"`Message     string                 `json:"message"`Fields      map[string]interface{} `json:"fields,omitempty"`
}type Logger struct {zap        *zap.Loggerelastic    *elastic.ClientserviceName string
}func NewLogger(serviceName string, elasticURL string) (*Logger, error) {// 配置zap loggerconfig := zap.NewProductionConfig()config.EncoderConfig.TimeKey = "timestamp"config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderzapLogger, err := config.Build()if err != nil {return nil, err}// 配置Elasticsearch客户端client, err := elastic.NewClient(elastic.SetURL(elasticURL),elastic.SetSniff(false),)if err != nil {return nil, err}return &Logger{zap:         zapLogger,elastic:     client,serviceName: serviceName,}, nil
}func (l *Logger) Info(ctx context.Context, msg string, fields ...zap.Field) {l.log(ctx, "info", msg, fields...)
}func (l *Logger) Error(ctx context.Context, msg string, fields ...zap.Field) {l.log(ctx, "error", msg, fields...)
}func (l *Logger) log(ctx context.Context, level, msg string, fields ...zap.Field) {// 获取追踪信息span := opentracing.SpanFromContext(ctx)var traceID, spanID stringif span != nil {spanContext := span.Context().(jaeger.SpanContext)traceID = spanContext.TraceID().String()spanID = spanContext.SpanID().String()}// 创建日志条目entry := LogEntry{Timestamp:   time.Now(),Level:       level,Service:     l.serviceName,TraceID:     traceID,SpanID:      spanID,Message:     msg,Fields:      make(map[string]interface{}),}// 添加额外字段for _, field := range fields {entry.Fields[field.Key] = field.Interface}// 本地日志if level == "error" {l.zap.Error(msg, fields...)} else {l.zap.Info(msg, fields...)}// 异步发送到Elasticsearchgo func() {_, err := l.elastic.Index().Index(fmt.Sprintf("logs-%s", time.Now().Format("2006-01-02"))).Type("_doc").BodyJson(entry).Do(context.Background())if err != nil {l.zap.Error("Failed to send log to Elasticsearch",zap.Error(err),zap.Any("entry", entry),)}}()
}// 日志查询接口
func (l *Logger) QueryLogs(ctx context.Context, params QueryParams) ([]LogEntry, error) {query := elastic.NewBoolQuery()if params.Service != "" {query = query.Must(elastic.NewTermQuery("service", params.Service))}if params.Level != "" {query = query.Must(elastic.NewTermQuery("level", params.Level))}if params.TraceID != "" {query = query.Must(elastic.NewTermQuery("trace_id", params.TraceID))}if !params.StartTime.IsZero() {query = query.Must(elastic.NewRangeQuery("timestamp").Gte(params.StartTime))}if !params.EndTime.IsZero() {query = query.Must(elastic.NewRangeQuery("timestamp").Lte(params.EndTime))}result, err := l.elastic.Search().Index(fmt.Sprintf("logs-%s", time.Now().Format("2006-01-02"))).Query(query).Sort("timestamp", false).Size(params.Limit).From(params.Offset).Do(ctx)if err != nil {return nil, err}var logs []LogEntryfor _, hit := range result.Hits.Hits {var entry LogEntryif err := json.Unmarshal(hit.Source, &entry); err != nil {return nil, err}logs = append(logs, entry)}return logs, nil
}

13. 总结与最佳实践

在本节课中,我们详细探讨了微服务架构设计的核心内容:

  1. 服务拆分

    • 采用DDD方法
    • 明确业务边界
    • 保持适当粒度
  2. 接口设计

    • 使用gRPC和RESTful API
    • 版本控制
    • 接口文档
  3. 服务治理

    • 服务注册与发现
    • 熔断器模式
    • 限流措施
  4. 部署策略

    • 容器化部署
    • Kubernetes编排
    • 灰度发布

14. 实践检查清单

在这里插入图片描述
以上就是微服务架构设计的核心内容。在实际项目中,需要根据具体业务场景和技术栈选择合适的实现方案。重要的是要始终遵循微服务的设计原则,保持服务的独立性和可维护性。同时,要重视监控、日志、追踪等基础设施的建设,这些都是保证微服务架构稳定运行的关键因素。


怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

相关文章:

40分钟学 Go 语言高并发:微服务架构设计

微服务架构设计 一、知识要点总览 核心模块重要性掌握程度服务拆分高深入理解DDD领域驱动设计接口设计高掌握RESTful和gRPC设计规范服务治理高理解服务注册、发现、熔断、限流等机制部署策略中掌握DockerK8s容器化部署方案 二、详细内容讲解 1. 服务拆分 服务拆分是微服务…...

【蓝桥杯最新板】蓝桥杯嵌入式液晶上实现电子时钟

这几年蓝桥杯比赛比较适合学生技能学习&#xff0c;考虑板子功能&#xff0c;提出完成的任务。 要求在液晶完成如下图效果&#xff1a; 主要是实现液晶显示时钟和数字时钟&#xff0c;具体样式可以依据实际情况微调。 实现过程&#xff1a; 1.需要画圆&#xff08;外圆、内圆…...

Micropython 扩展C模块<HelloWorld>

开发环境 MCU&#xff1a;Pico1&#xff08;无wifi版&#xff09;使用固件&#xff1a;自编译版本开发环境&#xff1a;MacBook Pro Sonoma 14.5开发工具&#xff1a;Thonny 4.1.6开发语言&#xff1a;MicroPython 1.24 执行示例 在github上获取micropython&#xff0c;我使…...

数据分析学习Day1-使用matplotlib生成2小时每分钟的气温可视化分析图

注意&#xff1a;需要提前下载matplotlib包 pip install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple import matplotlib.pyplot as plt import random# 数据准备 x list(range(121)) # 使用 list() 转换为列表 y [random.randint(15,28) for i in range(121)]…...

域渗透-域内密码喷洒

域内密码喷洒 密码喷洒配合用户枚举使用&#xff0c;先进行枚举再进行喷洒 为什么不爆破&#xff0c;而是喷洒&#xff1a; 规避账户锁定策略 学习怎么设置密码锁定策略&#xff1a; 在工具找到组策略管理&#xff0c;在域下的Default Domain右键点击编辑 在计算机配置>…...

5G Multi-TRP R16~R18演进历程

提升小区边缘用户的性能&#xff0c;在覆盖范围内提供更为均衡的服务质量&#xff0c;NR中引入了多TRP协作传输的方案。多TRP协作传输通过多个TRP之间进行非相干联合传输&#xff08;Non Coherent-Joint Transmission&#xff0c;NC-JT&#xff09;、重复传输&#xff0f;接收或…...

Android 屏幕采集并编码为H.264

前言 我们前面基于摄像机的图像采集以及编解码已经完成了&#xff0c;那么接下来计划后面的三篇博文分别实现Android屏幕采集实现并进行H.264编解码、MIC音频采集并编码为AAC以及AAC解码播放&#xff0c;希冀可以通过这六篇博文能够对Android上面的音视频编解码有一个初步的学…...

xuggle操作视频

文章目录 xuggle操作视频 xuggle操作视频 有个需求是要读取视频的宽高&#xff0c;找到了Xuggle和FFmpeg两种方式&#xff0c;FFmpeg很强大&#xff0c;但是我并不需要那些功能&#xff0c;所以使用了轻量一点的Xuggle 引入依赖 <dependency><groupId>xuggle<…...

Python|Pyppeteer实现自动获取eBay商品数据【进阶版】(27)

前言 本文是该专栏的第27篇,结合优质项目案例持续分享Pyppeteer的干货知识,记得关注。 在本专栏的上一篇文章中,笔者有详细介绍基于python中的pyppeteer,“根据目标关键词,来实现自动获取eBay的商品数据”。而本文,笔者将在上一篇文章《Python|Pyppeteer实现自动获取eBa…...

Android Studio新版本的一个资源id无法找到的bug解决

Android Studio新版本的一个资源id无法找到的bug解决 文章目录 Android Studio新版本的一个资源id无法找到的bug解决一、前言二、Android Studio的无法获取到资源id的bug1、一段简单的Java代码1、错误现象2、错误解决方法 三、其他1、小结2、gradle.properties文件 其他相关属性…...

CAPL如何设置或修改CANoe TCP/IP协议栈的底层配置

在CANoe中创建网络节点作为以太网主机时,可以给其配置独立的TCP/IP Stack。 配置的协议栈有一些底层配置参数可以在界面上设置或修改,比如: MTU上图中MTU显示500只是图形界面显示错误,正确值是1500。 TCP延迟确认这些参数也可以通过CAPL动态配置,甚至CAPL还可以配置很多界…...

【git】--- 通过 git 和 gitolite 管理单仓库的 SDK

在编程的艺术世界里,代码和灵感需要寻找到最佳的交融点,才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里,我们将共同追寻这种完美结合,为未来的世界留下属于我们的独特印记。【git】--- 通过 git 和 gitolite 管理单仓库的 SDK 开发环境一、安装配置 gitolite二…...

Avalonia实战实例一:使用Prism创建项目,并创建窗口

文章目录 一、安装Avalonia的项目模板二、安装Prism框架三、简单更改App.axaml为Prism基类四、创建窗口一、安装Avalonia的项目模板 这里安装的是Avalonia 11.2.1,.Net 6.0 安装完成,创建Avalonia项目。 二、安装Prism框架 打开Nuget,搜索Prism: 不要安装Prism.Core: …...

单元测试,集成测试,系统测试的区别是什么?

实际的测试工作当中&#xff0c;我们会从不同的角度对软件测试的活动进行分类&#xff0c;题主说的“单元测试&#xff0c;集成测试&#xff0c;系统测试”&#xff0c;是按照开发阶段进行测试活动的划分。这种划分完整的分类&#xff0c;其实是分为四种 “单元测试&#xff0c…...

Composer在安装的过程中经常找不到刚更新的包

明明有v2.1.0版本&#xff0c;安装就是找不到这个版本的包。 1. Composer 官方网址&#xff1a;https://getcomposer.org 中文网站&#xff1a;https://www.phpcomposer.com 官方文档&#xff1a;https://docs.phpcomposer.com 2. Packagist Packagist 是 Composer的组件仓库…...

ue5 motion matching

ue5.5 gameanimationsample 先看动画蓝图 核心两个node 第一个是根据数据选择当前的pose 第二个是缓存一段历史记录&#xff0c;为第一个node选择的时候提供数据。 在animinstance的update方法中 每帧都更新这个函数&#xff0c;每帧更新trajectory的数据 看看第一个node的…...

ThinkPHP知识库文档系统源码

知识库文档系统 一款基于ThinkPHP开发的知识库文档系统&#xff0c;可用于企业工作流程的文档管理&#xff0c;结构化记录沉淀高价值信息&#xff0c;形成完整的知识体系&#xff0c;能够轻松提升知识的流转和传播效率&#xff0c;更好地成就组织和个人。为部门、团队或项目搭…...

如何将自己的PHP类库发布到composer仓库

将自己的 PHP 类库发布到 Composer 仓库&#xff0c;需要经过一系列的准备和操作步骤&#xff0c;以下是详细说明&#xff1a; 准备工作 创建类库项目&#xff1a;确保你的 PHP 类库项目具有清晰的目录结构&#xff0c;遵循 PSR-4 等 PHP 编码规范。通常&#xff0c;类文件应…...

vue监听点击自己和点击其他元素;el-popover通过visible控制显隐;点击其他隐藏el-popover

场景&#xff1a; 一般点击元素自己&#xff0c;显示弹框&#xff1b;点击页面其他元素都是关闭 特别是处理el-popover通过visible控制显隐的时候。点击其他隐藏el-popover <el-popover :visible <template><div><div style"border: 1px solid #ccc; p…...

Strawberry Fields:探索学习量子光学编程的奇妙世界

一、Strawberry Fields 简介 Strawberry Fields 是由加拿大量子计算公司Xanadu开发的全栈 Python 库&#xff0c;在量子计算领域中占据着重要的地位。它为设计、模拟和优化连续变量&#xff08;CV&#xff09;量子光学电路提供强大工具&#xff0c;Strawberry Fields 的强大之…...

【初阶数据结构与算法】初阶数据结构总结之顺序表、单链表、双链表、栈、队列、二叉树顺序结构堆、二叉树链式结构(附源码)

文章目录 一、顺序表二、单链表三、双链表四、栈(先进后出)五、队列六、二叉树链式结构---堆七、二叉树链式结构接口实现 在之前我们学习了大部分初阶数据结构&#xff0c;我们现在从特点、优缺点以及应用场景大致总结一下&#xff0c;放出源码&#xff0c;如果想要看具体分析请…...

209. 长度最小的子数组 C++

文章目录 一、题目链接二、参考代码 一、题目链接 链接: 209. 长度最小的子数组 二、参考代码 暴力思路&#xff1a;两个for循环&#xff0c;找符合条件的子序列&#xff0c;复杂度&#xff08;O&#xff08;n^2&#xff09;&#xff09; int minSubArrayLen(const vector&l…...

基于python的一个简单的压力测试(DDoS)脚本

DDoS测试脚本 声明&#xff1a;本文所涉及代码仅供学习使用&#xff0c;任何人利用此造成的一切后果与本人无关 源码 import requests import threading# 目标URL target_url "http://47.121.xxx.xxx/"# 发送请求的函数 def send_request():while True:try:respo…...

异步操作、Promise和axios

1.Javascript是单线程的 什么是进程&#xff0c;什么是线程&#xff1f; 进程&#xff1a;进程是操作系统分配资源和调度的基本单位。它是一个程序的实例&#xff0c;包含了运行程序所需的代码和数据以及其它资源。 线程&#xff1a;线程是进程中的实际运行单位&#xff0c;也是…...

微信小程序开发简易教程

微信小程序文件结构详解 1. 项目配置文件 project.config.json 项目的配置文件包含项目名称、appid、编译选项等配置示例&#xff1a; {"description": "项目配置文件","packOptions": {"ignore": []},"setting": {&quo…...

基于 Java 实现的环形数组队列详解

1. 环形数组队列简介 队列&#xff08;Queue&#xff09;是一种常用的线性数据结构&#xff0c;具有先进先出&#xff08;FIFO&#xff09;的特点。在传统的线性队列中&#xff0c;随着出队操作&#xff0c;队列前端会出现空闲空间&#xff0c;但这些空间无法重复使用&#xf…...

opencv函数

1、二值化图 二值化图&#xff1a;就是将图像中的像素改成只有两种值&#xff0c;其操作的图像必须是灰度图。 2.1、阈值法(THRESH_BINARY) 通过设置一个阈值&#xff0c;将灰度图中的每一个像素值与该阈值进行比较&#xff0c;小于等于阈值的像素就被设置为0&#xff08;黑&…...

fastadmin集成kindeditor编辑器解决段后距问题--全网唯一

背景 由于项目的需求使用fastadmin推荐的编辑器kindeditor&#xff0c;使用过程中发现没有段后距这个bug。查询搜索了所有的网上来源&#xff0c;都没有解决方案。鉴宇客户非常需要该功能&#xff0c;奋战几天写端代码实现了该功能。 插件实现 KindEditor.plugin(paragra…...

【Mybatis】Mybatis 魔法世界探秘:从配置起航,开启数据持久化的奇幻入门之旅

目录 1.JDBC回顾 1.1JDBC编程 2.Mybatis使用 2.1什么是Mybatis 2.2Mybatis环境配置 1.引入依赖 2.lombok的操作 2.3Mybatis编程 1.数据库创建 2.创建实体类 3.配置数据库 4.Mapper持久层编写 5.单元测试 2.4常见的问题日志 1.密码错误 2.SQL语句错误 3.数据库…...

uni-app在image上绘制点位并回显

在 Uni-app 中绘制多边形可以通过使用 Canvas API 来实现。Uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;同时支持编译为 H5、小程序等多个平台。由于 Canvas 是 H5 和小程序中都支持的 API&#xff0c;所以通过 Canvas 绘制多边形是一个比较通用的方法。 1.…...

top命令和系统负载

1 top中的字段说明 top是一个实时系统监视工具&#xff0c;可以动态展现出 CPU 使用率、内存使用情况、进程状态等信息&#xff0c;注意这些显示的文本不能直接使用 > 追加到文件中。 [rootvv~]# top -bn 1 | head top - 20:08:28 up 138 days, 10:29, 4 users, load av…...

算法之要求对任意的i,j,k三个位置,如果i < j < k,都有arr[i] + arr[k] != arr[j],返回构造出的arr。

目录 1. 题目2. 解释3. 思路4. 代码 Code06_MakeNo5. 总结 1. 题目 给定一个正整数M&#xff0c;请构造出一个长度为M的数组arr&#xff0c;要求对任意的i&#xff0c;j&#xff0c;k三个位置&#xff0c;如果i < j < k&#xff0c;都有arr[i] arr[k] ! arr[j]返回构造…...

Y3编辑器文档4:触发器

文章目录 一、触发器简介1.1 触发器界面1.2 ECA语句编辑及快捷键1.3 参数设置1.4 变量设置1.5 实体触发器1.6 函数库与触发器复用 二、触发器的多层结构2.1 子触发器&#xff08;在游戏内对新的事件进行注册&#xff09;2.2 触发器变量作用域2.3 复合条件2.4 循环2.5 计时器2.6…...

Ubuntu中安装配置交叉编译工具并进行测试

01-下载获取交叉编译工具的源码 按照博文 https://blog.csdn.net/wenhao_ir/article/details/144325141的方法&#xff0c;把imx6ull的BSP下载好后&#xff0c;其中就有交叉编译工具。 当然&#xff0c;为了将来使用方便&#xff0c;我已经把它压缩并传到了百度网盘&#xff…...

HCIA笔记7--OSPF协议入门

文章目录 0. 路由分类1. OSPF介绍1.1 概念1.2 报文类型 2. 邻接关系的建立2.1 邻居关系的建立2.2 邻接关系的形成2.3 ospf状态机 3. DR与BDR3.1 为什么要有DR和BDR&#xff1f;3.2 DR和BDR的选举原则 4. ospf的配置4.1 内部优先级 5. 问题5.1 三层环路如何解决&#xff1f; Ref…...

文件系统--底层架构(图文详解)

一、文件系统的底层存储与寻址 当我们谈到文件系统的底层结构时&#xff0c;最关键的问题是&#xff1a;文件的数据与元数据&#xff08;属性&#xff09;如何存储在磁盘上&#xff0c;以及系统是如何定位这些数据的&#xff1f;在谈及文件系统之前&#xff0c;我们要先对储存…...

温州医院儿童自闭症康复中心:为孩子打开光明未来

在自闭症这一神秘而复杂的神经发育障碍面前&#xff0c;无数家庭曾陷入迷茫与无助。然而&#xff0c;在中国的大地上&#xff0c;有两座灯塔般的存在&#xff0c;它们分别为温州医院儿童自闭症康复中心和广州星贝育园自闭症儿童寄宿制学校&#xff0c;它们用专业的技术和无尽的…...

Tr0ll: 1 Vulnhub靶机渗透笔记

Tr0ll: 1 本博客提供的所有信息仅供学习和研究目的&#xff0c;旨在提高读者的网络安全意识和技术能力。请在合法合规的前提下使用本文中提供的任何技术、方法或工具。如果您选择使用本博客中的任何信息进行非法活动&#xff0c;您将独自承担全部法律责任。本博客明确表示不支…...

网络通信技术

网络通信技术 IP路由基础 什么是路由 路由是指导报文转发的路径信息,通过路由可以确认转发IP报文的路径。路由设备是依据路由转发报文到目的网段的网络设备,最常见的路由设备:路由器。路由设备维护着一张路由表,保存着路由信息。路由的功能 路径选择数据转发、数据过滤维…...

十一、容器化 vs 虚拟化-Docker 使用

文章目录 前言一、Docker Hello World二、Docker 容器使用三、Docker 镜像使用四、Docker 容器连接五、Docker 仓库管理六、Docker Dockerfile七、Docker Compose八、Docker Machine九、Swarm 集群管理 前言 Docker 使用‌ Docker 容器使用、镜像使用、容器连接、仓库管理、Do…...

npm error Error: Command failed: F:\360Downloads\Software\nodejs\node.exe

前言&#xff1a; 电脑环境&#xff1a;win7 node版本&#xff1a;18.20.0 npm版本&#xff1a;10.9.2 情景再现&#xff1a;电脑上是存在的vuevite的项目且可以正常运行。想着摸鱼的时间复习一下ts语法&#xff0c;所以想创建一个demo。按照 开始 | Vite 官方中文文档 官网创建…...

html中,实现通过拖拽调整图像尺寸

<!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>html中拖拽修改图像尺寸</title> <styl…...

sqlmap详解

一.sqlmap -u URL --forms sqlmap -u http://192.168.11.136:1337//978345210/index.php --forms 针对特定的 URL 进行 SQL 注入测试&#xff0c;特别是针对表单&#xff08;form&#xff09;的 POST 注入 forms&#xff1a;这个参数告诉 sqlmap 解析并测试目标 URL 中的表单…...

浏览器插件开发实战

浏览器插件开发实战 [1] 入门DEMO一、创建项目二、创建manifest.json三、加载插件四、配置 service-worker.js五、以书签管理器插件为例manifest.jsonpopup.htmlpopup.js查看效果 [2] Vue项目改造成插件一、复习Vue项目的结构二、删除、添加个别文件三、重写build [3] 高级开发…...

【特殊子序列 DP】力扣552. 学生出勤记录 II

可以用字符串表示一个学生的出勤记录&#xff0c;其中的每个字符用来标记当天的出勤情况&#xff08;缺勤、迟到、到场&#xff09;。记录中只含下面三种字符&#xff1a; ‘A’&#xff1a;Absent&#xff0c;缺勤 ‘L’&#xff1a;Late&#xff0c;迟到 ‘P’&#xff1a;Pr…...

C/C++流星雨

系列文章 序号直达链接1C/C爱心代码2C/C跳动的爱心3C/C李峋同款跳动的爱心代码4C/C满屏飘字表白代码5C/C大雪纷飞代码6C/C烟花代码7C/C黑客帝国同款字母雨8C/C樱花树代码9C/C奥特曼代码10C/C精美圣诞树11C/C俄罗斯方块12C/C贪吃蛇13C/C孤单又灿烂的神-鬼怪14C/C闪烁的爱心15C/C…...

Docker 安装 Jenkins:2.346.3

准备&#xff1a;已安装Docker&#xff0c;已配置服务器安全组规则 1581 1、拉取镜像 [rootTseng ~]# docker pull jenkins/jenkins:2.346.3 2.346.3: Pulling from jenkins/jenkins 001c52e26ad5: Pull complete 6b8dd635df38: Pull complete 2ba4c74fd680: Pull complet…...

枫清科技高雪峰:从数据到知识,重塑产业智能化的核心驱动力

2024 年 12 月 5 日&#xff0c;由智东西主办的“2024 中国生成式 AI 大会”在上海盛大开幕&#xff0c;汇聚了全球 AI 领域的顶尖专家、行业领袖与技术创新者。枫清科技&#xff08;Fabarta&#xff09;创始人兼 CEO 高雪峰应邀出席&#xff0c;并在大会上发表主题演讲&#x…...

【过滤器】.NET开源 ORM 框架 SqlSugar 系列

目录 0、 过滤器介绍 1、表过滤器 &#xff08;推荐&#xff09; 1.1 手动添加过滤器 1.2 禁用、清空、备份和还原 1.3 联表查询设置 1.4 动态添加 2、修改和删除用过滤器 2.1 局部设置 2.2 全局设置 &#xff08;5.1.4.62&#xff09; 3、子查询用过滤器 4、联表过滤…...

在 Ansys Q3D 中求解直流和交流电感

提取电缆的电感对于确保电气和电子系统的性能和可靠性至关重要。本篇博客文章将介绍使用 Ansys Q3D 求解直流和交流电感的过程。 概述 在这个例子中&#xff0c;我们将考虑一个由两组电缆组成的简单几何&#xff1a;正极和负极&#xff0c;如下所示&#xff1a; 可以使用“自…...