github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/lokifrontend/frontend/v2/frontend.go (about) 1 package v2 2 3 import ( 4 "context" 5 "flag" 6 "fmt" 7 "math/rand" 8 "net/http" 9 "sync" 10 "time" 11 12 "github.com/go-kit/log" 13 "github.com/go-kit/log/level" 14 "github.com/grafana/dskit/flagext" 15 "github.com/grafana/dskit/grpcclient" 16 "github.com/grafana/dskit/netutil" 17 "github.com/grafana/dskit/ring" 18 "github.com/grafana/dskit/services" 19 "github.com/opentracing/opentracing-go" 20 "github.com/pkg/errors" 21 "github.com/prometheus/client_golang/prometheus" 22 "github.com/prometheus/client_golang/prometheus/promauto" 23 "github.com/weaveworks/common/httpgrpc" 24 "go.uber.org/atomic" 25 26 "github.com/grafana/dskit/tenant" 27 28 "github.com/grafana/loki/pkg/lokifrontend/frontend/v2/frontendv2pb" 29 "github.com/grafana/loki/pkg/querier/stats" 30 lokigrpc "github.com/grafana/loki/pkg/util/httpgrpc" 31 util_log "github.com/grafana/loki/pkg/util/log" 32 ) 33 34 // Config for a Frontend. 35 type Config struct { 36 SchedulerAddress string `yaml:"scheduler_address"` 37 DNSLookupPeriod time.Duration `yaml:"scheduler_dns_lookup_period"` 38 WorkerConcurrency int `yaml:"scheduler_worker_concurrency"` 39 GRPCClientConfig grpcclient.Config `yaml:"grpc_client_config"` 40 41 // Used to find local IP address, that is sent to scheduler and querier-worker. 42 InfNames []string `yaml:"instance_interface_names"` 43 44 // If set, address is not computed from interfaces. 45 Addr string `yaml:"address" doc:"hidden"` 46 Port int `doc:"hidden"` 47 } 48 49 func (cfg *Config) RegisterFlags(f *flag.FlagSet) { 50 f.StringVar(&cfg.SchedulerAddress, "frontend.scheduler-address", "", "DNS hostname used for finding query-schedulers.") 51 f.DurationVar(&cfg.DNSLookupPeriod, "frontend.scheduler-dns-lookup-period", 10*time.Second, "How often to resolve the scheduler-address, in order to look for new query-scheduler instances. Also used to determine how often to poll the scheduler-ring for addresses if the scheduler-ring is configured.") 52 f.IntVar(&cfg.WorkerConcurrency, "frontend.scheduler-worker-concurrency", 5, "Number of concurrent workers forwarding queries to single query-scheduler.") 53 54 cfg.InfNames = netutil.PrivateNetworkInterfacesWithFallback([]string{"eth0", "en0"}, util_log.Logger) 55 f.Var((*flagext.StringSlice)(&cfg.InfNames), "frontend.instance-interface-names", "Name of network interface to read address from. This address is sent to query-scheduler and querier, which uses it to send the query response back to query-frontend.") 56 f.StringVar(&cfg.Addr, "frontend.instance-addr", "", "IP address to advertise to querier (via scheduler) (resolved via interfaces by default).") 57 f.IntVar(&cfg.Port, "frontend.instance-port", 0, "Port to advertise to querier (via scheduler) (defaults to server.grpc-listen-port).") 58 59 cfg.GRPCClientConfig.RegisterFlagsWithPrefix("frontend.grpc-client-config", f) 60 } 61 62 // Frontend implements GrpcRoundTripper. It queues HTTP requests, 63 // dispatches them to backends via gRPC, and handles retries for requests which failed. 64 type Frontend struct { 65 services.Service 66 67 cfg Config 68 log log.Logger 69 70 lastQueryID atomic.Uint64 71 72 // frontend workers will read from this channel, and send request to scheduler. 73 requestsCh chan *frontendRequest 74 75 schedulerWorkers *frontendSchedulerWorkers 76 requests *requestsInProgress 77 } 78 79 type frontendRequest struct { 80 queryID uint64 81 request *httpgrpc.HTTPRequest 82 userID string 83 statsEnabled bool 84 85 cancel context.CancelFunc 86 87 enqueue chan enqueueResult 88 response chan *frontendv2pb.QueryResultRequest 89 } 90 91 type enqueueStatus int 92 93 const ( 94 // Sent to scheduler successfully, and frontend should wait for response now. 95 waitForResponse enqueueStatus = iota 96 97 // Failed to forward request to scheduler, frontend will try again. 98 failed 99 ) 100 101 type enqueueResult struct { 102 status enqueueStatus 103 104 cancelCh chan<- uint64 // Channel that can be used for request cancellation. If nil, cancellation is not possible. 105 } 106 107 // NewFrontend creates a new frontend. 108 func NewFrontend(cfg Config, ring ring.ReadRing, log log.Logger, reg prometheus.Registerer) (*Frontend, error) { 109 requestsCh := make(chan *frontendRequest) 110 111 schedulerWorkers, err := newFrontendSchedulerWorkers(cfg, fmt.Sprintf("%s:%d", cfg.Addr, cfg.Port), ring, requestsCh, log) 112 if err != nil { 113 return nil, err 114 } 115 116 f := &Frontend{ 117 cfg: cfg, 118 log: log, 119 requestsCh: requestsCh, 120 schedulerWorkers: schedulerWorkers, 121 requests: newRequestsInProgress(), 122 } 123 // Randomize to avoid getting responses from queries sent before restart, which could lead to mixing results 124 // between different queries. Note that frontend verifies the user, so it cannot leak results between tenants. 125 // This isn't perfect, but better than nothing. 126 f.lastQueryID.Store(rand.Uint64()) 127 128 promauto.With(reg).NewGaugeFunc(prometheus.GaugeOpts{ 129 Name: "cortex_query_frontend_queries_in_progress", 130 Help: "Number of queries in progress handled by this frontend.", 131 }, func() float64 { 132 return float64(f.requests.count()) 133 }) 134 135 promauto.With(reg).NewGaugeFunc(prometheus.GaugeOpts{ 136 Name: "cortex_query_frontend_connected_schedulers", 137 Help: "Number of schedulers this frontend is connected to.", 138 }, func() float64 { 139 return float64(f.schedulerWorkers.getWorkersCount()) 140 }) 141 142 f.Service = services.NewIdleService(f.starting, f.stopping) 143 return f, nil 144 } 145 146 func (f *Frontend) starting(ctx context.Context) error { 147 return errors.Wrap(services.StartAndAwaitRunning(ctx, f.schedulerWorkers), "failed to start frontend scheduler workers") 148 } 149 150 func (f *Frontend) stopping(_ error) error { 151 return errors.Wrap(services.StopAndAwaitTerminated(context.Background(), f.schedulerWorkers), "failed to stop frontend scheduler workers") 152 } 153 154 // RoundTripGRPC round trips a proto (instead of a HTTP request). 155 func (f *Frontend) RoundTripGRPC(ctx context.Context, req *httpgrpc.HTTPRequest) (*httpgrpc.HTTPResponse, error) { 156 if s := f.State(); s != services.Running { 157 return nil, fmt.Errorf("frontend not running: %v", s) 158 } 159 160 tenantIDs, err := tenant.TenantIDs(ctx) 161 if err != nil { 162 return nil, err 163 } 164 userID := tenant.JoinTenantIDs(tenantIDs) 165 166 // Propagate trace context in gRPC too - this will be ignored if using HTTP. 167 tracer, span := opentracing.GlobalTracer(), opentracing.SpanFromContext(ctx) 168 if tracer != nil && span != nil { 169 carrier := (*lokigrpc.HeadersCarrier)(req) 170 if err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, carrier); err != nil { 171 return nil, err 172 } 173 } 174 175 ctx, cancel := context.WithCancel(ctx) 176 defer cancel() 177 178 freq := &frontendRequest{ 179 queryID: f.lastQueryID.Inc(), 180 request: req, 181 userID: userID, 182 statsEnabled: stats.IsEnabled(ctx), 183 184 cancel: cancel, 185 186 // Buffer of 1 to ensure response or error can be written to the channel 187 // even if this goroutine goes away due to client context cancellation. 188 enqueue: make(chan enqueueResult, 1), 189 response: make(chan *frontendv2pb.QueryResultRequest, 1), 190 } 191 192 f.requests.put(freq) 193 defer f.requests.delete(freq.queryID) 194 195 retries := f.cfg.WorkerConcurrency + 1 // To make sure we hit at least two different schedulers. 196 197 enqueueAgain: 198 var cancelCh chan<- uint64 199 select { 200 case <-ctx.Done(): 201 return nil, ctx.Err() 202 203 case f.requestsCh <- freq: 204 // Enqueued, let's wait for response. 205 enqRes := <-freq.enqueue 206 207 if enqRes.status == waitForResponse { 208 cancelCh = enqRes.cancelCh 209 break // go wait for response. 210 } else if enqRes.status == failed { 211 retries-- 212 if retries > 0 { 213 goto enqueueAgain 214 } 215 } 216 217 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "failed to enqueue request") 218 } 219 220 select { 221 case <-ctx.Done(): 222 if cancelCh != nil { 223 select { 224 case cancelCh <- freq.queryID: 225 // cancellation sent. 226 default: 227 // failed to cancel, ignore. 228 level.Warn(f.log).Log("msg", "failed to send cancellation request to scheduler, queue full") 229 } 230 } 231 return nil, ctx.Err() 232 233 case resp := <-freq.response: 234 if stats.ShouldTrackHTTPGRPCResponse(resp.HttpResponse) { 235 stats := stats.FromContext(ctx) 236 stats.Merge(resp.Stats) // Safe if stats is nil. 237 } 238 239 return resp.HttpResponse, nil 240 } 241 } 242 243 func (f *Frontend) QueryResult(ctx context.Context, qrReq *frontendv2pb.QueryResultRequest) (*frontendv2pb.QueryResultResponse, error) { 244 tenantIDs, err := tenant.TenantIDs(ctx) 245 if err != nil { 246 return nil, err 247 } 248 userID := tenant.JoinTenantIDs(tenantIDs) 249 250 req := f.requests.get(qrReq.QueryID) 251 // It is possible that some old response belonging to different user was received, if frontend has restarted. 252 // To avoid leaking query results between users, we verify the user here. 253 // To avoid mixing results from different queries, we randomize queryID counter on start. 254 if req != nil && req.userID == userID { 255 select { 256 case req.response <- qrReq: 257 // Should always be possible, unless QueryResult is called multiple times with the same queryID. 258 default: 259 level.Warn(f.log).Log("msg", "failed to write query result to the response channel", "queryID", qrReq.QueryID, "user", userID) 260 } 261 } 262 263 return &frontendv2pb.QueryResultResponse{}, nil 264 } 265 266 // CheckReady determines if the query frontend is ready. Function parameters/return 267 // chosen to match the same method in the ingester 268 func (f *Frontend) CheckReady(_ context.Context) error { 269 workers := f.schedulerWorkers.getWorkersCount() 270 271 // If frontend is connected to at least one scheduler, we are ready. 272 if workers > 0 { 273 return nil 274 } 275 276 msg := fmt.Sprintf("not ready: number of schedulers this worker is connected to is %d", workers) 277 level.Info(f.log).Log("msg", msg) 278 return errors.New(msg) 279 } 280 281 const stripeSize = 1 << 6 282 283 type requestsInProgress struct { 284 locks []sync.Mutex 285 requests []map[uint64]*frontendRequest 286 } 287 288 func newRequestsInProgress() *requestsInProgress { 289 x := &requestsInProgress{ 290 requests: make([]map[uint64]*frontendRequest, stripeSize), 291 locks: make([]sync.Mutex, stripeSize), 292 } 293 294 for i := range x.requests { 295 x.requests[i] = map[uint64]*frontendRequest{} 296 } 297 298 return x 299 } 300 301 func (r *requestsInProgress) count() (res int) { 302 for i := range r.requests { 303 r.locks[i].Lock() 304 res += len(r.requests[i]) 305 r.locks[i].Unlock() 306 } 307 return 308 } 309 310 func (r *requestsInProgress) put(req *frontendRequest) { 311 i := req.queryID & uint64(stripeSize-1) 312 r.locks[i].Lock() 313 r.requests[i][req.queryID] = req 314 r.locks[i].Unlock() 315 } 316 317 func (r *requestsInProgress) delete(queryID uint64) { 318 i := queryID & uint64(stripeSize-1) 319 r.locks[i].Lock() 320 delete(r.requests[i], queryID) 321 r.locks[i].Unlock() 322 323 } 324 325 func (r *requestsInProgress) get(queryID uint64) *frontendRequest { 326 i := queryID & uint64(stripeSize-1) 327 r.locks[i].Lock() 328 req := r.requests[i][queryID] 329 r.locks[i].Unlock() 330 return req 331 }